【问题标题】:Sum two dataframes with NA values and factors将两个具有 NA 值和因子的数据帧相加
【发布时间】:2018-02-23 13:47:20
【问题描述】:

我正在尝试对两个包含 NA 值和因子变量的数据框求和。这些值应该逐个单元格地求和,仅适用于非因子变量,并忽略 NA。

例如,我正在处理的数据帧如下两个:

data1 <- data.frame(NAMES=c("name1", "name2", "name3"),
                X1=c(1, NA, 3),
                X2 = c(10, 11, 12))
data2 <- data.frame(NAMES=c("name1", "name2", "name3"),
                X1=c(4,NA,6),
                X2 = c(NA, 11, 12))

如果我使用函数sum (..., na.rm=TRUE),我不会得到数据框。我期待使用运算符+ 返回的输出。也就是说,具有相同行数和列数的单个数据帧,但其中每个单元格是在添加数据帧的相同坐标中添加单元格的结果。但是,需要忽略 NA 和因素,例如:

  NAMES X1 X2
1    name1  5 10
2    name2 NA 22
3    name3  9 24

这可能吗?

【问题讨论】:

  • 如果忽略 NA 的值,X1 的第 2 行将给出零,这就是 sum(..., na.rm = TRUE) 的行为。如果您使用 + 如何“忽略 NA”?能说的清楚点吗?
  • + 运算符可让您以直接的方式逐个单元格地计算两个数据帧的总和,但如果只有一个(或多个)加数是 NA 值,则结果为 NA,即使其中一个数据帧中存在非 NA 值。 O 想避免这种行为
  • 知道了。看我的回答。

标签: r


【解决方案1】:

Base R版本:

library(dplyr) # only for pipe operator
rbind(data1, data2) %>%
  split(.$NAMES) %>%
  lapply(function(x){
    data.frame(NAMES = unique(x$NAMES),as.list(colSums(x[,-1]))) 
  }) %>%
  do.call(rbind, .)

#       NAMES X1 X2
# name1 name1  5 NA
# name2 name2 NA 22
# name3 name3  9 24

请注意,NAMES 现在也显示为行名。这是因为split 输出了一个命名列表。您可以保留行名并删除NAMES = unique(x$NAMES),或者在split 之后添加unname() 管道:

rbind(data1, data2) %>%
  split(.$NAMES) %>%
  lapply(function(x){
    data.frame(as.list(colSums(x[,-1]))) 
  }) %>%
  do.call(rbind, .)

#       X1 X2
# name1  5 NA
# name2 NA 22
# name3  9 24

rbind(data1, data2) %>%
  split(.$NAMES) %>%
  unname() %>%
  lapply(function(x){
    data.frame(NAMES = unique(x$NAMES),as.list(colSums(x[,-1]))) 
  }) %>%
  do.call(rbind, .)

#   NAMES X1 X2
# 1 name1  5 NA
# 2 name2 NA 22
# 3 name3  9 24

要将 NA 视为零,只需将 na.rm = TRUE 添加到 colSums

rbind(data1, data2) %>%
  split(.$NAMES) %>%
  unname() %>%
  lapply(function(x){
    data.frame(NAMES = unique(x$NAMES),as.list(colSums(x[,-1], na.rm = TRUE))) 
  }) %>%
  do.call(rbind, .)

#   NAMES X1 X2
# 1 name1  5 10
# 2 name2  0 22
# 3 name3  9 24

dplyr + purrr 版本:

library(purrr)
library(dplyr)

list(data1, data2) %>%
  reduce(function(x, y) cbind(NAMES = x$NAMES, x[,-1] + y[-1]))

结果:

  NAMES X1 X2
1 name1  5 NA
2 name2 NA 22
3 name3  9 24

将 NA 视为零:

list(data1, data2) %>%
  map(function(x){
    modify_if(x, is.numeric, function(y) ifelse(is.na(y), 0, y))
  }) %>% 
  reduce(function(x, y) cbind(NAMES = x$NAMES, x[,-1] + y[-1]))

结果:

  NAMES X1 X2
1 name1  5 10
2 name2  0 22
3 name3  9 24

重要提示:

用零替换 NA 通常是个坏主意,因为它们的含义不同。 NA 可能意味着数据丢失,不一定为零,因此用零替换所有 NA 可能会使您的结果产生偏差。请仅在您确定数据上下文中 NA 的平均值为零时才这样做。

附加说明:

  1. mapmodify_if 都来自purrr 包。 map 将函数应用于列表的每个元素并始终返回一个列表。 modify 的作用相同,只是它返回与输入相同的类型。
  2. modify_if 仅“映射”满足条件的元素。
  3. 在第一个管道中,我使用maplist(data1, data2) 的每个元素与modify_if 函数“映射”在一起,而modify_if 仅将每个数字列的NA 替换为零。这样我就可以在下一个管道中使用 + 运算符,而不必担心 NA。
  4. reducedata1data2 进行矩阵加法,然后将 cbinds 与来自 data1NAMES 列一起添加。

【讨论】:

  • 非常好的选择,非常感谢!再次感谢您的补充说明
【解决方案2】:

另一种选择:

as.data.frame(
    mapply(function(x, y)
        if(is.numeric(x) && is.numeric(y))
            ifelse(is.na(y), x, x + y)
        else x,
        data1, data2, SIMPLIFY = FALSE))

输出:

  NAMES X1 X2
1 name1  5 10
2 name2 NA 22
3 name3  9 24

【讨论】:

    【解决方案3】:

    这是一个仅使用基础 R 的解决方案。

    icol <- which(!(sapply(data1, is.factor) | sapply(data2, is.factor)))
    result <- sapply(icol, function(i) rowSums(cbind(data1[i], data2[i]), na.rm = TRUE))
    result <- cbind(data1[1], result)
    is.na(result[icol]) <- is.na(data1[icol]) & is.na(data2[icol])
    result
    #  NAMES X1 X2
    #1 name1  5 10
    #2 name2 NA 22
    #3 name3  9 24
    

    【讨论】:

    • 我在运行第二行命令时收到Error in rowSums(cbind(data1[i], data2[i]), na.rm = TRUE) : 'x' must be numeric 错误,为什么会这样?
    • @EuGENE 我再次测试了它,这次是在一个干净的 R 会话中,没有错误。我无法重现该问题。在运行第二行命令之前,可以检查icol 的值吗?它应该是一个命名向量,名称为X1 X2,值为2 3
    • 好吧,现在我似乎没有收到那个错误(不知道为什么)。我得到了我想要的输出,非常感谢!!
    【解决方案4】:

    这是使用data.table的解决方案。

    require(data.table)
    
    data1 <- data.table(NAMES = c("name1", "name2", "name3"),
                        X1 = c(1, NA, 3),
                        X2 = c(10, 11, 12))
    
    data2 <- data.table(NAMES = c("name1", "name2", "name3"),
                        X1 = c(4, NA, 6),
                        X2 = c(NA, 11, 12))
    
    dat <- rbind(data1, data2)
    dat[, lapply(.SD, sum, na.rm = T), keyby = "NAMES", .SDcols = c("X1", "X2")]
    

    请注意,结果与您在问题中显示的不完全相同。希望它仍然有用。

    > dat[, lapply(.SD, sum, na.rm = T), keyby = "NAMES", .SDcols = c("X1", "X2")]
       NAMES X1 X2
    1: name1  5 10
    2: name2  0 22
    3: name3  9 24
    

    【讨论】:

      猜你喜欢
      • 2019-02-07
      • 2022-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多