【问题标题】:Selecting non `NA` values from duplicate rows with `data.table` -- when having more than one grouping variable使用“data.table”从重复行中选择非“NA”值——当有多个分组变量时
【发布时间】:2022-01-05 15:12:54
【问题描述】:

我想在数据框中保留不同的行,并使用一种算法选择每个组的 last 值(如 dplyr::distinct() 默认情况下所做的那样),但前提是它不是 NA。我见过 this great answer on SO 依赖于 data.table,但我无法将其扩展到具有多个分组变量的数据。

为了演示这个问题,我从一个可行的最小示例开始,然后将其放大。所以首先,考虑以下数据:

library(tibble)

df_id_and_type <-
  tibble::tribble(
        ~id, ~type,
          1,   "A",
          1,    NA,
          2,   "B",
          3,   "A",
          3,    NA,
          3,   "D",
          3,    NA,
          4,    NA,
          4,   "C",
          5,   "A",
          6,    NA,
          6,   "B",
          6,    NA
        )

我想通过选择最后一个值来获得每个 id 的不同 type 值,除非它是 NA。如果最后一个NA,则向上直到有非NA。所以this answer 向我们展示了如何使用data.table

library(data.table)

dt_id_and_type        <- as.data.table(df_id_and_type)
dt_id_and_type$typena <- is.na(dt_id_and_type$type)
setorderv(dt_id_and_type, c("typena","id"), order = c(-1, 1))
dt_id_and_type[!duplicated(id, fromLast = TRUE), c("id", "type"), with = FALSE]
#>    id type
#> 1:  1    A
#> 2:  2    B
#> 3:  3    D
#> 4:  4    C
#> 5:  5    A
#> 6:  6    B

但是如果我们有多个分组变量(即不仅仅是id)怎么办?在以下示例中,我添加了一个 year 变量:

df_id_year_and_type <-
  df_id_and_type %>%
  add_column(year = c(2002, 2002, 2008, 2010, 2010, 2010, 2013, 2020, 2020, 2009, 2010, 2010, 2012), 
             .before = "type")

df_id_year_and_type
#> # A tibble: 13 x 3
#>       id  year type 
#>    <dbl> <dbl> <chr>
#>  1     1  2002 A    
#>  2     1  2002 <NA> 
#>  3     2  2008 B    
#>  4     3  2010 A    
#>  5     3  2010 <NA> 
#>  6     3  2010 D    
#>  7     3  2013 <NA> 
#>  8     4  2020 <NA> 
#>  9     4  2020 C    
#> 10     5  2009 A    
#> 11     6  2010 <NA> 
#> 12     6  2010 B    
#> 13     6  2012 <NA>

我的预期输出是:

## # A tibble: 8 x 3
##      id  year type 
##   <dbl> <dbl> <chr>
## 1     1  2002 A    
## 2     2  2008 B    
## 3     3  2010 D    
## 4     3  2013 NA   # for id 3 in year 2013 there was only `NA`, so that's what we get
## 5     4  2020 C    
## 6     5  2009 A    
## 7     6  2010 B    
## 8     6  2012 NA   # same as comment above

知道如何将适用于 1-grouping-var 情况的解决方案扩展到当前数据吗?前两行代码很简单:

dt_id_year_and_type        <- as.data.table(df_id_year_and_type)
dt_id_year_and_type$typena <- is.na(dt_id_year_and_type$type)
setorderv(dt_id_year_and_type, c("typena","id"), order = c(-1, 1)) # <--- how to account for `year`?
dt_id_year_and_type[!duplicated(id, fromLast = TRUE), c("id", "type"), with = FALSE] # <--- here too...

