23

ゴール

リストのリストが与えられた場合、私の目標はその構造を逆にすることです(R 言語)。

そのため、ネストされたリストの要素を第 1 層のリストの要素にしたいと考えています。

おそらく、例の方が私の目的をより明確に示しています。与えられた:

z <- list(z1 = list(a = 1, b = 2, c = 3), z2 = list(b = 4, a = 1, c = 0))

後続の R オブジェクトと同等の出力が必要です。

o <- list(a = list(z1 = 1, z2 = 1), b = list(z1 = 2, z2 = 4), c = list(z1 = 3, z2 = 0))

ソリューション

私の解決策

以下に添付する独自のソリューションを作成しましたが、もっと良いものがあれば教えてください。

revert_list_str_1 <- function(ls) {
  res <- lapply(names(ls[[1]]), function(n, env) {
    name <- paste(n, 'elements', sep = '_')
    assign(name, vector('list', 0))
    inner <- sapply(ls, function(x) {
      assign(name, c(get(name), x[which(names(x) == n)]))
    })
    names(inner) <- names(ls)

    inner
  })
  names(res) <- names(ls[[1]])

  res
}

実行str(revert_list_str_1(z))すると、必要なものに対応する後続の出力が得られます。

List of 3
 $ a:List of 2
  ..$ z1: num 1
  ..$ z2: num 1
 $ b:List of 2
  ..$ z1: num 2
  ..$ z2: num 4
 $ c:List of 2
  ..$ z1: num 3
  ..$ z2: num 0

しかし、私が言ったように、よりエレガントで動的なソリューションの存在を調査 (および学習) したいと考えています

実際、私のソリューションは、ネストされたすべてのリストが同じ名前(順序も異なる) である場合にのみ完全に機能します。これはnames(ls[[1]]). また、報告されているように、2 レベルのリストでのみ機能することも指摘します。

では、より動的な他のソリューションを知っていますか? rapplyおよび/またはFilter関数はこのタスクに役立ちますか?

編集終了 1.

提案されたソリューションの分析

提案されたソリューションを少し分析しました。ありがとうございます。. 分析は、すべての機能について次の点を確認することで構成されます。

  1. 受け入れられたクラス (ネストされたリスト要素)
    1. タイプの異なる要素が存在する場合にもタイプが保持されます (それらがアトミックである場合)
    2. 保存された要素に含まれるオブジェクト (例: マトリックス)
  2. 考慮される列(列の場合、ネストされたリストの名前を意味します)
    1. 一般的ではない列は無視されます (この場合、「not」という分類は肯定的に解釈されます)
    2. 共通ではない列が保持される
    3. 列が一致しない場合にも機能します(最初のネストされたリストの名前のみに基づいて)

このすべての場合において、「はい」という分類は、ポイント 2.1 を除いて肯定的に理解されます。

これは私が検討したすべての機能です(コメントは上記の分析項目に関連しています):

# yes 1.1
# yes 1.2
# yes 2.1, not 2.2, not 2.3
revert_list_str_1 <- function(ls) { # @leodido
    # see above
}

# not 1.1
# not 1.2
# not 2.1, not 2.2, not 2.3
revert_list_str_2 <- function(ls) { # @mnel
  # convert each component of list to a data.frame
  # so rbind.data.frame so named elements are matched
  x <- data.frame((do.call(rbind, lapply(ls, data.frame))))
  # convert each column into an appropriately named list
  o <- lapply(as.list(x), function(i, nam) as.list(`names<-`(i, nam)), nam = rownames(x))

  o
}

# yes 1.1
# yes 1.2
# yes 2.1, not 2.2, yes 2.3
revert_list_str_3 <- function(ls) { # @mnel
  # unique names
  nn <- Reduce(unique, lapply(ls, names))
  # convert from matrix to list `[` used to ensure correct ordering
  as.list(data.frame(do.call(rbind,lapply(ls, `[`, nn))))
}

# yes 1.1
# yes 1.2
# yes 2.1, not 2.2, yes 2.3
revert_list_str_4 <- function(ls) { # @Josh O'Brien
  # get sub-elements in same order
  x <- lapply(ls, `[`, names(ls[[1]]))
  # stack and reslice
  apply(do.call(rbind, x), 2, as.list) 
}

# not 1.1
# not 1.2
# not 2.1, not 2.2, not 2.3
revert_list_str_5 <- function(ls) { # @mnel
  apply(data.frame((do.call(rbind, lapply(ls, data.frame)))), 2, as.list)
}

# not 1.1
# not 1.2
# not 2.1, yes 2.2, yes 2.3
revert_list_str_6 <- function(ls) { # @baptiste + @Josh O'Brien
  b <- recast(z, L2 ~ L1)
  apply(b, 1, as.list)
}

