Поєднайте два кадри даних за рядками (rbind), коли вони мають різні набори стовпців


232

Чи можливо прив'язувати рядки до двох кадрів даних, що не мають однакового набору стовпців? Я сподіваюся зберегти стовпчики, які не збігаються після прив’язки.

Відповіді:


223

rbind.fillз пакета plyrможе бути те, що ви шукаєте.


12
rbind.fillі bind_rows()обидва мовчки скидають назви рядків.
MERose

3
@MERose Hadley: "Так, всі методи dplyr ігнорують назви рядків."
zx8754

Ось посилання на документацію: rdocumentation.org/packages/plyr/versions/1.8.4/topics/…
Габріель Ярмарок,

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

37

Альтернатива з 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)

також буде працювати в цій ситуації. Це може бути кращим, коли у вас є пара даних.tables і не хочете складати список.


Це найпростіше нестандартне рішення, яке легко узагальнюється до будь-якої кількості фреймів даних, оскільки їх можна зберігати в окремих елементах списку. Інші відповіді, як-от intersectпідхід, працюють лише для 2-х фреймів даних і не можуть легко узагальнити.
Багатий Паулуо

35

Більшість базових відповідей R стосуються ситуації, коли лише один data.frame має додаткові стовпці або що отриманий data.frame мав би перетин стовпців. Оскільки в ОП пише, що я сподіваюся зберегти стовпці, які не збігаються після прив’язки , відповідь, використовуючи базові методи R для вирішення цього питання, ймовірно, варто опублікувати.

Нижче я представляю два базових методу R: один, який змінює вихідні кадри data.frames, і той, який не робить. Крім того, я пропоную метод, який узагальнює неруйнівний метод для більш ніж двох фреймів 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. Обмеження полягає в тому, що ви можете комбінувати одночасно два кадри 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 не любив працювати з датами, ймовірно, тому, що це було as.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.