Đồng thời hợp nhất nhiều data.frames trong một danh sách


259

Tôi có một danh sách nhiều data.frames mà tôi muốn hợp nhất. Vấn đề ở đây là mỗi data.frame khác nhau về số lượng hàng và cột, nhưng tất cả chúng đều chia sẻ các biến chính (mà tôi đã gọi "var1""var2"trong mã bên dưới). Nếu data.frames giống hệt nhau về các cột, tôi chỉ có thể rbind, mà rbind.fill của plyr sẽ thực hiện công việc, nhưng đó không phải là trường hợp của những dữ liệu này.

mergelệnh chỉ hoạt động trên 2 data.frames, tôi đã chuyển sang Internet để lấy ý tưởng. Tôi đã nhận được cái này từ đây , nó hoạt động hoàn hảo trong R 2.7.2, đó là những gì tôi có vào thời điểm đó:

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

Và tôi sẽ gọi hàm như vậy:

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

Nhưng trong bất kỳ phiên bản R nào sau 2.7.2, bao gồm 2.11 và 2.12, mã này không thành công với lỗi sau:

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

(Ngẫu nhiên, tôi thấy các tài liệu tham khảo khác về lỗi này ở nơi khác không có độ phân giải).

Có cách nào để giải quyết điều này?

Câu trả lời:


183

Một câu hỏi khác hỏi cụ thể làm thế nào để thực hiện nhiều trái tham gia sử dụng dplyr trong R . Câu hỏi được đánh dấu là một bản sao của câu hỏi này vì vậy tôi trả lời ở đây, sử dụng 3 khung dữ liệu mẫu bên dưới:

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)

Cập nhật tháng 6 năm 2018 : Tôi chia câu trả lời thành ba phần đại diện cho ba cách khác nhau để thực hiện hợp nhất. Bạn có thể muốn sử dụng purrrcách nếu bạn đã sử dụng các gói tidyverse . Đối với mục đích so sánh bên dưới, bạn sẽ tìm thấy phiên bản R cơ sở sử dụng cùng một bộ dữ liệu mẫu.


1) Tham gia với họ reducetừ purrrgói:

Các purrrgói cung cấp một reducechức năng trong đó có một cú pháp ngắn gọn:

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

Bạn cũng có thể thực hiện các phép nối khác, chẳng hạn như một full_joinhoặc 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()với cơ sở 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) Cơ sở R merge()với cơ sở R Reduce():

Và để so sánh, đây là phiên bản R cơ sở của phép nối trái

 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
Biến thể full_join hoạt động hoàn hảo và trông ít đáng sợ hơn câu trả lời được chấp nhận. Không có nhiều sự khác biệt về tốc độ, mặc dù.
bshor

1
@Axeman đúng, nhưng bạn có thể tránh (rõ ràng) trả lại danh sách các khung dữ liệu bằng cách sử dụng map_dfr()hoặcmap_dfc()
DaveRGP

Tôi mặc dù tôi có thể tham gia một số DF dựa trên một mẫu bằng cách sử dụng ọls (mẫu = "DF_name_contains_this"), nhưng không. Đã qua sử dụng'noquote (dán (()) ', nhưng tôi vẫn đang sản xuất một vector nhân vật thay vì một danh sách các DF tôi đã kết thúc cách gõ tên, đó là đáng ghét..
George William Russel của bút

Một câu hỏi khác cung cấp một triển khai python : danh sách các khung dữ liệu gấu trúc dfs = [df1, df2, df3]sau đó reduce(pandas.merge, dfs).
Paul Rougieux 18/03/19

222

Giảm làm cho điều này khá dễ dàng:

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

Đây là một ví dụ đầy đủ sử dụng một số dữ liệu giả:

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

Và đây là một ví dụ sử dụng những dữ liệu này để nhân rộng 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>

