【问题标题】:Merge/Join prioritizing non-missing values合并/加入优先非缺失值
【发布时间】:2016-10-09 10:22:06
【问题描述】:

是否有一个合并函数可以优先处理公共变量中的非缺失值?

考虑以下示例。

首先,我们生成两个具有相同 ID 但在特定变量上具有互补缺失值的 data.frame:

set.seed(1)
missings  <- sample.int(6, 3)
df1  <- data.frame(ID = letters[1:6], V1 = NA)
df2  <- data.frame(ID = letters[1:6], V1 = NA)
df1$V1[missings]  <- rnorm(3)
df2$V1[setdiff(1:6, missings)]  <- rnorm(3)

应用 mergedplyr 包中的任何 join 函数会产生类似于以下的结果:

> merge(df1, df2, by = 'ID')
  ID      V1.x       V1.y
1  a        NA -1.5399500
2  b 1.3297993         NA
3  c 0.4146414         NA
4  d        NA -0.9285670
5  e        NA -0.2947204
6  f 1.2724293         NA

我们希望以一种“更智能”的方式连接这两个 data.frame,在一个 data.frame 中没有缺失值时忽略另一个 data.frame 中的缺失值,以获得以下输出:

> output <- df1
> output$V1[is.na(df1$V1)]  <- df2$V1[!(is.na(df2$V1))]
> output
  ID         V1
1  a -1.5399500
2  b  1.3297993
3  c  0.4146414
4  d -0.9285670
5  e -0.2947204
6  f  1.2724293

我们可以假设df1df2 具有完全互补的缺失值V1

编辑

适用于任意数量变量的解决方案将是理想的。

【问题讨论】:

  • 但是如果它们不是互补的呢?如果 ID 确实 在 df1 和 df2 中都有非缺失值,您是要保留两者还是优先考虑一个? SQL 通常会让您使用 coalesce 函数 - see here for implementations of coalesce in R 对其中一个进行优先级排序。当然,如果它们也是互补的,它仍然可以工作。
  • @Gregor dplyr 的开发版本有一个coalsece 的实现,所以你可以简单地做dplyr::coalesce(df1, df2)
  • 正确——在这种情况下,您想要保留两者。这不是我的情况,但coalesce 听起来不错——谢谢你们。也许一个更一般(更好?)的问题会问如何实现基于某种条件选择两个值之一的合并,而不仅仅是缺失...
  • @Gregor 明白了。想到的问题是可能有很多这样的变量(我目前的情况),自动化合并会很棒!
  • 这很好。在这种情况下,尽管当您没有真正加入 na.omit(rbind(df1, df2)) 之类的东西时也可以正常工作(相当于 merge(na.omit(df1), na.omit(df2), by = 'ID'),您或多或少会显示)。不知道为什么这种方法不能令人满意。您想保留的其他列中是否存在潜在缺失值?

标签: r join dataframe merge dplyr


【解决方案1】:

感谢@Gregor 和@StevenBeaupré 非常有帮助的cmets,我想出了一个使用coalesce.na 的解决方案,该解决方案来自kimisc 包,可以扩展到任意数量的变量:

mapply(function(x,y) coalesce.na(x,y), df1$V1, df2$V1)
[1] -1.5399500  1.3297993  0.4146414 -0.9285670 -0.2947204  1.2724293

请注意,df1$V1df2$V1 可以替换变量列表,允许类似:

> set.seed(1)
> missings  <- sample.int(6, 3)
> df1  <- data.frame(ID = letters[1:6],
+                    V1 = NA,
+                    V2 = NA)
> df2  <- data.frame(ID = letters[1:6],
+                    V1 = NA,
+                    V2 = NA)
> df1$V1[missings]  <- rnorm(3)
> df2$V1[setdiff(1:6, missings)]  <- rnorm(3)
> df1$V2[setdiff(1:6, missings)]  <- rnorm(3)
> df2$V2[missings]  <- rnorm(3)

