使用“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
之前不需要的行。如果一个组的所有观察值都是NA
,sum(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值以用于所选列*? data.frame,data.table
如何在data.table中使用某些列名的字符向量选择列?[重复]