【问题标题】:How to filter a grouped dataframe with a conditional statement using dplyr?如何使用 dplyr 过滤带有条件语句的分组数据框?
【发布时间】:2020-03-29 02:17:32
【问题描述】:

我想使用条件过滤使用 dplyr 的数据框。我要测试的条件是国家-年份组合是否有两个版本。

df <- data.frame(country = c("country1", "country2", "country1", "country2", "country3"), year = rep(2011,5), version = c("versionA", "versionA", "versionB", "versionB", "versionB"))

这是我在查看here后尝试的:

df %>%
     group_by(country, year) %>%
     {if unique(version)==1 . else filter(version == "versionA")}

我希望得到的是一个如下所示的数据框:

country     year     version

country1    2011     versionA
country2    2011     versionA
country3    2011     versionB

【问题讨论】:

    标签: r filter dplyr conditional-statements


    【解决方案1】:

    要计算唯一值的数量,我们可以使用n_distinct 并根据它过滤行。

    library(dplyr)
    
    df %>%
      group_by(country, year) %>%
      filter(if(n_distinct(version) == 2) version == 'versionA' else TRUE)
    
    
    #  country   year version 
    #  <fct>    <dbl> <fct>   
    #1 country1  2011 versionA
    #2 country2  2011 versionA
    #3 country3  2011 versionB
    

    【讨论】:

    • 您能谈谈过滤行末尾的 else TRUE 的作用吗?
    • @TeaTree TRUE 表示如果version 中唯一值的数量不等于2,它将选择组中的所有行。如果等于2,它将仅选择行其中version == 'versionA'
    【解决方案2】:

    按“国家”、“年份”、filter 分组后,如果不同元素的数量大于 1,则返回“版本A”,否则返回第一个元素

    library(dplyr)
    df %>%
      group_by(country, year)  %>% 
      filter((n_distinct(version)  > 1 & version == 'versionA')|row_number() == 1)
    # A tibble: 3 x 3
    # Groups:   country, year [3]
    #  country   year version 
    #  <fct>    <dbl> <fct>   
    #1 country1  2011 versionA
    #2 country2  2011 versionA
    #3 country3  2011 versionB
    

    或者这可以添加到if/else

    df %>%
        group_by(country, year) %>%
        filter(if(n_distinct(version) > 1) version == 'versionA'
           else row_number() ==1)
    # A tibble: 3 x 3
    # Groups:   country, year [3]
    #  country   year version 
    #  <fct>    <dbl> <fct>   
    #1 country1  2011 versionA
    #2 country2  2011 versionA
    #3 country3  2011 versionB
    

    或者另一个选项是arrange

    df %>% 
        arrange(country, year, version != 'versionA') %>% 
        group_by(country, year) %>% 
        slice(1)
    

    summarize

    df %>%
        group_by(country, year) %>%
        summarise(version = if(n_distinct(version) > 1) 'versionA' else first(version))
    

    或使用data.table

    library(data.table)
    setDT(df)[, .SD[if(n_distinct(version) > 1) version == 'versionA' 
              else 1], .(country, year)]
    

    【讨论】:

      【解决方案3】:

      Base R 单线感谢 (@akrun):

      df[!(duplicated(df[1:2])),]
      

      Base R 单线:

      df[!(duplicated(df$country, df$year)),]
      

      Tidyverse 解决方案:

      library(tidyverse)
      df %>%
        filter(!(duplicated(country, year)))
      

      更通用的基础 R 解决方案:

      # Create a counter of versions for each year and country: 
      
      df$tmp <- with(lapply(df, function(x){if(is.factor(x)){as.character(x)}else{x}}),
                     ave(version, paste0(country, year), FUN = seq.int))
      
      # Subset the dataframe to hold only the first record for each year/country: 
      
      df[which(df$tmp == 1), ]
      

      更通用的 tidyverse 解决方案:

      df %>%
        arrange(version) %>% 
        filter(!(duplicated(country, year)))
      

      【讨论】:

      • 这是一个很好的 line_liner,可以使用df[!duplicated(df[1:2]),] 使其更紧凑,但这是基于发布的问题,即如果行的顺序发生变化,这会产生不同的结果
      • @akrun 是的,我相信这不会很好地概括。感谢您的提示,我会将其添加到我的解决方案中 (+1)。
      • 我认为你需要先order 然后它应该可以工作
      • df %&gt;% arrange(country, year, version != 'versionA') %&gt;% distinct(country, year, .keep_all = TRUE) 或您的filter( 版本
      猜你喜欢
      • 2021-06-19
      • 2021-10-25
      • 1970-01-01
      • 1970-01-01
      • 2019-06-10
      • 2021-07-10
      • 2019-03-29
      • 1970-01-01
      • 2016-02-16
      相关资源
      最近更新 更多