リスト内の複数のdata.framesを同時にマージする


259

マージしたい多くのdata.framesのリストがあります。ここでの問題は、行と列の数の点で各data.frameが異なるということですが、(私が呼ばれてきた彼らのすべての共有キーの変数"var1""var2"以下のコードで)。data.framesが列の点で同一である場合、私は単にrbindplyrのrbind.fillがその仕事をすることができますが、これらのデータの場合はそうではありません。

このmergeコマンドは2つのdata.framesでのみ機能するため、アイデアを求めてインターネットを利用しました。私はこれをここから入手しましたが、R 2.7.2で完全に機能しました。

merge.rec <- function(.list, ...){
    if(length(.list)==1) return(.list[[1]])
    Recall(c(list(merge(.list[[1]], .list[[2]], ...)), .list[-(1:2)]), ...)
}

そして、私はそのように関数を呼び出します:

df <- merge.rec(my.list, by.x = c("var1", "var2"), 
                by.y = c("var1", "var2"), all = T, suffixes=c("", ""))

しかし、2.11と2.12を含む2.7.2以降のRバージョンでは、このコードは次のエラーで失敗します。

Error in match.names(clabs, names(xi)) : 
  names do not match previous names

(ちなみに、私は他の場所でこのエラーへの他の参照を見ますであり、解決策がありません)。

これを解決する方法はありますか?

回答:


183

別の質問では、Rでdplyrを使用して複数の左結合を実行する方法を具体的に尋ねました。質問はこれの重複としてマークされたので、以下の3つのサンプルデータフレームを使用して、ここで回答します。

x <- data.frame(i = c("a","b","c"), j = 1:3, stringsAsFactors=FALSE)
y <- data.frame(i = c("b","c","d"), k = 4:6, stringsAsFactors=FALSE)
z <- data.frame(i = c("c","d","a"), l = 7:9, stringsAsFactors=FALSE)

2018年6月の更新:マージを実行する3つの異なる方法を表す3つのセクションに回答を分割しました。tidyverseパッケージをpurrr既に使用している場合は、この方法を使用することをお勧めします。以下の比較のために、同じサンプルデータセットを使用したベースRバージョンを見つけます。


1)パッケージreduceから参加するpurrr

purrrパッケージは提供reduce簡潔な構文を持っている機能を:

library(tidyverse)
list(x, y, z) %>% reduce(left_join, by = "i")
#  A tibble: 3 x 4
#  i       j     k     l
#  <chr> <int> <int> <int>
# 1 a      1    NA     9
# 2 b      2     4    NA
# 3 c      3     5     7

full_joinまたはなどの他の結合を実行することもできますinner_join

list(x, y, z) %>% reduce(full_join, by = "i")
# A tibble: 4 x 4
# i       j     k     l
# <chr> <int> <int> <int>
# 1 a     1     NA     9
# 2 b     2     4      NA
# 3 c     3     5      7
# 4 d     NA    6      8

list(x, y, z) %>% reduce(inner_join, by = "i")
# A tibble: 1 x 4
# i       j     k     l
# <chr> <int> <int> <int>
# 1 c     3     5     7

2)dplyr::left_join()ベースRありReduce()

list(x,y,z) %>%
    Reduce(function(dtf1,dtf2) left_join(dtf1,dtf2,by="i"), .)

#   i j  k  l
# 1 a 1 NA  9
# 2 b 2  4 NA
# 3 c 3  5  7

3)ベースR merge()とベースR Reduce()

比較のために、左結合のベースRバージョンを示します

 Reduce(function(dtf1, dtf2) merge(dtf1, dtf2, by = "i", all.x = TRUE),
        list(x,y,z))
#   i j  k  l
# 1 a 1 NA  9
# 2 b 2  4 NA
# 3 c 3  5  7

1
full_joinバリアントは完全に機能し、受け入れられた回答よりもはるかに恐ろしくないように見えます。ただし、速度の違いはそれほどありません。
bshor 16

1
@Axemanは正しいですが、map_dfr()またはmap_dfc()
DaveRGP

「ls(pattern = "DF_name_contains_this")」を使用したパターンに基づいて、いくつかのDFに参加することはできましたが、できませんでした。使用済み'noquote((()を貼り付け) "が、私はまだ代わりにDFのリストの文字ベクトルを生成するよ、私は名前を入力してしまった、不快である。。
ジョージ・ウィリアム・ラッセルペン

もう一つの問題は、提供Python実装パンダデータフレームのリスト:dfs = [df1, df2, df3]その後を reduce(pandas.merge, dfs)
Paul Rougieux

222

Reduceはこれをかなり簡単にします。

merged.data.frame = Reduce(function(...) merge(..., all=T), list.of.data.frames)