【问题讨论】:

    标签: r data.table distinct-values


    【解决方案1】:

    我会提出这个解决方案,您可以在其中排除 unique 之前不需要的行。如果一个组的所有观察值都是NAsum(is.na(x)) / .N 等于 1,我们从那里开始

    library(tibble)
    library(data.table)
    
    df_id_and_type <-
      tibble::tribble(
        ~id, ~type,
        1,   "A",
        1,    NA,
        2,   "B",
        3,   "A",
        3,    NA,
        3,   "D",
        3,    NA,
        4,    NA,
        4,   "C",
        5,   "A",
        6,    NA,
        6,   "B",
        6,    NA
      )
    
    
    df_id_year_and_type <-
      df_id_and_type %>%
      add_column(year = c(2002, 2002, 2008, 2010, 2010, 2010, 2013, 2020, 2020, 2009, 2010, 2010, 2012), 
                 .before = "type")
    
    # convert to data.table
    dt_id_year_and_type <- as.data.table(df_id_year_and_type)
    
    # define grouping vars
    grouping_vars <- c("id", "year")
    
    # are all types na for a group?
    dt_id_year_and_type[, na_ratio := sum(is.na(type)) / .N, 
                        by = c(grouping_vars)]
    
    # remove all lines that are NA, except they are from a group in which all 
    # observations are NA
    dt_id_year_and_type <- dt_id_year_and_type[na_ratio == 1 | !is.na(type)]
    
    # sort correctly
    setorderv(dt_id_year_and_type, grouping_vars) 
    dt_id_year_and_type
    #>    id year type  na_ratio
    #> 1:  1 2002    A 0.5000000
    #> 2:  2 2008    B 0.0000000
    #> 3:  3 2010    A 0.3333333
    #> 4:  3 2010    D 0.3333333
    #> 5:  3 2013 <NA> 1.0000000
    #> 6:  4 2020    C 0.5000000
    #> 7:  5 2009    A 0.0000000
    #> 8:  6 2010    B 0.5000000
    #> 9:  6 2012 <NA> 1.0000000
    
    # keep only the last observation of each group
    dt_unique <- unique(dt_id_year_and_type, by = grouping_vars, fromLast = TRUE)
    
    remove no longer needed helper column
    dt_unique[, na_ratio := NULL]
    dt_unique
    #>    id year type
    #> 1:  1 2002    A
    #> 2:  2 2008    B
    #> 3:  3 2010    D
    #> 4:  3 2013 <NA>
    #> 5:  4 2020    C
    #> 6:  5 2009    A
    #> 7:  6 2010    B
    #> 8:  6 2012 <NA>
    

    【讨论】:

      【解决方案2】:

      另一种可能的解决方案:

      library(tidyverse) 
      
      df_id_year_and_type %>% 
        group_by(id, year) %>% 
        fill(type, .direction = "downup") %>% 
        summarise(type = last(type), .groups = "drop")
      
      #> # A tibble: 8 × 3
      #>      id  year type 
      #>   <dbl> <dbl> <chr>
      #> 1     1  2002 A    
      #> 2     2  2008 B    
      #> 3     3  2010 D    
      #> 4     3  2013 <NA> 
      #> 5     4  2020 C    
      #> 6     5  2009 A    
      #> 7     6  2010 B    
      #> 8     6  2012 <NA>
      

      【讨论】:

        【解决方案3】:

        这里有一些基于 data.table 的解决方案。

        setDT(df_id_year_and_type)
        

        方法一

        na.omit(df_id_year_and_type, cols="type") 基于列 type 删除 NA 行。 unique(df_id_year_and_type[, .(id, year)], fromLast=TRUE) 查找所有组。 通过加入它们(使用最后一个匹配:mult="last"),我们获得了所需的输出。

        na.omit(df_id_year_and_type, cols="type"
                )[unique(df_id_year_and_type[, .(id, year)], fromLast=TRUE), 
                  on=c('id', 'year'), 
                  mult="last"]
        
        #       id  year   type
        #    <num> <num> <char>
        # 1:     1  2002      A
        # 2:     2  2008      B
        # 3:     3  2010      D
        # 4:     3  2013   <NA>
        # 5:     4  2020      C
        # 6:     5  2009      A
        # 7:     6  2010      B
        # 8:     6  2012   <NA>
        

        方法2

        df_id_year_and_type[df_id_year_and_type[, .I[which.max(cumsum(!is.na(type)))], .(id, year)]$V1,]
        

        方法3

        (可能会因为[ 开销而变慢)

        df_id_year_and_type[, .SD[which.max(cumsum(!is.na(type)))], .(id, year)]
        

        【讨论】:

          【解决方案4】:
          library(dplyr)
          

          一个简单易读的基本案例示例是

          df_id_and_type %>% filter(!is.na(type)) %>%  
                             filter(id != lead(id) | id == max(id))
          

          扩展到第二个条件

          df_id_year_and_type %>% filter(!is.na(type)) %>%  
                                  filter((id != lead(id) | id == max(id)) & 
                                          (year != lead(year) | year == max(year)))
          

          它清晰易懂。如果您希望保留不同的分组而没有结果,您可以合并不同的分组或在过滤器中插入另一个 OR 子句

          【讨论】:

            【解决方案5】:

            为什么不使用简单的最大值?

            setDT(df_id_year_and_type)
            df_id_year_and_type[,max(type, na.rm=T), by=.(id, year)]
            

            当只有 NA 并且选项 na.rm 为 TRUE 时,您会收到警告,但您可以轻松地抑制它:

            df_id_year_and_type[,suppressWarnings(max(type, na.rm=T)), by=.(id, year)]
            

            或者,测试所有值是否为 NA:

            df_id_year_and_type[,ifelse(all(is.na(type)), NA_character_, max(type, na.rm=T)), by=.(id, year)]
            

            【讨论】:

              猜你喜欢
              • 2017-03-11
              • 1970-01-01
              • 2020-08-04
              • 2021-05-27
              • 1970-01-01
              • 2017-11-20
              • 1970-01-01
              • 1970-01-01
              • 2021-01-03
              相关资源
              最近更新 更多