web-dev-qa-db-fra.com

Combinez deux cadres de données par lignes (rbind) lorsqu'ils ont des ensembles de colonnes différents

Est-il possible de lier deux trames de données ne contenant pas le même ensemble de colonnes? J'espère conserver les colonnes qui ne correspondent pas après la liaison. 

170
Btibert3

rbind.fill du paquet plyr pourrait être ce que vous cherchez.

185

Une solution plus récente consiste à utiliser la fonction bind_rows de dplyr qui, je suppose, est plus efficace que smartbind.

89
xiaodai

Vous pouvez utiliser smartbind à partir du package gtools.

Exemple:

library(gtools)
df1 <- data.frame(a = c(1:5), b = c(6:10))
df2 <- data.frame(a = c(11:15), b = c(16:20), c = LETTERS[1:5])
smartbind(df1, df2)
# result
     a  b    c
1.1  1  6 <NA>
1.2  2  7 <NA>
1.3  3  8 <NA>
1.4  4  9 <NA>
1.5  5 10 <NA>
2.1 11 16    A
2.2 12 17    B
2.3 13 18    C
2.4 14 19    D
2.5 15 20    E
45
neilfws

Si les colonnes dans df1 est un sous-ensemble de celles dans df2 (par noms de colonnes):

df3 <- rbind(df1, df2[, names(df1)])
30
Aaron Statham

Une alternative avec data.table:

library(data.table)
df1 = data.frame(a = c(1:5), b = c(6:10))
df2 = data.frame(a = c(11:15), b = c(16:20), c = LETTERS[1:5])
rbindlist(list(df1, df2), fill = TRUE)

rbind fonctionnera également dans data.table tant que les objets sont convertis en objets data.table, donc 

rbind(setDT(df1), setDT(df2), fill=TRUE)

travaillera également dans cette situation. Cela peut être préférable lorsque vous avez quelques tables de données et que vous ne voulez pas créer de liste.

22
kdauria

Vous pouvez également extraire les noms de colonnes communs. 

> cols <- intersect(colnames(df1), colnames(df2))
> rbind(df1[,cols], df2[,cols])
16
Jonathan Chang

La plupart des réponses de base R concernent le cas où un seul fichier data.frame a des colonnes supplémentaires ou que le fichier data.frame résultant aurait l'intersection des colonnes. Comme le PO écrit j'espère conserver les colonnes qui ne correspondent pas après le bind, une réponse utilisant des méthodes de base R pour résoudre ce problème vaut probablement la peine d'être publiée.

Ci-dessous, je présente deux méthodes de base R: une méthode qui modifie les données.frames d'origine et une méthode qui ne modifie pas. De plus, je propose une méthode qui généralise la méthode non destructive à plus de deux data.frames.

Premièrement, obtenons des exemples de données.

# sample data, variable c is in df1, variable d is in df2
df1 = data.frame(a=1:5, b=6:10, d=month.name[1:5])
df2 = data.frame(a=6:10, b=16:20, c = letters[8:12])