Lưu ý: Có vẻ như đây là một lỗi merge. Vấn đề là không có kiểm tra rằng việc thêm các hậu tố (để xử lý các tên không trùng nhau) thực sự làm cho chúng trở nên độc nhất. Tại một thời điểm nào đó nó sử dụng [.data.framekhông make.unique tên, gây ra rbindthất bại.

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

Cách dễ nhất để khắc phục là không để lại trường đổi tên cho các trường trùng lặp (trong đó có nhiều trường ở đây) merge. Ví dụ:

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

Các merge/ Reducesau đó sẽ làm việc tốt.


Cảm ơn! Tôi thấy giải pháp này cũng trên đường dẫn từ Ramnath. Trông đủ dễ dàng. Nhưng tôi nhận được lỗi sau: "Lỗi trong match.names (clabs, name (xi)): tên không khớp với tên trước". Các biến tôi phù hợp đều có mặt trong tất cả các tệp dữ liệu trong danh sách, vì vậy tôi không nắm bắt được lỗi này đang nói gì với tôi.
bshor

1
Tôi đã thử nghiệm giải pháp này trên R2.7.2 và tôi gặp lỗi match.names tương tự. Vì vậy, có một số vấn đề cơ bản hơn với giải pháp này và dữ liệu của tôi. Tôi đã sử dụng mã: Giảm (hàm (x, y) hợp nhất (x, y, all = T, by.x = match.by, by.y = match.by), my.list, tích lũy = F)
bshor

1
Thật kỳ lạ, tôi đã thêm mã mà tôi đã kiểm tra nó chạy tốt. Tôi đoán có một số đổi tên trường xảy ra dựa trên các đối số hợp nhất bạn đang sử dụng? Kết quả đã hợp nhất vẫn phải có các khóa có liên quan để được hợp nhất với khung dữ liệu tiếp theo.
Charles

Tôi nghi ngờ điều gì đó xảy ra với khung dữ liệu trống. Tôi đã thử một số ví dụ như thế này: empty <- data.frame(x=numeric(0),a=numeric(0); L3 <- c(empty,empty,list.of.data.frames,empty,empty,empty)và có một số thứ kỳ lạ xảy ra mà tôi chưa tìm ra.
Ben Bolker

@Charles Bạn vào một cái gì đó. Mã của bạn chạy tốt ở trên cho tôi. Và khi tôi điều chỉnh nó để khai thác, nó cũng chạy tốt - ngoại trừ việc nó hợp nhất bỏ qua các biến chính tôi muốn. Khi tôi cố gắng thêm các biến chính thay vì bỏ chúng đi, tôi gặp một lỗi mới "Lỗi trong is.null (x): 'x' bị thiếu". Dòng mã là "test.reduce <- Giảm (hàm (...) hợp nhất (by = match.by, all = T), my.list)" trong đó match.by là vectơ của các tên biến chính tôi muốn hợp nhất bởi.
bshor

52

Bạn có thể làm điều đó bằng cách sử dụng merge_alltrong reshapegói. Bạn có thể truyền tham số cho mergeviệc sử dụng ...đối số

reshape::merge_all(list_of_dataframes, ...)

Đây là một nguồn tài nguyên tuyệt vời về các phương pháp khác nhau để hợp nhất các khung dữ liệu .


có vẻ như tôi vừa sao chép merge_recurse =) thật tốt khi biết hàm này đã tồn tại.
SFun28

16
Đúng. Bất cứ khi nào tôi có ý tưởng, tôi luôn kiểm tra xem @hadley đã thực hiện chưa, và hầu hết các lần anh ấy có :-)
Ramnath

1
Tôi có chút bối rối; Tôi nên làm merge_all hay merge_recurse? Trong mọi trường hợp, khi tôi cố gắng thêm vào các đối số bổ sung của mình vào một trong hai, tôi nhận được lỗi "đối số chính thức" tất cả "khớp với nhiều đối số thực tế".
bshor

2
Tôi nghĩ rằng tôi đã bỏ cái này từ reshape2. Giảm + hợp nhất chỉ đơn giản như vậy.
hadley

2
@Ramnath, link đã chết, có gương không?
Eduardo

4

Bạn có thể sử dụng đệ quy để làm điều này. Tôi chưa xác minh những điều sau đây, nhưng nó sẽ cho bạn ý kiến ​​đúng:

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

