使用“data.table”从重复行中选择非“NA”值——当有多个分组变量时

Posted

技术标签:

【中文标题】使用“data.table”从重复行中选择非“NA”值——当有多个分组变量时【英文标题】:Selecting non `NA` values from duplicate rows with `data.table` -- when having more than one grouping variable 【发布时间】: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...

【问题讨论】:

【参考方案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)]

【讨论】:

以上是关于使用“data.table”从重复行中选择非“NA”值——当有多个分组变量时的主要内容,如果未能解决你的问题,请参考以下文章

R语言dataframe(data.table)使用用最近的前一个非NA值向前填充缺失值NA实战

根据组值填写NA [重复]

如何替换表*中的NA值以用于所选列*? data.frame,data.table

如何在data.table中使用某些列名的字符向量选择列?[重复]

在双错误类型的连接列中使用 NA 的 data.table 内部/外部连接?

在双错误类型的连接列中使用 NA 的 data.table 内部/外部连接?