使用 apply 函数遍历两个列表

Iterate through two lists with apply functions

我有一个数据框列表,其中数据框的每一列在第一行都有一个名称,在列的某些位置有 x-s。如果有 x,则第一行中的名称被视为已选中。 在现实世界的问题中,我读取了一个包含许多 sheet 的 xlsx 文件,其中每个 sheet 包含一个大矩阵:每一列在第一行都有一个名称,在一个有点稀疏的矩阵中有许多 x-s。每个 sheet 成为数据帧列表中的一个数据帧。行名称包含与查找相关但与我的问题无关的标识符,如此处所述。

data1 <- data.frame(Col1 = c("Mark", "x", "", "x", "", ""),
                    Col2 = c("Paul", "", "", "", "x", ""),
                    Col3 = c("Jane", "", "", "", "", ""),
                    Col4 = c("Mary", "x", "x", "x", "", ""),
                    Col5 = c("Peter", "x", "x", "x", "", ""),
                    stringsAsFactors = FALSE)

data2 <- data.frame(Col1 = c("Mark", "x", "x", "", "", ""),
                    Col2 = c("Paul", "", "", "", "", ""),
                    Col3 = c("Jane", "", "", "", "", ""),
                    Col4 = c("Mary", "x", "", "x", "", ""),
                    Col5 = c("Peter", "x", "x", "", "", ""),
                             stringsAsFactors = FALSE)

data <- list(data1 = data1, data2 = data2)

列表中的每个数据框都具有以下结构(为方便起见显示为矩阵),其中列表中每个数据框的名称都相同。只有 x-s 不同:

> as.matrix(data1)
     Col1   Col2   Col3   Col4   Col5   
[1,] "Mark" "Paul" "Jane" "Mary" "Peter"
[2,] "x"    ""     ""     "x"    "x"    
[3,] ""     ""     ""     "x"    "x"    
[4,] "x"    ""     ""     "x"    "x"    
[5,] ""     "x"    ""     ""     ""     
[6,] ""     ""     ""     ""     ""  

如果列中有 'x',我想向列表中的每个数据框添加一列 ("Approvers"),这是第 1 行中名称的串联:

     Col1   Col2   Col3   Col4   Col5    Approvers          
[1,] "Mark" "Paul" "Jane" "Mary" "Peter" ""                 
[2,] "x"    ""     ""     "x"    "x"     "Mark; Mary; Peter"
[3,] ""     ""     ""     "x"    "x"     "Mary; Peter"      
[4,] "x"    ""     ""     "x"    "x"     "Mark; Mary; Peter"
[5,] ""     "x"    ""     ""     ""      "Paul"             
[6,] ""     ""     ""     ""     ""      ""   

目前我分两步解决这个问题:

  1. 我创建了另一个列表列表,其中包含每个 x
  2. 的列位置
  3. 在嵌套的 for 循环中,我查找第一行中的所有名称并将它们连接起来。

代码如下:

position <- lapply(data, function(x) apply(x, 1, function(y) which(y %in% "x")))
position <- lapply(position, function(x) lapply(x, function(y) {if (length(y) == 0L) return(0) else return(y)})) # remove int(0) and replace with 0
position <- lapply(position, function(x) lapply(x, function(x) paste(x, collapse = ","))) # flatten second level list into string


for (i in 1:length(data)) {
  for (j in 1:nrow(data[[i]])) {
    if (as.numeric(unlist(strsplit(position[[i]][[j]], ",")))[[1]] == 0) {
      data[[i]][j, "Approvers"] <- ""
    } else {
      data[[i]][j, "Approvers"] <- paste(data[[i]][1, as.numeric(unlist(strsplit(position[[i]][[j]], ",")))], collapse = "; ")
    }
  }
}

对我来说这很笨拙,我想使用 lapply 和 mapply 通过同时循环遍历两个列表来做到这一点,但我不知道如何做到这一点。此外,创建位置对象并将 x-s 的列索引折叠成一个字符串并在循环中将它们分开过于复杂。

我们可以使用 lapply 遍历 list,然后使用 apply 遍历行和 paste 第一行的元素,其中值为 x:

res <- lapply(data, function(x) {
       x$Approvers <- apply(x, 1, FUN = function(y) paste(x[1,][y =="x"], collapse=";"))
       x})
res
#$data1
#  Col1 Col2 Col3 Col4  Col5       Approvers
#1 Mark Paul Jane Mary Peter                
#2    x              x     x Mark;Mary;Peter
#3                   x     x      Mary;Peter
#4    x              x     x Mark;Mary;Peter
#5         x                            Paul
#6                                          

#$data2
#  Col1 Col2 Col3 Col4  Col5       Approvers
#1 Mark Paul Jane Mary Peter                
#2    x              x     x Mark;Mary;Peter
#3    x                    x      Mark;Peter
#4                   x                  Mary
#5                                          
#6                                          

注意:数据集的 names 似乎应该是“Mark”、'Paul' 等而不是 'Col1'、'Col2'、..

作为替代方案,整理这些数据可能是值得的,这样更容易操作和推理。此外,您想要的输出可能并不总是令人满意,因为它 returns 整行 NAs。此处的代码重组了您的数据框,以便列名成为人名。然后它重塑数据,以便有两列,name 和来自原始数据框 (row_ix) 的行索引,其中 "x" 出现在该名称列中。然后我删除 NAs,按 row_ix 分组并将名称粘贴在一起,返回更整洁的数据帧。

我很欣赏这有点复杂,但以更整洁的方式存储数据可能会在较长的 运行 中为您解决问题。

library(dplyr)
library(purrr)
library(tidyr)
library(magrittr)

data %>% 
  map(function(x) #map function to all dataframes in list
  x %>% set_colnames(.[1, ]) %>% # set column names equal to first row values
  dmap(~ifelse(. == "x", seq_along(.), NA)) %>% # check for "x" in all rows of all columns
  gather(name, row_ix) %>% # reshape from wide to long, call new columns name and row_ix
  drop_na() %>% # drop NAs in the dataframe
  group_by(row_ix) %>% # group by row index
  summarise(approvers = paste0(name, collapse = ";")) # concatenate names from each group
  )

$data1
# A tibble: 4 × 2
  row_ix       approvers
   <int>           <chr>
1      2 Mark;Mary;Peter
2      3      Mary;Peter
3      4 Mark;Mary;Peter
4      5            Paul

$data2
# A tibble: 3 × 2
  row_ix       approvers
   <int>           <chr>
1      2 Mark;Mary;Peter
2      3      Mark;Peter
3      4            Mary