【问题标题】:Split data frame string column into multiple columns将数据框字符串列拆分为多列
【发布时间】:2011-05-20 00:34:12
【问题描述】:

我想获取表单的数据

before = data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2'))
  attr          type
1    1   foo_and_bar
2   30 foo_and_bar_2
3    4   foo_and_bar
4    6 foo_and_bar_2

并在上面的“type”列上使用split() 来获得类似这样的内容:

  attr type_1 type_2
1    1    foo    bar
2   30    foo  bar_2
3    4    foo    bar
4    6    foo  bar_2

我想出了一些令人难以置信的复杂问题,其中涉及某种形式的 apply 有效,但后来我放错了地方。这似乎太复杂了,不是最好的方法。我可以使用strsplit,如下所示,但不清楚如何将其恢复到数据框中的 2 列中。

> strsplit(as.character(before$type),'_and_')
[[1]]
[1] "foo" "bar"

[[2]]
[1] "foo"   "bar_2"

[[3]]
[1] "foo" "bar"

[[4]]
[1] "foo"   "bar_2"

感谢您的任何指点。我还没有完全了解 R 列表。

【问题讨论】:

    标签: r string dataframe split r-faq


    【解决方案1】:

    使用stringr::str_split_fixed

    library(stringr)
    str_split_fixed(before$type, "_and_", 2)
    

    【讨论】:

    • 这对我今天的问题也很有效。但它在每行的开头添加了一个“c”。知道这是为什么吗??? left_right <- str_split_fixed(as.character(split_df),'\">',2)
    • 我想使用具有“...”的模式进行拆分,当我应用该函数时,它什么也不返回。可能是什么问题呢。我的类型类似于“测试...分数”
    • @user3841581 - 我知道你的旧查询,但这在文档中有所介绍 - str_split_fixed("aaa...bbb", fixed("..."), 2)fixed() 可以很好地匹配 pattern= 参数中的“匹配固定字符串”。 . 表示正则表达式中的“任何字符”。
    • 谢谢hadley,很方便的方法,但是有一点可以改进,如果原始列中有NA,分离后结果列中会变成几个空字符串,这是不需要的,我想在分离后保持 NA 还是 NA
    • 效果很好,即如果缺少分隔符!即如果我有一个向量 'a
    【解决方案2】:

    另一种选择是使用新的 tidyr 包。

    library(dplyr)
    library(tidyr)
    
    before <- data.frame(
      attr = c(1, 30 ,4 ,6 ), 
      type = c('foo_and_bar', 'foo_and_bar_2')
    )
    
    before %>%
      separate(type, c("foo", "bar"), "_and_")
    
    ##   attr foo   bar
    ## 1    1 foo   bar
    ## 2   30 foo bar_2
    ## 3    4 foo   bar
    ## 4    6 foo bar_2
    

    【讨论】:

    • 有没有办法单独限制拆分的数量?假设我只想在 '_' 上拆分一次(或者使用 str_split_fixed 并在现有数据框中添加列)?
    • @hadley 如果我想根据第二个_ 进行拆分呢?我想要的值为foo_and, bar/bar_2?
    【解决方案3】:

    5 年后添加强制性 data.table 解决方案

    library(data.table) ## v 1.9.6+ 
    setDT(before)[, paste0("type", 1:2) := tstrsplit(type, "_and_")]
    before
    #    attr          type type1 type2
    # 1:    1   foo_and_bar   foo   bar
    # 2:   30 foo_and_bar_2   foo bar_2
    # 3:    4   foo_and_bar   foo   bar
    # 4:    6 foo_and_bar_2   foo bar_2
    

    我们还可以确保生成的列具有正确的类型通过添加type.convertfixed 参数来提高性能(因为"_and_" 不是真正的正则表达式)

    setDT(before)[, paste0("type", 1:2) := tstrsplit(type, "_and_", type.convert = TRUE, fixed = TRUE)]
    

    【讨论】:

    • 如果您的'_and_' 模式的数量不同,您可以找出与max(lengths(strsplit(before$type, '_and_'))) 匹配的最大数量(即未来的列)
    • 这是我最喜欢的答案,效果很好!你能解释一下它是如何工作的吗?为什么 transpose(strsplit(…)) 而不是 paste0 用于连接字符串 - 不拆分它们......
    • @Gecko 我不确定问题是什么。如果您只使用strsplit,它会在每个插槽中创建一个包含 2 个值的单个向量,因此 tstrsplit 会将其转置为 2 个向量,每个向量中都有一个值。 paste0 仅用于创建列名,不用于值。等式的 LHS 上是列名,RHS 上是列上的拆分 + 转置操作。 := 代表“就地赋值”,因此您看不到 &lt;- 赋值运算符。
    【解决方案4】:

    另一种方法:在out 上使用rbind

    before <- data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2'))  
    out <- strsplit(as.character(before$type),'_and_') 
    do.call(rbind, out)
    
         [,1]  [,2]   
    [1,] "foo" "bar"  
    [2,] "foo" "bar_2"
    [3,] "foo" "bar"  
    [4,] "foo" "bar_2"
    

    并结合:

    data.frame(before$attr, do.call(rbind, out))
    

    【讨论】:

    • 新 R 版本的另一种选择是strcapture("(.*)_and_(.*)", as.character(before$type), data.frame(type_1 = "", type_2 = ""))
    【解决方案5】:

    请注意,带有“[”的 sapply 可用于提取这些列表中的第一项或第二项:

    before$type_1 <- sapply(strsplit(as.character(before$type),'_and_'), "[", 1)
    before$type_2 <- sapply(strsplit(as.character(before$type),'_and_'), "[", 2)
    before$type <- NULL
    

    这是一个 gsub 方法:

    before$type_1 <- gsub("_and_.+$", "", before$type)
    before$type_2 <- gsub("^.+_and_", "", before$type)
    before$type <- NULL
    

    【讨论】:

      【解决方案6】:

      这里有一个与 aniko 的解决方案相同的线路,但使用 hadley 的 stringr 包:

      do.call(rbind, str_split(before$type, '_and_'))
      

      【讨论】:

      • 很好,对我来说是最好的解决方案。虽然比使用 stringr 包要慢一些。
      • 这个函数是否被重命名为strsplit()
      【解决方案7】:

      要添加选项,您还可以像这样使用我的splitstackshape::cSplit 函数:

      library(splitstackshape)
      cSplit(before, "type", "_and_")
      #    attr type_1 type_2
      # 1:    1    foo    bar
      # 2:   30    foo  bar_2
      # 3:    4    foo    bar
      # 4:    6    foo  bar_2
      

      【讨论】:

      • 3 年后 - 此选项最适合我遇到的类似问题 - 但是我正在使用的数据框有 54 列,我需要将它们分成两列。有没有办法使用这种方法来做到这一点 - 没有输入上述命令 54 次?非常感谢,尼基。
      • @Nicki,您是否尝试过提供列名或列位置的向量?应该这样做....
      • 这不仅仅是重命名列 - 我需要像上面一样逐列拆分,有效地将我的 df 中的列数加倍。下面是我最后使用的: df2
      【解决方案8】:

      这个主题几乎用尽了,我想为一个稍微更通用的版本提供一个解决方案,你不知道输出列的数量,先验。所以例如你有

      before = data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2', 'foo_and_bar_2_and_bar_3', 'foo_and_bar'))
        attr                    type
      1    1             foo_and_bar
      2   30           foo_and_bar_2
      3    4 foo_and_bar_2_and_bar_3
      4    6             foo_and_bar
      

      我们不能使用 dplyr separate() 因为我们不知道拆分前结果列的数量,所以我创建了一个使用 stringr 拆分列的函数,给定模式和生成列的名称前缀。我希望使用的编码模式是正确的。

      split_into_multiple <- function(column, pattern = ", ", into_prefix){
        cols <- str_split_fixed(column, pattern, n = Inf)
        # Sub out the ""'s returned by filling the matrix to the right, with NAs which are useful
        cols[which(cols == "")] <- NA
        cols <- as.tibble(cols)
        # name the 'cols' tibble as 'into_prefix_1', 'into_prefix_2', ..., 'into_prefix_m' 
        # where m = # columns of 'cols'
        m <- dim(cols)[2]
      
        names(cols) <- paste(into_prefix, 1:m, sep = "_")
        return(cols)
      }
      

      然后我们可以在 dplyr 管道中使用split_into_multiple,如下所示:

      after <- before %>% 
        bind_cols(split_into_multiple(.$type, "_and_", "type")) %>% 
        # selecting those that start with 'type_' will remove the original 'type' column
        select(attr, starts_with("type_"))
      
      >after
        attr type_1 type_2 type_3
      1    1    foo    bar   <NA>
      2   30    foo  bar_2   <NA>
      3    4    foo  bar_2  bar_3
      4    6    foo    bar   <NA>
      

      然后我们就可以使用gather来收拾...

      after %>% 
        gather(key, val, -attr, na.rm = T)
      
         attr    key   val
      1     1 type_1   foo
      2    30 type_1   foo
      3     4 type_1   foo
      4     6 type_1   foo
      5     1 type_2   bar
      6    30 type_2 bar_2
      7     4 type_2 bar_2
      8     6 type_2   bar
      11    4 type_3 bar_3
      

      【讨论】:

        【解决方案9】:

        一个简单的方法是使用sapply()[ 函数:

        before <- data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2'))
        out <- strsplit(as.character(before$type),'_and_')
        

        例如:

        > data.frame(t(sapply(out, `[`)))
           X1    X2
        1 foo   bar
        2 foo bar_2
        3 foo   bar
        4 foo bar_2
        

        sapply() 的结果是一个矩阵,需要转置并转换回数据框。然后是一些简单的操作会产生您想要的结果:

        after <- with(before, data.frame(attr = attr))
        after <- cbind(after, data.frame(t(sapply(out, `[`))))
        names(after)[2:3] <- paste("type", 1:2, sep = "_")
        

        此时,after 就是你想要的

        > after
          attr type_1 type_2
        1    1    foo    bar
        2   30    foo  bar_2
        3    4    foo    bar
        4    6    foo  bar_2
        

        【讨论】:

          【解决方案10】:

          这是一个基本的 R one 衬垫,它与许多以前的解决方案重叠,但返回一个具有正确名称的 data.frame。

          out <- setNames(data.frame(before$attr,
                            do.call(rbind, strsplit(as.character(before$type),
                                                    split="_and_"))),
                            c("attr", paste0("type_", 1:2)))
          out
            attr type_1 type_2
          1    1    foo    bar
          2   30    foo  bar_2
          3    4    foo    bar
          4    6    foo  bar_2
          

          它使用strsplit 分解变量,并使用data.framedo.call/rbind 将数据放回data.frame。额外的增量改进是使用 setNames 将变量名称添加到 data.frame。

          【讨论】:

            【解决方案11】:

            从 R 版本 3.4.0 开始,您可以使用 utils 包(包含在基本 R 安装中)中的 strcapture(),将输出绑定到其他列。

            out <- strcapture(
                "(.*)_and_(.*)",
                as.character(before$type),
                data.frame(type_1 = character(), type_2 = character())
            )
            
            cbind(before["attr"], out)
            #   attr type_1 type_2
            # 1    1    foo    bar
            # 2   30    foo  bar_2
            # 3    4    foo    bar
            # 4    6    foo  bar_2
            

            【讨论】:

              【解决方案12】:

              这个问题很老了,但我会添加我认为目前最简单的解决方案。

              library(reshape2)
              before = data.frame(attr = c(1,30,4,6), type=c('foo_and_bar','foo_and_bar_2'))
              newColNames <- c("type1", "type2")
              newCols <- colsplit(before$type, "_and_", newColNames)
              after <- cbind(before, newCols)
              after$type <- NULL
              after
              

              【讨论】:

              • 这是迄今为止管理 df 向量最简单的方法
              【解决方案13】:

              基本但可能很慢:

              n <- 1
              for(i in strsplit(as.character(before$type),'_and_')){
                   before[n, 'type_1'] <- i[[1]]
                   before[n, 'type_2'] <- i[[2]]
                   n <- n + 1
              }
              
              ##   attr          type type_1 type_2
              ## 1    1   foo_and_bar    foo    bar
              ## 2   30 foo_and_bar_2    foo  bar_2
              ## 3    4   foo_and_bar    foo    bar
              ## 4    6 foo_and_bar_2    foo  bar_2
              

              【讨论】:

                【解决方案14】:

                如果您想坚持使用strsplit(),另一种方法是使用unlist() 命令。这里有一个解决方案。

                tmp <- matrix(unlist(strsplit(as.character(before$type), '_and_')), ncol=2,
                   byrow=TRUE)
                after <- cbind(before$attr, as.data.frame(tmp))
                names(after) <- c("attr", "type_1", "type_2")
                

                【讨论】:

                  【解决方案15】:

                  这是另一种基本的 R 解决方案。我们可以使用read.table,但由于它只接受一个字节的sep 参数并且这里我们有多字节分隔符,我们可以使用gsub 将多字节分隔符替换为任何一字节分隔符并将其用作sep read.table中的参数

                  cbind(before[1], read.table(text = gsub('_and_', '\t', before$type), 
                                   sep = "\t", col.names = paste0("type_", 1:2)))
                  
                  #  attr type_1 type_2
                  #1    1    foo    bar
                  #2   30    foo  bar_2
                  #3    4    foo    bar
                  #4    6    foo  bar_2
                  

                  在这种情况下,我们还可以通过将其替换为默认的 sep 参数来缩短它,这样我们就不必明确提及它

                  cbind(before[1], read.table(text = gsub('_and_', ' ', before$type), 
                                   col.names = paste0("type_", 1:2)))
                  

                  【讨论】:

                    【解决方案16】:

                    令人惊讶的是,仍然缺少另一个 tidyverse 解决方案 - 您也可以使用 tidyr::extract 和正则表达式。

                    library(tidyr)
                    before <- data.frame(attr = c(1, 30, 4, 6), type = c("foo_and_bar", "foo_and_bar_2"))
                    
                    ## regex - getting all characters except an underscore till the first underscore, 
                    ## inspired by Akrun https://stackoverflow.com/a/49752920/7941188 
                    
                    extract(before, col = type, into = paste0("type", 1:2), regex = "(^[^_]*)_(.*)")
                    #>   attr type1     type2
                    #> 1    1   foo   and_bar
                    #> 2   30   foo and_bar_2
                    #> 3    4   foo   and_bar
                    #> 4    6   foo and_bar_2
                    

                    【讨论】:

                      猜你喜欢
                      • 2013-01-22
                      相关资源
                      最近更新 更多