通过匹配R中的嵌套列表来子集和连接数据帧

时间:2014-09-01 22:23:39

标签: r dataframe data.table dplyr

我尝试根据每个数据框中的元素连接两个数据框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中的数据。

思想?

3 个答案:

答案 0 :(得分:3)

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

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个可能的匹配项(例如&#34; a&#34;和&#34; g&#34;):

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

答案 1 :(得分:2)

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

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

答案 2 :(得分:2)

这是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