【问题标题】:How to split a string into multiple columns?如何将一个字符串拆分为多列?
【发布时间】:2021-03-28 18:52:07
【问题描述】:

我有一个如下所示的字符串:

# character string
string <- "lambs:    cows: 281        chickens: 20   goats: 3     trees: 13"

我想创建一个如下所示的数据框:

# structure
lambs <- NA
cows <- 281
chickens <- 20
goats <- 3
trees <- 13

# dataframe
df <- 
  cbind(lambs, cows, chickens, goats, trees)  %>% 
  as.data.frame()

这是我迄今为止尝试过的:

# split string
test <- strsplit(string, " ")
test

数据很不干净,所以间距并不总是一致的,有时有羔羊,有时没有羔羊(如:"lamb: 5 cow: 50""lamb: cow: 40"。使用 tidyverse 最简单的方法是什么? ?

【问题讨论】:

  • 它是一个固定宽度的文件吗?数据最初是如何变得如此混乱的?您需要对数据做出一些强有力的假设才能导入它。但是只有一个示例行,很难判断会发生什么。
  • 另一个:read.table(text = gsub('\\b(?=[A-z])', '\n', string, perl = TRUE), sep = ':')

标签: r string dplyr split tidyverse


【解决方案1】:

试试这个:

gre <- gregexpr("\\b([A-Za-z]+:\\s*[0-9]*)\\b", string)
regmatches(string, gre)
# [[1]]
# [1] "lambs:    "   "cows: 281"    "chickens: 20" "goats: 3"     "trees: 13"   
lapply(regmatches(string, gre), strcapture, pattern = "(.*):(.*)", proto = list(anim = character(0), n = character(0)))
# [[1]]
#       anim    n
# 1    lambs     
# 2     cows  281
# 3 chickens   20
# 4    goats    3
# 5    trees   13
frames <- lapply(regmatches(string, gre), strcapture,
                 pattern = "(.*):(.*)", proto = list(anim = character(0), n = character(0)))

如果您有多个字符串(而不仅仅是一个),那么这可以确保处理每个字符串,然后合并所有数据。

alldat <- do.call(rbind, frames)
alldat$n <- as.integer(alldat$n)
alldat
#       anim   n
# 1    lambs  NA
# 2     cows 281
# 3 chickens  20
# 4    goats   3
# 5    trees  13

如果您确实需要“宽”格式的数据,那么

do.call(rbind, lapply(frames, function(z) do.call(data.frame, setNames(as.list(as.integer(z$n)), z$anim))))
#   lambs cows chickens goats trees
# 1    NA  281       20     3    13

【讨论】:

    【解决方案2】:

    您可以使用str_match_all 并传递模式进行提取。

    tmp <- stringr::str_match_all(string, '\\s*(.*?):\\s*(\\d+)?')[[1]][, -1]
    data <- type.convert(data.frame(tmp), as.is = TRUE)
    
    #        X1  X2
    #1    lambs  NA
    #2     cows 281
    #3 chickens  20
    #4    goats   3
    #5    trees  13
    

    这会将数据分成两列,其中第一列是冒号 (:) 之前的所有内容,除了空格,第二列是后面的数字。我已将数字部分设为可选,以适应 'lambs' 这样没有数字的情况。

    【讨论】:

    • 一如既往的优秀方法。您可以在一本精彩的书中轻松转换所有解决方案。感谢分享
    • 谢谢!我只是用真实数据尝试过,但忘了提到一些数字有逗号(5,000 或 13,14,00)等。有没有办法解决这个问题?我注意到代码将其截断为逗号前的第一个数字。
    • 我认为在不更改大部分代码的情况下,一个简单的修复方法是使用 gsub 删除逗号,即 stringr::str_match_all(gsub(',', '', string), '\\s*(.*?):\\s*(\\d+)?')[[1]][, -1]
    • 太棒了,非常感谢!还刚刚注意到一些数字有一个空格,例如“5, 23,336” 您是否也知道一个简单的解决方法? (nvm,通过在模式参数中列出并取消列出来解决这个问题)
    • @KarthikS 是的,这是正确的,因为我们多次出现相同的模式。
    【解决方案3】:

    你可以试试read.table。 “没有羔羊”的问题可以通过在gsub 中输入一个零来解决。

    r <- na.omit(unlist(read.table(text=gsub(": ", " 0", string), sep=" ")))
    r <- replace(r, r == 0, NA)
    
    ## long format
    type.convert(as.data.frame(matrix(r, ncol=2, byrow=TRUE)), as.is=TRUE)
    #         V1  V2
    # 1    lambs  NA
    # 2     cows 281
    # 3 chickens  20
    # 4    goats   3
    # 5    trees  13
    
    ## wide format
    setNames(type.convert(r[seq(r) %% 2 == 0]), r[seq(r) %% 2 == 1])
    # lambs     cows chickens    goats    trees 
    #    NA      281       20        3       13 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-30
      相关资源
      最近更新 更多