代码之家  ›  专栏  ›  技术社区  ›  M. Stargardter

R: 结合lapply和left\u join以有条件地合并数据帧

  •  3
  • M. Stargardter  · 技术社区  · 7 年前

    我希望有人能帮助我找到我在R中的代码遇到的令人沮丧的问题的根源。我有一个由数据帧组成的列表,我想将其他两个数据帧(称为a和B)中的每一个元素左连接起来。要连接的次要数据帧取决于元素在列表中的位置。出于我的目的,我希望每个奇数元素都左连接到A,每个偶数元素都左连接到B。

    library(dplyr)
    DF <- data.frame(Num = c("1","2"), Let = c("a","b"), stringsAsFactors = FALSE)
    A <- data.frame(Let = c("a","b"), Col = c("Yellow","Red"), stringsAsFactors = FALSE)
    B <- data.frame(Let = c("a","b"), Col = c("Green","Blue"), stringsAsFactors = FALSE)
    LIST <- list(DF, DF)
    

    到目前为止,我试着用两种不同的方式来实现这一点。第一种方法涉及if-else语句。如果我应用这样一个语句根据位置分配一个整数值,我就会得到预期的结果。同样,当我离开 使用if-else语句,只需在列表上执行一系列左连接 元素,一切正常。

    lapply(seq_along(LIST), function(x, {ifelse((x %% 2)==0, y[[x]] <- 1, y[[x]] <- 2)}, y = LIST)
    lapply(seq_along(LIST), function(x, {left_join(y[[x]], A, by = c("Let"))}, y = LIST)
    

    遇到问题的地方是当我尝试组合if-else语句和left-join时。特别是,我最终得到了一个由列表组成的列表,每个 其中仅保留原始对应数据帧的第一列。

    lapply(seq_along(LIST), function(x, y) {ifelse((x %% 2)==0, left_join(y[[x]], A, by = c("Let")), left_join(y[[x]], B, by = c("Let")))}, y = LIST)
    

    以下是我希望获得的输出:

    [[1]]
      Let Num    Col
    1   a   1 Yellow
    2   b   2    Red
    
    [[2]]
      Let Num   Col
    1   a   1 Green
    2   b   2  Blue
    

    我确信这个问题有一个非常简单的解决方案。有人能看到吗?

    提前感谢! 马修

    附笔。: 我还尝试了第二种方法,应用子集而不是if-else语句。然而,我再次遇到了问题。下面的第一行按预期工作,但第二行返回一个错误,好像R不识别列表索引:

    lapply(seq_along(LIST), function(x, y) {left_join(y[[x > 0]], A, by = c("Let"))}, y = LIST)
    lapply(seq_along(LIST), function(x, y) {left_join(y[[x == 1]], A, by = c("Let"))}, y = LIST)
    
    Error in y[[x == 1]] : attempt to select less than one element in integerOneIndex 
    
    5 回复  |  直到 7 年前
        1
  •  3
  •   Maurits Evers    7 年前

    我不能完全肯定我理解你的问题。

    以下解决方案基于 lapply(seq_along(LIST), function(x, y) {left_join(y[[x > 0]], A, by = c("Let"))}, y = LIST) 从您的postscript。请注意,另一个 lapply 行抛出错误。

    library(tidyverse);
    map(list(A, B), function(x) left_join(DF, x))
    #Joining, by = "Let"
    #Joining, by = "Let"
    #[[1]]
    #  Num Let    Col
    #1   1   a Yellow
    #2   2   b    Red
    #
    #[[2]]
    #  Num Let   Col
    #1   1   a Green
    #2   2   b  Blue
    

    我们使用 purrr:map 具有 dplyr::left_join 加入 A B 具有 DF .


    在base R中也可以使用 Map merge :

    mapply(function(x) merge(DF, x, by = "Let"), list(A, B), SIMPLIFY = F)
    #[[1]]
    #  Let Num    Col
    #1   a   1 Yellow
    #2   b   2    Red
    #
    #[[2]]
    #  Let Num   Col
    #1   a   1 Green
    #2   b   2  Blue
    
        2
  •  2
  •   Cristian E. Nuno    7 年前

    概述

    使用 base::mapply() 返回按条件合并的数据帧列表。这里,我提供两个输入:

    1. seq.along( along.with = LIST ) 获取中的元素数 LIST ; 和
    2. 列表 它本身

    这个 FUN 参数是接受两个输入的匿名函数- i j -并测试当前元素是否 列表 在执行 left-join 使用 base::merge() .

    如果 result of the modulus operator 对于i th公司 中的元素 序号:。沿(沿=列表) 等于零,然后左连接 B 在j上 th公司 中的元素 列表 ; 如果不等于零,则执行左连接 A 在j上 th公司 中的元素 列表 .

    # load data
    DF <- data.frame(Num = c("1","2"), Let = c("a","b"), stringsAsFactors = FALSE)
    A <- data.frame(Let = c("a","b"), Col = c("Yellow","Red"), stringsAsFactors = FALSE)
    B <- data.frame(Let = c("a","b"), Col = c("Green","Blue"), stringsAsFactors = FALSE)
    LIST <- list(DF, DF)
    
    # goal: left join all odd elements in LIST[[j]]
    #       to `A` and all even elements to `B`
    merged.list <- 
      mapply( FUN = function( i, j )
              if( i %% 2 == 0 ){
                merge( x = j
                       , y = B
                       , by = "Let"
                       , all.x = TRUE )
              } else{
                merge( x = j
                       , y = A
                       , by = "Let"
                       , all.x = TRUE )
              }
            , seq_along( along.with = LIST )
            , LIST
            , SIMPLIFY = FALSE )
    
    # view results
    merged.list
    # [[1]]
    # Let Num    Col
    # 1   a   1 Yellow
    # 2   b   2    Red
    # 
    # [[2]]
    # Let Num   Col
    # 1   a   1 Green
    # 2   b   2  Blue
    
    # end of script #
    

    Tidyverse方法

    下面使用 purrr dplyr 包装。

    library( dplyr )
    library( purrr )
    
    merged.list <-
      map2( .x = seq_along( along.with = LIST )
            , .y = LIST
            , .f = function( i, j )
              if( i %% 2 == 0 ){
                left_join( x = j
                           , y = B
                           , by = "Let" )
              } else{
                left_join( x = j
                           , y = A
                           , by = "Let" )
              })
    
    # view results
    merged.list
    # [[1]]
    # Num Let    Col
    # 1   1   a Yellow
    # 2   2   b    Red
    # 
    # [[2]]
    # Num Let   Col
    # 1   1   a Green
    # 2   2   b  Blue
    
    # end of script #
    
        3
  •  1
  •   IRTFM    7 年前

    MauritsEvers可能已经回答了您的问题,但我想我会解决R语法和编程逻辑中的明显错误。关注第一个lapply调用:

    lapply(seq_along(LIST), function(x, {ifelse((x %% 2)==0, y[[x]] <- 1, y[[x]] <- 2)}, y = LIST)
    

    第一个,或许也是微不足道的,是缺少结尾 ) 第一个中的参数列表 lapply -行动。下一个更根本的问题是 ifelse 作为编程构造。这个 如果其他 函数不是为数据对象的串行测试而设计的。它仅设计为沿单个向量应用。这个 if(.){.}else{.} -函数可能应该用在 拉普拉 如果要进行串行选择,请调用。

    然而,(现在尝试实现第一段而不是继续更正代码),我认为在 LIST -对象,而不是任何循环进程。(这是 tidyverse解决方案。)此代码将列表分为“奇数”和“偶数”两部分:

      oddList <- LIST[ c(TRUE,FALSE) ]  # implicit seq-along by virtue of recycling
      evenList <- LIST[ c(FALSE,TRUE) ]
    

    我们可以使用这种类型的结果来制作两个一行程序,以实现您所述的目标。我将列表对象设置为四宽,而不是两宽。

    Abig <- Reduce( function(x,y) {merge(x,y,by="Let")}, LIST, init=A)
    Warning message:
    In merge.data.frame(x, y, by = "Let") :
      column names ‘Num.x’, ‘Num.y’ are duplicated in the result
    Bbig <- Reduce( function(x,y) {merge(x,y,by="Let")}, LIST, init=B)
    Warning message:
    In merge.data.frame(x, y, by = "Let") :
      column names ‘Num.x’, ‘Num.y’ are duplicated in the result
    

    这只是一个警告,在这里您可以看到它的警告内容:

    > Abig
      Let    Col Num.x Num.y Num.x Num.y
    1   a Yellow     1     1     1     1
    2   b    Red     2     2     2     2
    

    如果您需要那些唯一标记的重复列名(我认为这是个好主意),那么:

    names(Abig)[ grep("Num", names(Abig)) ] <- 
                        paste0("Num.", seq_along( grep("Num", names(Abig)) ) )
    Abig
      Let    Col Num.1 Num.2 Num.3 Num.4
    1   a Yellow     1     1     1     1
    2   b    Red     2     2     2     2
    
        4
  •  1
  •   MKR    7 年前

    此解决方案与 mapply (@Mauritservers&aspiringurbandatascientist)在此发布了基于的解决方案,但它使用了不同的方法 join 数据框架。这个 dplyr::left_join 已用于符合目的。

    library(dplyr)
    # Using mapply and left_join
    mapply(function(x,y){
      if(y %% 2 == 1){
        left_join(x, A, by="Let")
      }else {
        left_join(x, B, by="Let")
      }
    }, LIST, seq_along(LIST), SIMPLIFY = FALSE)
    
    # [[1]]
    #   Num Let    Col
    # 1   1   a Yellow
    # 2   2   b    Red
    # 
    # [[2]]
    #   Num Let   Col
    # 1   1   a Green
    # 2   2   b  Blue
    
        5
  •  0
  •   moodymudskipper    7 年前

    为了清晰起见,我修改了一些您的示例数据

    数据

    DF1 <- data.frame(Num1 = c("1","2"), Let = c("a","b"), stringsAsFactors = FALSE)
    DF2 <- data.frame(Num2 = c("3","4"), Let = c("a","b"), stringsAsFactors = FALSE)
    DF3 <- data.frame(Num3 = c("5","6"), Let = c("a","b"), stringsAsFactors = FALSE)
    DF4 <- data.frame(Num4 = c("7","8"), Let = c("a","b"), stringsAsFactors = FALSE)
    A <- data.frame(Let = c("a","b"), Col = c("Yellow","Red"), stringsAsFactors = FALSE)
    B <- data.frame(Let = c("a","b"), Col = c("Green","Blue"), stringsAsFactors = FALSE)
    LIST <- list(DF1, DF2, DF3, DF4)
    

    解决方案

    library(dplyr)
    library(purrr)
    LIST_odd  <- LIST[as.logical(seq_along(LIST)%%2)]
    LIST_even <- LIST[!as.logical(seq_along(LIST)%%2)]
    merge_odd <- reduce(LIST_odd,left_join,.init=A) 
    #   Let    Col Num1 Num3
    # 1   a Yellow    1    5
    # 2   b    Red    2    6
    
    merge_even <- reduce(LIST_even,left_join,.init=B)
    #   Let   Col Num2 Num4
    # 1   a Green    3    7
    # 2   b  Blue    4    8
    

    如果您不想使用 purrr 这些结果与 dplyr base :

    Reduce(left_join,LIST_odd,A)
    Reduce(left_join,LIST_even,B)
    

    或100%基础:

    Reduce(function(x,y) merge(x,y,all.x=TRUE),LIST_odd,A)
    Reduce(function(x,y) merge(x,y,all.x=TRUE),LIST_even,B)