Одновременно слияние нескольких кадров данных в списке
У меня есть список многих data.frames, которые я хочу объединить. Проблема здесь в том, что каждый data.frame отличается по количеству строк и столбцов, но все они разделяют ключевые переменные (которые я назвал "var1"
и "var2"
в коде ниже). Если data.frames были идентичны в терминах столбцов, я мог бы просто rbind
, для которого plyr rbind.fill выполнил бы эту работу, но это не так эти данные.
Поскольку команда merge
работает только на 2 файлах данных, я обратился к Интернету за идеями. Я получил этот от здесь, который отлично работал в 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
(Кстати, я вижу другие ссылки на эту ошибку в другом месте без разрешения).
Есть ли способ решить это?
Ответы
Ответ 1
Другой вопрос, заданный конкретно, как выполнить несколько левых объединений, используя dplyr в R. Этот вопрос был помечен как дубликат этого вопроса, поэтому я отвечу здесь, используя 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 года: я разделил ответ на три части, представляющих три различных способа выполнения слияния. Возможно, вы захотите использовать путь purrr
, если вы уже используете пакеты tidyverse. Для сравнения ниже вы найдете базовую версию 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_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
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
Base R merge()
с Base 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
Ответ 2
Уменьшить делает это довольно легко:
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
будет работать нормально.
Ответ 3
Вы можете сделать это, используя merge_all
в пакете reshape
. Параметры merge
можно передать с помощью аргумента ...
reshape::merge_all(list_of_dataframes, ...)
Вот отличный ресурс по различным методам для объединения кадров данных.
Ответ 4
Вы можете использовать рекурсию для этого. Я не проверял следующее, но он должен дать вам правильную идею:
MergeListOfDf = function( data , ... )
{
if ( length( data ) == 2 )
{
return( merge( data[[ 1 ]] , data[[ 2 ]] , ... ) )
}
return( merge( MergeListOfDf( data[ -1 ] , ... ) , data[[ 1 ]] , ... ) )
}
Ответ 5
Я буду повторно использовать пример данных из @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)
Ответ 6
Функция 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
Нам не нужно брать все столбцы, мы можем использовать помощники select из tidyselect и select (поскольку мы начинаем с .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
аргумент .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
.
Ответ 7
Другие решения здесь хорошо подходят для небольших данных, но они рекурсивно создают и уничтожают множество переменных для этого. Чтобы избежать сложности N ^ 2, сделав что-то вроде
X = A
X = merge(X,B)
X = merge(X,C)
...
X = merge(X,Z)
можно использовать rbind.
Вам просто нужно самостоятельно управлять именами столбцов, что является болью. Я не думаю, что есть хороший способ сделать это, кроме как выталкивать какой-то код, как показано ниже.
allnames <- unique(unlist(sapply(myBigDataframeList,names)))
for(i in 1:length(myBigDataframeList)){
columnmap <- match(allnames,names(myBigDataframeList[[i]]))
columnmap <- ifelse(is.na(columnmap),1,columnmap+1)
myBigDataframeList[[i]] <- cbind(data.frame(dummycolumn=NA),myBigDataframeList[[i]])[,columnmap]
names(myBigDataframeList[[i]]) <- allnames
}
myBiggerDataframe <- do.call(rbind,myBigDataframeList)
Единственное, на что нужно обратить внимание, это тип данных конечных столбцов. Все, что связано с NA, будет применено к правильному типу, но вам нужно будет управлять двумя столбцами с тем же именем, но с другим типом.
Ответ 8
У меня был список фреймов данных без общего столбца идентификатора.
У меня отсутствовали данные о многих DFS. Были нулевые значения. Кадры данных были созданы с использованием табличной функции. Снижение, Слияние, rbind, rbind.fill и тому подобное не могли помочь мне в моей цели. Моя цель состояла в том, чтобы создать понятный объединенный фрейм данных, не имеющий отношения к отсутствующим данным и общему столбцу идентификаторов.
Поэтому я сделал следующую функцию. Может быть, эта функция может кому-то помочь.
##########################################################
#### 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 )
Ответ 9
В пакете purrr
может быть доступно более новое решение. Для вашего точного вопроса вы можете использовать reduce()
отметить малый r по сравнению с base::Reduce
, но вы можете полностью устранить проблему, используя map_dfr()
или map_dfc
, что может помешать проблеме, сделав карту и уменьшите шаг в одном.