Объедините два фрейма данных по строкам (rbind), когда они имеют разные наборы столбцов


232

Можно ли связать строки двумя фреймами данных, которые не имеют одинаковый набор столбцов? Я надеюсь сохранить столбцы, которые не соответствуют после привязки.

Ответы:


223

rbind.fillиз пакета plyrможет быть то, что вы ищете.


13
rbind.fillи bind_rows()оба молча отбрасывают имена строк.
MERose

3
@Mose Хэдли: «Да, все методы dplyr игнорируют имена строк».
zx8754


124

Более свежее решение использовать dplyr«s bind_rowsфункцию , которую я предполагаю , является более эффективным , чем 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

Я пытаюсь объединить большое количество фреймов данных (16) с разными именами столбцов. Когда я пытаюсь это сделать, я получаю сообщение об ошибке. Ошибка: ABCневозможно преобразовать столбец из символьного в числовой. Есть ли способ сначала преобразовать столбцы?
Сар

46

Вы можете использовать smartbindиз gtoolsпакета.

Пример:

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

3
Я попытался smartbindс двумя большими кадрами данных (в общей сложности примерно 3 * 10 ^ 6 строк) и прервал его через 10 минут.
Джо

2
За 9 лет многое произошло :) Я бы сегодня не использовал smartbind. Также обратите внимание, что в исходном вопросе не указывались большие фреймы данных.
neilfws

42

Если столбцы в df1 являются подмножеством столбцов в df2 (по именам столбцов):

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

38

Альтернатива с 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также будет работать data.tableдо тех пор, пока объекты преобразуются в data.tableобъекты, поэтому

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

также будет работать в этой ситуации. Это может быть предпочтительнее, когда у вас есть пара data.tables и вы не хотите создавать список.


Это самое простое, готовое к использованию решение, которое легко обобщается на любое количество фреймов данных, поскольку вы можете хранить их все в отдельных элементах списка. Другие ответы, такие как intersectподход, работают только для двух информационных фреймов и не легко обобщаются.
Рич Пауло

35

Большинство базовых ответов R относятся к ситуации, когда только один data.frame имеет дополнительные столбцы или что результирующий data.frame будет иметь пересечение столбцов. Так как OP пишет, я надеюсь сохранить столбцы, которые не совпадают после привязки , ответ с использованием базовых методов R для решения этой проблемы, вероятно, стоит опубликовать.

Ниже я представляю два базовых метода R: один, который изменяет исходные данные. Frame, а другой - нет. Кроме того, я предлагаю метод, который обобщает неразрушающий метод для более чем двух data.frames.

Во-первых, давайте возьмем пример данных.

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

Два data.frames, изменение оригиналов.
Чтобы сохранить все столбцы из обоих data.frames в rbind(и позволить функции работать без ошибок), вы добавляете столбцы NA в каждый data.frame с заполнением соответствующих пропущенных имен. используя setdiff.

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

Теперь, rbind-ем

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

Обратите внимание, что первые две строки изменяют исходные data.frames, df1 и df2, добавляя к обоим полный набор столбцов.


Два data.frames, не изменяющие оригиналы.
Чтобы оставить исходные data.frames нетронутыми, сначала выполните цикл по разным именам, верните именованный вектор NA, которые объединены в список с использованием data.frame c. Затем data.frameпреобразует результат в соответствующий data.frame для 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)))
)

Многие data.frames не меняют оригиналы.
В случае, если у вас есть более двух data.frames, вы можете сделать следующее.

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

Может быть, немного приятнее не видеть имена строк оригинальных data.frames? Тогда сделай это.

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

У меня есть 16 фреймов данных с разными столбцами (всего около 70-90 столбцов в каждом). Когда я пытаюсь это сделать, я застреваю с первой командой <- mget (ls (pattern = "df \\ d +")). Мои фреймы данных имеют разные имена. Я попытался составить список, используя mydflist <- c (как, dr, kr, hyt, ed1, of), но это дало мне огромный список.
Сар

Просто ссылки на @GKi
сар

1
@Sar использовать mydflist <- list(as, dr, kr, hyt, ed1, of). Это должно создать объект списка, который не увеличивает размер вашей среды, а просто указывает на каждый элемент списка (до тех пор, пока вы не измените какое-либо содержимое впоследствии). После операции удалите объект списка, просто чтобы быть в безопасности.
LMO

20

Вы также можете просто извлечь общие имена столбцов.

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

6

Я написал функцию, чтобы сделать это, потому что мне нравится мой код, чтобы сказать мне, если что-то не так. Эта функция явно скажет вам, какие имена столбцов не совпадают, и если у вас есть несоответствие типов. Тогда он сделает все возможное, чтобы объединить data.frames в любом случае. Ограничение состоит в том, что вы можете одновременно комбинировать только два фрейма данных.

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

2

Может быть, я полностью неправильно понял ваш вопрос, но «я надеюсь сохранить столбцы, которые не совпадают после привязки», заставляет меня думать, что вы ищете left joinили right joinпохож на SQL-запрос. В R есть mergeфункция, которая позволяет вам указывать левое, правое или внутреннее соединение, аналогичное соединению таблиц в SQL.

Здесь уже есть большой вопрос и ответ по этой теме: Как объединить (объединить) фреймы данных (внутренний, внешний, левый, правый)?


2

gtools / smartbind не нравилось работать с датами, вероятно, потому что это было как .vectoring. Итак, вот мое решение ...

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

использование dplyr :: bind_rows (x, y) вместо rbind (x, y) сохраняет порядок столбцов на основе первого фрейма данных.
RanonKahn

2

Просто для документации. Вы можете попробовать Stackбиблиотеку и ее функции Stackв следующей форме:

Stack(df_1, df_2)

У меня также сложилось впечатление, что это быстрее, чем другие методы для больших наборов данных.


1

Вы также можете использовать sjmisc::add_rows(), который использует dplyr::bind_rows(), но в отличие от него bind_rows(), add_rows()сохраняет атрибуты и, следовательно, полезен для помеченных данных .

Смотрите следующий пример с помеченным набором данных. frq()-Функции печатают таблицы частот с метками значений, если данные помечены.

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

-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)]))
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.