簡體   English   中英

R:將lapply和left_join組合以有條件地合並數據幀

[英]R: Combining lapply and left_join to conditionally merge dataframes

我希望那里的某個人可以幫助我找到我在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語句和左連接時。 特別是,我最終得到一個由列表組成的列表,每個列表僅保留原始對應數據幀的第一列。

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

我確信這個問題有一個荒謬簡單的解決方案。 誰能看到它?

提前致謝! 馬修

PS:我還嘗試了第二種方法,應用子集而不是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(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 > 0]], A, by = c("Let"))}, y = LIST)你的附言中的lapply(seq_along(LIST), function(x, y) {left_join(y[[x > 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:map with dplyr::left_joinABDF


使用Mapmerge可以在base 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

概觀

使用base::mapply()返回有條件合並的數據幀列表。 在這里,我提供兩個輸入:

  1. seq.along( along.with = LIST )獲取LIST的元素數量;
  2. LIST本身。

FUN參數是一個匿名函數,它接受兩個輸入ij - 並在使用base :: merge()執行左連接之前測試LIST的當前元素是偶數還是奇數。

如果seq.along( along.with = LIST ) i 元素的模運算符結果等於零,則將左連接B連接到LIST j 元素; 如果它不等於零,則在LIST j 元素上執行左連接A

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

MauritsEvers可能已經回答了你的問題,但我想我會解決R語法和編程邏輯中的明顯錯誤。 專注於第一次lapply電話:

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

首先,也許是瑣碎的缺少一個右)在第一個參數列表lapply -action。 接下來更基本的是錯誤地使用ifelse作為編程結構。 ifelse函數不是為數據對象的串行測試而設計的。 它僅設計為沿單個矢量應用。 if(.){.}else{.} -function可能應該在該lapply調用中使用,如果要進行串行選擇的話。

但是,(現在嘗試實現第一段而不是繼續糾正代碼)我認為在LIST -object上使用邏輯索引(使用R的隱式回收過程)而不是任何循環過程會簡單得多。 (這不是一個整合的解決方案。)此代碼將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

這個解決方案非常類似於(@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

為清楚起見,我重復了一些您的示例數據

數據

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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM