基于正则表达式合并数据框中的变量对
Posted
技术标签:
【中文标题】基于正则表达式合并数据框中的变量对【英文标题】:Coalesce pairs of variables within a dataframe based on a regular expression 【发布时间】:2022-01-23 06:40:51 【问题描述】:我想使用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 对元素要合并。
【问题讨论】:
【参考方案1】:你可以使用transmute,例如
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,用户:@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 日创建
【讨论】:
您可以:df %>% transmute(across(ends_with("_1"), ~coalesce(., get(str_replace(cur_column(), '1$', '2'))), .names = ".col_coalesce")) %>% rename_at(vars(ends_with('coalesce')), ~ str_remove(., "\\_1"))
避免该功能?!或避免coalesce
两次。
是的!我尝试了这种方法,但无法弄清楚细节 - 感谢@TarJae!我认为值得将其添加到您的答案中:)
这很好用,谢谢 :) 但如果我可能要求对我的问题进行更多说明:我有大约 25 对或变量(实际上,每 25 个变量重复 3 次;例如:那里是变量A_1
,A_2
,A_3
...Z_1
,Z_2
,Z_3
,我想合并到A
,...Z
以减少变量的数量.有没有办法概括你的代码以避免指定A = ...
,B = ....
?其次,有没有办法在选择变量以填充合并变量时内置优先级?例如,如果我想优先拥有_2
超过 _1
作为填充?
我已经用一个潜在的解决方案 @AntoineLachance 更新了我的答案【参考方案2】:
我在这里提出了要求: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
【讨论】:
【参考方案3】:基本 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
【讨论】:
基础R兄弟!你知道我迷恋igraph
和网络分析。它完全改变了我对事物的看法:)
@AnoushiravanR 很高兴听到这个消息。享受您的igraph
之旅:)【参考方案4】:
编辑:我相信即使在您进行了编辑之后,此解决方案仍然有效。无论元素数量或每个元素的核心数量如何,它都可以工作。您只需要确保名称一致,格式为"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
【讨论】:
【参考方案5】:这里是旋转的替代方案:
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
【讨论】:
【参考方案6】:与我的另一个相比,这是另一个更简洁的解决方案。我认为在这里使用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
【讨论】:
很高兴在这里见到你! 兄弟永远是我的荣幸 :)以上是关于基于正则表达式合并数据框中的变量对的主要内容,如果未能解决你的问题,请参考以下文章
如何通过 Pyspark 中同一数据框中另一列的正则表达式值过滤数据框中的一列
Python - 正则表达式将数据框中的一列拆分为 2 [重复]