【问题标题】:Tidyverse: filtering n largest groups in grouped dataframeTidyverse:过滤分组数据框中的 n 个最大组
【发布时间】:2018-09-27 07:55:06
【问题描述】:

我想根据count过滤n个最大的组,然后对过滤后的dataframe做一些计算

这是一些数据

Brand <- c("A","B","C","A","A","B","A","A","B","C")
Category <- c(1,2,1,1,2,1,2,1,2,1)
Clicks <- c(10,11,12,13,14,15,14,13,12,11)
df <- data.frame(Brand,Category,Clicks)

|Brand | Category| Clicks|
|:-----|--------:|------:|
|A     |        1|     10|
|B     |        2|     11|
|C     |        1|     12|
|A     |        1|     13|
|A     |        2|     14|
|B     |        1|     15|
|A     |        2|     14|
|A     |        1|     13|
|B     |        2|     12|
|C     |        1|     11|

这是我的预期输出。我想按计数过滤掉两个最大的品牌,然后找到每个品牌/类别组合中的平均点击次数

|Brand | Category| mean_clicks|
|:-----|--------:|-----------:|
|A     |        1|        12.0|
|A     |        2|        14.0|
|B     |        1|        15.0|
|B     |        2|        11.5|

我认为可以用这样的代码实现(但不能)

df %>%
  group_by(Brand, Category) %>%
  top_n(2, Brand) %>% # Largest 2 brands by count
  summarise(mean_clicks = mean(Clicks))

编辑:理想的答案应该能够用于数据库表以及本地表

【问题讨论】:

  • 您可能想明确您打算使用的 DBMS,并非所有 DBMS 都支持窗口函数,dbplyr 可能需要它们进行某些翻译。我相信@Ronak 的答案应该适用于支持窗口函数但由于add_count 而对其他人失败的数据库,解决方法是将子查询分组和计数,将其加入原始查询并继续。
  • 实际上@Paul 的解决方案可能会立即起作用,因为它似乎就是这样做的

标签: r dplyr top-n


【解决方案1】:

另一个使用join 过滤数据框的dplyr 解决方案:

library(dplyr)

df %>%
  group_by(Brand) %>%
  summarise(n = n()) %>%
  top_n(2) %>% # select top 2
  left_join(df, by = "Brand") %>% # filters out top 2 Brands
  group_by(Brand, Category) %>%
  summarise(mean_clicks = mean(Clicks))

# # A tibble: 4 x 3
# # Groups:   Brand [?]
#   Brand Category mean_clicks
#   <fct>    <dbl>       <dbl>
# 1 A            1        12  
# 2 A            2        14  
# 3 B            1        15  
# 4 B            2        11.5

【讨论】:

  • +1 因为即使它比某些更长,我相信它也是唯一适用于dbplyr 支持的所有 DBMS 的解决方案。 arrange(desc(n)) %&gt;% head(2) 可以使用 top_n 替换
【解决方案2】:

不同的dplyr 解决方案:

df %>%
  group_by(Brand) %>%
  mutate(n = n()) %>%
  ungroup() %>%
  mutate(rank = dense_rank(desc(n))) %>%
  filter(rank == 1 | rank == 2) %>%
  group_by(Brand, Category) %>%
  summarise(mean_clicks = mean(Clicks))

# A tibble: 4 x 3
# Groups:   Brand [?]
  Brand Category mean_clicks
  <fct>    <dbl>       <dbl>
1 A           1.        12.0
2 A           2.        14.0
3 B           1.        15.0
4 B           2.        11.5

或简化版本(基于@camille 的建议):

df %>%
  group_by(Brand) %>%
  mutate(n = n()) %>%
  ungroup() %>%
  filter(dense_rank(desc(n)) < 3) %>%
  group_by(Brand, Category) %>%
  summarise(mean_clicks = mean(Clicks))

【讨论】:

  • 我建议的唯一改进是,由于您想要前 2 个等级,而不是 rank == 1 | rank == 2,您可以使用 rank &lt;= 2rank &lt; 3 使其更干净。这样,您还可以轻松跳过 mutate 行并在 filter 调用中创建排名
