У меня есть список многих 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
(Кстати, я вижу другие ссылки на эту ошибку в другом месте без разрешения).
Есть ли способ решить это?
Другой вопрос задал конкретно, как выполнять несколько левых объединений, используя dplyr в R. Вопрос был отмечен как дубликат этого, поэтому я отвечу здесь, используя три примера данных:
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
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
Уменьшить делает это довольно легко:
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
будет работать нормально.
Вы можете сделать это, используя merge_all
в пакете reshape
. Параметры merge
можно передать с помощью аргумента ...
reshape::merge_all(list_of_dataframes, ...)
Вот отличный ресурс по различным методам для объединения кадров данных.
Вы можете использовать рекурсию для этого. Я не проверял следующее, но он должен дать вам правильную идею:
MergeListOfDf = function( data , ... )
{
if ( length( data ) == 2 )
{
return( merge( data[[ 1 ]] , data[[ 2 ]] , ... ) )
}
return( merge( MergeListOfDf( data[ -1 ] , ... ) , data[[ 1 ]] , ... ) )
}
Я буду повторно использовать пример данных из @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)
Другие решения здесь хорошо подходят для небольших данных, но они рекурсивно создают и уничтожают множество переменных для этого. Чтобы избежать сложности 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, будет применено к правильному типу, но вам нужно будет управлять двумя столбцами с тем же именем, но с другим типом.
В пакете purrr
может быть доступно более новое решение. Для вашего точного вопроса вы можете использовать reduce()
отметить малый r по сравнению с base::Reduce
, но вы можете полностью устранить проблему, используя map_dfr()
или map_dfc
, что может помешать проблеме, сделав карту и уменьшите шаг в одном.
map_dfr()
илиmap_dfc()