【问题标题】:Splitting a string into multiple columns (with specific order)将字符串拆分为多列(具有特定顺序)
【发布时间】:2020-12-23 02:24:12
【问题描述】:

我收到的数据格式不是很好(我无法在上游更改它们)。有一列需要重新排序,并根据某些关键字分成 10 多列。

这是我收到的数据示例 - 对于每个人,他们选择了一种 3 种不同的食物。他们对每种食物类别的选择(food1food2food3)紧跟在正文之后:

list1 <- c(' food1 pasta food2 apple food3 carrot ')
list2 <- c(' food2 banana food3 cucumber food1 brown rice ')
list3 <- c(' food3 bell pepper food2 plum food1 bread ')

foodListDF <- as.data.frame(matrix(c(1,2,3, list1, list2, list3), nrow = 3), stringsAsFactors = FALSE)
colnames(foodListDF) <- c('Person', 'Choices')
foodListDF


  Person                                        Choices
1      1          food1 pasta food2 apple food3 carrot 
2      2  food2 banana food3 cucumber food1 brown rice 
3      3      food3 bell pepper food2 plum food1 bread 

以上是我接收数据的格式。我的最终目标是将Choices 列拆分为 3 个单独的列,分别标记为 food1、food2 和 food3,这需要正确订购:

  Person      food1  food2       food3
1      1      pasta  apple      carrot
2      2 brown rice banana    cucumber
3      3      bread   plum bell pepper

我知道我可以通过以下方式拆分选择:

library(stringr)
as.data.frame(str_split_fixed(foodListDF$Choices, c(' food1 | food2 | food3 '), 4))[,2:4]
           V2       V3          V4
1       pasta    apple     carrot 
2      banana cucumber brown rice 
3 bell pepper     plum      bread 

但这显然不会将它们分成适当的组/顺序,这是非常必要的。

我真的只是在努力思考如何为每个人从适当的群体中提取正确的食物。有什么想法吗?