> cbind(df1, df2)
  ID        V1           V2 ID         V1         V2
1  a        NA -0.005767173  a -1.5399500         NA
2  b 1.3297993           NA  b         NA -0.7990092
3  c 0.4146414           NA  c         NA -0.2894616
4  d        NA  2.404653389  d -0.9285670         NA
5  e        NA  0.763593461  e -0.2947204         NA
6  f 1.2724293           NA  f         NA -1.1476570

> dfMerged <- merge(df1, df2, by = 'ID')
> xList <- dfMerged[grep("\\.x$", names(dfMerged))]
> yList <- dfMerged[grep("\\.y$", names(dfMerged))]

> mapply(function(x,y) coalesce.na(x,y), xList, yList)
           V1.x         V2.x
[1,] -1.5399500 -0.005767173
[2,]  1.3297993 -0.799009249
[3,]  0.4146414 -0.289461574
[4,] -0.9285670  2.404653389
[5,] -0.2947204  0.763593461
[6,]  1.2724293 -1.147657009

因此,完整的解决方案如下所示:

library(kimisc)
smartMergeList <- function(dfList, idVar) {
    merged <- Reduce(x = dfList, 
                     f = function(x,y) merge(x, y, by = idVar, all = T))
    xList <- merged[grep("\\.x$", names(merged))]
    yList <- merged[grep("\\.y$", names(merged))]
    merged[names(xList)] <- mapply(function(x,y) coalesce.na(x,y),
                            xList, yList)
    merged[names(yList)] <- NULL
    merged
})

不过我希望看到更漂亮的东西!

