web-dev-qa-db-ja.com

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

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

mergeコマンドは2つのdata.framesに対してのみ機能するので、私はアイデアをインターネットに向けました。私はこれを here から手に入れました。これは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

(偶然にも、私はこのエラーへの他の参照を見ます 他の場所で 解決なしで)。

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

221
bshor

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

library(dplyr)
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)

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

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

ベースRを持つdplyr::left_join()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

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

そして比較のために、これが左結合のbase 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
131
Paul Rougieux

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は正常に動作します。

211
Charles

reshapeパッケージのmerge_allを使ってそれを行うことができます。 ...引数を使用して、パラメータをmergeに渡すことができます。

reshape::merge_all(list_of_dataframes, ...)

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

48
Ramnath

これを行うために再帰を使うことができます。次のことを確認していませんが、正しい考えを与えるはずです。

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

@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)
2
dmi3kno

私のパッケージの関数eatsafejoin はそのような特徴を持っています、あなたがそれに二番目の入力として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引数を使用して、たとえば最初/ 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)、またはby引数に~ X("var1") > Y("var2") & X("var3") < Y("var4")などの式を指定する。

1

一般的なID列のないデータフレームのリストがありました。
多くのDFSでデータが足りませんでした。 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