Jednoczesne Scalanie wielu danych.ramki na liście

Mam listę wielu danych.ramki, które chcę połączyć. Problem polega na tym, że każde dane.ramka różni się pod względem liczby wierszy i kolumn, ale wszystkie mają wspólne kluczowe zmienne(które nazwałem "var1" i "var2" w kodzie poniżej). Jeśli dane.ramki były identyczne pod względem kolumn, mogłem tylko rbind, dla których plyr jest rbind.fill zrobiłoby to zadanie, ale tak nie jest w przypadku tych danych.

Ponieważ polecenie merge działa tylko na 2 danych.ramki, odwróciłem do Internetu po pomysły. Mam ten z tutaj , który działał idealnie w R 2.7.2, czyli to co miałem wtedy:

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

I nazwałbym funkcję tak:

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

Ale w każdej wersji R po 2.7.2, w tym 2.11 i 2.12, kod ten nie działa z następującym błędem:

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

(przypadkowo widzę inne odniesienia do tego błędu gdzie indziej bez rozwiązania).

Czy można to jakoś rozwiązać?
Author: Community, 2011-11-11

5 answers

Kolejne pytanie zadane konkretnie Jak wykonać wiele lewych złączeń przy użyciu dplyr w R . Pytanie zostało oznaczone jako DUPLIKAT tego, więc odpowiadam tutaj, używając 3 przykładowych ramek danych poniżej:

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)

Aktualizacja czerwiec 2018 : podzieliłem odpowiedź na trzy sekcje reprezentujące trzy różne sposoby wykonania połączenia. Prawdopodobnie chcesz użyć purrr, Jeśli używasz już pakietów tidyverse. Dla celów porównania poniżej, znajdziesz podstawową wersję R przy użyciu tego samego przykładowego zestawu danych.

Połącz je z reduce z pakietu purrr

Pakiet purrr dostarcza reduce funkcji o zwięzłej składni:

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

Możesz również wykonywać inne złączenia, takie jak full_join lub 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() z bazą 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

Baza R merge() z bazą R Reduce()

I dla celów porównania, tutaj jest podstawowa wersja R lewego połączenia

 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
 78
Author: Paul Rougieux,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-30 06:33:15

Redukcja sprawia, że jest to dość łatwe:

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

Oto w pełni przykładowy przykład użycia niektórych przykładowych danych:

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

A oto przykład wykorzystania tych danych do replikacji 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>

Uwaga: wygląda na to, że jest to prawdopodobnie błąd w merge. Problem polega na tym, że nie ma sprawdzania, czy dodanie sufiksów (aby obsłużyć nakładające się niepasujące nazwy) czyni je unikalnymi. W pewnym momencie używa [.data.frame, Które robi make.unique nazwy, powodując, że rbind do porażka.

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

Najprostszym sposobem naprawienia jest nie opuszczanie nazwy pola dla duplikatów pól (których jest tu wiele) aż do merge. Eg:

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

The merge/Reduce wtedy będzie działać dobrze.

 203
Author: Charles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-22 13:41:15

Możesz to zrobić używając merge_all w pakiecie reshape. Możesz przekazać parametry merge używając argumentu ...

reshape::merge_all(list_of_dataframes, ...)

Oto doskonałe źródło informacji na temat różnych metod łączenia ramek danych.

 47
Author: Ramnath,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-08 09:15:58

Możesz użyć rekurencji, aby to zrobić. Nie zweryfikowałem tego, co następuje, ale powinno dać ci właściwy pomysł:

MergeListOfDf = function( data , ... )
{
    if ( length( data ) == 2 ) 
    {
        return( merge( data[[ 1 ]] , data[[ 2 ]] , ... ) )
    }    
    return( merge( MergeListOfDf( data[ -1 ] , ... ) , data[[ 1 ]] , ... ) )
}
 4
Author: SFun28,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-11 15:19:05

Wykorzystam ponownie przykład danych z @ 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)

Oto krótkie i słodkie rozwiązanie za pomocą purrr i tidyr

library(tidyverse)

 list(x, y, z) %>% 
  map_df(gather, key=key, value=value, -i) %>% 
  spread(key, value)
 1
Author: dmi3kno,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-28 10:59:54