Odpowiedzi:
rbind.fill
z paczki plyr
może być tym, czego szukasz.
Nowsza rozwiązaniem jest użycie dplyr
„s bind_rows
funkcję które zakładam, że jest bardziej wydajny niż 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
ABC
nie można przekonwertować z postaci na cyfrę. Czy istnieje sposób, aby najpierw przekonwertować kolumny?
Możesz użyć smartbind
z gtools
pakietu.
Przykład:
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
smartbind
z dwoma dużymi ramkami danych (w sumie około 3 * 10 ^ 6 wierszy) i przerwałem je po 10 minutach.
Jeśli kolumny w df1 są podzbiorem tych w df2 (według nazw kolumn):
df3 <- rbind(df1, df2[, names(df1)])
Alternatywa z 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
będzie również działać, data.table
dopóki obiekty zostaną przekonwertowane na data.table
obiekty, więc
rbind(setDT(df1), setDT(df2), fill=TRUE)
będzie również działać w tej sytuacji. Może to być preferowane, gdy masz kilka tabel data.tab i nie chcesz budować listy.
intersect
podejście, działają tylko dla 2 ramek danych i nie dają się łatwo generalizować.
Większość podstawowych odpowiedzi R dotyczy sytuacji, w której tylko jedna ramka danych ma dodatkowe kolumny lub że wynikowa ramka danych ma przecięcie kolumn. Ponieważ OP pisze, że mam nadzieję zachować kolumny, które nie pasują po powiązaniu , prawdopodobnie warto opublikować odpowiedź przy użyciu podstawowych metod R w celu rozwiązania tego problemu.
Poniżej przedstawiam dwie podstawowe metody R. Jedna zmienia oryginalne data.frames, a druga nie. Dodatkowo oferuję metodę, która uogólnia metodę nieniszczącą na więcej niż dwie ramki danych.
Najpierw zdobądźmy przykładowe dane.
# 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])
Dwie data.frame, zmień oryginały
Aby zachować wszystkie kolumny z obu data.frame w rbind
(i umożliwić działanie funkcji bez powodowania błędu), dodajesz kolumny NA do każdej data.frame z wypełnionymi odpowiednimi brakującymi nazwami za pomocą setdiff
.
# fill in non-overlapping columns with NAs
df1[setdiff(names(df2), names(df1))] <- NA
df2[setdiff(names(df1), names(df2))] <- NA
Teraz, 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
Zauważ, że pierwsze dwa wiersze zmieniają oryginalne dane. Ramki, df1 i df2, dodając pełny zestaw kolumn do obu.
Dwie ramki data.frame, nie zmieniaj oryginałów
Aby pozostawić nienaruszone oryginalne ramki data.frame, najpierw wykonaj pętlę przez nazwy, które się różnią, zwróć nazwany wektor NA, które są połączone w listę za pomocą data.frame c
. Następnie data.frame
konwertuje wynik na odpowiednią ramkę data.frame dla pliku 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)))
)
Wiele data.frames, nie zmieniaj oryginałów
W przypadku, gdy masz więcej niż dwa data.frames, możesz wykonać następujące czynności.
# 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)))))
Może nieco milej jest nie widzieć nazw wierszy oryginalnych ramek data.frames? Zrób to.
do.call(rbind,
c(lapply(mydflist,
function(x) data.frame(c(x, sapply(setdiff(allNms, names(x)),
function(y) NA)))),
make.row.names=FALSE))
mydflist <- list(as, dr, kr, hyt, ed1, of)
. To powinno skonstruować obiekt listy, który nie zwiększy wielkości twojego środowiska, ale po prostu wskaże każdy element listy (o ile nie zmienisz żadnej zawartości później). Po operacji usuń obiekt listy, aby być bezpiecznym.
Możesz także po prostu wyciągnąć wspólne nazwy kolumn.
> cols <- intersect(colnames(df1), colnames(df2))
> rbind(df1[,cols], df2[,cols])
Napisałem funkcję, aby to zrobić, ponieważ podoba mi się mój kod informujący, czy coś jest nie tak. Ta funkcja wyraźnie powie ci, które nazwy kolumn nie pasują i czy masz niezgodność typu. Wtedy i tak dołoży wszelkich starań, aby połączyć dane. Ramki. Ograniczeniem jest to, że możesz łączyć tylko dwie ramki danych jednocześnie.
### 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)
}
Być może całkowicie źle odczytałem twoje pytanie, ale „Mam nadzieję zachować kolumny, które nie pasują po powiązaniu”, sprawia, że myślę, że szukasz zapytania podobnego do zapytania SQL left join
lub right join
podobnego. R ma merge
funkcję, która pozwala określić lewe, prawe lub wewnętrzne sprzężenia podobne do łączenia tabel w SQL.
Tutaj jest już świetne pytanie i odpowiedź na ten temat: Jak połączyć (scalić) ramki danych (wewnętrzne, zewnętrzne, lewe, prawe)?
gtools / smartbind nie lubił pracować z Dates, prawdopodobnie dlatego, że było tak jak wektory. Oto moje rozwiązanie ...
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)
}
Tylko dla dokumentacji. Możesz wypróbować Stack
bibliotekę i jej funkcję Stack
w następującej formie:
Stack(df_1, df_2)
Mam również wrażenie, że jest szybszy niż inne metody dla dużych zbiorów danych.
Możesz również użyć sjmisc::add_rows()
, który używa dplyr::bind_rows()
, ale w przeciwieństwie do bind_rows()
, add_rows()
zachowuje atrybuty, a zatem jest użyteczny dla danych z etykietami .
Zobacz następujący przykład z etykietowanym zestawem danych. Funkcja frq()
drukuje tabele częstotliwości z etykietami wartości, jeśli dane są oznaczone.
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
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)]))
}
rbind.fill
ibind_rows()
oba po cichu upuszczają nazwy.