为列表中的每个数据框创建一个包含列总和的新行

Create a new row containing column sums for every data frame in a list

我有一个包含多个数据框的列表。示例数据:

df1 <- data.frame(Name=c("A", "B", "C"), E1=c(0, NA, 1), E2=c(1, 0, 1))
df2 <- data.frame(Name=c("A", "C", "F"), E1=c(1, 0, 1), E2=c(0, 0, 0))
ls <- list(df1, df2)

对于每个数据框,我想在底部创建一个包含每列总和的新行。所以对于 df1 是这样的:

Name E1 E2
"A"  0  1
"B"  NA 0
"C"  1  1
Sum  1  2

这是我试过的:

ls <- lapply(ls, function(x) {
  x[nrow(x)+1, -1] <- colSums(x[,-1], na.rm=TRUE)
})

我收到以下错误消息:

Error in colSums(x[,-1], na.rm = TRUE) : 'x' must be numeric

除了 "Names" 之外,我的所有专栏都只包含 1、0 和 NA,所以我认为它们可能被解读为因子而不是数字。我第一次尝试强制转换为数字(看起来像下面的函数但没有 "unlist")导致错误(对象类型列表不能强制输入 'double')所以我根据答案尝试了以下操作在 this other post:

ls <- lapply(ls, function(x) {
  x[,-1] <- as.numeric(unlist(x[,-1]))
})

但这只是给了我一个数字字符串列表,而不是我想要的数据框列表。非常感谢任何有关修复我的原始 colSums 函数或成功将我的数据转换为数字的建议!

你很亲近!您当前的功能仅 return 最后一行,因为默认情况下 return 最后一行上的任何对象都会起作用。所以你需要像下面这样的东西。 as.character 是因为字符串是作为因子输入的,这不会让你以正确的方式将 "Sum" 放入框架中。

但一般来说,除非这是为了某种输出,将摘要统计信息存储为 table 中的一行,否则这不是一个非常整洁的做法,因为有些行包含数据而其他行不包含数据会变得混乱.

df1 <- data.frame(Name=c("A", "B", "C"), E1=c(0, NA, 1), E2=c(1, 0, 1))
df2 <- data.frame(Name=c("A", "C", "F"), E1=c(1, 0, 1), E2=c(0, 0, 0))
ls <- list(df1, df2)

lapply(ls, function(x) {
  x[nrow(x)+1, -1] <- colSums(x[,-1], na.rm=TRUE)
  x[, 1] <- as.character(x[, 1])
  x[nrow(x), 1] <- "Sum"
  return(x)
})
#> [[1]]
#>   Name E1 E2
#> 1    A  0  1
#> 2    B NA  0
#> 3    C  1  1
#> 4  Sum  1  2
#> 
#> [[2]]
#>   Name E1 E2
#> 1    A  1  0
#> 2    C  0  0
#> 3    F  1  0
#> 4  Sum  2  0

reprex package (v0.2.0) 创建于 2018-03-16。

另一种选择是使用 rbindMap 作为:

Map(rbind, ls, lapply(ls, 
        function(x)sapply(x, 
         function(x)if(class(x) == "character"){ "Sum:" }else{ sum(x, na.rm = TRUE)})))
# [[1]]
# Name   E1 E2
# 1    A    0  1
# 2    B <NA>  0
# 3    C    1  1
# 4 Sum:    1  2
# 
# [[2]]
# Name E1 E2
# 1    A  1  0
# 2    C  0  0
# 3    F  1  0
# 4 Sum:  2  0

数据

注意:Name 列已更改为上述解决方案的“字符”。

df1 <- data.frame(Name=c("A", "B", "C"), E1=c(0, NA, 1), E2=c(1, 0, 1),
        stringsAsFactors = FALSE)
df2 <- data.frame(Name=c("A", "C", "F"), E1=c(1, 0, 1), E2=c(0, 0, 0),
        stringsAsFactors = FALSE)
