【问题标题】:How to delimit a string field into two different numeric columns in R如何在R中将字符串字段分隔为两个不同的数字列
【发布时间】:2018-08-25 16:39:00
【问题描述】:

我有一个数据框,其中有一个文本字段,用于记录一个人在城市中停留的时间。它的格式为y year(s) m month(s),其中y 和m 为数字。如果此人在城市居住不到一年,则该值将仅采用 m months 格式

我想将此列转换为两个单独的数字列,其中一个显示生活的年数,另一个显示生活的月份。

这是我的数据框示例:

df <- structure(list(Time.in.current.role = c("1 year 1 month", "11 
months", 
"3 years 11 months", "1 year 1 month", "8 months"), City = 
c("Philadelphia", 
"Seattle", "Washington D.C.", "Ashburn", "Cork, Ireland")), .Names = 
c("Time.in.current.role", 
"City"), row.names = c(NA, 5L), class = "data.frame")

我想要的数据框看起来像:

result <- structure(list(Year = c(1, 0, 3, 1, 0), Month = c(1, 11, 
11, 
1, 8), City = structure(c(3L, 4L, 5L, 1L, 2L), .Label = c("Ashburn", 
"Cork, Ireland", "Philadelphia", "Seattle", "Washington D.C."
), class = "factor")), .Names = c("Year", "Month", "City"), row.names 
= c(NA, 
-5L), class = "data.frame")

我正在考虑使用 grep 来定位哪些行中包含子字符串“year”,哪些行中包含子字符串“month”。但在那之后,我在尝试获取与“年”或“月”适当关联的数字时遇到了麻烦。

* 编辑 * 在我原来的帖子中,我忘了说明可能只有y year(s) 的情况。这是新的原始数据框和所需的数据框:

df <- structure(list(Time.in.current.role = c("1 year 1 month", "11 
months", 
"3 years 11 months", "1 year 1 month", "8 months", "2 years"), 
City = c("Philadelphia", "Seattle", "Washington D.C.", "Ashburn", 
"Cork, Ireland", "Washington D.C.")), .Names = 
c("Time.in.current.role", 
"City"), row.names = c(1L, 2L, 3L, 4L, 5L, 18L), class = 
"data.frame")

result <- structure(list(Year = c(1, 0, 3, 1, 0, 2), Month = c(1, 11, 
11, 
1, 8, 0), City = structure(c(3L, 4L, 5L, 1L, 2L, 5L), .Label = 
c("Ashburn", 
"Cork, Ireland", "Philadelphia", "Seattle", "Washington D.C."
), class = "factor")), .Names = c("Year", "Month", "City"), row.names 
= c(NA, 
-6L), class = "data.frame")

