Birden çok sütunu birbirine yapıştırın


100

Bir veri çerçevesinde birbirine yapıştırmak istediğim ("-" ile ayrılmış) aşağıdaki gibi bir sürü sütunum var:

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  

Hangisi olmak istiyorum:

a x  
1 a-d-g  
2 b-e-h  
3 c-f-i  

Bunu normalde şununla yapabilirim:

within(data, x <- paste(b,c,d,sep='-'))

ve sonra eski sütunları kaldırıyorum, ancak maalesef özellikle sütunların adlarını bilmiyorum, yalnızca tüm sütunlar için toplu bir isim, örn. cols <- c('b','c','d')

Bunu yapmanın bir yolunu bilen var mı?

Yanıtlar:


104
# your starting data..
data <- data.frame('a' = 1:3, 'b' = c('a','b','c'), 'c' = c('d', 'e', 'f'), 'd' = c('g', 'h', 'i')) 

# columns to paste together
cols <- c( 'b' , 'c' , 'd' )

# create a new column `x` with the three columns collapsed together
data$x <- apply( data[ , cols ] , 1 , paste , collapse = "-" )

# remove the unnecessary columns
data <- data[ , !( names( data ) %in% cols ) ]

8
burada başvurmaya gerek yok; macun vektörleştirildi ve bu daha verimli
baptiste

1
@baptiste .. olmadan mümkün do.callmü?
Anthony Damico

1
elbette, örneğin kullanabilirsiniz evil(parse(...)), ancak do.callburada doğru arama olduğuna inanıyorum .
vaftizci

Do.call burada daha iyi tekniktir; vektörleştirmeyi sürdürür.
Clayton Stanley

1
hmm .. nasıl geçersiniz collapse = "-"? için paste?
Anthony Damico

48

Baptiste'nin cevabının bir varyantı olarak , sahip olduğunuz gibi datatanımlanmış ve bir araya getirmek istediğiniz sütunlarlacols

cols <- c("b", "c", "d")

Yeni sütunu ekleyebilir datave eskilerini silebilirsiniz.

data$x <- do.call(paste, c(data[cols], sep="-"))
for (co in cols) data[co] <- NULL

hangi verir

> data
  a     x
1 1 a-d-g
2 2 b-e-h
3 3 c-f-i

"C (data [cols], ..." içinde bir virgül eksik mi?
Şöyle

2
@roschu Her ikisi de çalışacak. Bir Dizin Oluşturma data.frameİlk argüman genellikle satır dizini olmasına rağmen tek bir karakter vektör ile, bir sütun indeksleme olacaktır.
Brian Diggs

hızlı ve akıllı. Teşekkür ederim
Ali Khosro

33

tidyrPaketi kullanarak bu, 1 işlev çağrısında kolayca halledilebilir.

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

Düzenleme: İlk sütunu hariç tutun, diğer her şey yapıştırılır.

# 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

3
Sanırım OP, sütun adını önceden bilmediklerinden bahsetti, aksi takdirde aynen within(data, x <- paste(b,c,d,sep='-'))gösterildiği gibi yapabilirdi.
David Arenburg

@DavidArenburg'a katılıyorum, bu OP'nin durumunu ele almıyor. Bence unite_(data, "b_c_d", cols)olur veya gerçek data.frame'e bağlı olarak unite(data, b_c_d, -a)da bir aday olabilir.
Sam Firke

14

Yeni bir data.frame oluşturardım:

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 = '-'))))

bunun yerine sütun hariç tümünün birbirine yapıştırılması d[ , cols]gerekiyorsa kullanmak isteyebileceğinizi unutmayın . d[ , names(d) != 'a']a
vaftizci

2
cbind(a = d['a'], x = do.call(paste, c(d[cols], sep = '-')))listdata.framedata.framecbind
SO'daki

9

Sadece Reducemuhtemelen daha yavaş olan do.callancak muhtemelen dönüşümü applyengelleyeceğinden daha iyi olan ek bir çözüm eklemek için matrix. Ayrıca, bunun yerine istenmeyen sütunları kaldırmak için forkullanabileceğimiz bir döngüsetdiff

cols <- 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

Alternatif olarak data, data.tablepaketi kullanarak yerinde güncelleme yapabiliriz (yeni veriler varsayarak)

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

Başka bir seçenek kullanmaktır .SDcolsyerine mgetolduğu gibi

setDT(data)[, x := Reduce(function(...) paste(..., sep = "-"), .SD), .SDcols = cols]

5

Anthony Damico, Brian Diggs ve data_steve'nin cevaplarını küçük bir örnek üzerinde karşılaştırdım tbl_dfve aşağıdaki sonuçları aldım.

> 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

Ancak tbl_df~ 1 milyon satır ve 10 sütunla kendi başıma değerlendirdiğimde sonuçlar oldukça farklıydı.

> 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

5

Kanımca sprintffonksiyon bu cevaplar arasında da bir yeri hak ediyor. Aşağıdaki gibi kullanabilirsiniz sprintf:

do.call(sprintf, c(d[cols], '%s-%s-%s'))

hangi verir:

 [1] "a-d-g" "b-e-h" "c-f-i"

Ve gerekli veri çerçevesini oluşturmak için:

data.frame(a = d$a, x = do.call(sprintf, c(d[cols], '%s-%s-%s')))

veren:

  a     x
1 1 a-d-g
2 2 b-e-h
3 3 c-f-i

Her ne kadar sprintfüzerinde açık bir avantaja sahip değildir do.call/ paste@BrianDiggs kombinasyonu size hane sayısını belirtmek istediğinizde, özellikle de istenen dize ped belirli bölümlerine istediğinizde kullanılan yaklaşımlar veya. ?sprintfÇeşitli seçenekler için bakın .

Bir başka varyant kullanmak olacaktır pmapdan:

pmap(d[2:4], paste, sep = '-')

Not: Bu pmapçözüm yalnızca sütunlar faktör olmadığında işe yarar.


Daha büyük bir veri kümesinde bir kıyaslama:

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

sonuçlanır:

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 

Kullanılan veriler:

d <- data.frame(a = 1:3, b = c('a','b','c'), c = c('d','e','f'), d = c('g','h','i')) 

3

İşte oldukça alışılmadık (ancak hızlı) bir yaklaşım: sütunları birbirine "yapıştırmak" ve tekrar okumak için " fwritedan data.table" işlevini kullanın fread. Kolaylık olması için, adımları bir işlev olarak yazdım fpaste:

fpaste <- function(dt, sep = ",") {
  x <- tempfile()
  fwrite(dt, file = x, sep = sep, col.names = FALSE)
  fread(x, sep = "\n", header = FALSE)
}

İşte bir örnek:

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

Nasıl işliyor?

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

Ya ramdisk'e yazıp okursanız? Karşılaştırma biraz daha adil olur.
jangorecki

@jangorecki, bunu doğru yapıp yapmadığımdan emin değilim (R ile başladım TMPDIR=/dev/shm R) ancak bu sonuçlara kıyasla çok büyük bir fark görmüyorum. Ben de iş parçacığı için kullanılan sayısı ile her etrafında oynamadım freadya fwriteda sonuçlarını nasıl etkilediğini görmek için.
A5C1D2H2I1M1N2O1R2T1

1
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="-"))))    

0

Bunun eski bir soru olduğunu biliyorum, ancak soruyu soran kişinin önerdiği gibi paste () işlevini kullanarak basit çözümü sunmam gerektiğini düşündüm:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.