R语言 防止data.table将不同长度的向量列表强制到data.table中

acruukt9  于 12个月前  发布在  其他
关注(0)|答案(2)|浏览(113)

data.table中的(合理的)默认值似乎是尽可能地保留列表中向量的矩形性质,必要时重复单元素项,然后将结果强制转换为data. table。
假设您有一个数据表foo

foo <- data.table(
  a = c(1, 2, 3),
  b = c(TRUE, TRUE, FALSE)
)

字符串
你想要的是返回一个非data.table命名的(不规则的)列表,像这样:

list(
  a = foo[(b), a],
  n = foo[(b), .N]
)

#$a
#[1] 1 2
#n
#[1] 2


我一直在用不同的方法处理列表的列表,但我无法阻止data.table将j中返回的内容强制转换到data.table中(如果它有能力这样做的话)。例如:

foo[(b), {
  .(a = a,
    n = .N) 
}]


返回一个data.table,其中有两行,列名为an
将其嵌套在另一个列表中,我将列表作为一个列返回,并且没有名称:

foo[(b),
  .(.("a" = a,
      "n" = .N))
]

#     V1
# 1: 1,2
# 2:   2


无论如何,我觉得我错过了一些东西,但我已经用尽了搜索和阅读文档的方法。
我还想知道在索引结果列表时,拐点可能在哪里,因为也许这并不重要。如果我在列表中有一个长度为1000万的向量,以及100个长度为1的向量,我假设ragged列表在内存方面比data. table更有效。但是除了内存问题,这两个选项中的一个在计算上更有效:

bar_list$singleInt

# vs

bar_dt[1, singleInt]

编辑:澄清最后一个问题

library(data.table)

set.seed(123)
foo <- data.table(a = sample(1:100, 1e8, TRUE),
                  b = sample(c(TRUE, FALSE), 1e8, TRUE))

在我看来,给出了答案,我现在有三个潜在的对象可以包含我想要的信息。
有一个参差不齐的list(无论我如何到达那里)

bar_list <- list(
  a = foo[(b), a],
  n = foo[(b), .N],
  s = foo[(b), sum(b)],
  m = foo[(b), max(a)]  
)

# List of 4
# $ a: int [1:50005713] 9 84 37 56 63 60 55 74 99 32 ...
# $ n: int 50005713
# $ s: int 50005713
# $ m: int 100

这里有一个例子,这几乎就像你在转置列表。你生成了一个data.table,其中的列是命名的列表元素,正如@jblood94所建议的那样。(这对长向量有一个不幸的副作用,它们现在需要列表索引来访问它们,但在其他方面是一个有吸引力的选择。)

bar_dt   <- foo[(b),
                .(a = .(a),
                  n = .N)]
# Classes ‘data.table’ and 'data.frame':    1 obs. of  2 variables:
#   $ a:List of 1
# ..$ : int  9 84 37 56 63 60 55 74 99 32 ...
# $ n: int 50005713

然后还有这个,它允许data.table通过重复单个元素向量来将列表强制转换为传统的矩形形式,使它们与其他向量具有相同的长度。

bar_dt2  <- foo[(b),
                .(a = a,
                  n = .N,
                  s = sum(b),
                  m = max(a))]

# Classes ‘data.table’ and 'data.frame':    50005713 obs. of  4 variables:
#   $ a: int  9 84 37 56 63 60 55 74 99 32 ...
# $ n: int  50005713 50005713 50005713 50005713 50005713 50005713 50005713 50005713 50005713 50005713 ...
# $ s: int  50005713 50005713 50005713 50005713 50005713 50005713 50005713 50005713 50005713 50005713 ...
# $ m: int  100 100 100 100 100 100 100 100 100 100 ...

访问元素的基准测试内存方面,bar_listbar_dt是等价的(此处为190.7 MB),而bar_dt2要大得多(762.9 MB)。我们有两种我们感兴趣的索引类型:索引像n这样的单元素向量,和索引像a这样的长向量。从你的microbenchmark@jblood94没有太大的变化。最快的方法仍然是列表,避免了data.table开销。

microbenchmark::microbenchmark(
  bar_list$n,
  bar_dt[1, n],
  bar_dt[, n],
  bar_dt$n,
  bar_dt[["n"]],
  bar_dt[[2]],
  bar_dt2[1, n],
  check = "identical"
)

# Unit: nanoseconds
# expr             min     lq   mean median     uq    max neval
# bar_list$n       200    500    940    850   1100   8700   100
# bar_dt[1, n]  237000 270450 371931 324100 465800 898100   100
# bar_dt[, n]   237100 278500 378559 340950 465800 954300   100
# bar_dt$n         800   1700   2961   2700   3250  19700   100
# bar_dt[["n"]]   5000   7750  10832   9850  12100  45200   100
# bar_dt[[2]]     5000   6950  10125   9050  11300  44400   100
# bar_dt2[1, n] 238800 278950 381454 338700 418350 925300   100

