R data.table - 按包含列表的列进行分组

5

我尝试在R中使用data.table包的group by函数。

start <- as.Date('2014-1-1')
end <- as.Date('2014-1-6')
time.span <- seq(start, end, "days")
a <- data.table(date = time.span, value=c(1,2,3,4,5,6), group=c('a','a','b','b','a','b'))

        date  value group
1   2014-01-01  1   a
2   2014-01-02  2   a
3   2014-01-03  3   b
4   2014-01-04  4   b
5   2014-01-05  5   a
6   2014-01-06  6   b

a[,mean(value),by=group]
> group      V1
 1:   a    2.6667
 2:   b    4.3333

这很好用。

因为我正在处理日期,所以特殊日期可能不仅有一个组,而是有两个组。

a <- data.table(date = time.span, value=c(1,2,3,4,5,6), group=list('a',c('a','b'),'b','b','a','b'))

        date   value  group
1   2014-01-01  1   a
2   2014-01-02  2   c("a", "b")
3   2014-01-03  3   b
4   2014-01-04  4   b
5   2014-01-05  5   a
6   2014-01-06  6   b

a[,mean(value),by=group]
> Error in `[.data.table`(a, , mean(value), by = group) : 
  The items in the 'by' or 'keyby' list are length (1,2,1,1,1,1). Each must be same length as rows in x or number of rows returned by i (6).

我希望使用两个组的日期来计算A组和B组的平均值。

期望结果:

mean a: 2.6667
mean b: 3.75

使用data.table包能否实现这个功能?

更新

感谢akrun,我的初始问题已经解决。在“分割”数据表并计算不同的因素后(基于组),我需要将数据表恢复到其“原始”形式,并基于日期具有唯一行。目前我的解决方案如下:

a <- data.table(date = time.span, value=c(1,2,3,4,5,6), group=list('a',c('a','b'),'b','b','a','b'))
b <- a[rep(1:nrow(a), lengths(group))][, group:=unlist(a$group)]

       date   value  group
1   2014-01-01  1   a
2   2014-01-02  2   a
3   2014-01-02  2   b
4   2014-01-03  3   b
5   2014-01-04  4   b
6   2014-01-05  5   a
7   2014-01-06  6   b

# creates new column with mean based on group
b[,factor := mean(value), by=group] 

#creates new data.table c without duplicate rows (based on date) + if a row has group a & b it creates the product of their factors
c <- b[,.(value = unique(value), group = list(group), factor = prod(factor)),by=date]

date     value  group       factor
01/01/14    1   a           2.666666667
02/01/14    2   c("a", "b") 10
03/01/14    3   b           3.75
04/01/14    4   b           3.75
05/01/14    5   a           2.666666667
06/01/14    6   b           3.75

我想这不是最完美的方法,但它有效。你有什么更好的建议吗?

替代方案(非常慢!!!):

d <- a[rep(1:nrow(a), lengths(group))][,group:=unlist(a$group)][, mean(value), by = group]
for(i in 1:NROW(a)){
   y1 <- 1
   for(j in a[i,group][[1]]){
       y1 <- y1 * d[group==j, V1]
   }
   a[i, factor := y1]
}

目前为止我最快的解决方案:

# split rows that more than one group
b <- a[rep(1:nrow(a), lengths(group))][, group:=unlist(a$group)]
# calculate mean of different groups
b <- b[,factor := mean(value), by=group]
# only keep date + factor columns
b <- b[,.(date, factor)]
# summarise rows by date 
b <- b[,lapply(.SD,prod), by=date]
# add summarised factor column to initial data.table
c <- merge(a,b,by='date')

有没有办法让它更快?

2个回答

4

一种选择是按照行序列进行分组,我们使用unlist函数将list列展开为向量('group'),使用toString(..)函数将list元素拼接在一起,再使用splitstackshape包中的cSplit函数以direction='long'的方式将其转换为“长”格式,然后使用'grp'作为分组变量获取'value'列的平均值。

library(data.table)
library(splitstackshape)
a[, grp:= toString(unlist(group)), 1:nrow(a)]
cSplit(a, 'grp', ', ', 'long')[, mean(value), grp]
#  grp       V1
#1:   a 2.666667
#2:   b 3.750000

刚刚意识到使用splitstackshape的另一个选项是listCol_l,它将list列转换为长格式。由于输出是一个data.table,因此我们可以使用data.table方法计算mean。获取mean要简洁得多。

 listCol_l(a, 'group')[, mean(value), group_ul]
 #  group_ul       V1
 #1:        a 2.666667
 #2:        b 3.750000

另一种选项是不使用 splitstackshape,而是通过数据集的行数来复制数据集的行。使用 lengths 可以方便地包装 sapply(group, length) 并且速度更快。然后,我们通过将原始数据集 'a' 中的 'group' 列进行 unlist 操作,并按 'group' 分组获取 'value' 的平均值。

 a[rep(1:nrow(a), lengths(group))][,
        group:=unlist(a$group)][, mean(value), by = group]
 #  group       V1
 #1:     a 2.666667
 #2:     b 3.750000

1
太棒了!就我所知,“cSplit”将具有两个组的行拆分为两个相同的行(一个用于第一组,一个用于第二组),然后我们可以轻松使用正常的data.table函数来计算平均值()。真是太好的解决方案@akrun-我对这个“splitstackshape”包一无所知... - RandomDude
@RandomDude 是的,它将具有多个元素的行拆分为单独的行。感谢您的反馈。 - akrun
我在哪里可以找到“lengths”函数?无法通过谷歌弄清楚,而且根据我的R Studio版本,该函数不可用... - RandomDude
@RandomDude 这是一个base R函数。我认为它是在R 3.1.2或者那个版本之后引入的。如果你使用的是早期版本,可以用sapply(group, length)来替代。 - akrun

0

正如@mike-h在这个问题中发布的更短的解决方案一样,它也使用了unlist(),但是按照剩余的列进行分组:

require(data.table)

a = data.table(date = time.span,
               value = c(1,2,3,4,5,6),
               group = list('a',c('a','b'),'b','b','a','b'))

a[ , .(group = unlist(group)), .(date, value)][ , mean(value), group ]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接