Combinez deux trames de données par lignes (rbind) lorsqu'elles ont différents ensembles de colonnes

232

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

Btibert3
la source

Réponses:

223

rbind.fillde l'emballage plyrpourrait être ce que vous recherchez.

Jyotirmoy Bhattacharya
la source
12
rbind.fillet les bind_rows()deux suppriment silencieusement les noms de domaine.
MERose
3
@MERose Hadley: "Oui, toutes les méthodes dplyr ignorent les noms de domaine."
zx8754
Voici un lien vers la documentation: rdocumentation.org/packages/plyr/versions/1.8.4/topics/…
Gabriel Fair
124

Une solution plus récente consiste à utiliser dplyrla bind_rowsfonction de qui, je suppose, est plus efficace que smartbind.

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])
dplyr::bind_rows(df1, df2)
    a  b    c
1   1  6 <NA>
2   2  7 <NA>
3   3  8 <NA>
4   4  9 <NA>
5   5 10 <NA>
6  11 16    A
7  12 17    B
8  13 18    C
9  14 19    D
10 15 20    E
xiaodai
la source
J'essaye de combiner un grand nombre de dataframes (16) avec différents noms de colonne Quand j'essaye ceci j'obtiens une erreur Erreur: La colonne ABCne peut pas être convertie de caractère en numérique. Existe-t-il un moyen de convertir les colonnes en premier?
sar
46

Vous pouvez utiliser à smartbindpartir du gtoolspackage.

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
neilfws
la source
3
J'ai essayé smartbindavec deux grandes trames de données (au total environ 3 * 10 ^ 6 lignes) et je l'ai abandonné après 10 minutes.
Joe
2
Il s'est passé beaucoup de choses en 9 ans :) Je n'utiliserais peut-être pas smartbind aujourd'hui. Notez également que la question d'origine ne spécifiait pas de grandes trames de données.
neilfws
42

Si les colonnes de df1 sont un sous-ensemble de celles de df2 (par nom de colonne):

df3 <- rbind(df1, df2[, names(df1)])
Aaron Statham
la source
38

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)

rbindfonctionnera également data.tabletant que les objets sont convertis en data.tableobjets, donc

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

fonctionnera également dans cette situation. Cela peut être préférable lorsque vous avez quelques data.tables et que vous ne voulez pas construire de liste.

kdauria
la source
Il s'agit de la solution prête à l'emploi la plus simple qui se généralise facilement à n'importe quel nombre de trames de données, car vous pouvez toutes les stocker dans des éléments de liste séparés. D'autres réponses, comme l' intersectapproche, ne fonctionnent que pour 2 trames de données et ne se généralisent pas facilement.
Rich Pauloo
35

La plupart des réponses R de base concernent le cas où un seul data.frame a des colonnes supplémentaires ou que le data.frame résultant aurait l'intersection des colonnes. Étant donné que l'OP écrit, j'espère conserver les colonnes qui ne correspondent pas après la liaison , 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 R de base: une qui modifie les data.frames d'origine et une qui ne le fait pas. De plus, je propose une méthode qui généralise la méthode non destructive à plus de deux data.frames.

Tout d'abord, obtenons quelques 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 data.frames, modifier les originaux
Afin de conserver toutes les colonnes des deux data.frames dans un rbind(et permettre à la fonction de fonctionner sans entraîner d'erreur), vous ajoutez des colonnes NA à chaque data.frame avec les noms manquants appropriés remplis en utilisant 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 data.frames d'origine, df1 et df2, en ajoutant l'ensemble complet de colonnes aux deux.


Deux data.frames, ne modifient pas les originaux
Pour laisser les data.frames d'origine intacts, parcourez d'abord les noms qui diffèrent, renvoyez un vecteur nommé des NA qui sont concaténés dans une liste avec data.frame en utilisant c. , Puis data.frameconvertit le résultat en un data.frame approprié 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)))
)

De nombreux cadres de données ne modifient pas les originaux
Dans le cas où vous avez plus de deux cadres de données, 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 des 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))
lmo
la source
J'ai 16 cadres de données dont certains avec des colonnes différentes (environ 70 à 90 colonnes au total dans chacune). Lorsque j'essaye, je suis bloqué avec la première commande <- mget (ls (pattern = "df \\ d +")). Mes trames de données ont des noms différents. J'ai essayé de faire une liste en utilisant mydflist <- c (as, dr, kr, hyt, ed1, of) mais cela m'a donné une énorme liste.
sar
Juste un lien vers @GKi
sar
1
@sar use mydflist <- list(as, dr, kr, hyt, ed1, of). Cela devrait construire un objet liste qui n'augmente pas la taille de votre environnement, mais pointe simplement vers chaque élément de la liste (tant que vous ne modifiez aucun contenu par la suite). Après l'opération, supprimez l'objet liste, juste pour être sûr.
lmo
20

Vous pouvez également simplement extraire les noms de colonnes communs.

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

J'ai écrit une fonction pour ce faire parce que j'aime mon code pour me dire si quelque chose ne va pas. Cette fonction vous indiquera explicitement quels noms de colonnes 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 data.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)
}

la source
2

J'ai peut-être mal lu 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 une left joinou right joinsimilaire à une requête SQL. R a la mergefonction qui vous permet de spécifier des jointures gauche, droite ou internes similaires à la jointure de tables dans SQL.

Il y a déjà une grande question et réponse sur ce sujet ici: Comment joindre (fusionner) des trames de données (interne, externe, gauche, droite)?

Chasse
la source
2

gtools / smartbind n'aimait pas travailler avec Dates, probablement parce que c'était as.vectoring. Voici donc 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)
}
Aaron
la source
l'utilisation de dplyr :: bind_rows (x, y) à la place de rbind (x, y) conserve l'ordre des colonnes en fonction du premier bloc de données.
RanonKahn
2

Juste pour la documentation. Vous pouvez essayer la Stackbibliothèque et sa fonction Stacksous la forme suivante:

Stack(df_1, df_2)

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

Cro-Magnon
la source
1

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 les données étiquetées .

Voir l'exemple suivant avec un ensemble de données étiqueté. La frq()fonction-imprime des tables de fréquences avec des étiquettes de valeur, si 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
Daniel
la source
-1
rbind.ordered=function(x,y){

  diffCol = setdiff(colnames(x),colnames(y))
  if (length(diffCol)>0){
    cols=colnames(y)
    for (i in 1:length(diffCol)) y=cbind(y,NA)
    colnames(y)=c(cols,diffCol)
  }

  diffCol = setdiff(colnames(y),colnames(x))
  if (length(diffCol)>0){
    cols=colnames(x)
    for (i in 1:length(diffCol)) x=cbind(x,NA)
    colnames(x)=c(cols,diffCol)
  }
  return(rbind(x, y[, colnames(x)]))
}
RockScience
la source