いくつかのモックデータを使用した完全な例を次に示します。

set.seed(1)
list.of.data.frames = list(data.frame(x=1:10, a=1:10), data.frame(x=5:14, b=11:20), data.frame(x=sample(20, 10), y=runif(10)))
merged.data.frame = Reduce(function(...) merge(..., all=T), list.of.data.frames)
tail(merged.data.frame)
#    x  a  b         y
#12 12 NA 18        NA
#13 13 NA 19        NA
#14 14 NA 20 0.4976992
#15 15 NA NA 0.7176185
#16 16 NA NA 0.3841037
#17 19 NA NA 0.3800352

そして、これらのデータを使用して複製する例を次に示しますmy.list

merged.data.frame = Reduce(function(...) merge(..., by=match.by, all=T), my.list)
merged.data.frame[, 1:12]

#  matchname party st district chamber senate1993 name.x v2.x v3.x v4.x senate1994 name.y
#1   ALGIERE   200 RI      026       S         NA   <NA>   NA   NA   NA         NA   <NA>
#2     ALVES   100 RI      019       S         NA   <NA>   NA   NA   NA         NA   <NA>
#3    BADEAU   100 RI      032       S         NA   <NA>   NA   NA   NA         NA   <NA>

注:これは間違いなくのバグのようmergeです。問題は、サフィックスを追加すること(一致しない名前の重複を処理するため)が実際にそれらを一意にすることのチェックがないことです。ある時点で、[.data.frameどれ make.unique名前を使用するかを使用て、rbindを失敗させます。

# first merge will end up with 'name.x' & 'name.y'
merge(my.list[[1]], my.list[[2]], by=match.by, all=T)
# [1] matchname    party        st           district     chamber      senate1993   name.x      
# [8] votes.year.x senate1994   name.y       votes.year.y
#<0 rows> (or 0-length row.names)
# as there is no clash, we retain 'name.x' & 'name.y' and get 'name' again
merge(merge(my.list[[1]], my.list[[2]], by=match.by, all=T), my.list[[3]], by=match.by, all=T)
# [1] matchname    party        st           district     chamber      senate1993   name.x      
# [8] votes.year.x senate1994   name.y       votes.year.y senate1995   name         votes.year  
#<0 rows> (or 0-length row.names)
# the next merge will fail as 'name' will get renamed to a pre-existing field.

修正する最も簡単な方法は、重複するフィールド(ここには多数あります)のフィールド名の変更をまで残さないことmergeです。例えば:

my.list2 = Map(function(x, i) setNames(x, ifelse(names(x) %in% match.by,
      names(x), sprintf('%s.%d', names(x), i))), my.list, seq_along(my.list))

merge/は、Reduceその後、罰金を動作します。


ありがとう!Ramnathからのリンクでもこの解決策を見ました。簡単そうに見えます。しかし、「match.names(clabs、names(xi))のエラー:名前が以前の名前と一致しません」というエラーが表示されます。私が照合している変数はすべて、リスト内のすべてのデータフレームに存在するので、このエラーが何を伝えているのか把握できません。
bshor

1
このソリューションをR2.7.2でテストしたところ、同じmatch.namesエラーが発生しました。したがって、このソリューションと私のデータには、さらに根本的な問題があります。私はコードを使用しました:Reduce(function(x、y)merge(x、y、all = T、by.x = match.by、by.y = match.by)、my.list、accumulate = F)
bshor

1
奇妙なことに、私がテストしたコードを追加しました。使用しているマージ引数に基づいてフィールド名の変更が発生していると思いますか?マージされた結果には、後続のデータフレームとマージするために、関連するキーが含まれている必要があります。
Charles

空のデータフレームで何かが起こっていると思います。私はこのようないくつかの例を試してみました:empty <- data.frame(x=numeric(0),a=numeric(0); L3 <- c(empty,empty,list.of.data.frames,empty,empty,empty)そして、まだ理解していない奇妙なことが起こっていました。
Ben Bolker、

@チャールズあなたは何かにしています。あなたのコードは上でうまく動作します。そして、私がそれを私のものに適合させると、うまく動作します-ただし、必要な主要な変数を無視してマージを行う点が異なります。キー変数を省略せずに追加しようとすると、「Error in is.null(x): 'x' is missing」という新しいエラーが発生します。コード行は「test.reduce <-Reduce(function(...)merge(by = match.by、all = T)、my.list)」です。ここで、match.byは、マージしたいキー変数名のベクトルです。沿って。
bshor

52

あなたは使用してそれを行うことができますmerge_allreshapeパッケージ。引数をmerge使用してパラメータを渡すことができ...ます

reshape::merge_all(list_of_dataframes, ...)

以下は、データフレームをマージするさまざまな方法に関する優れたリソースです。


merge_recurseを複製したように見えます=)この関数がすでに存在することを知っておくと役に立ちます。
SFun28

16
はい。私はアイデアがあるときはいつでも、@ hadleyがすでにそれを行っているかどうか、そしてほとんどの場合彼が持っているかどうかを常にチェックします:-)
Ramnath

