在同一个调用中总结所有组值和条件子集

75
我将用一个例子来阐述我的问题。
样本数据:
 df <- data.frame(ID = c(1, 1, 2, 2, 3, 5), A = c("foo", "bar", "foo", "foo", "bar", "bar"), B =     c(1, 5, 7, 23, 54, 202))

df
  ID   A   B
1  1 foo   1
2  1 bar   5
3  2 foo   7
4  2 foo  23
5  3 bar  54
6  5 bar 202

我想做的是按ID总结B列和A列为“foo”时B列的总和。我可以通过以下几个步骤完成这个任务:
require(magrittr)
require(dplyr)

df1 <- df %>%
  group_by(ID) %>%
  summarize(sumB = sum(B))

df2 <- df %>%
  filter(A == "foo") %>%
  group_by(ID) %>%
  summarize(sumBfoo = sum(B))

left_join(df1, df2)

  ID sumB sumBfoo
1  1    6       1
2  2   30      30
3  3   54      NA
4  5  202      NA

然而,我正在寻找更加优雅/更快的方法,因为我正在处理超过10GB的sqlite内存溢出数据。

require(sqldf)
my_db <- src_sqlite("my_db.sqlite3", create = T)
df_sqlite <- copy_to(my_db, df)

我想使用 "mutate" 来定义一个新的 "Bfoo" 列:
df_sqlite %>%
  mutate(Bfoo = ifelse(A=="foo", B, 0))

不幸的是,这在数据库方面行不通。

Error in sqliteExecStatement(conn, statement, ...) : 
  RS-DBI driver: (error in statement: no such function: IFELSE)

我认为问题在于您试图在Bfoo中混合字符和数字,这是不可能的。 - talat
@beginneR 这都是数字,代码在本地运行良好... - kevinykuo
抱歉我的原帖有些混乱。我会编辑它。 - kevinykuo
7
尝试使用if(A=="foo") B else 0 - dplyr会尝试将其转换为SQL case语句,这可能对您有帮助。 - hadley
如果有人想根据数学条件进行总结(而不是匹配字符串),可以参考以下链接:https://stackoverflow.com/questions/59199273/summarize-with-mathematical-conditions-in-dplyr/59199366#59199366 - TheSciGuy
显示剩余3条评论
4个回答

133

你可以在一个单独的dplyr语句中完成这两个求和:

df1 <- df %>%
  group_by(ID) %>%
  summarize(sumB = sum(B),
            sumBfoo = sum(B[A=="foo"]))

这是一个 data.table 版本:

library(data.table)

dt = setDT(df) 

dt1 = dt[ , .(sumB = sum(B),
              sumBfoo = sum(B[A=="foo"])), 
          by = ID]

dt1
   ID sumB sumBfoo
1:  1    6       1
2:  2   30      30
3:  3   54       0
4:  5  202       0

如果我能把所有东西都放进内存,这将起作用。我一定会记住的。问题是 SQL 不识别 [。抱歉我在问题中没有表述清楚! - kevinykuo
1
dplyr数据库文献可能对未来的参考有所帮助(如果您还没有看到它):http://cran.rstudio.com/web/packages/dplyr/vignettes/databases.html - eipi10
1
我怎么会在文档中错过那个?在处理数据时,尤其是在使用整洁数据时,dplyr的最有用的功能之一。 - r0bert
7
很棒的答案!我之前没有遇到过这种子集操作:df %>% summarize(function(colname[conditional_colname])) - vagabond
哇,从来不知道你可以这样子进行子集操作。非常感谢你! - ktyagi
@eipi10 我知道这个回答很老了,但是你是否知道data.table中用于条件A == "foo"的替代方法,就像你在sumBfoo中使用的那样? - PLY

38

将@hadley的评论整理成答案

df_sqlite %>%
  group_by(ID) %>%
  mutate(Bfoo = if(A=="foo") B else 0) %>%
  summarize(sumB = sum(B),
            sumBfoo = sum(Bfoo)) %>%
  collect

1
这里dplyr::collect函数的作用是什么? - Elliot
1
collect 强制在它之前的动词被求值。 - kevinykuo

16
如果您想要进行计数而不是汇总,那么答案会有些不同。代码的更改很小,尤其是在条件计数部分。

如果您想进行计数而不是汇总,则答案会稍有不同。代码更改较少,特别是在条件计数部分。

df1 <- df %>%
    group_by(ID) %>%
    summarize(countB = n(),
              countBfoo = sum(A=="foo"))

df1
Source: local data frame [4 x 3]

  ID countB countBfoo
1  1      2         1
2  2      2         2
3  3      1         0
4  5      1         0

-1

如果您想要计算行数而不是对它们求和,您可以将一个变量传递给该函数吗:

    df1 <- df %>%
group_by(ID) %>%
summarize(RowCountB = n(),
          RowCountBfoo = n(A=="foo"))

我在使用 n()nrow() 时都遇到了错误。


这是一个答案还是另一个问题? - Rekamanon

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