【问题标题】:Subset and join a data frame by matching on nested list in R通过匹配 R 中的嵌套列表来子集和连接数据框
【发布时间】:2014-10-26 03:18:18
【问题描述】:

我正在尝试根据每个列中的元素连接两个数据框 df 和 myData。 df 中的列故意包含嵌套列表,如果嵌套列表中的元素与 myData 的元素匹配,我想加入。我想在 df 中保留不匹配的行(左连接)。

这是一个示例,首先在 df 中没有嵌套列表。

df = data.frame(a=1:5)
df$x1= c("a", "b", "g", "a", "a")
str(df)

'data.frame':   5 obs. of  2 variables:
$ a : int  1 2 3 4 5
$ x1: chr  "a" "b" "g" "a" ...

myData <- data.frame(x1=c("a", "g", "q"), x2= c("za", "zg", "zq"), stringsAsFactors = FALSE)

现在,我们可以加入第 x1 列:

#using a for loop
df$x2 <- NA
for(id in 1:nrow(myData)){
  df$x2[df$x1 %in% myData$x1[id]] <- myData$x2[id]
}

或者使用 dplyr:

library(dplyr)
df = data.frame(a=1:5)
df$x1= c("a", "b", "g", "a", "a")
df %>%
  left_join(myData)

现在,考虑使用嵌套列表的 df。

l1 = list(letters[1:5])
l2 = list(letters[6:10])
df = data.frame(a=1:5)
df$x1= c("a", "b", "g", l1, l2)

正如我们所料,使用 for 循环无法匹配嵌套列表的元素:

df$x2 <- NA
for(id in 1:nrow(myData)){
  df$x2[df$x1 %in% myData$x1[id]] <- myData$x2[id]
}

输出:

df
  a            x1   x2
1 1             a   za
2 2             b <NA>
3 3             g   zg
4 4 a, b, c, d, e <NA>
5 5 f, g, h, i, j <NA>

使用 dplyr:

df %>%
  left_join(myData)

抛出错误:

Joining by: c("x1", "x2")
Error: cannot join on column 'x1'

我认为解决方案需要取消列出嵌套列表,但还没有整理出如何将 unlist 函数工作到上述策略中。

我也尝试过使用 data.table 包进行上述操作。如何使用 data.table 完成此操作可能是一个额外的问题。但是,就 data.table 处理数据框中的列表而言,我想将其包含在内,因为它可能提供最佳解决方案。

我的实际数据大约是 100,000 行,因此在列表中匹配以 R 为基数的列表可能会影响性能(考虑 data.table 的另一个原因?)

Fwiw,在数据帧中使用嵌套列表(和其他结构)是我在 Python 中经常做的事情,而且可能有更好的方法首先在 R 中构造数据。

想法?

【问题讨论】:

  • 这个问题呼唤我。它说:“使用 rapply。它可能不是最快的。它可能不是最干净的。它可能不是最容易理解的。但它会是最酷的。”
  • 或许,最好把你的数据改成一个很长的“data.frame”DF = data.frame(a = rep(df$a, sapply(df$x1, length)), x1 = unlist(df$x1))并对其进行操作。对于您正在尝试做的事情,merge (merge(DF, myData, by = "x1", all.x = T)) 很有用,如果您真的想要您拥有的格式,您可以使用 aggregate 来压缩您的长数据。通常,R 可以轻松处理具有多个分类变量的数据,而不是用于最终目标的嵌套列表。

标签: r dataframe data.table dplyr


【解决方案1】:

我认为这可能有效。当您对列表进行递归操作时,最好编写一个辅助函数来获取值。

getMatch <- function(x, y) {
      z <- y[[2]][sort(match(x, y[[1]]))]
      z[!length(z)] <- NA
      z
}
> rapply(unname(df[-1]), getMatch, y = myData)
# [1] "za" NA   "zg" "za" "zg"

或者我们可以使用within分配一个新列

> within(df, { x2 <- sapply(df$x1, getMatch, y = myData) })
#  a            x1   x2
#1 1             a   za
#2 2             b <NA>
#3 3             g   zg
#4 4 a, b, c, d, e   za
#5 5 f, g, h, i, j   zg