Deux images. Modifier les originaux
Afin de conserver toutes les colonnes des deux data.frames dans une rbind (et de permettre à la fonction de fonctionner sans entraîner d'erreur), vous ajoutez des colonnes NA à chaque data.frame avec les noms manquants appropriés renseignés avec setdiff.

# fill in non-overlapping columns with NAs
df1[setdiff(names(df2), names(df1))] <- NA
df2[setdiff(names(df1), names(df2))] <- NA

Maintenant, rbind- em

rbind(df1, df2)
    a  b        d    c
1   1  6  January <NA>
2   2  7 February <NA>
3   3  8    March <NA>
4   4  9    April <NA>
5   5 10      May <NA>
6   6 16     <NA>    h
7   7 17     <NA>    i
8   8 18     <NA>    j
9   9 19     <NA>    k
10 10 20     <NA>    l

Notez que les deux premières lignes modifient les données d'origine data.frames, df1 et df2, en ajoutant l'ensemble complet des colonnes aux deux.


Deux images.frames, ne modifie pas les originaux
Pour conserver les données.frames d'origine intactes, commencez par parcourir les noms qui diffèrent, renvoyez un vecteur nommé d'AN concaténé dans une liste contenant le nom data.frame à l'aide de c. Ensuite, data.frame convertit le résultat en un fichier data.frame approprié pour la variable rbind.

rbind(
  data.frame(c(df1, sapply(setdiff(names(df2), names(df1)), function(x) NA))),
  data.frame(c(df2, sapply(setdiff(names(df1), names(df2)), function(x) NA)))
)

Plusieurs images.frames, ne modifie pas les originaux
Dans le cas où vous avez plus de deux data.frames, vous pouvez effectuer les opérations suivantes.

# put data.frames into list (dfs named df1, df2, df3, etc)
mydflist <- mget(ls(pattern="df\\d+")
# get all variable names
allNms <- unique(unlist(lapply(mydflist, names)))

# put em all together
do.call(rbind,
        lapply(mydflist,
               function(x) data.frame(c(x, sapply(setdiff(allNms, names(x)),
                                                  function(y) NA)))))

Peut-être un peu plus agréable de ne pas voir les noms de lignes de data.frames d'origine? Alors fais ça.

do.call(rbind,
        c(lapply(mydflist,
                 function(x) data.frame(c(x, sapply(setdiff(allNms, names(x)),
                                                    function(y) NA)))),
          make.row.names=FALSE))
15
lmo

J'ai écrit une fonction pour le faire parce que j'aime bien que mon code me dise si quelque chose ne va pas. Cette fonction vous indiquera explicitement quels noms de colonne ne correspondent pas et si vous avez une incompatibilité de type. Ensuite, il fera de son mieux pour combiner les data.frames de toute façon. La limitation est que vous ne pouvez combiner que deux données.frames à la fois.

### combines data frames (like rbind) but by matching column names
# columns without matches in the other data frame are still combined
# but with NA in the rows corresponding to the data frame without
# the variable
# A warning is issued if there is a type mismatch between columns of
# the same name and an attempt is made to combine the columns
combineByName <- function(A,B) {
    a.names <- names(A)
    b.names <- names(B)
    all.names <- union(a.names,b.names)
    print(paste("Number of columns:",length(all.names)))
    a.type <- NULL
    for (i in 1:ncol(A)) {
        a.type[i] <- typeof(A[,i])
    }
    b.type <- NULL
    for (i in 1:ncol(B)) {
        b.type[i] <- typeof(B[,i])
    }
    a_b.names <- names(A)[!names(A)%in%names(B)]
    b_a.names <- names(B)[!names(B)%in%names(A)]
    if (length(a_b.names)>0 | length(b_a.names)>0){
        print("Columns in data frame A but not in data frame B:")
        print(a_b.names)
        print("Columns in data frame B but not in data frame A:")
        print(b_a.names)
    } else if(a.names==b.names & a.type==b.type){
        C <- rbind(A,B)
        return(C)
    }
    C <- list()
    for(i in 1:length(all.names)) {
        l.a <- all.names[i]%in%a.names
        pos.a <- match(all.names[i],a.names)
        typ.a <- a.type[pos.a]
        l.b <- all.names[i]%in%b.names
        pos.b <- match(all.names[i],b.names)
        typ.b <- b.type[pos.b]
        if(l.a & l.b) {
            if(typ.a==typ.b) {
                vec <- c(A[,pos.a],B[,pos.b])
            } else {
                warning(c("Type mismatch in variable named: ",all.names[i],"\n"))
                vec <- try(c(A[,pos.a],B[,pos.b]))
            }
        } else if (l.a) {
            vec <- c(A[,pos.a],rep(NA,nrow(B)))
        } else {
            vec <- c(rep(NA,nrow(A)),B[,pos.b])
        }
        C[[i]] <- vec
    }
    names(C) <- all.names
    C <- as.data.frame(C)
    return(C)
}
6
user399470

Juste pour la documentation. Vous pouvez essayer la bibliothèque Stack et sa fonction Stack sous la forme suivante:

Stack(df_1, df_2)

J'ai aussi l'impression que c'est plus rapide que les autres méthodes pour les grands ensembles de données.

2
Cro-Magnon

J'ai peut-être mal interprété votre question, mais le message "J'espère conserver les colonnes qui ne correspondent pas après la liaison" me fait penser que vous recherchez un left join ou un right join semblable à une requête SQL. R possède la fonction merge qui vous permet de spécifier des jointures à gauche, à droite ou internes similaires à des jointures de tables en SQL.

Il existe déjà une excellente question et une réponse à ce sujet ici: Comment joindre (fusionner) des trames de données (intérieure, extérieure, gauche, droite)?

1
Chase

gtools/smartbind n'aimait pas travailler avec Dates, probablement parce que c'était as.vectoring. Alors voici ma solution ...

sbind = function(x, y, fill=NA) {
    sbind.fill = function(d, cols){ 
        for(c in cols)
            d[[c]] = fill
        d
    }

    x = sbind.fill(x, setdiff(names(y),names(x)))
    y = sbind.fill(y, setdiff(names(x),names(y)))

    rbind(x, y)
}
1
aaron

Vous pouvez également utiliser sjmisc::add_rows() , qui utilise dplyr::bind_rows(), mais contrairement à bind_rows(), add_rows() préserve les attributs et est donc utile pour label data .

Voir l'exemple suivant avec un jeu de données étiqueté. La fonction frq()- imprime les tables de fréquences avec les étiquettes de valeur, if les données sont étiquetées.

library(sjmisc)
library(dplyr)

data(efc)
# select two subsets, with some identical and else different columns
x1 <- efc %>% select(1:5) %>% slice(1:10)
x2 <- efc %>% select(3:7) %>% slice(11:20)

str(x1)
#> 'data.frame':    10 obs. of  5 variables:
#>  $ c12hour : num  16 148 70 168 168 16 161 110 28 40
#>   ..- attr(*, "label")= chr "average number of hours of care per week"
#>  $ e15relat: num  2 2 1 1 2 2 1 4 2 2
#>   ..- attr(*, "label")= chr "relationship to elder"
#>   ..- attr(*, "labels")= Named num  1 2 3 4 5 6 7 8
#>   .. ..- attr(*, "names")= chr  "spouse/partner" "child" "sibling" "daughter or son -in-law" ...
#>  $ e16sex  : num  2 2 2 2 2 2 1 2 2 2
#>   ..- attr(*, "label")= chr "elder's gender"
#>   ..- attr(*, "labels")= Named num  1 2
#>   .. ..- attr(*, "names")= chr  "male" "female"
#>  $ e17age  : num  83 88 82 67 84 85 74 87 79 83
#>   ..- attr(*, "label")= chr "elder' age"
#>  $ e42dep  : num  3 3 3 4 4 4 4 4 4 4
#>   ..- attr(*, "label")= chr "elder's dependency"
#>   ..- attr(*, "labels")= Named num  1 2 3 4
#>   .. ..- attr(*, "names")= chr  "independent" "slightly dependent" "moderately dependent" "severely dependent"

bind_rows(x1, x1) %>% frq(e42dep)
#> 
#> # e42dep <numeric> 
#> # total N=20  valid N=20  mean=3.70  sd=0.47
#>  
#>   val frq raw.prc valid.prc cum.prc
#>     3   6      30        30      30
#>     4  14      70        70     100
#>  <NA>   0       0        NA      NA

add_rows(x1, x1) %>% frq(e42dep)
#> 
#> # elder's dependency (e42dep) <numeric> 
#> # total N=20  valid N=20  mean=3.70  sd=0.47
#>  
#>  val                label frq raw.prc valid.prc cum.prc
#>    1          independent   0       0         0       0
#>    2   slightly dependent   0       0         0       0
#>    3 moderately dependent   6      30        30      30
#>    4   severely dependent  14      70        70     100
#>   NA                   NA   0       0        NA      NA
0
Daniel