1
私は少し混乱しています。merge_allまたはmerge_recurseを実行する必要がありますか?いずれの場合でも、追加の引数をいずれかに追加しようとすると、「複数の実際の引数と一致する正式な引数「すべて」」というエラーが発生します。
bshor

2
これをreshape2から削除したと思います。削減+マージも同じくらい簡単です。
ハドリー

2
@ラムナス、リンクが死んでいる、鏡はありますか?
エドゥアルド

4

これを行うには、再帰を使用できます。私は以下を確認していませんが、それはあなたに正しい考えを与えるはずです:

MergeListOfDf = function( data , ... )
{
    if ( length( data ) == 2 ) 
    {
        return( merge( data[[ 1 ]] , data[[ 2 ]] , ... ) )
    }    
    return( merge( MergeListOfDf( data[ -1 ] , ... ) , data[[ 1 ]] , ... ) )
}

2

@PaulRougieuxのデータ例を再利用します

x <- data_frame(i = c("a","b","c"), j = 1:3)
y <- data_frame(i = c("b","c","d"), k = 4:6)
z <- data_frame(i = c("c","d","a"), l = 7:9)

ここで使用して、短いと甘いソリューションだpurrrtidyr

library(tidyverse)

 list(x, y, z) %>% 
  map_df(gather, key=key, value=value, -i) %>% 
  spread(key, value)

1

eat私のパッケージsafejoinの機能はこのような機能があり番目の入力としてdata.framesのリストを指定すると、それらは最初の入力に再帰的に結合されます。

承認された回答のデータを借用および拡張する:

x <- data_frame(i = c("a","b","c"), j = 1:3)
y <- data_frame(i = c("b","c","d"), k = 4:6)
z <- data_frame(i = c("c","d","a"), l = 7:9)
z2 <- data_frame(i = c("a","b","c"), l = rep(100L,3),l2 = rep(100L,3)) # for later

# devtools::install_github("moodymudskipper/safejoin")
library(safejoin)
eat(x, list(y,z), .by = "i")
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <int> <int>
# 1 a         1    NA     9
# 2 b         2     4    NA
# 3 c         3     5     7

すべての列を取得する必要はありません。tidyselectから選択ヘルパーを使用して選択できます(.xすべての.x列から開始するため、保持されます)。

eat(x, list(y,z), starts_with("l") ,.by = "i")
# # A tibble: 3 x 3
#   i         j     l
#   <chr> <int> <int>
# 1 a         1     9
# 2 b         2    NA
# 3 c         3     7

または特定のものを削除します:

eat(x, list(y,z), -starts_with("l") ,.by = "i")
# # A tibble: 3 x 3
#   i         j     k
#   <chr> <int> <int>
# 1 a         1    NA
# 2 b         2     4
# 3 c         3     5

リストに名前が付けられている場合、名前は接頭辞として使用されます。

eat(x, dplyr::lst(y,z), .by = "i")
# # A tibble: 3 x 4
#   i         j   y_k   z_l
#   <chr> <int> <int> <int>
# 1 a         1    NA     9
# 2 b         2     4    NA
# 3 c         3     5     7

列の競合がある場合、.conflict引数を使用すると、たとえば、1番目と2番目の競合、追加、結合、またはネストによって、それを解決できます。

最初に保つ:

eat(x, list(y, z, z2), .by = "i", .conflict = ~.x)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <int> <int>
# 1 a         1    NA     9
# 2 b         2     4    NA
# 3 c         3     5     7

最後に保つ:

eat(x, list(y, z, z2), .by = "i", .conflict = ~.y)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <int> <dbl>
# 1 a         1    NA   100
# 2 b         2     4   100
# 3 c         3     5   100

追加:

eat(x, list(y, z, z2), .by = "i", .conflict = `+`)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <int> <dbl>
# 1 a         1    NA   109
# 2 b         2     4    NA
# 3 c         3     5   107

合体:

eat(x, list(y, z, z2), .by = "i", .conflict = dplyr::coalesce)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <int> <dbl>
# 1 a         1    NA     9
# 2 b         2     4   100
# 3 c         3     5     7

ネスト:

eat(x, list(y, z, z2), .by = "i", .conflict = ~tibble(first=.x, second=.y))
# # A tibble: 3 x 4
#   i         j     k l$first $second
#   <chr> <int> <int>   <int>   <int>
# 1 a         1    NA       9     100
# 2 b         2     4      NA     100
# 3 c         3     5       7     100