【问题讨论】:

    标签: r regex substring delimiter


    【解决方案1】:

    这定义了一个函数extr(另见末尾的替代定义),它将从其第一个参数中提取与第二个参数的捕获组的匹配,即与括号内正则表达式部分的匹配。然后将匹配转换为数字,或者如果找不到模式,则返回 0。

    它只有 3 行代码,在处理年和月方面具有令人愉悦的对称性,不仅可以处理年和月,还可以处理年和月。它允许在 y 和 m 之前出现垃圾,例如问题中示例数据中显示的 \n。

    library(gsubfn)
    
    extr <- function(x, pat) strapply(x, pat, as.numeric, empty = 0, simplify = TRUE)
    transform(df, Year = extr(Time.in.current.role, "(\\d+) +\\W*y"),
                  Month = extr(Time.in.current.role, "(\\d+) +\\W*m"))
    

    给(针对问题中定义的数据框):

      Time.in.current.role            City Year Month
    1       1 year 1 month    Philadelphia    1     1
    2          11 \nmonths         Seattle    0    11
    3    3 years 11 months Washington D.C.    3    11
    4       1 year 1 month         Ashburn    1     1
    5             8 months   Cork, Ireland    0     8
    

    请注意strapply 默认使用 tcl 正则表达式引擎,但如果 tcltk 在您的系统上不起作用,则使用这个稍长的extr 版本,或者更好的是修复您的安装,因为 tcltk 是一个基本包,并且如果这不起作用,则您的 R 安装已损坏。

    extr <- function(x, pat) {
      sapply(strapply(x, pat, as.numeric), function(x) if (is.null(x)) 0 else x)
    }
    

    【讨论】:

    • 运行此解决方案时似乎出现以下错误:Error in (function (..., row.names = NULL, check.rows = FALSE, check.names = TRUE, : arguments imply differing number of rows: 1, 0
    • 我无法通过问题中的输入重现这一点。你使用了什么输入法?
    • 我正在使用示例 df 运行它。我认为这可能是加载 gsubfn 包的问题,​​因为当我安装它时,我收到一条警告,它无法加载共享对象/库
    • 您的 R 安装可能有问题,您缺少 tcl/tk。在这种情况下,请使用末尾显示的备用 extr(或者更好的是修复您的 R 安装)。
    【解决方案2】:

    一个快速的'n脏解决方案:

    代码:

    ym <- gsub("[^0-9|^ ]", "", df$Time.in.current.role)
    ym <- gsub("^ | $", "", ym)
    df$Year <- ifelse(
      grepl(" ", ym), 
      gsub("([0-9]+) .+", "\\1", ym), 
      0
    )
    df$Month <- gsub(".+ ([0-9]+)$", "\\1", ym)
    df$Time.in.current.role <- NULL
    df
    
                 City Year Month
    1    Philadelphia    1     1
    2         Seattle    0    11
    3 Washington D.C.    3    11
    4         Ashburn    1     1
    5   Cork, Ireland    0     8
    

    单词

    • 首先删除不是数字或空格的所有内容
    • 删除字符串开头或结尾的所有空格
    • 如果字符串包含两个数字先提取为年份,否则year = 0
    • 最后一个数字始终是月份。
    • 从 data.frame 中删除原始列
    • 享受

    【讨论】:

      【解决方案3】:

      您可以执行以下操作:

      z = regmatches(x = df$Time.in.current.role, gregexpr("\\d+", df$Time.in.current.role))
      years = sapply(z, function(x){ifelse(length(x)==1, 0, x[1])})
      months = sapply(z, function(x){ifelse(length(x)==1, x[1], x[2])})
      

      这给出了:

      > years
      [1] "1" "0" "3" "1" "0"
      > months
      [1] "1"  "11" "11" "1"  "8" 
      

      如果有或两个数字,此方法有效。如果只有一个,则假定它对应于月份。这不起作用的情况是,例如,"5 years"

      在这种情况下,您可以执行以下操作:

      m = regmatches(x = df$Time.in.current.role, gregexpr("\\d+ m", df$Time.in.current.role))
      y = regmatches(x = df$Time.in.current.role, gregexpr("\\d+ y", df$Time.in.current.role))
      y2 = sapply(y, function(x){ifelse(length(x)==0,0,gsub("\\D+","",x))})
      m2 = sapply(m, function(x){ifelse(length(x)==0,0,gsub("\\D+","",x))})
      

      例子:

      > df
        Time.in.current.role            City
      1       1 year 1 month    Philadelphia
      2            11 months         Seattle
      3    3 years 11 months Washington D.C.
      4       1 year 1 month         Ashburn
      5             8 months   Cork, Ireland
      6              5 years           Miami
      
      > y2
      [1] "1" "0" "3" "1" "0" "5"
      > m2
      [1] "1"  "11" "11" "1"  "8"  "0" 
      

      【讨论】:

      • 是的,我没有考虑只有年没有月的情况,但我的数据集中确实存在该数据。感谢您注意到这一点!
      【解决方案4】:

      另一种方法是使用包splitstackshape 将列一分为二。为此,您首先需要使用 gsub 在年份和月份之间设置分隔符,然后删除所有字符,然后使用 cSplit

      # replace delimiter year with ;
      df$Time.in.current.role <- gsub("year", ";", df$Time.in.current.role)
      
      # If no year was found add 0; at the beginning of the cell
      df$Time.in.current.role[!grepl(";", df$Time.in.current.role)] <- paste0("0;", df$Time.in.current.role[!grepl(";", df$Time.in.current.role)])
      
      # remove characters and whitespace
      df$Time.in.current.role <- gsub("[[:alpha:]]|\\s+", "", df$Time.in.current.role)
      
      # Split column by ;
      df <- splitstackshape::cSplit(df, "Time.in.current.role", sep = ";")
      
      # Rename new columns
      colnames(df)[2:3] <- c("Year", "Month")
      
      df
                    City  Year  Month
      1:    Philadelphia     1      1
      2:         Seattle     0     11
      3: Washington D.C.     3     11
      4:         Ashburn     1      1
      5:   Cork, Ireland     0      8
      

      【讨论】:

        猜你喜欢
        • 2014-11-18
        • 1970-01-01
        • 2016-04-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多