ls <- list(df1, df2)
lapply(ls,function(i) 
data.frame(rbind(apply(i,2,as.vector),c("Sum",colSums(i[,-1],na.rm = TRUE) ))))

您可以使用 rbind:

df1 <- data.frame(Name=c("A", "B", "C"), E1=c(0, NA, 1), E2=c(1, 0, 1), stringsAsFactors = FALSE)
df2 <- data.frame(Name=c("A", "C", "F"), E1=c(1, 0, 1), E2=c(0, 0, 0), stringsAsFactors = FALSE)
ls <- list(df1, df2)

ls <- lapply(ls, function(x) {
  x <- rbind(x, c(
    "Sum", 
    sum(x[, "E1"], na.rm = TRUE),
    sum(x[, "E2"], na.rm = TRUE)))
})
ls

产生

[[1]]
  Name   E1 E2
1    A    0  1
2    B <NA>  0
3    C    1  1
4  Sum    1  2

[[2]]
  Name E1 E2
1    A  1  0
2    C  0  0
3    F  1  0
4  Sum  2  0

为了完整起见,这里也提供一个data.table的解决方案。 data.table 在将字符值添加到因子列时容忍度更高。不需要显式类型转换。

此外,我想推荐一个替代“data.frames列表”的方法。

library(data.table)
lapply(ls, function(x) rbind(setDT(x),  
  x[, c(.(Name = "sum"), lapply(.SD, sum, na.rm = TRUE)), .SDcols = c("E1", "E2")]
))
   Name E1 E2
1:    A  0  1
2:    B NA  0
3:    C  1  1
4:  sum  1  2

[[2]]
   Name E1 E2
1:    A  1  0
2:    C  0  0
3:    F  1  0
4:  sum  2  0

Name 列仍然是因子,但有一个额外的因子水平,可以通过对结果应用 str() 看出:

List of 2
 $ :Classes ‘data.table’ and 'data.frame':    4 obs. of  3 variables:
  ..$ Name: Factor w/ 4 levels "A","B","C","sum": 1 2 3 4
  ..$ E1  : num [1:4] 0 NA 1 1
  ..$ E2  : num [1:4] 1 0 1 2
  ..- attr(*, ".internal.selfref")=<externalptr> 
 $ :Classes ‘data.table’ and 'data.frame':    4 obs. of  3 variables:
  ..$ Name: Factor w/ 4 levels "A","C","F","sum": 1 2 3 4
  ..$ E1  : num [1:4] 1 0 1 2
  ..$ E2  : num [1:4] 0 0 0 0
  ..- attr(*, ".internal.selfref")=<externalptr>

替代 data.frames

列表

如果列表中的data.frames都具有相同的结构,即相同的列数、类型和名称,那么我更愿意将数据存储在一个对象中:

library(data.table)
DT <- rbindlist(ls, idcol = "df.id")
DT
   df.id Name E1 E2
1:     1    A  0  1
2:     1    B NA  0
3:     1    C  1  1
4:     2    A  1  0
5:     2    C  0  0
6:     2    F  1  0

每行的来源由 df.id 中的数字标识。现在,我们可以使用分组而不是循环遍历列表的元素,例如,

DT[, lapply(.SD, sum, na.rm = TRUE), .SDcols = c("E1", "E2"), by = df.id]
   df.id E1 E2
1:     1  1  2
2:     2  2  0

或者,如果要将 sum 行散布在原始数据中:

rbind(
  DT,
  DT[, c(.(Name = "sum"), lapply(.SD, sum, na.rm = TRUE)), .SDcols = c("E1", "E2"), by = df.id]
)[order(df.id)]
   df.id Name E1 E2
1:     1    A  0  1
2:     1    B NA  0
3:     1    C  1  1
4:     1  sum  1  2
5:     2    A  1  0
6:     2    C  0  0
7:     2    F  1  0
8:     2  sum  2  0