按多列对数据框进行分组

3

假设我有这个数据框:

data <- data.frame(foo = c(1, 1, 2, 2 ),
                   bar = c(10,10,10,20),
                   baz = c(1, 2, 3, 4 ),
                   qux = c(5, 6, 7, 8 ))

我希望按照 foobar 列进行分组,得到以下结果:

expected <- list(
  data.frame(foo = c(1, 1),
             bar = c(10, 10),
             baz = c(1, 2),
             qux = c(5, 6)),
  data.frame(foo = 2,
             bar = 10,
             baz = 3,
             qux = 7),
  data.frame(foo = 2,
             bar = 20,
             baz = 4,
             qux = 8)
)

我可以生成一个框架,每个组都有一行,但是我找不到一个MATCH函数;当给定一个带有列foo,bar,baz,qux的输入框架和一个带有列foo,bar的过滤器框架时,返回foo,bar单元格内容匹配的行。

groups <- unique(data[c("foo","bar")])
MATCH(data, groups[1,]) == expected[[1]]
MATCH(data, groups[2,]) == expected[[2]]
MATCH(data, groups[3,]) == expected[[3]]

或者更高级别的GROUP函数只返回一列列表,其中给定属性匹配:

GROUP(data, by=c("foo","bar")) == expected

我最接近的一次是:
out <- aggregate(. ~ foo + bar, data, list)

这里的单元格 bazqux 是列表:

> out
  foo bar  baz  qux
1   1  10 1, 2 5, 6
2   2  10    3    7
3   2  20    4    8
> class(out[,"baz"])
[1] "list"

每个组在out中是一行,但我如何展开它,使得out[1,]变成一个有两行的数据框,就像expected[[1]]一样?

3个回答

7
看起来你只需要使用split选项1:保留“foo”和“bar”组合的所有“级别”,即使结果是一个空的data.frame
> split(data, list(data$foo, data$bar))
$`1.10`
  foo bar baz qux
1   1  10   1   5
2   1  10   2   6

$`2.10`
  foo bar baz qux
3   2  10   3   7

$`1.20`
[1] foo bar baz qux
<0 rows> (or 0-length row.names)

$`2.20`
  foo bar baz qux
4   2  20   4   8

选项2:删除“foo”和“bar”的组合中的空“级别”,就像您在期望的输出中所做的那样。
> split(data, list(data$foo, data$bar), drop=TRUE)
$`1.10`
  foo bar baz qux
1   1  10   1   5
2   1  10   2   6

$`2.10`
  foo bar baz qux
3   2  10   3   7

$`2.20`
  foo bar baz qux
4   2  20   4   8

另一种类似的选项是 split(data, paste(data$foo, data$bar)) - eddi
@eddi,当然。我只是按照split的设计/文档使用它,当分割因素超过一个时,提供一个分组因素的listpaste解决了使用drop的需要。您是否认为pasteinteraction更有优势(这是split默认使用的)?我似乎记得在SO上看到过一个答案,其中pasteinteraction快得多,但现在找不到了。 - A5C1D2H2I1M1N2O1R2T1
据我所知,interaction 只是添加了很多额外的东西(最终执行 paste),而这些在这种特定情况下并不需要。 - eddi
谢谢,从手册上看,好像不是我需要的split - pascal

3

plyr中的dlply正是为了这个目的而设计的:

require(plyr)    
dlply( data , .(foo , bar) )

$`1.10`
  foo bar baz qux
1   1  10   1   5
2   1  10   2   6

$`2.10`
  foo bar baz qux
1   2  10   3   7

$`2.20`
  foo bar baz qux
1   2  20   4   8

我从来没有完全掌握“plyr”,但是因为分享替代方案而给你点赞! - A5C1D2H2I1M1N2O1R2T1
@AnandaMahto 谢谢。我在等待eddi发布data.table的解决方案.... :-) - Simon O'Hanlon
我看到了你的表情符号,但是data.table会从这样的结构中受益吗?我认为不会。我猜测key在很大程度上已经解决了这个问题。 - A5C1D2H2I1M1N2O1R2T1
@AnandaMahto 是的,我也这么认为。这只是一个玩笑,因为我看到 eddi 访问了这个问题,他是 data.table 的高手。 - Simon O'Hanlon
1
@SimonO101 - 对于这个问题使用data.table有点过度了 - 你可以,但我不认为有必要 - 在data.table框架中适当的做法应该是执行此步骤,因为我想不出有太多情况值得这样做。 - eddi

0

试试这个,它类似于 @Ananda 的解决方案,但使用了 interaction

> split(data,interaction(data$foo,data$bar))
$`1.10`
  foo bar baz qux
1   1  10   1   5
2   1  10   2   6

$`2.10`
  foo bar baz qux
3   2  10   3   7

$`1.20`
[1] foo bar baz qux
<0 rækker> (eller 0-længde row.names)

$`2.20`
  foo bar baz qux
4   2  20   4   8

> split(data,interaction(data$foo,data$bar), drop=TRUE)
$`1.10`
  foo bar baz qux
1   1  10   1   5
2   1  10   2   6

$`2.10`
  foo bar baz qux
3   2  10   3   7

$`2.20`
  foo bar baz qux
4   2  20   4   8

2
你认为这个答案与@AnandaMahto的答案明显相同,但使用不同的方法来创建分组变量,是否最好作为对Ananda答案的评论或编辑呢? - Simon O'Hanlon
@Simono101 我在他发布答案的时候发布了一个初始版本。然后我在看到他的答案后进行了编辑...那么...我该怎么办... - Thomas
@SimonO101,更好的方法是查看split.default的代码或者"split"帮助页面中关于f的解释。f参数正好是interaction - A5C1D2H2I1M1N2O1R2T1
@Thomas 这取决于你自己。我只是在问问题而已。我喜欢两个答案 :-) - Simon O'Hanlon

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