Одночасно об’єднувати декілька фреймів data.frames у списку


258

У мене є список багатьох фреймів data.frames, які я хочу об'єднати. Проблема тут полягає в тому, що кожен data.frame відрізняється за кількістю рядків і стовпців, але всі вони поділяють ключові змінні (які я викликав "var1"і "var2"в коді нижче). Якщо data.frames були однакові за стовпцями, я міг би просто rbind, для чого plyr's 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("", ""))

Але в будь-якій версії R після 2.7.2, включаючи 2.11 та 2.12, цей код не працює із наступною помилкою:

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

(Між іншим, я бачу інші посилання на цю помилку в інших місцях без вирішення).

Чи є спосіб вирішити це?

Відповіді:


182

Саме запитав Інше питання , як виконати кілька вліво приєднується з допомогою dplyr в R . Питання було позначене як дублікат цього, тому я відповідаю тут, використовуючи 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 року : я розділив відповідь на три розділи, що представляють три різні способи здійснення злиття. Можливо, ви хочете скористатися таким purrrспособом, якщо ви вже використовуєте пакети sidyverse . Для порівняння нижче, ви знайдете базову 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

1
@Axeman має рацію, але ви, можливо, зможете взагалі уникнути повернення списку кадрів даних, використовуючи map_dfr()абоmap_dfc()
DaveRGP

Я хоч міг би приєднатись до деякої кількості DF на основі шаблону, використовуючи 'ls (pattern = "DF_name_contains_this")', але ні. Б'noquote (паста (()), але я до сих пір виробляє символьний вектор замість списку DF я в кінцевому підсумку набравши імена, що противний ..
Джорджа Вільяма Рассела ручка

Інше питання , забезпечує реалізацію пітона : список кадрів панди даних dfs = [df1, df2, df3]потім reduce(pandas.merge, dfs).
Пол Рудьо

222

Зменшити це робить досить просто:

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. Отже, є якась більш фундаментальна проблема з цим рішенням та моїми даними. Я використав код: Зменшити (функція (x, y) злиття (x, y, all = T, by.x = match.by, by.y = match.by), мій список, накопичити = F)
bshor

1
Як не дивно, я додав код, який перевіряв, і працює нормально. Я думаю, що деякі перейменування поля відбуваються на основі аргументів злиття, які ви використовуєте? Об'єднаний результат все ще повинен мати відповідні ключі, щоб бути об'єднаним з наступним фреймом даних.
Чарльз

Я підозрюю, що щось відбувається з порожніми кадрами даних. Я спробував кілька таких прикладів: empty <- data.frame(x=numeric(0),a=numeric(0); L3 <- c(empty,empty,list.of.data.frames,empty,empty,empty)і сталося щось дивне, що я ще не з'ясував.
Бен Болкер

@Charles Ти щось робиш. Ваш код для мене добре працює. І коли я адаптую його до свого, він також працює добре - за винятком того, що він робить злиття, ігноруючи ключові змінні, які я хочу. Коли я намагаюся додати ключові змінні, а не залишати їх, я отримую нову помилку "Помилка в is.null (x): 'x' відсутня". Рядок коду - "test.reduce <- Зменшити (функцію (...) злиття (за = match.by, все = T), мій список)", де match.by - вектор ключових імен змінних, які я хочу об'єднати від.
bshor

52

Зробити це можна merge_allза допомогою reshapeпакета. Ви можете передати параметри, mergeвикористовуючи ...аргумент

reshape::merge_all(list_of_dataframes, ...)

Ось чудовий ресурс для різних методів для об'єднання кадрів даних .


схоже, що я просто копіював merge_recurse =) добре, що ця функція вже існує.
SFun28

16
так. всякий раз, коли у мене є ідея, я завжди перевіряю, чи @hadley це вже зробив, і більшість випадків він має :-)
Ramnath

1
Я трохи розгублений; я повинен зробити merge_all або merge_recurse? У будь-якому випадку, коли я намагаюся додати свої додаткові аргументи до будь-якого, я отримую помилку "формальний аргумент" всі ", зіставлений з декількома фактичними аргументами".
bshor

2
Я думаю, що я відмовився від переформатування2. Зменшити + злиття так само просто.
Хадлі

2
@Ramnath, посилання мертва, є дзеркало?
Едуардо

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)

Ось коротке і солодке рішення з використанням purrrтаtidyr

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 аргумент дозволяє вирішити його, наприклад, взявши перший / другий, додавши їх, об'єднавши їх або вклавши їх.

тримайся першим:

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

У мене був список фреймів даних із загальним стовпцем ідентифікатора.
У мене були відсутні дані про багато dfs. Були нульові значення. Рамки даних були виготовлені за допомогою функції таблиці. Зниження, злиття, зв'язання, 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 і стовпець містить "ідентифікатор", але в деяких списках деякі ідентифікатори відсутні, то ви можете використовувати цю версію зменшення / об'єднання, щоб приєднатися до декількох 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) у перші круглі дужки та названими параметрами у другій дужках (наприклад, by =або suffix =). Якщо немає названих параметрів, залиште другі дужки порожніми.

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)()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.