【问题标题】:Iterate through two lists with apply functions使用 apply 函数遍历两个列表
【发布时间】:2017-09-25 08:38:01
【问题描述】:

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

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”,我想为列表中的每个数据框添加一列(“批准者”),该列是第 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. 在嵌套的 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 的列索引折叠成一个字符串并在循环中将它们分隔开来过于复杂。

【问题讨论】:

    标签: r list lapply mapply


    【解决方案1】:

    我们可以使用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”、..

    【讨论】:

    • 非常优雅。谢谢阿克伦。
    • 嵌套的 apply 函数可能有点慢,因为它会将数据帧强制转换为矩阵。有没有办法避免在这里应用(而不是重新引入 for 循环)?
    【解决方案2】:

    作为替代方案,整理这些数据可能是值得的,这样更容易操作和推理。此外,您想要的输出可能并不总是可取的,因为它返回完整的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
    

    【讨论】:

    • 没问题,保罗。我采用这种方法的原因是因为您想要的输出将包含冗余信息 - 这可能确实适合您的需求,但考虑替代方案总是有帮助的 :)
    猜你喜欢
    • 1970-01-01
    • 2011-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-23
    • 1970-01-01
    相关资源
    最近更新 更多