【问题标题】:Paste multiple columns together将多列粘贴在一起
【发布时间】:2013-01-12 04:23:04
【问题描述】:

我在数据框中有一堆列,我想将它们粘贴在一起(用“-”分隔),如下所示:

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 &lt;- c('b','c','d')

有人知道这样做的方法吗?

【问题讨论】:

    标签: r paste r-faq


    【解决方案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 ) ]
    

    【讨论】:

    • 这里不需要申请; paste 被矢量化了,效率更高
    • @baptiste ..可能没有do.call
    • 当然,例如,您可以使用evil(parse(...)),但我相信do.call 是正确的选择。
    • Do.call 这里是更好的技术;保持矢量化。
    • hmm.. 你将如何传递collapse = "-"?到paste?
    【解决方案2】:

    作为baptiste's 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
    

    【讨论】:

    • "c(data[cols],..." 中是否缺少逗号?像这样:"c(data[,cols],..."
    • @roschu 两者都可以。尽管第一个参数通常是行索引,但使用单个字符向量索引 data.frame 将是列索引。
    • 快速而智能。谢谢
    【解决方案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
    

    【讨论】:

    • 我认为 OP 提到他们事先不知道列名。否则他们可以用within(data, x &lt;- paste(b,c,d,sep='-')) 来做,如图所示。
    • 我同意@DavidArenburg,这并不能解决 OP 的情况。我认为unite_(data, "b_c_d", cols) 会,或者根据他们的实际data.frame,unite(data, b_c_d, -a) 也可能是候选人。
    【解决方案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 = '-'))))
    

    【讨论】:

    • 请注意,如果要将除a 之外的所有列粘贴在一起,您可能希望使用d[ , names(d) != 'a'] 而不是d[ , cols]
    • 关于 SO 的规范解决方案之一,我认为您可以将其缩短为 cbind(a = d['a'], x = do.call(paste, c(d[cols], sep = '-'))),例如在使用cbinddata.frame 方法时避免使用逗号listdata.frame
    【解决方案5】:

    只需使用Reduce 添加其他解决方案,它可能比do.call 慢,但比apply 更好,因为它将避免matrix 转换。此外,我们可以使用setdiff 来代替for 循环来删除不需要的列

    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.table 包更新data(假设新数据)

    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】:

      我在一个小样本tbl_df 上对 Anthony Damico、Brian Diggs 和 data_steve 的答案进行了基准测试,得到了以下结果。

      > 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
      

      但是,当我使用约 100 万行和 10 列自行评估 tbl_df 时,结果完全不同。

      > 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
      

      【讨论】:

        【解决方案7】:

        在我看来,sprintf-function 也应该在这些答案中占有一席之地。您可以使用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

        另一个变体是使用来自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')) 
        

        【讨论】:

          【解决方案8】:

          这是一种非常规(但速度很快)的方法:使用 fwrite 中的 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
          

          【讨论】:

          • 如果你在 ramdisk 上读写怎么办?比较会更公平一些。
          • @jangorecki,不确定我是否做得正确(我以TMPDIR=/dev/shm R 开始R),但与这些结果相比,我没有注意到巨大的差异。我也没有玩过freadfwrite 使用的线程数,看看它如何影响结果。
          【解决方案9】:

          我知道这是一个老问题,但我认为无论如何我都应该按照提问者的建议使用 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
          

          【讨论】:

            【解决方案10】:
            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="-"))))    
            

            【讨论】:

              猜你喜欢
              • 2017-01-01
              • 2011-03-20
              • 1970-01-01
              • 2023-01-10
              • 2022-01-18
              • 2019-12-08
              • 2010-11-17
              • 1970-01-01
              相关资源
              最近更新 更多