【问题标题】:How to add a column to lists within a list without losing their names?如何在不丢失名称的情况下向列表中的列表添加一列?
【发布时间】:2018-12-10 18:05:03
【问题描述】:

我做了几次尝试分别将特定列添加到数据框。列表中的列表,但所有 *apply() 尝试都未能保留数据框的名称。

例如对于列表l

l <- list(alpha=data.frame(1:3), bravo=data.frame(4:6), charly=data.frame(7:9))

> l
$`alpha`
  X1.3
1    1
2    2
3    3

$bravo
  X4.6
1    4
2    5
3    6

$charly
  X7.9
1    7
2    8
3    9

我想要列表名称的首字母作为第二个id 列。我尝试了这些尝试,它们基本上可以满足我的需求:

lapply(seq_along(l), function(x) cbind(l[[x]], id=substr(names(l)[x], 1, 1)))
# or
lapply(seq_along(l), function(x) data.frame(l[[x]], id=substr(names(l)[x], 1, 1)))
# [[1]]
# X1.3 id
# 1    1  a
# 2    2  a
# 3    3  a
# 
# [[2]]
# X4.6 id
# 1    4  b
# 2    5  b
# 3    6  b
# 
# [[3]]
# X7.9 id
# 1    7  c
# 2    8  c
# 3    9  c

但是内部列表已经失去了他们的名字。 lapply() 文档中的选项 USE.NAMES=TRUE 无效。

我也尝试了这两种尝试,但失败得更惨。

lapply(seq_along(l), function(x) mapply(cbind, l[[x]], id=substr(names(l)[x], 1, 1), 
                                        SIMPLIFY=FALSE))
rapply(l, function(x) cbind(x, id=substr(names(l)[x], 1, 1)), how="list")

我知道我可以这样做:

l1 <- lapply(seq_along(l), function(x) cbind(l[[x]], id=substr(names(l)[x], 1, 1)))
names(l1) <- names(l)

或者做一个for循环:

for(i in seq_along(l)) {
  l[[i]] <- data.frame(l[[i]], id=substr(names(l)[i], 1, 1))
}

但我想知道是否可以改进 *apply() 解决方案以带来预期的输出,即:

$`alpha`
  X1.3 id
1    1  a
2    2  a
3    3  a

$bravo
  X4.6 id
1    4  b
2    5  b
3    6  b

$charly
  X7.9 id
1    7  c
2    8  c
3    9  c

【问题讨论】:

    标签: r list lapply


    【解决方案1】:

    试试Map

    Map(`[<-`, l, "id", value = substr(names(l), 1, 1))
    #$alpha
    #  X1.3 id
    #1    1  a
    #2    2  a
    #3    3  a
    
    #$bravo
    #  X4.6 id
    #1    4  b
    #2    5  b
    #3    6  b
    
    #$charly
    #  X7.9 id
    #1    7  c
    #2    8  c
    #3    9  c
    

    第一个参数是一个函数。 Map 然后将函数“应用于每个 ... 参数的第一个元素、第二个元素、第三个元素,等等。”,请参阅 ?mapply

    【讨论】:

    • 这确实很划算。
    【解决方案2】:

    sapply 覆盖带有simplify = FALSE 的名称。

    addId <- function(x) cbind(l[[x]], id = substring(x, 1, 1))
    sapply(names(l), addId, simplify = FALSE)
    

    给予:

    $`alpha`
      X1.3 id
    1    1  a
    2    2  a
    3    3  a
    
    $bravo
      X4.6 id
    1    4  b
    2    5  b
    3    6  b
    
    $charly
      X7.9 id
    1    7  c
    2    8  c
    3    9  c
    

    交替:

    replace(l, TRUE, lapply(names(l), addId))
    

    【讨论】:

      【解决方案3】:

      如果您不介意从 apply 系列切换到 purrr::map 系列,purrr::imap 采用 2 个参数:被映射的项目和被映射的项目的名称。然后您可以使用相同的 cbind 调用,但您现在可以轻松访问数据框的名称。

      l <- list(alpha=data.frame(1:3), bravo=data.frame(4:6), charly=data.frame(7:9))
      
      purrr::imap(l, function(df, name) cbind(df, id = substr(name, 1, 1)))
      #> $alpha
      #>   X1.3 id
      #> 1    1  a
      #> 2    2  a
      #> 3    3  a
      #> 
      #> $bravo
      #>   X4.6 id
      #> 1    4  b
      #> 2    5  b
      #> 3    6  b
      #> 
      #> $charly
      #>   X7.9 id
      #> 1    7  c
      #> 2    8  c
      #> 3    9  c
      

      或者,如果您想填写完整的tidyverse,您可以在您的imap 中添加一个带有dplyr::mutate 的列。

      library(tidyverse)
      
      imap(l, function(df, name) df %>% mutate(id = str_sub(name, 1, 1)))
      #> $alpha
      #>   X1.3 id
      #> 1    1  a
      #> 2    2  a
      #> 3    3  a
      #> 
      #> $bravo
      #>   X4.6 id
      #> 1    4  b
      #> 2    5  b
      #> 3    6  b
      #> 
      #> $charly
      #>   X7.9 id
      #> 1    7  c
      #> 2    8  c
      #> 3    9  c
      

      正如@markus 所指出的,您还可以使用~. 公式符号速记,而不是拼写您的函数。在这种情况下,purrr::imap 的两个参数变为.x(数据帧)和.y(名称)。这看起来像:

      purrr::imap(l, ~cbind(.x, id = substr(.y, 1, 1)))
      

      【讨论】:

      • 是的。我通常喜欢写出我的函数,至少一开始是为了让我自己和 SO 读者清楚。不过谢谢你提醒我
      • 抱歉,该评论并非无所不知。
      • 没问题,没把它当成这样!我不经常使用公式符号,所以我忘记了其他人很喜欢它
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-11-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多