如何从 R 中的多个列创建合并值的新数据帧

How to a create a new dataframe of consolidated values from multiple columns in R

提问人:code_rookie 提问时间:10/29/2021 最后编辑:code_rookie 更新时间:10/29/2021 访问量:92

问:

我有一个数据帧 df1,如下所示:

样本 99_Ape_1 93_Cat_1 87_Ape_2 84_Cat_2 90_Dog_1 92_Dog_2
一个 2 3 1 7 4 6
B 5 9 7 0 3 7
C 6 8 9 2 3 0
D 3 9 0 5 8 3

我想通过根据标题行中存在的动物(即“猿”、“猫”、“狗”)对值求和来合并数据帧,并最终得到以下数据帧:

样本
一个 3 10 10
B 12 9 10
C 15 10 3
D 3 14 11

我创建了一个列表,代表所有称为“animals_list”的动物

然后,我创建了一个数据帧列表,该列表将每个动物子集到一个单独的数据帧中,其中包含:

animals_extract <- c()

for (i in 1:length(animals_list)){
  species_extract[[i]] <- df1[, grep(animals_list[i], names(df1))]
}

然后,我尝试按样本对行中的每个变量求和:

for (i in 1:length(species_extract)){
  species_extract[[i]]$total <- rowSums(species_extract[[i]])
} 

然后通过绑定新“总计”列中的所有值来创建数据帧“animal_total”。

animal_total <- NULL

for (i in 1:length(species_extract)){
  animal_total[i] <- cbind(species_extract[[i]]$total)
}

不幸的是,这似乎根本行不通,我想我可能走错了路。任何帮助将不胜感激!

编辑:我的数据框有 300 多只动物,这意味着使用我的标识符列表 (animals_list) 将不胜感激!我还要指出,有些列名不遵循结构“number_animal_number”,因此我不能使用重复搜索(对不起!

R for 循环 求和 数据操作

评论


答:

4赞 Wimpel 10/29/2021 #1

一种方法data.table

library(data.table)
library(rlist)
#set data to data.table format
setDT(df1)
# split column 2:n by regex on column names
L <- split.default(df1[,-1], gsub(".*_(.*)_.*", "\\1", names(df1)[-1]))
# Bind together again
data.table(sample = df1$sample, 
           as.data.table(list.cbind(lapply(L, rowSums))))
#    sample Ape Cat Dog
# 1:      A   3  10  10
# 2:      B  12   9  10
# 3:      C  15  10   3
# 4:      D   3  14  11

评论

0赞 code_rookie 10/29/2021
谢谢!这正是我要找的,但不幸的是,并非所有列名都具有相同的“number_animal_number”结构。有没有办法使用我创建的名称列表来做同样的事情?对不起,我的问题没有说清楚。
0赞 TarJae 10/29/2021
完善。不知何故,我必须学习 data.table 编码!
0赞 Wimpel 10/30/2021
@code_rookie是可以在正则表达式中捕获想要的列(可能的答案是 Yes ;-)),如果是这样,您可以使用上面的方法。如果你不确定如何做,你可以问一个新问题,要求一个正则表达式,用你的colnames作为样本数据。
3赞 TarJae 10/29/2021 #2

更新:澄清后: 这可能有效,具体取决于您的动物的其他名称。但这只是一个开始:

library(dplyr)
library(tidyr)
df %>% 
  pivot_longer(
    cols = -sample
  ) %>% 
  mutate(name1 = str_extract(name, '(?<=\\_)(.*?)(?=\\_)')) %>% 
  group_by(sample, name1) %>% 
  summarise(sum=sum(value)) %>% 
  pivot_wider(
    names_from = name1,
    values_from= sum
  )

输出:

  sample   Ape   Cat   Dog
  <chr>  <int> <int> <int>
1 A          3    10    10
2 B         12     9    10
3 C         15    10     3
4 D          3    14    11

第一个答案:以下是我们如何做到这一点:dplyr

library(dplyr)

df %>% 
  mutate(Cat = rowSums(select(., contains("Cat"))),
         Ape = rowSums(select(., contains("Ape"))),
         Dog = rowSums(select(., contains("Dog")))) %>% 
  select(sample, Cat, Ape, Dog)
  sample   Ape   Cat   Dog
  <chr>  <int> <int> <int>