# yes 1.1
# yes 1.2
# not 2.1, yes 2.2, yes 2.3
revert_list_str_7 <-  function(ll) { # @Josh O'Brien
  nms <- unique(unlist(lapply(ll, function(X) names(X))))
  ll <- lapply(ll, function(X) setNames(X[nms], nms))
  ll <- apply(do.call(rbind, ll), 2, as.list)
  lapply(ll, function(X) X[!sapply(X, is.null)])
}

考慮事項

この分析から、次のことがわかります。

  • 関数revert_list_str_7revert_list_str_6あり、ネストされたリストの名前に関して最も柔軟です
  • functions revert_list_str_4、それにrevert_list_str_3続く私自身の関数は十分に完全であり、適切なトレードオフです。
  • 絶対機能で最も完全revert_list_str_7なのは.

ベンチマーク

作業を完了するためにmicrobenchmark、この 4 つの関数でいくつかの小さなベンチマーク (R パッケージを使用) を行いました (各ベンチマークで時間 = 1000 )。

ベンチマーク 1

入力:

list(z1 = list(a = 1, b = 2, c = 3), z2 = list(a = 0, b = 3, d = 22, f = 9)).

結果:

Unit: microseconds
    expr       min         lq     median         uq       max
1 func_1   250.069   467.5645   503.6420   527.5615  2028.780
2 func_3   204.386   393.7340   414.5485   429.6010  3517.438
3 func_4    89.922   173.7030   189.0545   194.8590  1669.178
4 func_6 11295.463 20985.7525 21433.8680 21934.5105 72476.316
5 func_7   348.585   387.0265   656.7270   691.2060  2393.988

勝者: revert_list_str_4.

ベンチマーク 2

入力:

list(z1 = list(a = 1, b = 2, c = 'ciao'), z2 = list(a = 0, b = 3, c = 5)).

revert_list_str_6異なるタイプのネストされた子要素をサポートしていないため、除外されました。

結果:

Unit: microseconds
    expr     min       lq   median       uq      max
1 func_1 249.558 483.2120 502.0915 550.7215 2096.978
2 func_3 210.899 387.6835 400.7055 447.3785 1980.912
3 func_4  92.420 170.9970 182.0335 192.8645 1857.582
4 func_7 257.772 469.9280 477.8795 487.3705 2035.101

勝者: revert_list_str_4.

ベンチマーク 3

入力:

list(z1 = list(a = 1, b = m, c = 'ciao'), z2 = list(a = 0, b = 3, c = m)).

mは整数の行列 3x3 であり、revert_list_str_6再び除外されました。

結果:

Unit: microseconds
expr     min       lq   median       uq      max
1 func_1 261.173 484.6345 503.4085 551.6600 2300.750
2 func_3 209.322 393.7235 406.6895 449.7870 2118.252
3 func_4  91.556 174.2685 184.5595 196.2155 1602.983
4 func_7 252.883 474.1735 482.0985 491.9485 2058.306

勝者: revert_list_str_4. また!

編集終了 2.

結論

まず第一に、素晴らしいソリューションに感謝します。

私の意見では、リストに同じ名前のネストされたリストがあることを事前に知っている場合reverse_str_4は、パフォーマンスとさまざまなタイプのサポートの間の最良の妥協点として勝者です。

最も完全な解決策は、 (ネストされたリストの名前が異なる場合に役立ちます)revert_list_str_7と比較して、完全な柔軟性が平均で約 2.5 倍のパフォーマンスの悪化を引き起こすことです。reverse_str_4

4

6 に答える 6

13

編集:

これは、要素に必ずしも同じサブ要素のセットが含まれていないリストで機能する、より柔軟なバージョンです。

fun <-  function(ll) {
    nms <- unique(unlist(lapply(ll, function(X) names(X))))
    ll <- lapply(ll, function(X) setNames(X[nms], nms))
    ll <- apply(do.call(rbind, ll), 2, as.list)
    lapply(ll, function(X) X[!sapply(X, is.null)])
}

## An example of an 'unbalanced' list
z <- list(z1 = list(a = 1, b = 2), 
          z2 = list(b = 4, a = 1, c = 0))
## Try it out
fun(z)

元の回答

z <- list(z1 = list(a = 1, b = 2, c = 3), z2 = list(b = 4, a = 1, c = 0))

zz <- lapply(z, `[`, names(z[[1]]))   ## Get sub-elements in same order
apply(do.call(rbind, zz), 2, as.list) ## Stack and reslice
于 2013-03-07T05:25:22.887 に答える
12

編集 - @Josh O'Briens の提案と私自身の改善点から作業

