【问题标题】:Convert text within string to numeric将字符串中的文本转换为数字
【发布时间】:2017-12-29 21:24:46
【问题描述】:

我正在努力从文本字符串中创建一个新变量。这是我的数据框示例:

  Brand     Pack_Content
1 Dove      4X25 G
2 Snickers  250 G
3 Twix      2X20.7 G
4 Korkunov  BULK

我想创建一个名为 Grams 的数值变量。我已经尝试过使用 gsub 或单独的解决方案,但是需要逐行提供不同的解决方案(即,有些需要将品牌包与多个包(即 4X25 G)相乘)让我很难过。首选使用 dplyr 的解决方案。

  Brand     Pack_Content    Grams
1 Dove      4X25 G          100
2 Snickers  250 G           250
3 Twix      2X20.7 G        41.4
4 Korkunov  BULK            1000

【问题讨论】:

  • 我认为没有灵丹妙药可以解决您的问题。我会考虑构建一个函数,它结合了if 语句、gsubstrsplit,然后在Pack_Content 表变量上使用sapply。您将需要处理“BULK”的条件和在“X”上拆分的列表。这假定您提供的示例包含在 Pack_Content 中找到的所有元素版本。
  • 您可以使用像这样的正则表达式来提取数字,然后使用前面的注释构建一个满足您需要的函数:^\s*([0-9]*\.[0-9]+|[0-9]+)(?:\s*[xX]\s*([0-9]*\.[0-9]+|[0-9]+))?(?:\s*G)?\s*$ 一如既往,测试我们的常规regex101.com 的表达式以及一些测试数据,以确保您的正则表达式按预期工作。此外,您需要转义 R 字符串文字中的斜杠...

标签: r string text dplyr


【解决方案1】:

使用 的解决方案。关键是在使用separate 分隔Pack_Content_new 列之前,将所有字符串,如“G”或“BULK”替换为“”或有意义的数字。如果您有多个有意义的字符串,例如“BULK”,除了recode,您可能还想使用case_when。在separate 函数之后,我们可以将Number 列中的NA 替换为1。最后,我们可以根据NumberUnit_Weight 中的数字计算Grams

library(dplyr)
library(tidyr)

dat2 <- dat %>%
  mutate(Pack_Content_new = sub("G$", "", Pack_Content)) %>%       # Remove the last G
  mutate(Pack_Content_new = recode(Pack_Content_new,               # Replace BULK with 1000
                                   `BULK` = "1000")) %>% 
  separate(Pack_Content_new, into = c("Number", "Unit_Weight"),    # Separate the Pack_Content_new column
           sep = "X", convert = TRUE, 
           fill = "left") %>%
  replace_na(list(Number = 1)) %>%                                 # Replace NA in Number with 1
  mutate(Grams = Number * Unit_Weight)                             # Calculate the Grams
dat2
#      Brand Pack_Content Number Unit_Weight  Grams
# 1     Dove       4X25 G      4        25.0  100.0
# 2 Snickers        250 G      1       250.0  250.0
# 3     Twix     2X20.7 G      2        20.7   41.4
# 4 Korkunov         BULK      1      1000.0 1000.0

数据

dat <- read.table(text = "  Brand     Pack_Content
1 Dove      '4X25 G'
                  2 Snickers  '250 G'
                  3 Twix      '2X20.7 G'
                  4 Korkunov  'BULK'",
                  header = TRUE, stringsAsFactors = FALSE)

