4

我想通过data.frame中一个变量的累积总和等量聚合一个R data.frame。我用谷歌搜索了很多,但可能我不知道正确的术语来找到任何有用的东西。

假设我有这个data.frame:


> x <- data.frame(cbind(p=rnorm(100, 10, 0.1), v=round(runif(100, 1, 10))))
> head(x)
           p  v
1  10.002904  4
2  10.132200  2
3  10.026105  6
4  10.001146  2
5   9.990267  2
6  10.115907  6
7  10.199895  9
8   9.949996  8
9  10.165848  8
10  9.953283  6
11 10.072947 10
12 10.020379  2
13 10.084002  3
14  9.949108  8
15 10.065247  6
16  9.801699  3
17 10.014612  8
18  9.954638  5
19  9.958256  9
20 10.031041  7

我想将 x 减少到一个较小的 data.frame 中,其中每一行包含 p 的加权平均值,由 v 加权,对应于 v 的 n 个单位的数量。这种东西:


> n <- 100
> cum.v <- cumsum(x$v)
> f <- cum.v %/% n
> x.agg <- aggregate(cbind(v*p, v) ~ f, data=x, FUN=sum)
> x.agg$'v * p' <- x.agg$'v * p' / x.agg$v
> x.agg
  f     v * p   v
1 0 10.039369  98
2 1  9.952049  94
3 2 10.015058 104
4 3  9.938271 103
5 4  9.967244 100
6 5  9.995071  69

第一个问题,我想知道上面的代码是否有更好(更有效的方法)。第二个更重要的问题是如何更正上面的代码以获得更精确的分桶。也就是说,中的每一行都x.agg应该包含精确的100单位v,而不是像上面的情况那样近似。例如,第一行包含前 17 行的聚合,x其中对应 98 个单位v。下一行(第 18 行)包含 5 个单位,v并且完全包含在下一个存储桶中。我想要实现的是将第 18 行的 2 个单元分配给第一个存储桶,将剩余的 3 个单元分配给下一个存储桶。

提前感谢您提供的任何帮助。

4

2 回答 2

3

如果您正在寻找精确的分桶,我假设 p 的值对于 2 个“拆分”是相同的,即在您的示例中,进入第一个桶的第 18 行的 2 个单位的 p 值是 9.954638

有了上述假设,您可以对非超大型数据集进行以下操作。

> set.seed(12345)
> x <- data.frame(cbind(p=rnorm(100, 10, 0.1), v=round(runif(100, 1, 10))))
> z <- unlist(mapply(function(x,y) rep(x,y), x$p, x$v, SIMPLIFY=T))

这将创建一个向量,其中 p 的每个值对每一行重复 v 次,然后使用 unlist 将结果组合成单个向量。

在这个聚合是微不足道的使用aggregate函数之后

> aggregate(z, by=list((1:length(z)-0.5)%/%100), FUN=mean)
  Group.1         x
1       0  9.999680
2       1 10.040139
3       2  9.976425
4       3 10.026622
5       4 10.068623
6       5  9.982733
于 2013-02-26T18:15:08.373 回答
3

这是另一种无需重复的方法p v。我的理解是,它超过 100 的地方(见下文)

18  9.954638  5  98
19  9.958256  9 107

应改为:

18    9.954638  5  98
19.1  9.958256  2 100 # ---> 2 units will be considered with previous group
19.2  9.958256  7 107 # ----> remaining 7 units will be split for next group

编码:

n <- 100
# get cumulative sum, an id column (for retrace) and current group id
x <- transform(x, cv = cumsum(x$v), id = seq_len(nrow(x)), grp = cumsum(x$v) %/% n)

# Paste these two lines in R to install IRanges
source("http://bioconductor.org/biocLite.R")
biocLite("IRanges")

require(IRanges)
ir1 <- successiveIRanges(x$v)
ir2 <- IRanges(seq(n, max(x$cv), by=n), width=1)
o <- findOverlaps(ir1, ir2)

# gets position where multiple of n(=100) occurs
# (where we'll have to do something about it)
pos <- queryHits(o)
# how much do the values differ from multiple of 100?
val <- start(ir2)[subjectHits(o)] - start(ir1)[queryHits(o)] + 1
# we need "pos" new rows of "pos" indices
x1 <- x[pos, ]
x1$v <- val # corresponding values
# reduce the group by 1, so that multiples of 100 will
# belong to the previous row
x1$grp <- x1$grp - 1
# subtract val in the original data x
x$v[pos] <- x$v[pos] - val
# bind and order them    
x <- rbind(x1,x)
x <- x[with(x, order(id)), ]
# remove unnecessary entries
x <- x[!(duplicated(x$id) & x$v == 0), ]
x$cv <- cumsum(x$v) # updated cumsum

x$id <- NULL
require(data.table)
x.dt <- data.table(x, key="grp")
x.dt[, list(res = sum(p*v)/sum(v), cv = tail(cv, 1)), by=grp]

在您的数据上运行:

#    grp       res  cv
# 1:   0 10.037747 100
# 2:   1  9.994648 114

在@geektrader 的数据上运行:

#    grp       res  cv
# 1:   0  9.999680 100
# 2:   1 10.040139 200
# 3:   2  9.976425 300
# 4:   3 10.026622 400
# 5:   4 10.068623 500
# 6:   5  9.982733 562

这是一个相对大数据的基准:

set.seed(12345)
x <- data.frame(cbind(p=rnorm(1e5, 10, 0.1), v=round(runif(1e5, 1, 10))))

require(rbenchmark)
benchmark(out <- FN1(x), replications=10)

#            test replications elapsed relative user.self
# 1 out <- FN1(x)           10  13.817        1    12.586

1e5 行大约需要1.4 秒

于 2013-02-26T21:04:49.253 回答