問題は、名前の一致を行う do.call rbind が呼び出されなかったことrbind.data.frameです。rbind.data.framedata.frames はリストであり、各サブリストはリストであるため、動作するはずです。したがって、直接呼び出すことができます。

apply(do.call(rbind.data.frame, z), 1, as.list)

ただし、これは簡潔かもしれませんが、do.call(rbind.data.frame, ...)本質的に遅いため遅いです。


のようなもの(2段階で)

 # convert each component of z to a data.frame
 # so rbind.data.frame so named elements are matched
 x <- data.frame((do.call(rbind, lapply(z, data.frame))))
 # convert each column into an appropriately named list
 o <- lapply(as.list(x), function(i,nam) as.list(`names<-`(i, nam)), nam = rownames(x))
 o
$a
$a$z1
[1] 1

$a$z2
[1] 1


$b
$b$z1
[1] 2

$b$z2
[1] 4


$c
$c$z1
[1] 3

$c$z2
[1] 0

そして代替

# unique names
nn <- Reduce(unique,lapply(z, names))
# convert from matrix to list `[` used to ensure correct ordering
as.list(data.frame(do.call(rbind,lapply(z, `[`, nn))))
于 2013-03-07T04:53:44.113 に答える
6

形を変えることで、あなたを近づけることができます。

library(reshape)
b = recast(z, L2~L1)
split(b[,-1], b$L2)
于 2013-03-07T06:01:33.547 に答える
3

最近リリースされた には、リスト構造を「元に戻す」ことを目的としpurrrた関数 が含まれています。transpose関数には大きな注意点があります。transpose名前ではなく位置で一致します https://cran.r-project.org/web/packages/purrr/purrr.pdf。これらは、上記のベンチマーク 1 の正しいツールではないことを意味します。したがって、以下のベンチマーク 2 と 3 のみを検討します。

ベンチマーク 2

B2 <- list(z1 = list(a = 1, b = 2, c = 'ciao'), z2 = list(a = 0, b = 3, c = 5))

revert_list_str_8 <-  function(ll) { # @z109620
  transpose(ll)
}

microbenchmark(revert_list_str_1(B2), revert_list_str_3(B2), revert_list_str_4(B2), revert_list_str_7(B2), revert_list_str_8(B2), times = 1e3)
Unit: microseconds
                  expr     min       lq       mean   median       uq      max neval
 revert_list_str_1(B2) 228.752 254.1695 297.066646 268.8325 293.5165 4501.231  1000
 revert_list_str_3(B2) 211.645 232.9070 277.149579 250.9925 278.6090 2512.361  1000
 revert_list_str_4(B2)  79.673  92.3810 112.889130 100.2020 111.4430 2522.625  1000
 revert_list_str_7(B2) 237.062 252.7030 293.978956 264.9230 289.1175 4838.982  1000
 revert_list_str_8(B2)   2.445   6.8440   9.503552   9.2880  12.2200  148.591  1000

明らかに関数transposeが勝者です! また、使用するコードもはるかに少なくなります。

ベンチマーク 3

B3 <- list(z1 = list(a = 1, b = m, c = 'ciao'), z2 = list(a = 0, b = 3, c = m))

microbenchmark(revert_list_str_1(B3), revert_list_str_3(B3), revert_list_str_4(B3), revert_list_str_7(B3), revert_list_str_8(B3), times = 1e3)

 Unit: microseconds
                  expr     min       lq       mean  median      uq      max neval
 revert_list_str_1(B3) 229.242 253.4360 280.081313 266.877 281.052 2818.341  1000
 revert_list_str_3(B3) 213.600 232.9070 271.793957 248.304 272.743 2739.646  1000
 revert_list_str_4(B3)  80.161  91.8925 109.713969  98.980 108.022 2403.362  1000
 revert_list_str_7(B3) 236.084 254.6580 287.274293 264.922 280.319 2718.628  1000
 revert_list_str_8(B3)   2.933   7.3320   9.140367   9.287  11.243   55.233  1000

繰り返しますが、transpose他のすべてよりも優れています。

これらの上記のベンチマーク テストの問題は、非常に小さなリストに焦点を当てていることです。このため、関数 1 ~ 7 内にネストされた多数のループは、あまり問題になりません。リストのサイズが大きくなり、反復回数が増えると、 の速度が向上transposeする可能性があります。

purrrパッケージがすごい!リストを元に戻すだけではありません。dplyrパッケージと組み合わせることで、このpurrrパッケージは強力で美しい関数型プログラミング パラダイムを使用してほとんどのコーディングを行うことができます。ハドリーの主に感謝します!

于 2016-06-21T23:44:00.527 に答える