여러 열을 함께 붙여 넣기


100

다음과 같이 함께 붙여 넣을 ( "-"로 구분) 데이터 프레임에 여러 열이 있습니다.

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

누구든지 이것을하는 방법을 알고 있습니까?

답변:


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

7
여기에서 신청할 필요가 없습니다. 붙여 넣기가 벡터화되고 더 효율적입니다
baptiste

1
@baptiste ..possible없이 do.call?
안토니 다 미코

1
예를 들어를 사용할 수 evil(parse(...))있지만 do.call여기에서 올바른 호출 이라고 생각 합니다.
baptiste

Do.call은 더 나은 기술입니다. 벡터화를 유지합니다.
Clayton Stanley

1
흠 .. 어떻게 통과 collapse = "-"하시겠습니까? 에 paste?
Anthony Damico 2014 년

48

Baptiste의 답변 에 대한 변형으로 , 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

"c (data [cols], ..."에 쉼표가 누락되어 있습니까? "c (data [, cols], ...")
roschu

2
@roschu 둘 중 하나가 작동합니다. 인덱싱 data.frame첫 번째 인수는 일반적으로 행 인덱스 임에도 불구하고 하나의 문자 벡터로하여, 열 인덱싱 될 것입니다.
Brian Diggs 2015

빠르고 똑똑합니다. 감사합니다
Ali Khosro 2017 년

33

tidyr패키지를 사용하면 한 번의 함수 호출로 쉽게 처리 할 수 ​​있습니다.

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

3
나는 OP가 그들이 열 이름을 미리 모른다고 언급했다고 생각한다. 그렇지 않으면 within(data, x <- paste(b,c,d,sep='-'))그들이 삽화 한대로 그것을 할 수 있었다 .
데이비드 Arenburg

@DavidArenburg에 동의합니다. 이것은 OP의 상황을 다루지 않습니다. 나는 unite_(data, "b_c_d", cols)실제 data.frame에 따라 unite(data, b_c_d, -a)후보가 될 수도 있다고 생각 합니다.
샘 Firke

14

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

대신에 열을 제외한 모든 항목을 함께 붙여 넣을 경우 d[ , cols]사용할 수 있습니다. d[ , names(d) != 'a']a
baptiste

2
SO에 대한 표준 솔루션 중 하나입니다. cbind(a = d['a'], x = do.call(paste, c(d[cols], sep = '-')))예를 들어 쉼표를 피하고 다음 listdata.frame같은 data.frame방법 을 사용하는 동안 이 값을로 줄일 수 있다고 생각합니다.cbind
David Arenburg

9

변환 을 피할 수 있기 때문에 더 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]

5

작은 샘플에서 Anthony Damico, Brian Diggs 및 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~ 100 만 행과 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

5

내 생각에 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오버 분명한 이점이없는 do.call/의 paste@BrianDiggs의 조합이 자리의 수를 지정하고자 할 때, 그것은 특히 당신이 또한 원하는 문자열의 패드 특정 부분을하고자 할 때 유용이나된다. ?sprintf여러 옵션을 참조하십시오 .

또 다른 변형은 사용하는 것입니다 pmap에서:

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

참고 :이 pmap솔루션은 열이 요인이 아닌 경우에만 작동합니다.


더 큰 데이터 세트에 대한 벤치 마크 :

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

3

다음은 상당히 틀렸지 만 빠른 접근 방식입니다. fwritefrom data.table을 사용 하여 열을 "붙여 넣기"하고 fread다시 읽어옵니다. 편의를 위해 다음과 같은 함수로 단계를 작성했습니다 fpaste.

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

예를 들면 다음과 같습니다.

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

어떻게 수행합니까?

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

램 디스크에 쓰고 읽는다면 어떨까요? 비교는 좀 더 공정 할 것입니다.
jangorecki

@jangorecki, 내가 올바르게하고 있는지 확실하지 않지만 (R로 시작했습니다 TMPDIR=/dev/shm R) 이러한 결과와 비교할 때 큰 차이를 느끼지 못합니다. 또한 스레드 사용의 수에 따라 전혀 주위를 연주 해본 적이 없어 fread또는 fwrite그 결과에 영향을 미치는 방법을 볼 수 있습니다.
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

나는 이것이 오래된 질문이라는 것을 알고 있지만 어쨌든 질문자가 제안한 것처럼 paste () 함수를 사용하여 간단한 해결책을 제시해야한다고 생각했습니다.

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.