1 A          3    10    10
2 B         12     9    10
3 C         15    10     3
4 D          3    14    11

评论

1赞 code_rookie 10/29/2021
谢谢!我的整个数据帧大约有 300 只动物,这意味着这种方式的代码会很长。有没有办法使用我创建的名称列表来做同样的事情?对不起,我的问题没有说清楚。我将编辑我的问题以使其明显。
0赞 TarJae 10/29/2021
哦,我明白。我去看看。
0赞 TarJae 10/29/2021
请参阅我的更新。这可能有效,具体取决于其他列名称的模式。告诉我!
0赞 Ben 10/29/2021
这也是我的方法。
1赞 code_rookie 10/29/2021
谢谢你们俩。在我清理了列名以赋予它一个规则模式后,这种方法奏效了,如示例数据帧所示。谢谢!
0赞 Sef 10/29/2021 #3

另一种 data.table 解决方案

library(data.table)

# Construct data table 
dt <- as.data.table(list(sample = c("A", "B", "C", "D"), 
                         `99_Ape_1` = c(2, 5, 6, 3), 
                         `93_Cat_1` = c(3, 9, 8, 9), 
                         `87_Ape_2` = c(1, 7, 9, 0),
                         `84_Cat_2` = c(7, 0, 2, 5),
                         `90_Dog_1` = c(4, 3, 3, 8),
                         `92_Dog_2` = c(6, 7, 0, 3)))

# Alternatively convert existing dataframe
# dt <- setDT(df)

# Use Regex pattern to drop ids from column names
names(dt) <- gsub("((^[0-9_]{3})|(_[0-9]{1}$))", "", names(dt))

# Pivot long (columns to rows)
dt <- melt(dt, id.vars = "sample")

# Aggregate sample by variable
dt <- dt[, .(value=sum(value)), by=.(sample, variable)]

# Unpivot (rows to colums)
dcast(dt, sample ~ variable)

#     sample Ape Cat Dog
# 1:      A   3  10  10
# 2:      B  12   9  10
# 3:      C  15  10   3
# 4:      D   3  14  11

或者,保持列名不变(在从 OP 到上一个答案的注释之后),并假设对同一样本有多个观察值:

dt <- as.data.table(list(sample = c("A", "B", "C", "D", "A"), 
                         `99_Ape_1` = c(2, 5, 6, 3, 1), 
                         `93_Cat_1` = c(3, 9, 8, 9, 1), 
                         `87_Ape_2` = c(1, 7, 9, 0, 1),
                         `84_Cat_2` = c(7, 0, 2, 5, 1),
                         `90_Dog_1` = c(4, 3, 3, 8, 1),
                         `92_Dog_2` = c(6, 7, 0, 3, 1)))

dt

#     sample 99_Ape_1 93_Cat_1 87_Ape_2 84_Cat_2 90_Dog_1 92_Dog_2
# 1:      A        2        3        1        7        4        6
# 2:      B        5        9        7        0        3        7
# 3:      C        6        8        9        2        3        0
# 4:      D        3        9        0        5        8        3
# 5:      A        1        1        1        1        1        1

# Pivot long (columns to rows)
dt <- melt(dt, id.vars = "sample")

# Aggregate sample by variable
dt <- dt[, .(value=sum(value)), by=.(sample, variable)]

# Unpivot (rows to colums)
dcast(dt, sample ~ variable)

#     sample 99_Ape_1 93_Cat_1 87_Ape_2 84_Cat_2 90_Dog_1 92_Dog_2
# 1:      A        3        4        2        8        5        7
# 2:      B        5        9        7        0        3        7
# 3:      C        6        8        9        2        3        0
# 4:      D        3        9        0        5        8        3

评论

0赞 code_rookie 10/29/2021
谢谢你的回答。我的编辑并不是说我希望保留相同的列名,我希望将它们聚合到基于动物的合并列中,但由于我有很多变量并且它们不遵循重复模式,我希望使用列表“animals_list”来搜索和合并。我希望这是有道理的。
0赞 Sef 10/29/2021
如果动物列表只是与列名匹配的值的字符向量,则答案是有效的。如果它是将列名中的值映射到相关动物种类的数据帧,则需要在透视长转换后进行额外的连接,以允许按目标值而不是列名聚合数据。如果动物清单是所需解决方案的关键部分,您应该在问题中包含一个最小工作示例。