在R数据框中按组应用计算

3

我有这样的数据:

object category country
495647 1        RUS  
477462 2        GER  
431567 3        USA  
449136 1        RUS  
367260 1        USA  
495649 1        RUS  
477461 2        GER  
431562 3        USA  
449133 2        RUS  
367264 2        USA  
...

其中一个对象出现在不同的(类别, 国家)对中,各国共享一个类别列表。

我想添加另一列,即每个国家的类别权重 - 在一个类别中出现的对象数量,归一化为国家内总和为1(仅针对唯一的(类别, 国家)对进行求和)。

我可以这样做:

aggregate(df$object, list(df$category, df$country), length)

然后从那里计算重量,但有没有更有效和优雅的方法直接在原始数据上进行计算。

期望的示例输出:

object category country weight
495647 1        RUS     .75
477462 2        GER     .5 
431567 3        USA     .5 
449136 1        RUS     .75
367260 1        USA     .25
495649 1        RUS     .75
477461 3        GER     .5
431562 3        USA     .5
449133 2        RUS     .25
367264 2        USA     .25
...

上述内容针对每个唯一的(类别,国家)对在国内求和后等于1。


2
可能更适合在StackOverflow上发布。 - Peter Ellis
1
一些期望的输出数据会很好;例如,澄清第1行和第4行的结果是否都应为50%或都应为100%。您要求按国家分类权重,直接在原始数据上进行,这似乎意味着后者?但是,这样做在国内不会总和为1,如果我理解正确的话。 - Matt Dowle
谢谢Matthew,你说得很对 - 第1行和第4行应该是100%(或1)。权重应该在每个国家内总和为1,仅考虑唯一的(类别,国家)对 - 我正在编辑问题。 - nikola
3个回答

3

特别是针对最后一句话:“有没有更加高效和优雅的方式直接在原始数据上进行操作。”,恰好data.table有一个新功能可以实现这个目的。

install.packages("data.table", repos="http://R-Forge.R-project.org")
# Needs version 1.8.1 from R-Forge.  Soon to be released to CRAN.

将您的数据存储在 DT 中:

> DT[, countcat:=.N, by=list(country,category)]     # add 'countcat' column
    category country countcat
 1:        1     RUS        3
 2:        2     GER        1
 3:        3     USA        2
 4:        1     RUS        3
 5:        1     USA        1
 6:        1     RUS        3
 7:        3     GER        1
 8:        3     USA        2
 9:        2     RUS        1
10:        2     USA        1

> DT[, weight:=countcat/.N, by=country]     # add 'weight' column
    category country countcat weight
 1:        1     RUS        3   0.75
 2:        2     GER        1   0.50
 3:        3     USA        2   0.50
 4:        1     RUS        3   0.75
 5:        1     USA        1   0.25
 6:        1     RUS        3   0.75
 7:        3     GER        1   0.50
 8:        3     USA        2   0.50
 9:        2     RUS        1   0.25
10:        2     USA        1   0.25

:= 是一个“旧”的功能,它通过引用向数据添加列。新功能是现在它可以按组工作。.N 是一个符号,它保存每个组中的行数。

这些操作具有内存效率,并且应该适用于大型数据;例如,1e81e9 行。

如果您不希望包括中间列 countcat,只需在之后将其删除。同样,这是一种高效的操作,无论表的大小如何,都可以立即工作(通过在内部移动指针)。

> DT[,countcat:=NULL]     # remove 'countcat' column
    category country weight
 1:        1     RUS   0.75
 2:        2     GER   0.50
 3:        3     USA   0.50
 4:        1     RUS   0.75
 5:        1     USA   0.25
 6:        1     RUS   0.75
 7:        3     GER   0.50
 8:        3     USA   0.50
 9:        2     RUS   0.25
10:        2     USA   0.25
> 

嘿,@MatthewDowle 我必须重新审视这个问题,因为我未能在我们的gentoo生产服务器上从r-project.org repo安装1.8.1版本(我只看到了mac和windows构建)。这个版本什么时候会发布到CRAN上,或者我能否获得1.8.1源代码并自行编译;或者您是否可能详细说明一下1.8.0版本的解决方案?非常感谢您的任何反馈。 - nikola
@nikola 安装失败了?你输入了什么?出现了什么错误信息?显然某处出现了错误,但我不是读心术士。 - Matt Dowle
@MatthewDowle 对不起,我应该表达得更清楚一些;我已经成功地在 OS X 上安装了它,使用 install.packages("data.table", repos="http://R-Forge.R-project.org"),但是同样的方法在一个 64 位的 gentoo linux 机器上却没有成功,出现了这个警告信息:_package 'data.table' is not available (for R version 2.15.0)_。 - nikola
@MatthewDowle 谢谢,但问题是 - 我已经在那台机器上运行了2.15.0版本。所以我想知道 - 这个http://download.r-forge.r-project.org/bin/可能没有Linux / Unix索引吗? - nikola
@MatthewDowle,最新的svn checkout在gentoo linux r 2.15.0下编译成功(R CMD INSTALL),谢谢! - nikola
显示剩余9条评论

2
我之前曾经问过一个类似的问题。data.table对于此非常好,特别是现在已经实现了按组:=的功能,不再需要自连接-就像上面所示的那样。基本R中最好的解决方案是ave()tapply()也可以使用。
这与上面的解决方案类似,使用ave()。然而,我强烈建议您查看data.table。
df$count <- ave(x = df$object, df$country, df$category, FUN = length)
df$weight <- ave(x = df$count, df$country, FUN = function(x) x/length(x))

1

我没有看到一行可读的方法来完成它。但是它可以非常紧凑。

# Use table to get the counts.
counts <- table(df[,2:3])
# Normalize the table
weights <- t(t(counts)/colSums(counts))
# Use 'matrix' selection by names.
df$weight <- weights[as.matrix(df[,2:3])]

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