【问题标题】:R: Conditionally replacing values based on column pre-fixes and suffixesR:根据列前缀和后缀有条件地替换值
【发布时间】:2017-11-05 17:21:34
【问题描述】:

我有两个数据框。数据框 A 有许多观察/行、每个观察的 ID 以及许多附加列。对于观测值 X 的子集,一组列的值缺失/NA。数据框 B 包含 X 中的观察子集(可以使用 ID 跨数据框进行匹配)和与数据框 A 中名称相同的变量,但包含用缺失值替换列集中缺失值的值/不。

我下面的代码(使用连接操作)只是添加列而不是替换缺失值。对于 B 中的每个附加变量(我们将它们命名为 W),结果表会生成 W.x 和 W.y。

library(dplyr)

foo <- data.frame(id = seq(1:6), x = c(NA, NA, NA, 1, 3, 8), z = seq_along(10:15))
bar <- data.frame(id = seq(1:2), x = c(10, 9))
dplyr::left_join(x = foo, y = bar, by = "id")

我正在尝试使用基于 ID 的 B 中的值替换 A 中的缺失值,但由于我有很多列和很多行,所以这样做很有效。我的目标是:

  id   x z 
1  1  10 1  
2  2   9 2  
3  3  NA 3  
4  4   1 4  
5  5   3 5  
6  6   8 6 

一个想法是在加入后使用 ifelse(),但是为所有变量键入 ifelse() 函数是不可行的。有没有办法在没有数据库连接的情况下简单地做到这一点,或者有没有办法在所有以 .x 结尾的列上应用一个函数,如果 .x 中的值丢失,则用 .y 中的值替换 .x 中的值?

【问题讨论】:

  • 这是“更新加入”问题,我还没有看到一个很好的答案。目前我已经转到left_join(foo, bar, by = 'id') %&gt;% mutate(x = coalesce(x.x, x.y)) %&gt;% select(-contains('.'))
  • 似乎应该有一种方法可以避免为每个变量手动输入 mutate(foo = coalesce(foo.x, foo.y)) 因为我有成千上万个变量。我一直在考虑使用apply 的方法,但有点不知所措。
  • data.table - foo[bar,on=.(id),x := i.x]
  • @user3614648 有一种对列名进行编程的方法,但坦率地说,它并不简单或漂亮。
  • nm &lt;- c("x"); foo[bar,on=.(id), (nm) := mget(paste0("i.",nm))] 或类似名称,如果您只想引用名称一次。 nm 也可以使用 names(foo)[c(-1,-length(foo))] 定义,以避免输入所有内容。

标签: r database merge data-manipulation


【解决方案1】:

您可以在非分组列的交叉点上迭代 dplyr::coalesce。它并不优雅,但应该可以很好地扩展:

library(tidyverse)

foo <- data.frame(id = seq(1:6), 
                  x = c(NA, NA, NA, 1, 3, 8), 
                  y = 1:6,    # add extra shared variable
                  z = seq_along(10:15))
bar <- data.frame(id = seq(1:2), 
                  y = c(1L, NA),
                  x = c(10, 9))

# names of non-grouping variables in both
vars <- intersect(names(foo), names(bar))[-1]

foobar <- left_join(foo, bar, by = 'id')

foobar <- vars %>% 
    map(paste0, c('.x', '.y')) %>%    # make list of columns to coalesce
    map(~foobar[.x]) %>%    # for each set, subset foobar to a two-column data.frame 
    invoke_map(.f = coalesce) %>%    # ...and coalesce it into a vector
    set_names(vars) %>%   # add names to list elements
    bind_cols(foobar) %>%   # bind into data.frame and cbind to foobar
    select(union(names(foo), names(bar)))    # drop duplicated columns

foobar
#> # A tibble: 6 x 4
#>      id     x     y     z
#>   <int> <dbl> <int> <int>
#> 1     1    10     1     1
#> 2     2     9     2     2
#> 3     3    NA     3     3
#> 4     4     1     4     4
#> 5     5     3     5     5
#> 6     6     8     6     6