2

Tôi sẽ sử dụng lại ví dụ dữ liệu từ @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)

Đây là một giải pháp ngắn và ngọt ngào sử dụng purrrtidyr

library(tidyverse)

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

1

Chức năng eatcủa gói safjoin của tôi có tính năng như vậy, nếu bạn cung cấp cho nó một danh sách data.frames làm đầu vào thứ hai, nó sẽ nối chúng theo cách đệ quy với đầu vào đầu tiên.

Mượn và mở rộng dữ liệu của câu trả lời được chấp nhận:

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

Chúng tôi không phải lấy tất cả các cột, chúng tôi có thể sử dụng các trợ giúp chọn lọc từ tidyselect và chọn (vì chúng tôi bắt đầu từ .xtất cả .xcác cột được giữ):

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

hoặc loại bỏ những cái cụ thể:

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

Nếu danh sách được đặt tên, tên sẽ được sử dụng làm tiền tố:

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

Nếu có xung đột cột, .conflictđối số cho phép bạn giải quyết nó, ví dụ bằng cách lấy cái đầu tiên / thứ hai, thêm chúng, kết hợp chúng hoặc lồng chúng.

giữ đầu tiên:

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

giữ cuối cùng:

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

thêm vào:

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

hợp nhất:

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

tổ:

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

NAcác giá trị có thể được thay thế bằng cách sử dụng .fillđối số.

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

Theo mặc định nó một tăng cường left_joinnhưng tất cả dplyr tham gia được hỗ trợ thông qua các .modeđối số, mờ tham gia cũng được hỗ trợ thông qua các match_fun tham số (nó quấn quanh gói fuzzyjoin) hoặc đưa ra một công thức như ~ X("var1") > Y("var2") & X("var3") < Y("var4")đến bytranh cãi.


0

Tôi đã có một danh sách các dataframes không có cột id chung.
Tôi đã thiếu dữ liệu trên nhiều dfs. Có giá trị Null. Các datafram được tạo ra bằng cách sử dụng chức năng bảng. Việc Giảm, Sáp nhập, rbind, rbind.fill và những thứ tương tự của họ không thể giúp tôi đạt được mục tiêu của mình. Mục đích của tôi là tạo ra một khung dữ liệu hợp nhất dễ hiểu, không liên quan đến dữ liệu bị thiếu và cột id chung.

Do đó, tôi đã thực hiện các chức năng sau. Có lẽ chức năng này có thể giúp ai đó.

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

nó đang theo chức năng

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

}

Chạy ví dụ

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

rbind_null_df_lists ( list_of_df )

0

Khi bạn có một danh sách các dfs và một cột chứa "ID", nhưng trong một số danh sách, một số ID bị thiếu, thì bạn có thể sử dụng phiên bản Giảm / Hợp nhất này để tham gia nhiều Dfs của Id hoặc Row bị thiếu:

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

0

Dưới đây là một trình bao bọc chung có thể được sử dụng để chuyển đổi hàm nhị phân thành hàm đa tham số. Lợi ích của giải pháp này là nó rất chung chung và có thể được áp dụng cho bất kỳ chức năng nhị phân nào. Bạn chỉ cần làm một lần và sau đó bạn có thể áp dụng nó bất cứ nơi nào.

Để demo ý tưởng, tôi sử dụng đệ quy đơn giản để thực hiện. Tất nhiên nó có thể được thực hiện với cách thức thanh lịch hơn, được hưởng lợi từ sự hỗ trợ tốt của R cho mô hình chức năng.

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]))
    })
})}

Sau đó, bạn có thể chỉ cần bọc bất kỳ hàm nhị phân nào với nó và gọi với các tham số vị trí (thường là data.frames) trong ngoặc đơn đầu tiên và các tham số được đặt tên trong ngoặc đơn thứ hai (chẳng hạn như by =hoặc suffix =). Nếu không có tham số được đặt tên, để trống dấu ngoặc đơn thứ hai.

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)()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.