基于正则表达式合并数据框中的变量对

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::coalescedplyr::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 %&gt;% transmute(across(ends_with("_1"), ~coalesce(., get(str_replace(cur_column(), '1$', '2'))), .names = ".col_coalesce")) %&gt;% rename_at(vars(ends_with('coalesce')), ~ str_remove(., "\\_1")) 避免该功能?!或避免coalesce 两次。 是的!我尝试了这种方法,但无法弄清楚细节 - 感谢@TarJae!我认为值得将其添加到您的答案中:) 这很好用,谢谢 :) 但如果我可能要求对我的问题进行更多说明:我有大约 25 对或变量(实际上,每 25 个变量重复 3 次;例如:那里是变量A_1A_2A_3...Z_1Z_2Z_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 [重复]

将一个系列中的数字添加到同一数据框中的一系列字母,可能使用正则表达式

在熊猫数据框中使用正则表达式替换列值

如何根据正则表达式从 pd 系列或数据框中删除行?