web-dev-qa-db-fra.com

Coller plusieurs colonnes ensemble

J'ai un tas de colonnes dans une base de données que je veux coller ensemble (séparés par "-") comme suit:

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  

Ce que je veux devenir:

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

Je pourrais normalement le faire avec:

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

puis en supprimant les anciennes colonnes, mais malheureusement, je ne connais pas spécifiquement les noms des colonnes, mais uniquement un nom collectif pour toutes les colonnes, par exemple. Je saurais que cols <- c('b','c','d')

Est-ce que quelqu'un connaît un moyen de faire cela?

81
user1165199
# 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 ) ]
85
Anthony Damico

En variante sur la réponse de baptiste , avec data défini comme vous l'avez et les colonnes que vous voulez assembler sont définies dans cols

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

Vous pouvez ajouter la nouvelle colonne à data et supprimer les anciennes avec

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

qui donne

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

En utilisant le paquet tidyr, cela peut être facilement traité en 1 appel de fonction.

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

Edit: Exclure la première colonne, tout le reste est collé.

# 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
27
data_steve

Je construirais un nouveau 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 = '-'))))
12
baptiste

Juste pour ajouter une solution supplémentaire avec Reduce qui est probablement plus lent que do.call mais nettement mieux que apply car cela évitera la conversion matrix. De plus, à la place d'une boucle for, nous pourrions simplement utiliser setdiff afin de supprimer les colonnes non désirées.

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

Alternativement, nous pourrions mettre à jour data à la place en utilisant le data.table package (en supposant de nouvelles données)

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

Une autre option consiste à utiliser .SDcols au lieu de mget comme dans

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

A mon avis, la fonction sprintf- mérite également une place parmi ces réponses. Vous pouvez utiliser sprintf comme suit:

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

qui donne:

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

Et pour créer le dataframe requis:

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

donnant:

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

Bien que sprintf n’ait pas d’avantage sur le do.call/paste combinaison de @BrianDiggs, il est particulièrement utile lorsque vous souhaitez également compléter certaines parties de la chaîne souhaitée ou lorsque vous souhaitez spécifier le nombre de chiffres. Voir ?sprintf pour les différentes options.

Une autre variante consisterait à utiliser pmap de purrr :

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

Remarque: cette solution pmap ne fonctionne que lorsque les colonnes ne sont pas des facteurs.


Un repère sur un jeu de données plus grand:

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

résulte en:

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 

Données utilisées:

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

J'ai comparé les réponses d'Anthony Damico, Brian Diggs et data_steve sur un petit échantillon tbl_df et a obtenu les résultats suivants.

> 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

Cependant, quand j’ai évalué moi-même tbl_df avec environ 1 million de lignes et 10 colonnes, les résultats étaient assez différents.

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