在R中将一些字符串附加到列表中

3

从网络爬虫中获取的数据被保存到一个列表中。然而,有些元素在每个页面上都不可用,导致列表具有不同长度的字符向量(如果所有数据都存在,则为4,否则为3)。

由于我需要保留列表的特定结构,所以我决定将相同的值附加到所有缺失数据的记录中。

手动这样做是有效的,但是当尝试编写循环时,它最终会用附加的值替换缺失的值,导致字符向量长度为1。

以下是一些示例数据:

things <- list(c("red", "small", "expensive", "car"),
               c("big", "expensive", "bike"),
               c("green", "big", "cheap", "bike"),
               c("small", "expensive", "car"))

假设我们知道每个缺失第一条记录(颜色)的物品都是蓝色的。手动添加它可以按预期工作,例如对于第二条记录返回("blue" "big" "expensive" "bike")。
c("blue", things[[2]])

然而,尝试将其转换为循环以遍历所有数据,基于字符长度的ifelse语句,它会用“blue”替换整个向量而不是附加它。

all_things <- list()

for(i in seq_along(things)) {
  all_things[i] <- ifelse(length(things[[i]]) == 3, c("blue", things[[i]]), things[i])
}

运行循环返回:

[[1]] [1] "红色" "小型" "昂贵的" "汽车"

[[2]] [1] "蓝色"

[[3]] [1] "绿色" "大型" "便宜的" "自行车"

[[4]] [1] "蓝色"

非常感谢您的帮助!


感谢大家提供的出色答案!最终,我选择了@Sotos的答案,但所有提出的解决方案都完美地工作。 - Gregor Fištravec
7个回答

3
我会分两步来完成这个操作:
  1. 将“缺失”的值添加到所有列表项之前
  2. 从每个列表项中仅获取最后4个值

这个两步的过程意味着我们不需要使用ifelse,也不需要循环:

lapply(things, append, "blue", after = 0L) |> lapply(tail, 4L)

结果:

[[1]]
[1] "red"       "small"     "expensive" "car"

[[2]]
[1] "blue"      "big"       "expensive" "bike"

[[3]]
[1] "green" "big"   "cheap" "bike"

[[4]]
[1] "blue"      "small"     "expensive" "car"

这是非常罕见的一种情况,使用append而不是c是合适的(尽管我们是prepend;再次表明append的命名不当)。

1
我很好奇为什么在这种情况下你更喜欢使用 append() 而不是 c() - dufei
2
为什么不只用一个lapply?lapply(things, \(i)tail(append(i, "blue", after = 0L),4L)) - Sotos
1
@Sotos 因为那样我需要使用一个lambda。 - Konrad Rudolph
@dufei 因为这样顺序就错了。我需要使用带有 after = 0L 参数的 append 来在前面添加值。当然,我也可以使用匿名函数,但如果可以避免使用它们(例如在这里),我通常会避免使用它们,通过将嵌套操作“解组”为多个应用程序(类似于因式分解的函数应用程序等效)来实现。 - Konrad Rudolph
所以你更喜欢使用循环两次而不是一次使用Lambda函数?好的。另外请注意,'append'在底层只是'C'。不过回答很棒。 - Onyambu
@onyambu 这里循环的次数并不是很重要。更重要的是可读性,如果我可以将调用分解为直接调用各个函数,则通常会避免使用lambda表达式。话虽如此,实际情况取决于具体情况,对于这种情况,我还是比较犹豫的:我也喜欢其他发布的答案,甚至不确定我是否会使用我发布的代码(实际上我更喜欢你的答案)。关于append:我知道它在内部使用了c,但这与问题有何关系? - Konrad Rudolph

3

如果您使用基本 R,可以尝试 lengths + Map

> things[lengths(things) == 3] <- Map(c, "blue", things[lengths(things) == 3])

> things
[[1]]
[1] "red"       "small"     "expensive" "car"

[[2]]
[1] "blue"      "big"       "expensive" "bike"

[[3]]
[1] "green" "big"   "cheap" "bike"

[[4]]
[1] "blue"      "small"     "expensive" "car"

3

你也可以使用

purrr::map_at(things, lengths(things) == 3, ~c('blue', .x))

[[1]]
[1] "red"       "small"     "expensive" "car"      

[[2]]
[1] "blue"      "big"       "expensive" "bike"     

[[3]]
[1] "green" "big"   "cheap" "bike" 

[[4]]
[1] "blue"      "small"     "expensive" "car"    

2
你可以像这样修复你的代码:
all_things <- list()

for(i in seq_along(things)) {
  all_things[[i]] <- if(length(things[[i]]) == 3) c("blue", things[[i]]) else things[[i]]
}

在这里,我们使用[[而不是[,并且使用if () {} else {}而不是ifelse。您应该学习ifelse的文档。


2
library(tidyverse)

things <- list(c("red", "small", "expensive", "car"),
               c("big", "expensive", "bike"),
               c("green", "big", "cheap", "bike"),
               c("small", "expensive", "car"))

map(
  things,
  ~if (length(.x) == 3L) c("blue", .x) else .x
)
#> [[1]]
#> [1] "red"       "small"     "expensive" "car"      
#> 
#> [[2]]
#> [1] "blue"      "big"       "expensive" "bike"     
#> 
#> [[3]]
#> [1] "green" "big"   "cheap" "bike" 
#> 
#> [[4]]
#> [1] "blue"      "small"     "expensive" "car"

使用reprex v2.0.2于2023年3月22日创建


2
你可以使用 lapply 来检查你的条件,例如:
lapply(things, \(i) if(length(i) == 3) {c('blue', i)}else{i})

[[1]]
[1] "red"       "small"     "expensive" "car"      

[[2]]
[1] "blue"      "big"       "expensive" "bike"     

[[3]]
[1] "green" "big"   "cheap" "bike" 

[[4]]
[1] "blue"      "small"     "expensive" "car"      

2
你可以使用lapply循环遍历每个长度为3的向量,并在其前面添加“蓝色”。
things[lengths(things) == 3] <- lapply(things[lengths(things) == 3], \(x) c("blue", x))
things
#> [[1]]
#> [1] "red"       "small"     "expensive" "car"      
#> 
#> [[2]]
#> [1] "blue"      "big"       "expensive" "bike"     
#> 
#> [[3]]
#> [1] "green" "big"   "cheap" "bike" 
#> 
#> [[4]]
#> [1] "blue"      "small"     "expensive" "car"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接