使用条件对dplyr的summarise进行汇总

3

I have this data frame:

library(dplyr)
library(tidyr)

data <- tribble(
  ~Date, ~A1, ~A2,~B1,~B2,
  as.Date("2019-01-01"), 20, 10,20, 10,
  as.Date("2019-01-01"), 20 ,5,20,5,
  as.Date("2019-01-01"), 10, 2,10,20,
  as.Date("2019-01-01"), 20, 60,0,0,
  as.Date("2019-01-01"), 30, 4,20,5,
  as.Date("2019-02-01"), 0, 0,16,8,
  as.Date("2019-02-01"), 0, 0,0,40,
  as.Date("2019-02-01"), 0, 0,4,2,
  as.Date("2019-02-01"), 4, 8,10,6,
  as.Date("2019-02-01"), 6, 3,0,0,
  as.Date("2019-03-01"), 20, 8,23,9,
  as.Date("2019-03-01"), 60, 4,0,0,
  as.Date("2019-03-01"), 4, 2,8,3,
  as.Date("2019-03-01"), 0, 6,10,0
)

对于每一天,我想计算(A1-B1)和(A2-B2)的平均值。
对于A1-B1,我只想使用A1>B1且A1>0,B1>0的行。
对于A2-B2,我只想使用A2>B2且A2>0,B2>0的行。

这是我尝试过的:

data_mean = data %>%
    group_by(Date) %>%
    dplyr::summarise(
      mean_1 = mean(A1[A1>=B1 & A1>0 & B1>0] - B1[A1>=B1 & A1>0 & B1>0]),
      mean_2 = mean(A2[A2>=B2 & A2>0 & B2>0] - B2[A2>=B2 & A2>0 & B2>0]))

有没有一种方法可以在使用summarise函数时使用filter函数?或者有更聪明的方法来应用我的代码吗?
3个回答

5

如果我们不想重复使用表达式,可以创建临时列。此外,在across中也可以对多个列执行此操作。

library(dplyr)
library(stringr)
data %>% 
   group_by(Date) %>% 
   summarise(across(c(A1, A2), ~ {
       tmp <- get(str_replace(cur_column(), 'A', 'B'))
       i1 <- . >= tmp & . > 0 & tmp >0
       mean(.[i1] - tmp[i1])})) %>%
   rename_with(~ str_replace(., 'A', 'mean_'), -Date)

-输出

# A tibble: 3 x 3
#  Date       mean_1 mean_2
#* <date>      <dbl>  <dbl>
#1 2019-01-01    2.5      0
#2 2019-02-01  NaN        2
#3 2019-03-01  NaN      NaN

另一个选项是使用 pivot_longer 将数据转换为“长”格式,然后进行 filter/group_by/summarise 操作,最后使用 pivot_wider 将其转换回“宽”格式。

library(tidyr)
data %>% 
   pivot_longer(cols = A1:B2, names_to = c('.value', 'grp'), 
       names_sep = '(?<=[A-Z])(?=\\d)') %>% 
   filter(A >= B, A > 0, B > 0) %>% 
   group_by(Date, grp = str_c('mean_', grp)) %>%
   summarise(mean = mean(A - B), .groups = 'drop') %>% 
   pivot_wider(names_from = grp, values_from = mean) %>%
   complete(Date = unique(data$Date))
# A tibble: 3 x 3
#  Date       mean_1 mean_2
#  <date>      <dbl>  <dbl>
#1 2019-01-01    2.5      0
#2 2019-02-01   NA        2
#3 2019-03-01   NA       NA

1
@john22,你能展示一下packageVersion('dplyr')吗?我使用了packageVersion('dplyr') [1] ‘1.0.4’ - akrun
@AnoushiravanR 那样做是行不通的,因为如果你使用 & 或 |,它仍然会同时用于两种情况,结果可能是比预期少几行或多几行。如果你正在使用 map,那么可能要使用以下代码:data %>% select(-Date) %>% split.default(str_remove(names(.), '\\D+')) %>% map_dfr(~ .x %>% bind_cols(., Date = data$Date) %>% filter(.[[1]] >= .[[2]] & if_all(-Date, ~ . > 0)) %>% group_by(Date) %>% summarise(mean = mean(cur_data()[[1]] - cur_data()[[2]])) ) %>% complete(Date = unique(data$Date)) - akrun
1
@akrun,我从来没有想过|会增加元素。我想我得注意一下这个问题。非常好的观点,谢谢。 - Anoushiravan R
1
@AnoushiravanR 的原因是在filter步骤中有一个日期被删除了。通过使用 complete,可以恢复数据中的原始唯一日期,并且该日期显示为 NA,对应于 mean 值。 - akrun
1
非常感谢亲爱的@akrun的解释。这似乎是一个非常有用的函数。 - Anoushiravan R
显示剩余7条评论

5

更新: 感谢akrun!!!现在它能正常工作了!

data %>%  
  filter(if_all(where(is.numeric),  ~ . > 0)) %>% 
  mutate(i1 = A1 >= B1, i2 = A2 >= B2) %>% 
  group_by(Date) %>% 
  summarise(mean1 = mean(A1[i1] - B1[i1]), mean2 = mean(A2[i2] - B2[i2]))

输出:

  Date       mean1 mean2
  <date>     <dbl> <dbl>
1 2019-01-01   2.5     0
2 2019-02-01 NaN       2
3 2019-03-01 NaN     NaN

第一版 我基本上采用了akrun的解决方案。但是无法处理负数。

data %>% 
  group_by(Date) %>% 
  filter_if(is.numeric, all_vars((.) != 0)) %>% 
  filter(A1>=B1 | A2>=B2) %>% 
  summarise(mean1 = mean(A1-B1),
            mean2 = mean(A2-B2))

输出结果:

  Date       mean1 mean2
  <date>     <dbl> <dbl>
1 2019-01-01   2.5 -4.75
2 2019-02-01  -6    2   

1
这里的问题也在于第二个过滤器中的 |。它可能会增加元素的数量,因为它检查其中一个是否为真,而 OP 想要 A1-B1 只使用 A1>=B1,同样地,A2-B2 也是如此。 - akrun
我刚才看到了你对Anoushiravan R的评论。谢谢你,akrun。 - TarJae
1
你可以将其更改为 data %>% filter(if_all(is.numeric, ~ . > 0)) %>% mutate(i1 = A1 >= B1, i2 = A2 >= B2) %>% group_by(Date) %>% summarise(mean1 = mean(A1[i1] - B1[i1]), mean2 = mean(A2[i2] - B2[i2])),其中的 if_all 是最新版本的函数。 - akrun

2
我希望这段代码能够帮助你得到你想要的输出结果:
library(dplyr)


data %>%
  group_by(Date) %>%
  filter(A1 >= B1 & B1 > 0 | A2 >= B2 & B2 > 0) %>%
  mutate(sub1 = A1 - B1, 
         sub2 = A2 - B2) %>% 
  summarise(mean1 = mean(sub1), 
            mean2 = mean(sub2))

# A tibble: 2 x 3
  Date       mean1 mean2
  <date>     <dbl> <dbl>
1 2019-01-01   2.5 -4.75
2 2019-02-01  -6    2 

不幸的是,这不是期望的输出。 - john22
我在筛选部分出了些问题,所以进行了一些微小的修改,我认为这就是你要找的输出结果。 - Anoushiravan R

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