【讨论】:

  • 我不确定我是否遵循,因为此解决方案在嵌套列表(或嵌套向量)中不匹配
  • 当然我遗漏了一些明显的东西,但是我们在这个函数 getMatch(x) 中作为 x 传递了什么?感谢您提供有关提供输出的有用建议。
  • @bassounds - 我添加了一个rapply 答案。不过,我的方法并没有太大的不同
  • 谢谢,这是我的数据的最佳解决方案,基于非正式的时间(比 for 循环快得多)和代码的简洁性。
【解决方案2】:

这是一个可能的解决方案:

df$x2 <- NA
for(id in 1:nrow(df)) 
  {
  df$x2[id] <- ifelse(
    length(ff <- myData$x2[which(myData$x1 == intersect(df$x1[[id]], myData$x1))])==0, 
    NA, 
    ff)
  }

df
#  a            x1   x2
#1 1             a   za
#2 2             b <NA>
#3 3             g   zg
#4 4 a, b, c, d, e   za
#5 5 f, g, h, i, j   zg

上述解决方案存在一些潜在的缺陷。例如,如果我们将 l1 更改为有 2 个可能的匹配项(例如“a”和“g”):

l1 = list(letters[1:7])
df$x1= c("a", "b", "g", l1, l2)

此解决方案不会同时捕获两个匹配项:

df$x2 <- NA
    for(id in 1:nrow(df)) 
      {
      df$x2[id] <- ifelse(
        length(ff <- myData$x2[which(myData$x1 == intersect(df$x1[[id]], myData$x1))])==0, 
        NA, 
        ff)
      }
Warning message:
In myData$x1 == intersect(df$x1[[id]], myData$x1) :
  longer object length is not a multiple of shorter object length

如果需要,您可以修改它以允许多个匹配项。这里有两种不同的方法,一种使用paste,另一种使用list,就像你在问题中所做的那样。

df$x2 <- NA
    for(id in 1:nrow(df)) 
      {
      df$x2[id] <- 
        paste(if (length(ff <- myData$x2[which(myData$x1 %in% intersect(df$x1[[id]], myData$x1))])==0)
        NA else
        ff, collapse=", ")
      }


df$x2 <- NA
    for(id in 1:nrow(df)) 
      {
      df$x2[id] <- 
        list(if (length(ff <- myData$x2[which(myData$x1 %in% intersect(df$x1[[id]], myData$x1))])==0)
        NA else
        ff)
      }

两者都将返回以下内容,但底层结构会有所不同:

  a                  x1     x2
1 1                   a     za
2 2                   b     NA
3 3                   g     zg
4 4 a, b, c, d, e, f, g za, zg
5 5       f, g, h, i, j     zg

【讨论】:

  • 谢谢,这绝对有效!我的实际数据不太可能有多个匹配项——但并非不可能。对于多个匹配的类似问题,修改将是至关重要的。也非常感谢您整理出这种可能性!
【解决方案3】:

这是一个data.table 选项:

library(data.table)

# convert to data.table in place
setDT(myData)

# using Frank's extended example
l1 = list(letters[1:7])
l2 = list(letters[6:10])
dt = data.table(a=1:5, x1 = c("a", "b", "g", l1, l2))

# unlist the lists (and to be honest, that's how I would store the data,
# I think the column of lists is a bad idea), then set the keys, merge, and
# go back to columns of lists
setkey(dt[, unlist(x1), by = a], V1)[myData, x2 := i.x2][,
            list(x1 = list(V1), x2 = list(na.omit(x2))), keyby = a]
#   a           x1    x2
#1: 1            a    za
#2: 2            b      
#3: 3            g    zg
#4: 4 a,b,c,d,e,f, za,zg
#5: 5    f,g,h,i,j    zg

【讨论】:

  • 我似乎收到一个错误:setkeyv(ans, names(ans)[seq_along(byval)]) 中的错误:列表的第 2 项不是向量
  • @bassounds 也许你有一个旧的data.table 版本,而且我现在不记得了。我使用的是 1.9.3 版。
  • 谢谢,我使用的是 1.9.2,这是最新的 CRAN 版本,但通过 github 更新到 1.9.3,它可以工作了。
猜你喜欢
  • 2023-03-05
  • 1970-01-01
  • 1970-01-01
  • 2020-07-12
  • 2022-01-14
  • 1970-01-01
  • 1970-01-01
  • 2018-09-21
  • 1970-01-01
相关资源
最近更新 更多