基于正则表达式合并数据框中的变量对
Coalesce pairs of variables within a dataframe based on a regular expression
我想使用 dplyr::coalesce
在包含多对变量的数据框中查找变量对之间的第一个非缺失值。目标是创建一个新的数据框,现在每对变量(一个没有 NA 值的合并变量)只有一个副本。
这是一个例子:
df <- data.frame(
A_1=c(NA, NA, 3, 4, 5),
A_2=c(1, 2, NA, NA, NA),
B_1=c(NA, NA, 13, 14, 15),
B_2=c(11, 12, NA, NA, NA))
Expected output:
A B
1 11
2 12
3 13
4 14
5 15
我猜测可以使用基于正则表达式的 dplyr::coalesce
和 dplyr::mutate_at
的混合,但我不确定该怎么做。有没有办法用 tidyverse 语法完成这个任务?
谢谢!
编辑:感谢大家的回答!但是,我应该包括我的变量的命名约定,以便于将您的答案转移到我的实际问题中。我对此感到抱歉。我的变量是地球化学变量,分为两部分命名(化学元素名称加上核心名称)。
示例:Al_TAC4.25.275
其中 Al
是元素,TAC4.25.275
是核心。我想为每个元素(名称的第一部分)合并来自 3 个不同核心(名称的第二部分)的数据。我有 25 对元素要合并。
你可以使用转化,例如
library(dplyr)
df <- data.frame(
A_1 = c(NA, NA, 3, 4, 5),
A_2 = c(1, 2, NA, NA, NA),
B_1 = c(NA, NA, 13, 14, 15),
B_2 = c(11, 12, NA, NA, NA)
)
df %>%
transmute(A = coalesce(A_1, A_2),
B = coalesce(B_1, B_2))
#> A B
#> 1 1 11
#> 2 2 12
#> 3 3 13
#> 4 4 14
#> 5 5 15
由 reprex package (v2.0.1)
于 2021-12-22 创建
另一种选择,如果您有很多“A_*”和“B_*”列(来源:Romain François, user: @Romain Francois):
library(dplyr)
df <- data.frame(
A_1 = c(NA, NA, 3, 4, 5),
A_2 = c(1, 2, NA, NA, NA),
B_1 = c(NA, NA, 13, 14, 15),
B_2 = c(11, 12, NA, NA, NA)
)
coacross <- function(...) {
coalesce(!!!across(...))
}
df %>%
transmute(A = coacross(starts_with("A_")),
B = coacross(starts_with("B_")))
#> A B
#> 1 1 11
#> 2 2 12
#> 3 3 13
#> 4 4 14
#> 5 5 15
由 reprex package (v2.0.1)
于 2021-12-22 创建
编辑
根据您更新后的问题,您没有很多“A_*”或“B_*”列,而是很多“*_1”、“*_2”和“*_3”列。我认为这是针对您的用例的最直接的解决方案:
library(dplyr)
df <- data.frame(Al_TAC4.25.275 = c(1, 1, 1, NA, NA, NA),
Al_TAC4.25.276 = c(NA, NA, 2, 2, 2, NA),
Al_TAC4.25.277 = c(NA, NA, 3, NA, NA, 3),
Au_TAC4.25.275 = c(1, 1, 1, NA, NA, NA),
Au_TAC4.25.276 = c(NA, NA, 2, 2, 2, NA),
Au_TAC4.25.277 = c(NA, NA, 3, NA, NA, NA),
Ar_TAC4.25.275 = c(1, 1, 1, NA, NA, 1),
Ar_TAC4.25.276 = c(NA, NA, 2, 2, 2, 2),
Ar_TAC4.25.277 = c(NA, NA, 3, NA, NA, 3))
df
#> Al_TAC4.25.275 Al_TAC4.25.276 Al_TAC4.25.277 Au_TAC4.25.275 Au_TAC4.25.276
#> 1 1 NA NA 1 NA
#> 2 1 NA NA 1 NA
#> 3 1 2 3 1 2
#> 4 NA 2 NA NA 2
#> 5 NA 2 NA NA 2
#> 6 NA NA 3 NA NA
#> Au_TAC4.25.277 Ar_TAC4.25.275 Ar_TAC4.25.276 Ar_TAC4.25.277
#> 1 NA 1 NA NA
#> 2 NA 1 NA NA
#> 3 3 1 2 3
#> 4 NA NA 2 NA
#> 5 NA NA 2 NA
#> 6 NA 1 2 3
names(df) %>%
split(str_extract(., '[:alpha:]+')) %>%
map_dfc(~ coalesce(!!!df[.x][c(1,2,3)]))
#> # A tibble: 6 × 3
#> Al Ar Au
#> <dbl> <dbl> <dbl>
#> 1 1 1 1
#> 2 1 1 1
#> 3 1 1 1
#> 4 2 2 2
#> 5 2 2 2
#> 6 3 1 NA
# change the order of the list to change the 'priority'
names(df) %>%
split(str_extract(., '[:alpha:]+')) %>%
map_dfc(~ coalesce(!!!df[.x][c(3,2,1)]))
#> # A tibble: 6 × 3
#> Al Ar Au
#> <dbl> <dbl> <dbl>
#> 1 1 1 1
#> 2 1 1 1
#> 3 3 3 3
#> 4 2 2 2
#> 5 2 2 2
#> 6 3 3 NA
names(df) %>%
split(str_extract(., '[:alpha:]+')) %>%
map_dfc(~ coalesce(!!!df[.x][c(2,1,3)]))
#> # A tibble: 6 × 3
#> Al Ar Au
#> <dbl> <dbl> <dbl>
#> 1 1 1 1
#> 2 1 1 1
#> 3 2 2 2
#> 4 2 2 2
#> 5 2 2 2
#> 6 3 2 NA
由 reprex package (v2.0.1)
于 2021-12-22 创建
我已在此处请求:https://github.com/tidyverse/dplyr/issues/6109 那里有一些可能的解决方案。例如
library(dplyr)
library(purrr)
df %>%
transmute(map2_dfc(.x = across(ends_with("_1"), .names = '{sub("_1","",.col)}'),
.y = across(ends_with("_2")),
.f = coalesce))
A B
1 1 11
2 2 12
3 3 13
4 4 14
5 5 15
或者也使用函数
coalesce_prefix <- function(prefix) {
exprs <- map(prefix, function(p) {
expr(coalesce(
!!sym(paste0(p, ".x")),
!!sym(paste0(p, ".y"))
))
})
names(exprs) <- prefix
exprs
}
基本 R 选项
list2DF(
lapply(
split.default(df, gsub("_.*", "", names(df))),
rowSums,
na.rm = TRUE
)
)
给予
A B
1 1 11
2 2 12
3 3 13
4 4 14
5 5 15
编辑:我相信即使在您进行编辑后,此解决方案仍然有效。无论元素数量或每个元素的核心数量如何,它都能正常工作。您只需要确保事物的命名一致,格式为 "{element}_{core}"
.
library(tidyverse)
df %>%
mutate(id = 1:n()) %>%
pivot_longer(-id) %>%
filter(!is.na(value)) %>%
mutate(variable = str_extract(name, "^[^_]+")) %>%
group_by(id, variable) %>%
# Arrange by name (e.g. A_1) so that we could select the first non-NA
arrange(name) %>%
summarise(value = value[1]) %>%
pivot_wider(names_from = "variable")
输出
# A tibble: 5 x 3
id A B
<int> <dbl> <dbl>
1 1 1 11
2 2 2 12
3 3 3 13
4 4 4 14
5 5 5 15
这是一个旋转的替代方法:
library(dplyr)
library(tidyr)
df %>%
pivot_longer(
everything()
) %>%
mutate(name = substr(name, 1, 1)) %>%
na.omit %>%
pivot_wider(
names_from = name,
values_from = value,
values_fn = list
) %>%
unnest(cols = c(A, B))
A B
<dbl> <dbl>
1 1 11
2 2 12
3 3 13
4 4 14
5 5 15
与我的另一个相比,这是另一个更简洁的解决方案。我认为使用 cur_data()
函数非常有帮助,但您也可以使用 across(everything())
代替它:
library(dplyr)
library(purrr)
unique(sub("(\D)_\d+", "\1", names(df))) %>%
map_dfc(~ df %>%
select(starts_with(.x)) %>%
summarise(!!.x := do.call(coalesce, cur_data())))
A B
1 1 11
2 2 12
3 3 13
4 4 14
5 5 15
这是尽可能多的配对的另一种解决方案。请注意,我使用 bang bang
运算符 !!!
将数据框的元素折叠成独立的单个参数,以便我可以对它们应用 coalesce
:
library(dplyr)
library(rlang)
as.data.frame(do.call(cbind, lapply(split.default(df, sub("(\D)_\d+", "\1", names(df))), function(x) {
coalesce(!!!x)
})))
A B
1 1 11
2 2 12
3 3 13
4 4 14
5 5 15
我想使用 dplyr::coalesce
在包含多对变量的数据框中查找变量对之间的第一个非缺失值。目标是创建一个新的数据框,现在每对变量(一个没有 NA 值的合并变量)只有一个副本。
这是一个例子:
df <- data.frame(
A_1=c(NA, NA, 3, 4, 5),
A_2=c(1, 2, NA, NA, NA),
B_1=c(NA, NA, 13, 14, 15),
B_2=c(11, 12, NA, NA, NA))
Expected output:
A B
1 11
2 12
3 13
4 14
5 15
我猜测可以使用基于正则表达式的 dplyr::coalesce
和 dplyr::mutate_at
的混合,但我不确定该怎么做。有没有办法用 tidyverse 语法完成这个任务?
谢谢!
编辑:感谢大家的回答!但是,我应该包括我的变量的命名约定,以便于将您的答案转移到我的实际问题中。我对此感到抱歉。我的变量是地球化学变量,分为两部分命名(化学元素名称加上核心名称)。
示例:Al_TAC4.25.275
其中 Al
是元素,TAC4.25.275
是核心。我想为每个元素(名称的第一部分)合并来自 3 个不同核心(名称的第二部分)的数据。我有 25 对元素要合并。
你可以使用转化,例如
library(dplyr)
df <- data.frame(
A_1 = c(NA, NA, 3, 4, 5),
A_2 = c(1, 2, NA, NA, NA),
B_1 = c(NA, NA, 13, 14, 15),
B_2 = c(11, 12, NA, NA, NA)
)
df %>%
transmute(A = coalesce(A_1, A_2),
B = coalesce(B_1, B_2))
#> A B
#> 1 1 11
#> 2 2 12
#> 3 3 13
#> 4 4 14
#> 5 5 15
由 reprex package (v2.0.1)
于 2021-12-22 创建另一种选择,如果您有很多“A_*”和“B_*”列(来源:Romain François, user: @Romain Francois):
library(dplyr)
df <- data.frame(
A_1 = c(NA, NA, 3, 4, 5),
A_2 = c(1, 2, NA, NA, NA),
B_1 = c(NA, NA, 13, 14, 15),
B_2 = c(11, 12, NA, NA, NA)
)
coacross <- function(...) {
coalesce(!!!across(...))
}
df %>%
transmute(A = coacross(starts_with("A_")),
B = coacross(starts_with("B_")))
#> A B
#> 1 1 11
#> 2 2 12
#> 3 3 13
#> 4 4 14
#> 5 5 15
由 reprex package (v2.0.1)
于 2021-12-22 创建编辑
根据您更新后的问题,您没有很多“A_*”或“B_*”列,而是很多“*_1”、“*_2”和“*_3”列。我认为这是针对您的用例的最直接的解决方案:
library(dplyr)
df <- data.frame(Al_TAC4.25.275 = c(1, 1, 1, NA, NA, NA),
Al_TAC4.25.276 = c(NA, NA, 2, 2, 2, NA),
Al_TAC4.25.277 = c(NA, NA, 3, NA, NA, 3),
Au_TAC4.25.275 = c(1, 1, 1, NA, NA, NA),
Au_TAC4.25.276 = c(NA, NA, 2, 2, 2, NA),
Au_TAC4.25.277 = c(NA, NA, 3, NA, NA, NA),
Ar_TAC4.25.275 = c(1, 1, 1, NA, NA, 1),
Ar_TAC4.25.276 = c(NA, NA, 2, 2, 2, 2),
Ar_TAC4.25.277 = c(NA, NA, 3, NA, NA, 3))
df
#> Al_TAC4.25.275 Al_TAC4.25.276 Al_TAC4.25.277 Au_TAC4.25.275 Au_TAC4.25.276
#> 1 1 NA NA 1 NA
#> 2 1 NA NA 1 NA
#> 3 1 2 3 1 2
#> 4 NA 2 NA NA 2
#> 5 NA 2 NA NA 2
#> 6 NA NA 3 NA NA
#> Au_TAC4.25.277 Ar_TAC4.25.275 Ar_TAC4.25.276 Ar_TAC4.25.277
#> 1 NA 1 NA NA
#> 2 NA 1 NA NA
#> 3 3 1 2 3
#> 4 NA NA 2 NA
#> 5 NA NA 2 NA
#> 6 NA 1 2 3
names(df) %>%
split(str_extract(., '[:alpha:]+')) %>%
map_dfc(~ coalesce(!!!df[.x][c(1,2,3)]))
#> # A tibble: 6 × 3
#> Al Ar Au
#> <dbl> <dbl> <dbl>
#> 1 1 1 1
#> 2 1 1 1
#> 3 1 1 1
#> 4 2 2 2
#> 5 2 2 2
#> 6 3 1 NA
# change the order of the list to change the 'priority'
names(df) %>%
split(str_extract(., '[:alpha:]+')) %>%
map_dfc(~ coalesce(!!!df[.x][c(3,2,1)]))
#> # A tibble: 6 × 3
#> Al Ar Au
#> <dbl> <dbl> <dbl>
#> 1 1 1 1
#> 2 1 1 1
#> 3 3 3 3
#> 4 2 2 2
#> 5 2 2 2
#> 6 3 3 NA
names(df) %>%
split(str_extract(., '[:alpha:]+')) %>%
map_dfc(~ coalesce(!!!df[.x][c(2,1,3)]))
#> # A tibble: 6 × 3
#> Al Ar Au
#> <dbl> <dbl> <dbl>
#> 1 1 1 1
#> 2 1 1 1
#> 3 2 2 2
#> 4 2 2 2
#> 5 2 2 2
#> 6 3 2 NA
由 reprex package (v2.0.1)
于 2021-12-22 创建我已在此处请求:https://github.com/tidyverse/dplyr/issues/6109 那里有一些可能的解决方案。例如
library(dplyr)
library(purrr)
df %>%
transmute(map2_dfc(.x = across(ends_with("_1"), .names = '{sub("_1","",.col)}'),
.y = across(ends_with("_2")),
.f = coalesce))
A B
1 1 11
2 2 12
3 3 13
4 4 14
5 5 15
或者也使用函数
coalesce_prefix <- function(prefix) {
exprs <- map(prefix, function(p) {
expr(coalesce(
!!sym(paste0(p, ".x")),
!!sym(paste0(p, ".y"))
))
})
names(exprs) <- prefix
exprs
}
基本 R 选项
list2DF(
lapply(
split.default(df, gsub("_.*", "", names(df))),
rowSums,
na.rm = TRUE
)
)
给予
A B
1 1 11
2 2 12
3 3 13
4 4 14
5 5 15
编辑:我相信即使在您进行编辑后,此解决方案仍然有效。无论元素数量或每个元素的核心数量如何,它都能正常工作。您只需要确保事物的命名一致,格式为 "{element}_{core}"
.
library(tidyverse)
df %>%
mutate(id = 1:n()) %>%
pivot_longer(-id) %>%
filter(!is.na(value)) %>%
mutate(variable = str_extract(name, "^[^_]+")) %>%
group_by(id, variable) %>%
# Arrange by name (e.g. A_1) so that we could select the first non-NA
arrange(name) %>%
summarise(value = value[1]) %>%
pivot_wider(names_from = "variable")
输出
# A tibble: 5 x 3
id A B
<int> <dbl> <dbl>
1 1 1 11
2 2 2 12
3 3 3 13
4 4 4 14
5 5 5 15
这是一个旋转的替代方法:
library(dplyr)
library(tidyr)
df %>%
pivot_longer(
everything()
) %>%
mutate(name = substr(name, 1, 1)) %>%
na.omit %>%
pivot_wider(
names_from = name,
values_from = value,
values_fn = list
) %>%
unnest(cols = c(A, B))
A B
<dbl> <dbl>
1 1 11
2 2 12
3 3 13
4 4 14
5 5 15
与我的另一个相比,这是另一个更简洁的解决方案。我认为使用 cur_data()
函数非常有帮助,但您也可以使用 across(everything())
代替它:
library(dplyr)
library(purrr)
unique(sub("(\D)_\d+", "\1", names(df))) %>%
map_dfc(~ df %>%
select(starts_with(.x)) %>%
summarise(!!.x := do.call(coalesce, cur_data())))
A B
1 1 11
2 2 12
3 3 13
4 4 14
5 5 15
这是尽可能多的配对的另一种解决方案。请注意,我使用 bang bang
运算符 !!!
将数据框的元素折叠成独立的单个参数,以便我可以对它们应用 coalesce
:
library(dplyr)
library(rlang)
as.data.frame(do.call(cbind, lapply(split.default(df, sub("(\D)_\d+", "\1", names(df))), function(x) {
coalesce(!!!x)
})))
A B
1 1 11
2 2 12
3 3 13
4 4 14
5 5 15