根据日期列按组获取最新的非 NA 值
Get the latest non-NA value based on date column by group
我有一个包含 country_name
、date
和几列的数据框:column_1
、column_2
和 column_3
。我正在尝试根据跨多个列的日期提取最新记录。
数据框如下所示:
| country_name | date | column_1| column_2| column_3|
| US | 2016-11-02 | 7.5 | NA | NA |
| US | 2017-09-12 | NA | NA | 9 |
| US | 2017-09-19 | NA | 8 | 10 |
| US | 2020-02-10 | 10 | NA | NA |
| US | 2021-03-10 | NA | NA | 7.3 |
| US | 2021-05-02 | NA | 3 | NA |
| UK | 2016-11-02 | NA | 2 | NA |
| UK | 2017-09-12 | 0.5 | 3 | NA |
.
.
对于美国,所需的输出是:
| country_name | column_1| column_2| column_3|
| US | 10 | 3 | 7.3 |
对于column_1
,最晚的值为10(日期:2020-02-10),
column_2
为 3(日期:2021-05-02),column_3
为 7.3(日期:2021-03-10)。我的目标是在多个国家/地区应用此逻辑。我该如何实现?
library(dplyr)
library(tidyr)
df1 %>%
mutate(date = as.Date(date)) %>%
group_by(country_name) %>%
arrange(date) %>%
select(-date) %>%
fill(everything()) %>%
slice(n())
#> # A tibble: 2 x 4
#> # Groups: country_name [2]
#> country_name column_1 column_2 column_3
#> <chr> <dbl> <int> <dbl>
#> 1 UK 0.5 3 NA
#> 2 US 10 3 7.3
数据:
read.table(text = "country_name date column_1 column_2 column_3
US 2016-11-02 7.5 NA NA
US 2017-09-12 NA NA 9
US 2017-09-19 NA 8 10
US 2020-02-10 10 NA NA
US 2021-03-10 NA NA 7.3
US 2021-05-02 NA 3 NA
UK 2016-11-02 NA 2 NA
UK 2017-09-12 0.5 3 NA",
header = T, stringsAsFactors = F) -> df1
更新:
感谢@Darren Tsai 处理警告:
Warning: Problem while computing `..1 = across(-country_name, ~parse_number(.)).
i 1 parsing failure. row col expected actual 1 -- a number NA NA
添加这行代码:
mutate(across(-country_name, ~str_trim(str_replace_all(., 'NA', ''))))
library(tidyverse)
library(lubridate)
df1 %>%
mutate(date = ymd(date)) %>%
group_by(country_name) %>%
arrange(date, .by_group = TRUE) %>%
summarise(across(starts_with("column"), ~paste(rev(.), collapse = ' '))) %>%
mutate(across(-country_name, ~str_trim(str_replace_all(., 'NA', '')))) %>%
mutate(across(-country_name, ~parse_number(.)))
country_name column_1 column_2 column_3
<chr> <dbl> <dbl> <dbl>
1 UK 0.5 3 NA
2 US 10 3 7.3
第一个回答:
我们可以这样做:
- 如有必要,使用
lubridate
. 中的 ymd()
函数将 date
列转换为日期 class
- 分组
country_name
- 现在出现了我们对 col1 col2...等使用
across
的技巧,并使用 paste(rev(.)....
反向折叠以使最后一个值排在首位。这对下一步很重要。
- 使用
readr
包中的 parse_number()
将提取第一个数字!
library(dplyr)
library(lubridate)
library(readr)
df %>%
mutate(date = ymd(date)) %>%
group_by(country_name) %>%
arrange(date, .by_group = TRUE) %>%
summarise(across(starts_with("column"), ~paste(rev(.), collapse = ' '))) %>%
mutate(across(-country_name, parse_number))
country_name column_1 column_2 column_3
<chr> <dbl> <dbl> <dbl>
1 UK 0.5 3 NA
2 US 10 3 7.3
您可以 na.omit
和 rev
删除每一列并取第一个 el
元素。然后rbind
。注意右边的order
,如果是as.Date
格式的。
by(transform(dat, date=as.Date(date)), dat$country_name, \(x) {
cbind(x[1, 1, drop=FALSE],
lapply(x[order(x$date), 3:5], \(z) {
z <- el(rev(na.omit(z)))
ifelse(length(z) == 1, z, NA_real_)
}))
}) |> c(make.row.names=FALSE) |> do.call(what=rbind)
# country_name column_1 column_2 column_3
# 1 UK 0.5 3 NA
# 2 US 10.0 3 7.3
数据:
dat <- structure(list(country_name = c("US", "US", "US", "US", "US",
"US", "UK", "UK"), date = c("2016-11-02", "2017-09-12", "2017-09-19",
"2020-02-10", "2021-03-10", "2021-05-02", "2016-11-02", "2017-09-12"
), column_1 = c(7.5, NA, NA, 10, NA, NA, NA, 0.5), column_2 = c(NA,
NA, 8L, NA, NA, 3L, 2L, 3L), column_3 = c(NA, 9, 10, NA, 7.3,
NA, NA, NA)), class = "data.frame", row.names = c(NA, -8L))
您可以使用 across()
跨多个列汇总每个国家/地区。最新的 non-NA 值可以通过 .x[date == max(date[!is.na(.x)])]
.
进行子集化
library(dplyr)
df %>%
group_by(country_name) %>%
summarise(across(starts_with("column"),
~ if(all(is.na(.x))) NA else .x[date == max(date[!is.na(.x)])])) %>%
ungroup()
# # A tibble: 2 × 4
# country_name column_1 column_2 column_3
# <chr> <dbl> <int> <dbl>
# 1 UK 0.5 3 NA
# 2 US 10 3 7.3
另一个想法:
df %>%
group_by(country_name) %>%
arrange(desc(date), .by_group = TRUE) %>%
summarise(across(starts_with("column"), ~ .x[!is.na(.x)][1])) %>%
ungroup()
这是一个基本的 R 解决方案。它使用两个 sapply 调用:一个用于国家/地区,一个用于列。
foo <- structure(list(country_name = c("US", "US", "US", "US", "US",
"US", "UK", "UK"), date = c("2016-11-02", "2017-09-12", "2017-09-19",
"2020-02-10", "2021-03-10", "2021-05-02", "2016-11-02", "2017-09-12"
), column_1 = c(7.5, NA, NA, 10, NA, NA, NA, 0.5), column_2 = c(NA,
NA, 8L, NA, NA, 3L, 2L, 3L), column_3 = c(NA, 9, 10, NA, 7.3,
NA, NA, NA)), class = "data.frame", row.names = c(NA, -8L))
split(foo, foo$country_name)|>
sapply( function(s) {
s = s[order(s$date),]
sapply(s[,3:5], function(x) {
y = na.omit(x)
ifelse(length(y)> 0, y[length(y)], NA) })}) |>
t()
# column_1 column_2 column_3
#UK 0.5 3 NA
#US 10.0 3 7.3
我有一个包含 country_name
、date
和几列的数据框:column_1
、column_2
和 column_3
。我正在尝试根据跨多个列的日期提取最新记录。
数据框如下所示:
| country_name | date | column_1| column_2| column_3|
| US | 2016-11-02 | 7.5 | NA | NA |
| US | 2017-09-12 | NA | NA | 9 |
| US | 2017-09-19 | NA | 8 | 10 |
| US | 2020-02-10 | 10 | NA | NA |
| US | 2021-03-10 | NA | NA | 7.3 |
| US | 2021-05-02 | NA | 3 | NA |
| UK | 2016-11-02 | NA | 2 | NA |
| UK | 2017-09-12 | 0.5 | 3 | NA |
.
.
对于美国,所需的输出是:
| country_name | column_1| column_2| column_3|
| US | 10 | 3 | 7.3 |
对于column_1
,最晚的值为10(日期:2020-02-10),
column_2
为 3(日期:2021-05-02),column_3
为 7.3(日期:2021-03-10)。我的目标是在多个国家/地区应用此逻辑。我该如何实现?
library(dplyr)
library(tidyr)
df1 %>%
mutate(date = as.Date(date)) %>%
group_by(country_name) %>%
arrange(date) %>%
select(-date) %>%
fill(everything()) %>%
slice(n())
#> # A tibble: 2 x 4
#> # Groups: country_name [2]
#> country_name column_1 column_2 column_3
#> <chr> <dbl> <int> <dbl>
#> 1 UK 0.5 3 NA
#> 2 US 10 3 7.3
数据:
read.table(text = "country_name date column_1 column_2 column_3
US 2016-11-02 7.5 NA NA
US 2017-09-12 NA NA 9
US 2017-09-19 NA 8 10
US 2020-02-10 10 NA NA
US 2021-03-10 NA NA 7.3
US 2021-05-02 NA 3 NA
UK 2016-11-02 NA 2 NA
UK 2017-09-12 0.5 3 NA",
header = T, stringsAsFactors = F) -> df1
更新:
感谢@Darren Tsai 处理警告:
Warning: Problem while computing `..1 = across(-country_name, ~parse_number(.)).
i 1 parsing failure. row col expected actual 1 -- a number NA NA
添加这行代码:
mutate(across(-country_name, ~str_trim(str_replace_all(., 'NA', ''))))
library(tidyverse)
library(lubridate)
df1 %>%
mutate(date = ymd(date)) %>%
group_by(country_name) %>%
arrange(date, .by_group = TRUE) %>%
summarise(across(starts_with("column"), ~paste(rev(.), collapse = ' '))) %>%
mutate(across(-country_name, ~str_trim(str_replace_all(., 'NA', '')))) %>%
mutate(across(-country_name, ~parse_number(.)))
country_name column_1 column_2 column_3
<chr> <dbl> <dbl> <dbl>
1 UK 0.5 3 NA
2 US 10 3 7.3
第一个回答:
我们可以这样做:
- 如有必要,使用
lubridate
. 中的 - 分组
country_name
- 现在出现了我们对 col1 col2...等使用
across
的技巧,并使用paste(rev(.)....
反向折叠以使最后一个值排在首位。这对下一步很重要。 - 使用
readr
包中的parse_number()
将提取第一个数字!
ymd()
函数将 date
列转换为日期 class
library(dplyr)
library(lubridate)
library(readr)
df %>%
mutate(date = ymd(date)) %>%
group_by(country_name) %>%
arrange(date, .by_group = TRUE) %>%
summarise(across(starts_with("column"), ~paste(rev(.), collapse = ' '))) %>%
mutate(across(-country_name, parse_number))
country_name column_1 column_2 column_3
<chr> <dbl> <dbl> <dbl>
1 UK 0.5 3 NA
2 US 10 3 7.3
您可以 na.omit
和 rev
删除每一列并取第一个 el
元素。然后rbind
。注意右边的order
,如果是as.Date
格式的。
by(transform(dat, date=as.Date(date)), dat$country_name, \(x) {
cbind(x[1, 1, drop=FALSE],
lapply(x[order(x$date), 3:5], \(z) {
z <- el(rev(na.omit(z)))
ifelse(length(z) == 1, z, NA_real_)
}))
}) |> c(make.row.names=FALSE) |> do.call(what=rbind)
# country_name column_1 column_2 column_3
# 1 UK 0.5 3 NA
# 2 US 10.0 3 7.3
数据:
dat <- structure(list(country_name = c("US", "US", "US", "US", "US",
"US", "UK", "UK"), date = c("2016-11-02", "2017-09-12", "2017-09-19",
"2020-02-10", "2021-03-10", "2021-05-02", "2016-11-02", "2017-09-12"
), column_1 = c(7.5, NA, NA, 10, NA, NA, NA, 0.5), column_2 = c(NA,
NA, 8L, NA, NA, 3L, 2L, 3L), column_3 = c(NA, 9, 10, NA, 7.3,
NA, NA, NA)), class = "data.frame", row.names = c(NA, -8L))
您可以使用 across()
跨多个列汇总每个国家/地区。最新的 non-NA 值可以通过 .x[date == max(date[!is.na(.x)])]
.
library(dplyr)
df %>%
group_by(country_name) %>%
summarise(across(starts_with("column"),
~ if(all(is.na(.x))) NA else .x[date == max(date[!is.na(.x)])])) %>%
ungroup()
# # A tibble: 2 × 4
# country_name column_1 column_2 column_3
# <chr> <dbl> <int> <dbl>
# 1 UK 0.5 3 NA
# 2 US 10 3 7.3
另一个想法:
df %>%
group_by(country_name) %>%
arrange(desc(date), .by_group = TRUE) %>%
summarise(across(starts_with("column"), ~ .x[!is.na(.x)][1])) %>%
ungroup()
这是一个基本的 R 解决方案。它使用两个 sapply 调用:一个用于国家/地区,一个用于列。
foo <- structure(list(country_name = c("US", "US", "US", "US", "US",
"US", "UK", "UK"), date = c("2016-11-02", "2017-09-12", "2017-09-19",
"2020-02-10", "2021-03-10", "2021-05-02", "2016-11-02", "2017-09-12"
), column_1 = c(7.5, NA, NA, 10, NA, NA, NA, 0.5), column_2 = c(NA,
NA, 8L, NA, NA, 3L, 2L, 3L), column_3 = c(NA, 9, 10, NA, 7.3,
NA, NA, NA)), class = "data.frame", row.names = c(NA, -8L))
split(foo, foo$country_name)|>
sapply( function(s) {
s = s[order(s$date),]
sapply(s[,3:5], function(x) {
y = na.omit(x)
ifelse(length(y)> 0, y[length(y)], NA) })}) |>
t()
# column_1 column_2 column_3
#UK 0.5 3 NA
#US 10.0 3 7.3