R中的快速子集

时间:2012-01-20 03:49:48

标签: r dataframe

我有一个大小为30000 x 50的数据帧数据。我还有一个单独的列表,其中包含来自此数据帧的行分组,例如,

rows <- list(c("34", "36", "39"), c("45", "46"))

这表示具有rownames(不是数字行indeces,但是字符rownames(dat))“34”,“36”,“39”的数据帧行构成一个分组,而“45”,“46”构成另一个分组。

现在我想将数据框中的分组拉出到并行列表中,但我的代码(下面)非常非常慢。我怎样才能加快速度?

> system.time(lapply(rows, function(r) {dat[r, ]}))
   user  system elapsed 
 246.09    0.01  247.23 

这是在非常快的计算机上,R 2.14.1 x64。

5 个答案:

答案 0 :(得分:17)

其中一个主要问题是行名称的匹配 - [.data.frame中的默认值是行名称的部分匹配,您可能不希望这样,所以最好使用{{1} }。为了进一步加快速度,如果需要,可以使用match中的fmatch。这是一个小修改,有一些加速:

fastmatch

如果您的# naive > system.time(res1 <- lapply(rows,function(r) dat[r,])) user system elapsed 69.207 5.545 74.787 # match > rn <- rownames(dat) > system.time(res1 <- lapply(rows,function(r) dat[match(r,rn),])) user system elapsed 36.810 10.003 47.082 # fastmatch > rn <- rownames(dat) > system.time(res1 <- lapply(rows,function(r) dat[fmatch(r,rn),])) user system elapsed 19.145 3.012 22.226 不重叠,则可以不使用[(数据帧速度慢),而是分割数据框(使用split),从而进一步加快速度覆盖所有行(因此您可以将每行映射到行中的一个条目)。

根据您的实际数据,您可能会更好地使用具有更快的子集运算符的矩阵,因为它们是原生的。

答案 1 :(得分:5)

更新

我的原帖是从这个错误的陈述开始的:

  

通过rownamescolnames建立索引的问题就在于此   正在为每个元素运行矢量/线性扫描,例如。你在打猎   通过每一行来查看哪个名为“36”,然后从中开始   开始为“34”再做一次。

Simon在评论中指出,R显然使用哈希表进行索引。抱歉错误。

原始答案

请注意,此答案中的建议假设您有非重叠的数据子集。

如果你想保留你的列表查找策略,我建议存储实际的行索引而不是字符串名称。

另一种方法是将您的“群组”信息作为另一列存储到data.frame,然后split将其data.frame存储在其群组中,例如。让我们说你的重新编码data.frame看起来像这样:

dat <- data.frame(a=sample(100, 10),
                  b=rnorm(10),
                  group=sample(c('a', 'b', 'c'), 10, replace=TRUE))

然后你可以这样做:

split(dat, dat$group)
$a
   a           b group
2 66 -0.08721261     a
9 62 -1.34114792     a

$b
    a          b group
1  32  0.9719442     b
5  79 -1.0204179     b
6  83 -1.7645829     b
7  73  0.4261097     b
10 44 -0.1160913     b

$c
   a          b group
3 77  0.2313654     c
4 74 -0.8637770     c
8 29  1.0046095     c

或者,根据您对“拆分”的真实想法,您可以将data.frame转换为data.table并将其密钥设置为新的group列:< / p>

library(data.table)
dat <- data.table(dat, key="group")

现在执行您的列表操作 - 这将为您提供与上面的split

相同的结果
 x <- lapply(unique(dat$group), function(g) dat[J(g),])

但是你可能想要“解决你的唾液”,你可以内联,例如:

ans <- dat[, {
  ## do some code over the data in each split
  ## and return a list of results, eg:
  list(nrow=length(a), mean.a=mean(a), mean.b=mean(b))
}, by="group"]

ans
     group nrow mean.a     mean.b
[1,]     a    2   64.0 -0.7141803
[2,]     b    5   62.2 -0.3006076
[3,]     c    3   60.0  0.1240660

您可以使用plyr以“类似方式”执行最后一步,例如:

library(plyr)
ddply(dat, "group", summarize, nrow=length(a), mean.a=mean(a),
      mean.b=mean(b))
  group nrow mean.a     mean.b
1     a    2   64.0 -0.7141803
2     b    5   62.2 -0.3006076
3     c    3   60.0  0.1240660

但是既然你提到你的数据集非常大,我想你会想要提速data.table

答案 2 :(得分:4)

这是加速的一次尝试 - 它取决于查找行索引比查找行名更快的事实,因此尝试在dat中将rowname映射到rownumber

首先创建一些与您相同大小的数据并指定一些数字rownames:

> dat <- data.frame(matrix(runif(30000*50),ncol=50))
> rownames(dat) <- as.character(sample.int(nrow(dat)))
> rownames(dat)[1:5]
[1] "21889" "3050"  "22570" "28140" "9576" 

现在生成一个包含15000个元素的随机rows,每个元素包含1到30000的50个随机数(在这种情况下为行*名*):

# 15000 groups of up to 50 rows each
> rows <- sapply(1:15000, function(i) as.character(sample.int(30000,size=sample.int(50,size=1))))

出于计时目的,请尝试问题中的方法( ouch!):

# method 1
> system.time((res1 <- lapply(rows,function(r) dat[r,])))
   user  system elapsed 
182.306   0.877 188.362 

现在,尝试从行名到行号进行映射。 map[i]应该为行号指定名称i

第一如果您的行名称1:nrow(dat)的排列,那么您很幸运!您所要做的就是对rownames进行排序,并返回索引:

> map <- sort(as.numeric(rownames(dat)), index.return=T)$ix
# NOTE: map[ as.numeric(rowname) ] -> rownumber into dat for that rowname.

现在查找行索引而不是行名称:

> system.time((res2 <- lapply(rows,function(r) dat[map[as.numeric(r)],])))
   user  system elapsed
 32.424   0.060  33.050

检查我们没有搞砸任何东西(注意它与rownames匹配就足够了,因为rownames在R中是唯一的):

> all(rownames(res1)==rownames(res2))
[1] TRUE

所以,速度加快了~6倍。虽然还不令人惊讶......

SECOND 如果你运气不好且你的rownames与nrow(dat)完全无关,那么你可以试试这个,但是仅当max(as.numeric(rownames(dat)))不比nrow(dat)大得多时。它基本上使map map[rowname]给出行号,但由于rownames不一定是连续的,map中可能有大量的空白浪费了一点内存:

map <- rep(-1,max(as.numeric(rownames(dat))))
obj <- sort(as.numeric(rownames(dat)), index.return=T)
map[obj$x] <- obj$ix

然后像以前一样使用mapdat[map[as.numeric(r),]])。

答案 3 :(得分:2)

你可以试试这个修改:

system.time(lapply(rows, function(r) {dat[ rownames(dat) %in% r, ]}))

答案 4 :(得分:1)

我同意数学咖啡,我也很快得到这个。

不知道是否可能,但通过将其列为矢量然后转换为数字,您可以获得速度提升。

dat <- data.frame(matrix(rnorm(30000*50), 30000, 50 ))
rows <- as.numeric(unlist(list(c("34", "36", "39"), c("45", "46"))))
system.time(lapply(rows, function(r) {dat[r, ]}))

编辑:

dat$observ <- rownames(dat)
rownames(dat) <- 1:nrow(dat)