Вставить несколько столбцов вместе
У меня есть куча столбцов в фрейме данных, который я хочу вставить вместе (разделенный на "-" ) следующим образом:
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
Которое хочу стать:
a x
1 a-d-g
2 b-e-h
3 c-f-i
Обычно я мог бы сделать это с помощью
within(data, x <- paste(b,c,d,sep='-'))
а затем удаление старых столбцов, но, к сожалению, я не знаю имена столбцов, а только коллективное имя для всех столбцов, например. Я бы знал, что cols <- c('b','c','d')
Кто-нибудь знает способ сделать это?
Ответы
Ответ 1
# 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 ) ]
Ответ 2
Как вариант на baptiste answer, с data
, определенным как у вас, и столбцами, которые вы хотите скомпоновать, определенными в cols
cols <- c("b", "c", "d")
Вы можете добавить новый столбец в data
и удалить старые с помощью
data$x <- do.call(paste, c(data[cols], sep="-"))
for (co in cols) data[co] <- NULL
который дает
> data
a x
1 1 a-d-g
2 2 b-e-h
3 3 c-f-i
Ответ 3
Используя пакет tidyr
, это может быть легко обработано в 1 вызове функции.
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
Изменить: Исключить первый столбец, все остальные вставляются.
# 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
Ответ 4
Я бы построил новый 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 = '-'))))
Ответ 5
Просто добавьте дополнительное решение с Reduce
, которое, вероятно, медленнее, чем do.call
, но лучше зондирует, чем apply
, потому что оно избежит преобразования matrix
. Кроме того, вместо цикла for
мы могли бы просто использовать 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
В качестве альтернативы мы могли бы обновить data
на месте с помощью пакета data.table
(при условии свежих данных)
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
Другой вариант - использовать .SDcols
вместо mget
, как в
setDT(data)[, x := Reduce(function(...) paste(..., sep = "-"), .SD), .SDcols = cols]
Ответ 6
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="-"))))
Ответ 7
Я сравнил ответы Антони Дамико, Брайана Диггса и data_steve с небольшим образцом tbl_df
и получил следующие результаты.
> 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
Однако, когда я оценил собственный tbl_df
с ~ 1 миллионом строк и 10 столбцов, результаты были совершенно разными.
> 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
Ответ 8
По моему мнению, sprintf
-функция заслуживает места среди этих ответов. Вы можете использовать sprintf
следующим образом:
do.call(sprintf, c(d[cols], '%s-%s-%s'))
который дает:
[1] "a-d-g" "b-e-h" "c-f-i"
И для создания требуемого фрейма данных:
data.frame(a = d$a, x = do.call(sprintf, c(d[cols], '%s-%s-%s')))
даяние:
a x
1 1 a-d-g
2 2 b-e-h
3 3 c-f-i
Хотя sprintf
не имеет явного преимущества по сравнению с комбинацией @brianDiggs do.call
/paste
, это особенно полезно, когда вы также хотите заполнить определенные части нужной строки или когда вы хотите указать число цифр. См. ?sprintf
для нескольких параметров.
Тест на более крупный набор данных:
# 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)
приводит к:
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
Используемые данные:
d <- data.frame(a = 1:3, b = c('a','b','c'), c = c('d','e','f'), d = c('g','h','i'))