【讨论】:

    【解决方案2】:

    如果避免指定列是output$V1[is.na(df1$V1)] &lt;- df2$V1[!(is.na(df2$V1))] 的唯一问题,那么您只需要使用na.omit() 而不是单独指定变量。我们也可以只对重叠的列执行此操作。

    让我们修改原始数据,使列不完全重叠,并且额外的列有一些我们想要保留的数据:

    set.seed(1)
    
    missings  <- sample.int(6, 3)
    df1  <- data.frame(ID = letters[1:6], V1 = NA, V2 = c(NA, 2, 3, NA, 5, 6))
    df2  <- data.frame(ID = letters[1:6], V1 = NA)
    df1$V1[missings]  <- rnorm(3)
    df2$V1[setdiff(1:6, missings)]  <- rnorm(3)
    
    # now df1 looks like this:
    df1
    #   ID        V1 V2
    # 1  a        NA NA
    # 2  b 1.3297993  2
    # 3  c 0.4146414  3
    # 4  d        NA NA
    # 5  e        NA  5
    # 6  f 1.2724293  6
    
    
    common_cols = intersect(names(df1), names(df2))
    result = na.omit(rbind(df1[common_cols], df2[common_cols]))
    result = merge(result, df1, all.x = T)
    result = merge(result, df2, all.x = T)
      # the merges are only necessary if there are additional columns to pick up
    
    result
    # ID           V1 V2
    # 1  a -1.5399500 NA
    # 2  b  1.3297993  2
    # 3  c  0.4146414  3
    # 4  d -0.9285670 NA
    # 5  e -0.2947204 NA
    # 6  f  1.2724293  6
    

    【讨论】:

    • 嗯,我发现我仍然有问题,因为 ID e 的 V2 值丢失了。可能解决方案是将 ID 列与其他 common_col 列分开处理,并在 ID 上合并,仅包括非常见列...
    【解决方案3】:

    根据上面的讨论和答案,这是我使用dplyr 的看法。不是最干净的代码,是的,我有 suppressWarnings()

    对于 OP 的可重现示例:

    set.seed(1)
    missings  <- sample.int(6, 3)
    df1  <- data.frame(ID = letters[1:6], V1 = NA)
    df2  <- data.frame(ID = letters[1:6], V1 = NA)
    df1$V1[missings]  <- rnorm(3)
    df2$V1[setdiff(1:6, missings)]  <- rnorm(3)
    

    简单的解决方案:

    library(dplyr)
    library(reshape2)
    coalesce <- function(...) {
      apply((...), 1, function(x) {
        x[which(!is.na(suppressWarnings(as.numeric(x))))[1]]
      })
    }
    
    full_join(df1, df2, by = 'ID') %>% mutate(V1 = coalesce(.)) %>% select(.,ID,V1)
    
      ID         V1
    1  a -1.5399500
    2  b  1.3297993
    3  c  0.4146414
    4  d -0.9285670
    5  e -0.2947204
    6  f  1.2724293
    

    对于具有多个(此处显示为 3 个)变量的一般解决方案:

    set.seed(1)
    df1  <- data.frame(ID = letters[1:6], V1 = NA, V2 = NA, V3 = NA)
    df2  <- data.frame(ID = letters[1:6], V1 = NA, V2 = NA, V3 = NA)
    df1$V1[sample.int(6, 3)]  <- rnorm(3)
    df2$V1[setdiff(1:6, sample.int(6, 3))]  <- rnorm(3)
    df1$V2[sample.int(6, 3)]  <- rnorm(3)
    df2$V2[setdiff(1:6, sample.int(6, 3))]  <- rnorm(3)
    df1$V3[sample.int(6, 3)]  <- rnorm(3)
    df2$V3[setdiff(1:6, sample.int(6, 3))]  <- rnorm(3)
    

    同样的coalesce()函数,更详细的dplyr逻辑:

    library(dplyr)
    library(reshape2)
    coalesce <- function(...) {
      apply((...), 1, function(x) {
        x[which(!is.na(suppressWarnings(as.numeric(x))))[1]]
      })
    }
    
    full_join(df1, df2, by = "ID") %>% 
      melt(., id.vars = "ID") %>%
      mutate(var = substr(as.character(variable),0,2)) %>%
      group_by(var,value) %>% 
      dcast(.,ID + var ~ variable, value.var = "value") %>%
      mutate(c = coalesce(.)) %>%
      dcast(.,ID ~ var, value.var = "c")
    
      ID         V1         V2          V3
    1  a -1.5399500  0.3898432        <NA>
    2  b -0.9285670 -0.3053884  0.80418951
    3  c -0.8356286       <NA>   0.5939013
    4  d  0.1836433 -0.4115108 -0.05710677
    5  e       <NA>       <NA>   0.8212212
    6  f -0.6264538  1.5117812   0.9189774
    

    coalesce() 函数选择第一个非 NA 值(如果存在)。您可以根据您的问题选择max 或其他内容。 ID 列或合并数据集中的任何其他列必须是非数字的。我希望这对解决您的实际问题有所帮助。

    【讨论】:

    • 我喜欢你的解决方案推广到除缺失之外的条件,即用which.max 替换which(!is.na()) 将选择所有值中的最大值。
    • 除了这个问题中的数据集,我还没有测试过它。让我知道它是否适用于您问题中的多个变量。
    【解决方案4】:

    这是一种可能的data.table 方法:

    library(data.table)
    setDT(df1); setDT(df2);
    
    df1[df2, V1 := ifelse(is.na(V1), i.V1, V1), on = "ID"]
    
    df1
    #    ID         V1
    # 1:  a -1.5399500
    # 2:  b  1.3297993
    # 3:  c  0.4146414
    # 4:  d -0.9285670
    # 5:  e -0.2947204
    # 6:  f  1.2724293
    

    【讨论】:

    • 干杯!但是你能想到一种避免手动指定变量的方法吗?可以任意多!
    猜你喜欢
    • 2021-10-15
    • 2014-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-18
    • 2021-08-28
    • 1970-01-01
    相关资源
    最近更新 更多