【讨论】:

    【解决方案2】:

    更新:添加了一些单位提取和转换,只是为了它的见鬼

    更新 2:加入一些验证步骤(如果没有其他人,供我自己参考),这可能应该是原始答案的一部分。一般来说,如果您使用正则表达式来提取值(并且您没有时间详细查看每一行输出),当出现一些未考虑的极端情况输入格式时,很容易被烧毁

    使用data.table,stringi,以及正则表达式的甜美魔力:

    此处关于工具选择的说明:

    由于正则表达式很难单独遵循,我认为专注于使转换步骤可读和明确定义而不是试图将其全部塞进一系列管道和几行代码是一个更安全的选择可能。

    由于dplyr 不允许在每次表达式后不重新分配小标题的情况下进行逐步操作(无管道),我觉得data.table 对于这种数据处理工作来说是更加优雅和高效的工具。

    创建数据

    library(data.table)
    library(stringi)
    
    DT <- data.table(Brand = c("Dove","Snickers","Twix","Korkunov","Reeses","M&M's"),
                     Pack = c("4X25 G","0.250 KG","2X20.7 G","BULK","2.5.5X4G","2 X 3 X 3G"))
    

    预清洁

    首先,我们将去掉空格并将所有内容变为大写

    ## Strip out Spaces
    DT[,Pack := gsub("[[:space:]]+","",Pack)]
    ## Make everything Uppercase
    DT[,Pack := toupper(Pack)]
    

    假设验证

    在我们使用正则表达式提取值并对它们进行一些数学运算之前,谨慎的做法可能是执行一些验证步骤,以确保我们不会被意外的极端情况烧毁。

    ## Start off by trusting nothing
    DT[,Valid := FALSE]
    ## Mark Packs that fit formats like "BULK" as valid
    DT[Pack %in% c("BULK"),Valid := TRUE]
    ## Mark Packs that fit formats like "4X20G" or "3.0X3KG" as valid
    DT[stri_detect_regex(Pack,"^([[:digit:]]+\\.){0,1}[[:digit:]]+X([[:digit:]]+\\.){0,1}[[:digit:]]+(G|KG)$"),
       Valid := TRUE]
    ## Mark Packs that fit formats like "250G" as valid
    DT[stri_detect_regex(Pack,"^([[:digit:]]+\\.){0,1}[[:digit:]]+(G|KG)$"),
       Valid := TRUE]
    
    print(DT)
    

    此时:

          Brand     Pack Valid
    1:     Dove    4X25G  TRUE
    2: Snickers  0.250KG  TRUE
    3:     Twix  2X20.7G  TRUE
    4: Korkunov     BULK  TRUE
    5:   Reeses 2.5.5X4G FALSE
    6:    M&M's   2X3X3G FALSE
    

    提取值

    请注意,我们只为满足有效格式的预定义期望的行填充值。

    ## Extract the first number at the beginning of the "Pack" column followed by an X
    DT[Valid == TRUE, Quantity := as.numeric(stri_extract_first_regex(Pack,"^([[:digit:]]+\\.){0,1}[[:digit:]]+(?=X)"))]
    ## Extract last number out of the "Pack" column
    DT[Valid == TRUE, Unit_Weight := as.numeric(stri_extract_last_regex(Pack,"([[:digit:]]+\\.){0,1}[[:digit:]]+"))]
    ## Extract the Units
    DT[Valid == TRUE, Units := stri_extract_last_regex(Pack,"[[:alpha:]]+$")]
    
    print(DT)
    

    现在我们得到了以下内容:

          Brand     Pack Valid Quantity Unit_Weight Units
    1:     Dove    4X25G  TRUE        4       25.00     G
    2: Snickers  0.250KG  TRUE       NA        0.25    KG
    3:     Twix  2X20.7G  TRUE        2       20.70     G
    4: Korkunov     BULK  TRUE       NA          NA  BULK
    5:   Reeses 2.5.5X4G FALSE       NA          NA    NA
    6:    M&M's   2X3X3G FALSE       NA          NA    NA
    

    换算单位,填写NA,计算权重

    现在我们只需返回并填写没有重量或数量的行,可选择转换单位等,以便我们计算重量。

    ## Start with a standard conversion factor of 1
    DT[Valid == TRUE, Unit_Factor := 1]
    ## Make some Unit Conversions
    DT[Units == "KG", Unit_Factor := 1000]
    ## Fill in Rows without a quantity with a value of 1
    DT[Valid == TRUE & is.na(Quantity), Quantity := 1]
    ## Fill in a weight for Bulk units
    DT[Pack == "BULK", `:=` (Unit_Weight = 1000, Units = "G")]
    ## And finally, calculate Weight in grams
    DT[Valid == TRUE, Grams := Unit_Weight*Quantity*Unit_Factor]
    
    print(DT)
    

    产生最终结果:

          Brand     Pack Valid Quantity Unit_Weight Units Unit_Factor  Grams
    1:     Dove    4X25G  TRUE        4       25.00     G           1  100.0
    2: Snickers  0.250KG  TRUE        1        0.25    KG        1000  250.0
    3:     Twix  2X20.7G  TRUE        2       20.70     G           1   41.4
    4: Korkunov     BULK  TRUE        1     1000.00     G           1 1000.0
    5:   Reeses 2.5.5X4G FALSE       NA          NA    NA          NA     NA
    6:    M&M's   2X3X3G FALSE       NA          NA    NA          NA     NA
    

    (所有步骤,精简)

    library(data.table)
    library(stringi)
    
    DT <- data.table(Brand = c("Dove","Snickers","Twix","Korkunov","Reeses","M&M's"),
                     Pack = c("4X25 G","0.250 KG","2X20.7 G","BULK","2.5.5X4G","2 X 3 X 3G"))
    
    DT[,Pack := gsub("[[:space:]]+","",Pack)]
    DT[,Pack := toupper(Pack)]
    DT[,Valid := FALSE]
    DT[Pack %in% c("BULK"),Valid := TRUE]
    DT[stri_detect_regex(Pack,"^([[:digit:]]+\\.){0,1}[[:digit:]]+X([[:digit:]]+\\.){0,1}[[:digit:]]+(G|KG)$"), Valid := TRUE]
    DT[stri_detect_regex(Pack,"^([[:digit:]]+\\.){0,1}[[:digit:]]+(G|KG)$"), Valid := TRUE]
    DT[Valid == TRUE, Quantity := as.numeric(stri_extract_first_regex(Pack,"^([[:digit:]]+\\.){0,1}[[:digit:]]+(?=X)"))]
    DT[Valid == TRUE, Unit_Weight := as.numeric(stri_extract_last_regex(Pack,"([[:digit:]]+\\.){0,1}[[:digit:]]+"))]
    DT[Valid == TRUE, Units := stri_extract_last_regex(Pack,"[[:alpha:]]+$")]
    DT[Valid == TRUE, Unit_Factor := 1]
    DT[Units == "KG", Unit_Factor := 1000]
    DT[Valid == TRUE & is.na(Quantity), Quantity := 1]
    DT[Pack == "BULK", `:=` (Unit_Weight = 1000, Units = "G")]
    DT[Valid == TRUE, Grams := Unit_Weight*Quantity*Unit_Factor]
    

    最后一点:

    我假设您没有包含所有关于原始数据分布的混乱、肮脏的细节,因此您可能需要添加更多步骤来捕获磅而不是克的情况(以及所有其他极端情况)。

    不过,使用 5-7 个正则表达式,我认为您可能至少能够涵盖相当多的潜在案例。

    我大部分时间都将this Regex cheatsheet on RStudio's website 放在怀里。

    相关的 XKCD:

    【讨论】:

      【解决方案3】:

      我知道您需要 plyr 解决方案。你试过Base R的所有方法吗?好吧,这里只是一个小问题。希望这会有所帮助,即使它不是 plyr 方法。

      首先,您需要保留数字并将X 替换为*。这是通过使用sub 函数来完成的。我们也将不包含数字的替换为1000。然后我们只是评估得到的内容:

      A=sub("X","*",sub("\\s.*","",dat$Pack_Content))
      transform(dat,Grams=sapply(parse(text=replace(A,-grep("\\d",A),1000)),eval))
           Brand Pack_Content  Grams
      1     Dove       4X25 G  100.0
      2 Snickers        250 G  250.0
      3     Twix     2X20.7 G   41.4
      4 Korkunov         BULK 1000.0
      

      使用的数据:

      dat=structure(list(Brand = c("Dove", "Snickers", "Twix", "Korkunov"
      ), Pack_Content = c("4X25 G", "250 G", "2X20.7 G", "BULK")), .Names = c("Brand", 
      "Pack_Content"), class = "data.frame", row.names = c("1", "2", 
      "3", "4"))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-01-08
        • 2011-02-03
        • 2013-11-08
        • 2022-12-19
        • 1970-01-01
        • 1970-01-01
        • 2021-04-02
        • 1970-01-01
        相关资源
        最近更新 更多