【问题标题】:R: Combining lapply and left_join to conditionally merge dataframesR:结合 lapply 和 left_join 有条件地合并数据帧
【发布时间】:2018-04-14 15:41:15
【问题描述】:

我希望有人能帮助我找到我在 R 中的代码遇到的令人沮丧的问题的根源。我有一个由数据框组成的列表,我想离开加入每个两个 OTHER 数据帧之一上的元素(称为 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 语句和左连接结合起来时,我遇到了问题。特别是,我最终得到一个由列表组成的列表,每个 其中只保留原始对应数据框的第一列。

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

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

提前致谢! 马修

P.S.:我还尝试了第二种方法,应用子集而不是 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 

【问题讨论】:

  • 您能否编辑您的帖子以包含您的预期输出。前两个lapply 语句抛出错误。
  • 您好 Maurits Evers - 感谢您的及时回复!我已按照您的要求编辑了问题。前两个语句抛出错误,因为我心不在焉地忘记调用 dplyr。
  • 顺便说一句,与其反复说你不要stringsAsFactors,不如说library(hellno) lol
  • install.packages("hellno") 抛出错误:p

标签: r if-statement left-join lapply


【解决方案1】:

我不确定我是否理解您的问题。

以下解决方案基于从您的后记中复制lapply(seq_along(LIST), function(x, y) {left_join(y[[x &gt; 0]], A, by = c("Let"))}, y = LIST) 的输出。请注意,其他 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:mapdplyr::left_join 来加入ABDF


使用Mapmerge 在基础R 中也可以实现同样的效果:

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

【讨论】:

  • 嗨!这当然可以解决问题……但是,问题是将这种方法扩展到包含任意大量元素的列表。因此,如果我有一个包含 100 个项目的列表,我将如何将第 1、3、5、... 99 个项目合并到数据框 A,以及将第 2、4、6 个 ... 第 100 个项目合并到数据框 B?
【解决方案2】:

概述

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

  1. seq.along( along.with = LIST )获取LIST中的元素个数;和
  2. LIST 自己。

FUN 参数是一个匿名函数,它接受两个输入 - ij - 并在使用 @ 执行 left-join 之前测试 LIST 中的当前元素是偶数还是奇数987654324@。

如果seq.along( along.with = LIST ) 中第ith 元素的result of the modulus operator 等于0,则将B 左连接到@ 中的第jth 元素987654340@;如果不等于 0,则将 A 左连接到 LIST 中的第 jth 元素。

# 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 方法

使用purrrdplyr 包中的函数在下面复制结果。

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 #

【讨论】:

  • 非常感谢您及时回复我的问题!我认为这实际上可以解决我的问题 :D - 尽管我仍然需要对代码进行一些验证以进行验证。出于某种愚蠢的原因,我认为只有当他或她有两个长度相等的列表/向量时才能使用 mapply。
  • @M.Stargardter 没问题!在这种情况下,LISTseq.along( LIST ) 的长度相等。但是,对于不正确的情况,不相等的输入将被回收。有关更多信息,请参阅 under what circumstances does R recycle?How to define multiple variables with lapply?
【解决方案3】:

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

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

第一个lapply-action 中缺少关闭) 参数列表,这可能是微不足道的。下一个也是更根本的是不正确地使用ifelse 作为编程结构。 ifelse 函数不是为数据对象的串行测试而设计的。它仅设计为沿单个向量应用。如果要进行串行选择,if(.){.}else{.} 函数可能应该在 lapply 调用中使用。

但是,(现在尝试实现第一段而不是继续更正代码)我认为在LIST-object 上使用逻辑索引(使用 R 的隐式回收过程)会简单得多,而不是任何循环过程. (这不是一个 tidyverse 解决方案。)此代码会将 LIST 分割为“奇数”和“偶数”组件:

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

我们可以使用这种类型的结果来制作两个可以实现您既定目标的单行脚本。我将 LIST 对象设为四宽而不是两宽。

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

【讨论】:

  • 感谢您花时间准备如此详细的回复!我特别感谢您对 if-else 语句的适当性的说明。其他人也观察到了这一点,所以我以后在应用时一定会更加谨慎。
【解决方案4】:

此解决方案与 (@MauritsEvers & @aspiringurbandatascientist) 已在此处发布的基于 mapply 的解决方案非常相似,但它使用与 join data.frames 不同的方法。 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

【讨论】:

  • 查看解决问题的多种方法总是有帮助的 - 非常感谢您抽出时间回答我的问题!
  • @M.Stargardter 随时欢迎您。我很感激有足够的数据来重现问题的问题。
【解决方案5】:

为了清楚起见,我对您的示例数据进行了一些修改

数据

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,则仅使用 dplyrbase 会得到相同的结果:

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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-12
    • 2019-12-01
    • 2017-12-30
    • 2018-06-25
    • 2020-09-16
    • 1970-01-01
    • 2021-08-08
    相关资源
    最近更新 更多