Mam kilka kolumn w ramce danych, które chcę wkleić razem (oddzielone znakiem „-”) w następujący sposób:
data <- data.frame('a' = 1:3,
'b' = c('a','b','c'),
'c' = c('d', 'e', 'f'),
'd' = c('g', 'h', 'i'))
i.e.
a b c d
1 a d g
2 b e h
3 c f i
Którym chcę się stać:
a x
1 a-d-g
2 b-e-h
3 c-f-i
Normalnie mógłbym to zrobić z:
within(data, x <- paste(b,c,d,sep='-'))
a potem usuwam stare kolumny ale niestety nie znam konkretnie nazw kolumn tylko zbiorcza nazwa dla wszystkich kolumn np bym wiedział że cols <- c('b','c','d')
Czy ktoś wie, jak to zrobić?
do.call
?evil(parse(...))
, ale uważam, żedo.call
jest to właściwe wezwanie.collapse = "-"
? dopaste
?Jako wariant odpowiedzi baptysty , ze
data
zdefiniowanymi jak masz i kolumnami, które chcesz połączyć, zdefiniowanymi wcols
cols <- c("b", "c", "d")
Możesz dodać nową kolumnę
data
i usunąć stare za pomocądata$x <- do.call(paste, c(data[cols], sep="-")) for (co in cols) data[co] <- NULL
co daje
> data a x 1 1 a-d-g 2 2 b-e-h 3 3 c-f-i
źródło
data.frame
z jednym wektorem znakowym będzie indeksowaniem kolumny, mimo że pierwszym argumentem jest zwykle indeks wiersza.Korzystając z
tidyr
pakietu, można to łatwo obsłużyć w jednym wywołaniu funkcji.data <- data.frame('a' = 1:3, 'b' = c('a','b','c'), 'c' = c('d', 'e', 'f'), 'd' = c('g', 'h', 'i')) tidyr::unite_(data, paste(colnames(data)[-1], collapse="_"), colnames(data)[-1]) a b_c_d 1 1 a_d_g 2 2 b_e_h 3 3 c_f_i
Edycja: wyklucz pierwszą kolumnę, wszystko inne zostanie wklejone.
# tidyr_0.6.3 unite(data, newCol, -a) # or by column index unite(data, newCol, -1) # a newCol # 1 1 a_d_g # 2 2 b_e_h # 3 3 c_f_i
źródło
within(data, x <- paste(b,c,d,sep='-'))
jak zilustrowali.unite_(data, "b_c_d", cols)
że lub w zależności od ich faktycznej ramki data.frameunite(data, b_c_d, -a)
może być również kandydatem.Skonstruowałbym nową ramkę data.frame:
d <- data.frame('a' = 1:3, 'b' = c('a','b','c'), 'c' = c('d', 'e', 'f'), 'd' = c('g', 'h', 'i')) cols <- c( 'b' , 'c' , 'd' ) data.frame(a = d[, 'a'], x = do.call(paste, c(d[ , cols], list(sep = '-'))))
źródło
d[ , cols]
ciebie możesz chcieć użyć,d[ , names(d) != 'a']
jeśli wszystkie oprócza
kolumny mają zostać wklejone razem.cbind(a = d['a'], x = do.call(paste, c(d[cols], sep = '-')))
, na przykład uniknąć przecinki,list
adata.frame
podczas korzystania zdata.frame
metodycbind
Wystarczy dodać dodatkowe rozwiązanie, z
Reduce
którym prawdopodobnie jest wolniejsze niż,do.call
ale sondowanie lepsze niż,apply
ponieważ pozwoli uniknąćmatrix
konwersji. Ponadto zamiast tegofor
możemy użyć pętlisetdiff
, aby usunąć niechciane kolumnycols <- c('b','c','d') data$x <- Reduce(function(...) paste(..., sep = "-"), data[cols]) data[setdiff(names(data), cols)] # a x # 1 1 a-d-g # 2 2 b-e-h # 3 3 c-f-i
Alternatywnie możemy zaktualizować
data
na miejscu za pomocądata.table
pakietu (zakładając świeże dane)library(data.table) setDT(data)[, x := Reduce(function(...) paste(..., sep = "-"), .SD[, mget(cols)])] data[, (cols) := NULL] data # a x # 1: 1 a-d-g # 2: 2 b-e-h # 3: 3 c-f-i
Inną opcją jest użycie
.SDcols
zamiastmget
jak wsetDT(data)[, x := Reduce(function(...) paste(..., sep = "-"), .SD), .SDcols = cols]
źródło
Porównałem odpowiedzi Anthony'ego Damico, Briana Diggsa i data_steve na małej próbce
tbl_df
i otrzymałem następujące wyniki.> data <- data.frame('a' = 1:3, + 'b' = c('a','b','c'), + 'c' = c('d', 'e', 'f'), + 'd' = c('g', 'h', 'i')) > data <- tbl_df(data) > cols <- c("b", "c", "d") > microbenchmark( + do.call(paste, c(data[cols], sep="-")), + apply( data[ , cols ] , 1 , paste , collapse = "-" ), + tidyr::unite_(data, "x", cols, sep="-")$x, + times=1000 + ) Unit: microseconds expr min lq mean median uq max neval do.call(paste, c(data[cols], sep = "-")) 65.248 78.380 93.90888 86.177 99.3090 436.220 1000 apply(data[, cols], 1, paste, collapse = "-") 223.239 263.044 313.11977 289.514 338.5520 743.583 1000 tidyr::unite_(data, "x", cols, sep = "-")$x 376.716 448.120 556.65424 501.877 606.9315 11537.846 1000
Jednak gdy samodzielnie
tbl_df
oceniłem z ~ 1 milionem wierszy i 10 kolumnami, wyniki były zupełnie inne.> microbenchmark( + do.call(paste, c(data[c("a", "b")], sep="-")), + apply( data[ , c("a", "b") ] , 1 , paste , collapse = "-" ), + tidyr::unite_(data, "c", c("a", "b"), sep="-")$c, + times=25 + ) Unit: milliseconds expr min lq mean median uq max neval do.call(paste, c(data[c("a", "b")], sep="-")) 930.7208 951.3048 1129.334 997.2744 1066.084 2169.147 25 apply( data[ , c("a", "b") ] , 1 , paste , collapse = "-" ) 9368.2800 10948.0124 11678.393 11136.3756 11878.308 17587.617 25 tidyr::unite_(data, "c", c("a", "b"), sep="-")$c 968.5861 1008.4716 1095.886 1035.8348 1082.726 1759.349 25
źródło
Moim zdaniem
sprintf
funkcja -funkcja również zasługuje na miejsce wśród tych odpowiedzi. Możesz użyćsprintf
w następujący sposób:do.call(sprintf, c(d[cols], '%s-%s-%s'))
co daje:
[1] "a-d-g" "b-e-h" "c-f-i"
Aby utworzyć wymaganą ramkę danych:
data.frame(a = d$a, x = do.call(sprintf, c(d[cols], '%s-%s-%s')))
dający:
a x 1 1 a-d-g 2 2 b-e-h 3 3 c-f-i
Chociaż
sprintf
nie ma wyraźnej przewagi nad kombinacjądo.call
/paste
@BrianDiggs, jest szczególnie przydatna, gdy chcesz również dopełnić określone części żądanego ciągu lub gdy chcesz określić liczbę cyfr. Zobacz?sprintf
kilka opcji.Innym wariantem byłoby użycie
pmap
frommruczenie:pmap(d[2:4], paste, sep = '-')
Uwaga: to
pmap
rozwiązanie działa tylko wtedy, gdy kolumny nie są czynnikami.Wzorzec dla większego zbioru danych:
# create a larger dataset d2 <- d[sample(1:3,1e6,TRUE),] # benchmark library(microbenchmark) microbenchmark( docp = do.call(paste, c(d2[cols], sep="-")), appl = apply( d2[, cols ] , 1 , paste , collapse = "-" ), tidr = tidyr::unite_(d2, "x", cols, sep="-")$x, docs = do.call(sprintf, c(d2[cols], '%s-%s-%s')), times=10)
prowadzi do:
Unit: milliseconds expr min lq mean median uq max neval cld docp 214.1786 226.2835 297.1487 241.6150 409.2495 493.5036 10 a appl 3832.3252 4048.9320 4131.6906 4072.4235 4255.1347 4486.9787 10 c tidr 206.9326 216.8619 275.4556 252.1381 318.4249 407.9816 10 a docs 413.9073 443.1550 490.6520 453.1635 530.1318 659.8400 10 b
Wykorzystane dane:
d <- data.frame(a = 1:3, b = c('a','b','c'), c = c('d','e','f'), d = c('g','h','i'))
źródło
Oto dość niekonwencjonalne (ale szybkie) podejście: użyj
fwrite
from,data.table
aby „wkleić” kolumny razem ifread
wczytać je z powrotem. Dla wygody zapisałem kroki jako funkcję o nazwiefpaste
:fpaste <- function(dt, sep = ",") { x <- tempfile() fwrite(dt, file = x, sep = sep, col.names = FALSE) fread(x, sep = "\n", header = FALSE) }
Oto przykład:
d <- data.frame(a = 1:3, b = c('a','b','c'), c = c('d','e','f'), d = c('g','h','i')) cols = c("b", "c", "d") fpaste(d[cols], "-") # V1 # 1: a-d-g # 2: b-e-h # 3: c-f-i
Jak to działa?
d2 <- d[sample(1:3,1e6,TRUE),] library(microbenchmark) microbenchmark( docp = do.call(paste, c(d2[cols], sep="-")), tidr = tidyr::unite_(d2, "x", cols, sep="-")$x, docs = do.call(sprintf, c(d2[cols], '%s-%s-%s')), appl = apply( d2[, cols ] , 1 , paste , collapse = "-" ), fpaste = fpaste(d2[cols], "-")$V1, dt2 = as.data.table(d2)[, x := Reduce(function(...) paste(..., sep = "-"), .SD), .SDcols = cols][], times=10) # Unit: milliseconds # expr min lq mean median uq max neval # docp 215.34536 217.22102 220.3603 221.44104 223.27224 225.0906 10 # tidr 215.19907 215.81210 220.7131 220.09636 225.32717 229.6822 10 # docs 281.16679 285.49786 289.4514 286.68738 290.17249 312.5484 10 # appl 2816.61899 3106.19944 3259.3924 3266.45186 3401.80291 3804.7263 10 # fpaste 88.57108 89.67795 101.1524 90.59217 91.76415 197.1555 10 # dt2 301.95508 310.79082 384.8247 316.29807 383.94993 874.4472 10
źródło
TMPDIR=/dev/shm R
), ale nie zauważam dużej różnicy w porównaniu do tych wyników. W ogóle nie bawiłem się też liczbą wątków używanych dofread
lubfwrite
nie sprawdzałem, jak wpływa to na wyniki.library(plyr) ldply(apply(data, 1, function(x) data.frame( x = paste(x[2:4],sep="",collapse="-")))) # x #1 a-d-g #2 b-e-h #3 c-f-i # and with just the vector of names you have: ldply(apply(data, 1, function(x) data.frame( x = paste(x[c('b','c','d')],sep="",collapse="-")))) # or equally: mynames <-c('b','c','d') ldply(apply(data, 1, function(x) data.frame( x = paste(x[mynames],sep="",collapse="-"))))
źródło
Wiem, że to stare pytanie, ale pomyślałem, że i tak powinienem przedstawić proste rozwiązanie za pomocą funkcji paste (), zgodnie z sugestią pytającego:
data_1<-data.frame(a=data$a,"x"=paste(data$b,data$c,data$d,sep="-")) data_1 a x 1 1 a-d-g 2 2 b-e-h 3 3 c-f-i
źródło