无循环的分组和条件(大数据)
Posted
技术标签:
【中文标题】无循环的分组和条件(大数据)【英文标题】:Grouping and conditions without loop (big data) 【发布时间】:2013-12-03 03:30:23 【问题描述】:我对同一组进行了多次观察,每次观察我都有一年的时间。
dat = data.frame(group = rep(c("a","b","c"),each = 3), year = c(2000, 1996, 1975, 2002, 2010, 1980, 1990 ,1986,1995))
group year
1 a 2000
2 a 1996
3 a 1975
4 b 2002
5 b 2010
6 b 1980
7 c 1990
8 c 1986
9 c 1995
对于每个观察,我想知道是否可以在相对于焦点观察的给定条件下找到同一组的另一个观察。例如:“在过去 6 年(从焦点年开始)在同一组中是否有任何其他观察(除了焦点观察)”。
理想情况下,数据框应该是这样的
group year six_years
1 a 2000 1 # there is another member of group a that is year = 1996 (2000-6 = 1994, this value is inside the threshold)
2 a 1996 0
3 a 1975 0
4 b 2002 0
5 b 2010 0
6 b 1980 0
7 c 1990 1
8 c 1986 0
9 c 1995 1
基本上,对于每一行,我们应该查看组的子集,看看是否有(dat$year == 条件)。使用 for 循环很容易做到,但在这里没有用:数据帧很大(数百万行),循环将永远持续下去。 我正在寻找一种使用矢量化函数或快速包的有效方法。
谢谢!
【问题讨论】:
【参考方案1】:已编辑
实际上考虑一下,您可能会有很多重复的年份/组组合,在这种情况下,使用count()
预先计算频率要快得多——这也是plyr
函数:
90M 行耗时约 4 秒
require(plyr)
dat <- data.frame(group = sample(c("a","b","c"),size=9000000,replace=TRUE),
year = sample(c(2000, 1996, 1975, 2002, 2010, 1980, 1990,1986,1995),size=9000000,replace=TRUE))
test<-function(y,g,df)
d<-df[df$year>=y-6 &
df$year<y &
df$group== g,]
return(nrow(d))
rollup<-function()
summ<-count(dat) # add a frequency to each combination
return(ddply(summ,.(group,year),transform,t=test(as.numeric(year),group,summ)*freq))
system.time(rollup())
user system elapsed
3.44 0.42 3.90
【讨论】:
确实,它就像一个魅力......但前提是你有几个不同的群体!在我的真实数据文件中,我有 + 200 000 个不同的组。有什么建议可以加速吗? :) (无论如何谢谢!感谢你,我发现了 plyr 的强大功能)【参考方案2】:我的数据集有太多不同的组,Troy 提出的 plyr 选项太慢了。 我发现了一个带有包 data.table 的 hack(专家可能会说“一个丑陋的”):想法是使用快速合并功能快速合并 data.table 与自身。它给出了一个组的给定年份和同一组的所有其他年份之间的所有可能组合。 然后根据您要查找的条件对每一行执行 ifelse。 最后,使用 sum 函数聚合所有内容,以了解在给定时间跨度内相对于另一年可以找到每个给定年份的次数。 在我的电脑上,这需要几毫秒,而不是 plyr 可能需要的几个小时
dat = data.table(group = rep(c("a","b","c"),each = 3), year = c(2000, 1996, 1975, 2002, 2010, 1980, 1990,1986,1995), key = "group")
产生这个:
group year
1 a 2000
2 a 1996
3 a 1975
4 b 2002
5 b 2010
6 b 1980
7 c 1990
8 c 1986
9 c 1995
然后:
z = merge(dat, dat, by = "group", all = T, allow.cartesian = T) # super fast
z$sixyears = ifelse(z$year.y >= z$year.x - 6 & z$year.y < z$year.x, 1, 0) # creates a 0/1 column for our condition
z$sixyears = as.numeric(z$sixyears) # we want to sum this up after
z$year.y = NULL # useless column now
z2 = z[ , list(sixyears = sum(sixyears)), by = list(group, year.x)]
(过去六年中有同一组的另一年的年份被赋予“1”:
group year x
1 a 1975 0
2 b 1980 0
3 c 1986 0
4 c 1990 1 # e.g. here there is another "c" which was in the timespan 1990 -6 ..
5 c 1995 1 # <== this one. This one too has another reference in the last 6 years, two rows above.
6 a 1996 0
7 a 2000 1
8 b 2002 0
9 b 2010 0
锦上添花:它无缝地处理 NA。
【讨论】:
【参考方案3】:这是另一种可能性,也使用data.table
,但包括diff()
。
dat <- data.table(group = rep(c("a","b","c"), each = 3),
year = c(2000, 1996, 1975, 2002, 2010, 1980, 1990,1986,1995),
key = "group")
valid_case <- subset(dt[,list(valid_case = diff(year)), by=key(dt)],
abs(valid_case)<6)
dat$valid_case <- ifelse(dat$group %in% valid_case$group, 1, 0)
我不确定这在速度或 NA 处理方面如何比较(我认为 NA 应该没问题,因为它们在 diff()
和 abs()
中传播),但我当然发现它更具可读性。 data.table
的连接速度非常快,但我不得不考虑避免这一切会有所帮助。在ifelse
语句中使用data.table
连接可能有一种更惯用的方法来执行条件。这可能会加快速度,尽管我的经验从未发现 %in%
是限制因素。
【讨论】:
嗯对我不起作用。它给了我一个不应该的“valid_case = 1”。一个 2000 1 ; 1996 1; 1975 1 通常这里只有 2000 的行应该有 1。此外,您的代码中有一个错字:将“dat”替换为“dt”。我会坚持我丑陋但有效的解决方案!以上是关于无循环的分组和条件(大数据)的主要内容,如果未能解决你的问题,请参考以下文章