【解决方案3】:

编辑

根据更新后的问题,我们可以先添加一个计数列,只过滤顶部的n 组计数,然后group_by BrandCategory 以找到每个组的mean

df %>%
  add_count(Brand, sort = TRUE) %>%
  filter(n %in% head(unique(n), 2)) %>%
  group_by(Brand, Category) %>%
  summarise(mean_clicks = mean(Clicks))


#   Brand Category mean_clicks
#   <fct>    <dbl>       <dbl>
#1 A            1        12  
#2 A            2        14  
#3 B            1        15  
#4 B            2        11.5

原答案

我们可以group_byBrand按组进行所有计算,然后按top_n过滤顶部组

library(dplyr)
df %>%
  group_by(Brand) %>%
  summarise(n = n(), 
            mean = mean(Clicks)) %>%
  top_n(2, n) %>%
  select(-n)

#  Brand  mean
#  <fct> <dbl>
#1  A      12.8
#2  B      12.7

【讨论】:

  • 感谢您的回答 Ronak,但我可以看到这不起作用的情况。我将编辑我的问题
  • 使用filter(n %in% head(unique(n), 2)) 是一个非常聪明的想法。但我忍不住想一定有更快/更优雅的解决方案?
  • @Shinobi_Atobe 我同意。即使我觉得应该有更好的东西,但现在想不出任何东西。
  • 如果根据dense_rankas @tmfmnk did)进行过滤,可以跳过add_count中的排序,使用df %&gt;% add_count(Brand) %&gt;% filter(dense_rank(desc(n)) %in% 1:2)。不知道这是否“更快/更优雅”..;)
  • @Henrik yes.definitely 但它不会减少任何步骤。只是用另一个替换一个。
【解决方案4】:

的想法是获取按Brands 分组的计数并过滤前两个(按降序排序后)。然后我们与原始数据框合并,找到(Brand, Category)分组的均值

library(data.table)

#Convert to data.table
dt1 <- setDT(df)

dt1[dt1[, .(cnt = .N), by = Brand][
             order(cnt, decreasing = TRUE), .SD[1:2]][,cnt := NULL], 
                   on = 'Brand'][, .(means = mean(Clicks)), by = .(Brand, Category)][]

给出,

   Brand Category means
1:     A        1  12.0
2:     A        2  14.0
3:     B        2  11.5
4:     B        1  15.0

【讨论】:

    【解决方案5】:

    这个方法怎么样,使用table,来自base R -

    df %>%
      filter(Brand %in% names(tail(sort(table(Brand)), 2))) %>%
      group_by(Brand, Category) %>%
      summarise(mean_clicks = mean(Clicks))
    
    # A tibble: 4 x 3
    # Groups:   Brand [?]
      Brand Category mean_clicks
      <chr>    <dbl>       <dbl>
    1 A         1.00        12.0
    2 A         2.00        14.0
    3 B         1.00        15.0
    4 B         2.00        11.5
    

    【讨论】:

    • names(tail(sort(table(x)),2)) 更紧凑一点,is_in_top_group &lt;- function(x,n) x %in% names(tail(sort(table(x)),n)) 函数可以方便重用。
    • @Moody_Mudskipper 谢谢!不知道我是怎么错过的。编辑了我的答案。
    【解决方案6】:

    与上面略有不同。只是因为我不喜欢使用大型数据集的连接。有些人可能不喜欢我制作并删除了一个小数据框,抱歉:(

    df %>% count(Brand) %>% top_n(2,n) -> Top2
    df %>% group_by(Brand, Category) %>% 
    filter(Brand %in% Top2$Brand) %>% 
    summarise(mean_clicks = mean(Clicks))
    remove(Top2)
    

    【讨论】:

      猜你喜欢
      • 2019-10-15
      • 2017-12-13
      • 2016-12-12
      • 1970-01-01
      • 1970-01-01
      • 2023-02-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多