【问题讨论】:

    标签: r


    【解决方案1】:

    您可以分别提取食物编号和食物项目(t1t2),将它们连接在一起,unnest 数据并获得宽格式。

    library(dplyr)
    library(tidyr)
    
    foodListDF %>%
      mutate(food = stringr::str_extract_all(Choices, 'food\\d+')) %>%
      select(-Choices) -> t1
     
      
    foodListDF %>%
      separate_rows(Choices, sep = 'food\\d+') %>%
      filter(Choices != ' ') %>%
      mutate(Choices = trimws(Choices)) %>%
      group_by(Person) %>%
      summarise(col = list(Choices)) -> t2
    
    
    inner_join(t1, t2, by = 'Person') %>%
      unnest(c(food, col)) %>%
      pivot_wider(names_from = food, values_from = col)
    
    #  Person food1      food2  food3      
    #  <chr>  <chr>      <chr>  <chr>      
    #1 1      pasta      apple  carrot     
    #2 2      brown rice banana cucumber   
    #3 3      bread      plum   bell pepper
    

    【讨论】:

    • 谢谢!我想我遵循一般原则,但在最终的inner_join 中出现以下错误:Error in mutate_impl(.data, dots) : Column c(food, col)` must be length 3 (the number of rows) or one, not 6 `
    • 您是在共享的相同数据还是不同的数据集上使用它?
    • 刚刚打开了一个新的 RStudio 会话,从这篇文章中复制了生成 foodListDF 的数据,然后按照您的代码进行操作,我得到了同样的错误。
    • 我只是做了同样的事情,它对我有用。也许我们有包版本问题。 packageVersion('dplyr') #[1] ‘1.0.2’packageVersion('tidyr') #[1] ‘1.1.2’
    【解决方案2】:

    基础 R

    这里有两种基本的 R 方法,都涉及regmatchesgregexpr

    第一个使用unstack。结果是data.frame

    splitfun1 <- function(string) {
      mat <- gregexpr("food\\d+ ", string)
      unstack(
        list(l1 = unlist(lapply(regmatches(string, mat), trimws), use.names = FALSE),
             l2 = unlist(lapply(regmatches(string, mat, invert = TRUE),
                         function(x) trimws(x[-1])), use.names = FALSE)), 
             l2 ~ l1)
    }
    splitfun1(foodListDF$Choices)
    #        food1  food2       food3
    # 1      pasta  apple      carrot
    # 2 brown rice banana    cucumber
    # 3      bread   plum bell pepper
    

    第二个使用矩阵索引来填充一个空矩阵。它可能比第一种选择更有效。它会产生一个矩阵。

    splitfun2 <- function(string) {
      mat <- gregexpr("food\\d+ ", string)
      l1 <- lapply(regmatches(string, mat), trimws)
      l2 <- lapply(regmatches(string, mat, invert = TRUE), 
                   function(x) trimws(x[-1]))
      ul <- unlist(l1, use.names = FALSE)
      cn <- sort(unique(ul))
      out <- matrix(NA_character_, nrow = length(string), ncol = length(cn),
                    dimnames = list(seq_along(string), cn))
      out[cbind(rep(seq_along(string), lengths(l1)), ul)] <- unlist(l2, use.names = FALSE)
      out
    }
    splitfun2(foodListDF$Choices)
    #   food1        food2    food3        
    # 1 "pasta"      "apple"  "carrot"     
    # 2 "brown rice" "banana" "cucumber"   
    # 3 "bread"      "plum"   "bell pepper"
    

    当然,对于其中任何一个,您都需要 cbind 将结果与来自源 data.frame 的相关列一起发送。

    cbind(foodListDF[1], splitfun2(foodListDF$Choices))
    

    splitstackshape + data.table

    另一种选择是使用我的“splitstackshape”包中的cSplit 以及一些非常简单的gsub 工作,然后使用dcast 进入宽格式。

    library(splitstackshape)
    # library(data.table) # if required
    
    # Basic helper function
    fun <- function(string) {
      list(gsub("(food\\d+) (.*)", "\\1", string),
           gsub("(food\\d+) (.*)", "\\2", string))
    }
    
    cSplit(as.data.table(foodListDF)[, Choices := gsub(" food", ",food", trimws(Choices))], 
           "Choices", ",", "long")[, fun(Choices), Person][, dcast(.SD, Person ~ V1, value.var = "V2")]
    #    Person      food1  food2       food3
    # 1:      1      pasta  apple      carrot
    # 2:      2 brown rice banana    cucumber
    # 3:      3      bread   plum bell pepper
    

    dplyr + tidyr

    将上面改成“dplyr”+“tidyr”,可以试试:

    library(dplyr)
    library(tidyr)
    
    foodListDF %>%
      mutate(Choices = gsub(" food", ",food", trimws(Choices))) %>%
      separate_rows(Choices, sep = ",") %>%
      separate(Choices, c("var", "val"), extra = "merge") %>%
      pivot_wider(names_from = var, values_from = val)
    # # A tibble: 3 x 4
    #   Person food1      food2  food3      
    #   <chr>  <chr>      <chr>  <chr>      
    # 1 1      pasta      apple  carrot     
    # 2 2      brown rice banana cucumber   
    # 3 3      bread      plum   bell pepper
    

    【讨论】:

      【解决方案3】:

      在一个复杂的 Base R 表达式中:

      data.frame(cbind(Person = foodListDF$Person, 
          do.call("rbind", Map(function(x){y <- setNames(x[[2]], x[[1]]); y[order(x[[1]])]},
            lapply(strsplit(foodListDF$Choices, "\\s+"), function(x) {
            res <- data.frame(t(grep("food\\d+", x, value = TRUE)), stringsAsFactors = FALSE)
            res2 <- unlist(strsplit(gsub("^&&\\s*", "", 
                      paste0(Filter(function(y){y != ""}, Vectorize(gsub)("food\\d+", "&&", x)), 
                             collapse = " ")), "\\s*&&\\s*"))
            list(res, res2)
              }
            )
          )
        )
      ), stringsAsFactors = FALSE)
      

      【讨论】:

        【解决方案4】:

        您可以在strsplitsort 结果中使用food 作为分隔符,删除带有substring 的第一个字符并将结果返回到您的数据集。

        foodListDF[paste0("food",1:3)] <- t(sapply(strsplit(foodListDF$Choices, "food"),
         function (x) trimws(substring(sort(x[-1]), 2))))
        foodListDF[-2]
        #  Person      food1  food2       food3
        #1      1      pasta  apple      carrot
        #2      2 brown rice banana    cucumber
        #3      3      bread   plum bell pepper
        

        或者,如果不是所有的时间都存在所有级别:

        j <- sort(unique(unlist(regmatches(foodListDF$Choices, gregexpr("food\\d+",
         foodListDF$Choices)))))
        k <- sub("food", "", j)
        foodListDF[j] <- t(sapply(strsplit(foodListDF$Choices, "food"), function(x)
         trimws(sub("^\\d+", "", x[charmatch(k, x)]))))
        foodListDF[-2]
        #  Person      food1  food2       food3
        #1      1      pasta  apple      carrot
        #2      2 brown rice banana    cucumber
        #3      3      bread   plum bell pepper
        

        【讨论】:

          猜你喜欢
          • 2018-04-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多