【讨论】:

    【解决方案2】:

    另一种尝试,本质上应该只是一个赋值操作。再次使用@alistaire 的数据:

    vars <- c("x","y")
    foo[vars] <- Map(pmax, foo[vars], bar[match(foo$id, bar$id), vars], na.rm=TRUE)
    foo
    
    #  id  x y z
    #1  1 10 1 1
    #2  2  9 2 2
    #3  3 NA 3 3
    #4  4  1 4 4
    #5  5  3 5 5
    #6  6  8 6 6
    

    【讨论】:

    • 如果我没记错的话,这只有在 bar 的 ID 在 foo 的第一行时才有效,对吗?
    • @user3614648 - 不,它可以按任何顺序工作 - match 将采用来自 bar 的选择的顺序。尝试foo &lt;- foo[6:1,] 并运行代码,它会按预期反转。
    【解决方案3】:

    编辑

    使用@alistaire 的示例数据框更新答案。

    我们可以使用mapply 扩展下面给出的相同答案,以便它可以处理foobar 的多个列。

    找出两个数据框之间的共同列,并对它们进行排序,使它们的顺序相同。

    vars <- sort(intersect(names(foo), names(bar))[-1])
    foo[vars] <- mapply(function(x, y) {
                 ind = is.na(x)
                 replace(x, ind, y[match(foo$id[ind], bar$id)])
                 }, foo[vars], bar[vars])
    
    foo
    #  id  x y z
    #1  1 10 1 1
    #2  2  9 2 2
    #3  3 NA 3 3
    #4  4  1 4 4
    #5  5  3 5 5
    #6  6  8 6 6
    

    原答案

    我认为这可以满足您的需求:

    foo[-1] <- sapply(foo[-1], function(x) {
        ind = is.na(x)
        replace(x, ind, bar$x[match(foo$id[ind], bar$id)])
    })
    
    
    foo
    #  id  x z
    #1  1 10 1
    #2  2  9 2
    #3  3 NA 3
    #4  4  1 4
    #5  5  3 5
    #6  6  8 6
    

    对于每一列(id 除外),我们在foo 中找到缺失值,并将其替换为bar 中的相应值。

    【讨论】:

      【解决方案4】:

      如果您不介意冗长的 baseR 方法,那么您可以使用 merge() 和对您的数据框进行仔细的子集轻松地完成此操作。

      df <- merge(foo, bar, by="id", all.x=TRUE)
      names(df) <- c("id", "x", "z", "y")
      df$x[is.na(df$x)] <- df$y[is.na(df$x)]
      df <- df[c("id", "x", "z")]
      
      > df
        id  x z
      1  1 10 1
      2  2  9 2
      3  3 NA 3
      4  4  1 4
      5  5  3 5
      6  6  8 6
      

      【讨论】:

      • 如果您能想出一种无需输入 $x 即可跨数千列执行此操作的方法,我很乐意接受作为答案。也许这可以通过定义一个执行上述操作但可以通过 sapply()'d 的函数来完成?
      • 我的解决方案可能存在更大的问题,即合并可以更改列名。我的猜测是,您至少需要使用我的解决方案手动重命名。像@akrun 这样的人会给你一个更好的答案。
      • merge() 部分大部分是无关的。我仍然可以使用 dplyr::left_join() 然后继续 df$xx[is.na(df$xx)
      • 也许你应该更新你的问题,让我们知道实际问题是什么样的。在这种情况下,一个最小的示例/解决方案似乎并没有削减它。
      • 数据是相同的,只是数据框中有更多变量,如 X 和 Z。
      猜你喜欢
      • 1970-01-01
      • 2017-04-20
      • 2021-06-25
      • 1970-01-01
      • 2021-09-07
      • 2018-11-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多