NA.fill引数を使用して値を置き換えることができます。

eat(x, list(y, z), .by = "i", .fill = 0)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <dbl> <dbl>
# 1 a         1     0     9
# 2 b         2     4     0
# 3 c         3     5     7

デフォルトでは拡張されてleft_joinいますが、すべてのdplyr結合は.mode引数を介してサポートされ、ファジー結合はmatch_fun 引数(パッケージをラップするfuzzyjoin)または引数などに式を与えることによってもサポートさ ~ X("var1") > Y("var2") & X("var3") < Y("var4")byます。


0

一般的なid列のないデータフレームのリストがありました。
多くのdfでデータが欠落していました。Null値がありました。データフレームは、テーブル関数を使用して作成されました。Reduce、Merging、rbind、rbind.fillなどは、目的を達成するのに役立ちませんでした。私の目的は、不足しているデータや共通のID列に関係なく、理解可能なマージされたデータフレームを作成することでした。

そこで、以下の機能を作りました。多分この機能は誰かを助けることができます。

##########################################################
####             Dependencies                        #####
##########################################################

# Depends on Base R only

##########################################################
####             Example DF                          #####
##########################################################

# Example df
ex_df           <- cbind(c( seq(1, 10, 1), rep("NA", 0), seq(1,10, 1) ), 
                         c( seq(1, 7, 1),  rep("NA", 3), seq(1, 12, 1) ), 
                         c( seq(1, 3, 1),  rep("NA", 7), seq(1, 5, 1), rep("NA", 5) ))

# Making colnames and rownames
colnames(ex_df) <- 1:dim(ex_df)[2]
rownames(ex_df) <- 1:dim(ex_df)[1]

# Making an unequal list of dfs, 
# without a common id column
list_of_df      <- apply(ex_df=="NA", 2, ( table) )

それは機能に従っています

##########################################################
####             The function                        #####
##########################################################


# The function to rbind it
rbind_null_df_lists <- function ( list_of_dfs ) {
  length_df     <- do.call(rbind, (lapply( list_of_dfs, function(x) length(x))))
  max_no        <- max(length_df[,1])
  max_df        <- length_df[max(length_df),]
  name_df       <- names(length_df[length_df== max_no,][1])
  names_list    <- names(list_of_dfs[ name_df][[1]])

  df_dfs <- list()
  for (i in 1:max_no ) {

    df_dfs[[i]]            <- do.call(rbind, lapply(1:length(list_of_dfs), function(x) list_of_dfs[[x]][i]))

  }

  df_cbind               <- do.call( cbind, df_dfs )
  rownames( df_cbind )   <- rownames (length_df)
  colnames( df_cbind )   <- names_list

  df_cbind

}

サンプルを実行する

##########################################################
####             Running the example                 #####
##########################################################

rbind_null_df_lists ( list_of_df )

0

DFSのリストがあり、列に「ID」が含まれているが、一部のリストでは一部のIDが欠落している場合、このバージョンのリデュース/マージを使用して、欠落している行IDまたはラベルの複数のDFSを結合できます。

Reduce(function(x, y) merge(x=x, y=y, by="V1", all.x=T, all.y=T), list_of_dfs)

0

以下は、バイナリー関数をマルチパラメーター関数に変換するために使用できる汎用ラッパーです。このソリューションの利点は、非常に汎用的であり、任意のバイナリ関数に適用できることです。一度行うだけで、どこにでも適用できます。

アイデアをデモするには、単純な再帰を使用して実装します。もちろん、Rの機能的パラダイムに対する優れたサポートの恩恵を受ける、よりエレガントな方法で実装することもできます。

fold_left <- function(f) {
return(function(...) {
    args <- list(...)
    return(function(...){
    iter <- function(result,rest) {
        if (length(rest) == 0) {
            return(result)
        } else {
            return(iter(f(result, rest[[1]], ...), rest[-1]))
        }
    }
    return(iter(args[[1]], args[-1]))
    })
})}

次に、バイナリ関数をラップして、最初の括弧内の位置パラメーター(通常はdata.frames)と2番目の括弧内の名前付きパラメーター(例: by =またはなどsuffix =)。名前付きパラメーターがない場合は、2番目の括弧を空のままにします。

merge_all <- fold_left(merge)
merge_all(df1, df2, df3, df4, df5)(by.x = c("var1", "var2"), by.y = c("var1", "var2"))

left_join_all <- fold_left(left_join)
left_join_all(df1, df2, df3, df4, df5)(c("var1", "var2"))
left_join_all(df1, df2, df3, df4, df5)()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.