矩形bar_dt2唯一的优点是它在这里稍微快一点,因为它不必用[[索引,但列表仍然更快。

microbenchmark::microbenchmark(
  bar_list$a,
  bar_dt[, a[[1]]],
  bar_dt$a[[1]],
  bar_dt[["a"]][[1]],
  bar_dt[[1]][[1]],
  bar_dt2[, a],
  bar_dt2$a,
  bar_dt2[["a"]],
  bar_dt2[[1]],
  check = "identical"
)

# Unit: nanoseconds
# expr                   min       lq     mean   median       uq       max neval
# bar_list$a             200      600     1966     1500     2600     19300   100
# bar_dt[, a[[1]]]    271900   420600   559898   537650   675300   1068900   100
# bar_dt$a[[1]]         1100     1900     5285     3700     6700     26500   100
# bar_dt[["a"]][[1]]    6600     8350    16123    10950    16650     70900   100
# bar_dt[[1]][[1]]      6100     8100    18479    11350    23750     91800   100
# bar_dt2[, a]      47781800 57865500 86941905 64417200 71424750 326081400   100
# bar_dt2$a              900     1500     4130     2250     5100     30600   100
# bar_dt2[["a"]]        5900     8100    18919    11550    26550     91700   100
# bar_dt2[[1]]          5800     8100    19484    11950    19300    282600   100

所以这回答了我最初的问题--当涉及到使用它的效率时,似乎有理由更喜欢一个不规则列表。然而,这引出了第二个问题,那就是创建哪一个更有效,效率有多高,因为如果bar_dt2的创建比任何获取不规则列表的方法都要有效得多,则bar_list$abar_dt$a之间的小差异可能无关紧要。

基准列表或数据.表创建

坚持使用microbenchmark,但只运行一次。

microbenchmark::microbenchmark(
  list1 <- with(
    foo[(b),],
    list("a" = a,
         "n" = length(a),
         "s" = sum(b),
         "m" = max(a))),
  list2 <- unlist(
    foo[(b),
        .(a = .(a),
          n = .N,
          s = sum(b),
          m = max(a))], FALSE),
  list3 <- list(
    "a" = foo[(b), a],
    "n" = foo[(b), .N],
    "s" = foo[(b), sum(b)],
    "m" = foo[(b), max(a)]),
  list4   <- foo[(b),
                 .(a = .(a),
                   n = .N,
                   s = sum(b),
                   m = max(a))],
  times = 1
)

# Unit: seconds
#      min       lq     mean   median       uq      max neval
# 1.263952 1.263952 **1.263952** 1.263952 1.263952 1.263952     1
# 1.928070 1.928070 1.928070 1.928070 1.928070 1.928070     1
# 4.501306 4.501306 4.501306 4.501306 4.501306 4.501306     1
# 1.800121 1.800121 1.800121 1.800121 1.800121 1.800121     1

**t1; dr:**看起来使用with结构是最快的,比之前快了很多,而(正如预期的那样)通过反复调用foo生成的列表要慢得多。由于这会自动生成一个不规则的列表,这也是访问其内容的最快方法,我认为这个结构对我来说是赢家。我绝对不会考虑这样做,所以我很高兴我问了这个问题。

lymnna71

lymnna711#

如果j参数是listdata.framedata.table,则data.table将返回data.table。但data.table获取不规则列表的方法是将其保留为data.table

(bar_dt <- foo[(b),.(a = .(a), n = .N)])
#>      a n
#> 1: 1,2 2

字符串
然而,data.tables是向量列表,所以如果你真的想要一个非data.tablelist

(bar_list <- unlist(foo[(b),.(a = .(a), n = .N)], FALSE))
#> $a
#> [1] 1 2
#> 
#> $n
#> [1] 2


至于从单行data.tablelist访问元素的问题,data.table增加了一些额外的开销,所以list访问速度会更快:

microbenchmark::microbenchmark(
  bar_list$n,
  bar_dt[1, n],
  bar_dt[, n],
  bar_dt$n,
  bar_dt[["n"]],
  bar_dt[[2]],
  check = "identical"
)
#> Unit: nanoseconds
#>           expr    min     lq   mean median     uq     max neval
#>     bar_list$n    200    400    882    800   1100    4200   100
#>   bar_dt[1, n] 274500 299850 347330 311750 358850 1217500   100
#>    bar_dt[, n] 269100 294900 343569 309900 355950  803000   100
#>       bar_dt$n    800   1350   2335   2300   2850    7600   100
#>  bar_dt[["n"]]   5200   6150   9860   8450  11100   64000   100
#>    bar_dt[[2]]   5000   6200   9601   8700  10550   49400   100


这与文件一致:
DT“v” #与DT[,v]相同,但速度更快

wgx48brx

wgx48brx2#

foo[(b), .(.("a" = a, "n" = .N)) ]
#        V1
#    <list>
# 1:    1,2
# 2:      2

字符串
它的名字是V1,因为外部的.(..)意味着返回一个新的列表/表,但是由于您没有命名任何直接内部的参数,所以它自己命名。

foo[(b), .(quux = .("a" = a, "n" = .N)) ]
#      quux
#    <list>
# 1:    1,2
# 2:      2


我还在R示例中设置了options(datatable.print.class=TRUE)(默认情况下),这允许我们看到这是一个列表列(正如你可能怀疑的那样)。这是一个列表,因为摘要中的 inner.(.)
不幸的是,data.table默认将j=参数中的任何list-return转换为data.table(为了方便起见,.(.)list(.)的同义词)。

with(foo[(b),],  list("a" = a, "n" = length(a)))
# $a
# [1] 1 2
# $n
# [1] 2


注意,我们不能再使用.N特殊符号,尽管在这里使用length(a)非常便宜。

相关问题