【问题标题】:Can I reasonably split these number strings?我可以合理地拆分这些数字字符串吗?
【发布时间】:2017-07-30 14:56:47
【问题描述】:

我有一堆这样的字符串:

x  <-  c("4/757.1%", "0/10%", "6/1060%", "0/0-%", "11/2055%")

它们是分数和所述分数的百分比值,不知何故在某个地方混合在一起。因此,示例中第一个数字的含义是 4 out of 7 是 57.1%。我可以很容易地得到 /(例如,stringr::word(x, 1, sep = "/"))之前的第一个数字,但第二个数字可以是一个或两个字符长,所以我很难想办法做到这一点。我不需要 % 值,因为一旦我得到数字就很容易重新计算。

任何人都可以看到这样做的方法吗?

【问题讨论】:

    标签: r regex string stringr


    【解决方案1】:

    一种看起来像你想要做的丑陋的解决方案:

    x  <-  c("4/757.1%", "0/10%", "6/1060%", "0/0-%", "11/2055%")
    
    split_perc <- function(x,signif_digits=1){
      x = gsub("%","",x)
      if(grepl("-",x)) return(list(NA,NA))
      index1 = gregexpr("/",x)[[1]][1]+1
      index2 = gregexpr("\\.",x)[[1]][1]-2
      if(index2==-3){index2=nchar(x)-1}
    
      found=FALSE
      indices = seq(index1,index2)
      k=1
      while(!found & k<=length(indices))
      {
        str1 =substr(x,1,indices[k])
        num1=as.numeric(strsplit(str1,"/")[[1]][1])
        num2 = as.numeric(strsplit(str1,"/")[[1]][2])
        value1 = round(num1/num2*100,signif_digits)
        value2 = round(as.numeric(substr(x,indices[k]+1,nchar(x))),signif_digits)
        if(value1==value2)
        {found=TRUE}
        else
        {k=k+1}
      }
      if(found)
        return(list(num1,num2))
      else
        return(list(NA,NA))
    }
    
    do.call(rbind,lapply(x,split_perc))
    

    输出:

         [,1] [,2]
    [1,] 4    7   
    [2,] 0    1   
    [3,] 6    10  
    [4,] NA   NA  
    [5,] 11   20  
    

    还有几个例子:

    y = c("11/2055.003%","11/2055.2%","40/7057.1%")
    do.call(rbind,lapply(y,split_perc))
    
         [,1] [,2]
    [1,] 11   20   # default significant digits is 1, so match found.
    [2,] NA   NA   # no match found since 55.1!=55.2
    [3,] 40   70  
    

    【讨论】:

    • 奇怪的是,一个月后,我发现了一个错误——“11/11100%”似乎有困难,应该是 11 和 11,但这个函数返回 11 和 1。我的错因为一开始没有给出任何 100% 的例子。但到目前为止,围绕它的所有其他案例都运行良好 - 10 和 10、10 和 11、11 和 12、12 和 12。
    【解决方案2】:

    正如您所指出的,一旦您有了分数,就可以重新计算百分比。你能利用这个事实来确定分裂应该在哪里吗?

    GuessSplit <- function(string) {
    
      tolerance <- 0.001 #How close should the fraction be?
      numerator <- as.numeric(word(string, 1, sep = "/"))
      second.half <-word(string, 2, sep = "/")
      second.half <- strsplit(second.half, '')[[1]]
    
      # assuming they all end in percent signs
      possibilities <- length(second.half) - 1
    
      for (position in 1:possibilities) {
    
        denom.guess <- as.numeric(paste0(second.half[1:position], collapse=''))
        percent.guess <- as.numeric(paste0(second.half[(position+1):possibilities], collapse='')) / 100
    
        value <- numerator / denom.guess
    
        if (abs(value - percent.guess) < tolerance) {
    
          return(list(numerator=numerator, denominator=denom.guess))
    
        }
      }
    }
    

    这需要一点爱心来处理更奇怪的情况,如果它无法在可能性中找到答案,可能会更优雅。我也不确定哪种返回类型最好。也许你只想要分母,因为分子很容易得到,但我认为两者都有的列表是最通用的。我希望这是一个合理的开始?

    【讨论】:

      【解决方案3】:

      来自tidyversestringr 的解决方案。我们可以定义一个函数来拆分第二个数字的所有可能位置并计算百分比以查看哪个有意义。 df2 是一个显示最佳分割位置的数据框,你想要的数字在V3 列中。

      library(tidyverse)
      library(stringr)
      
      x <- c("4/757.1%", "0/10%", "6/1060%", "0/0-%", "11/2055%")
      
      dt <- str_split_fixed(x, pattern = "/", n = 2) %>%
        as_data_frame() %>%
        mutate(ID = 1:n()) %>%
        select(ID, V1, V2)
      
      # Design a function to spit the second column based on position
      split_df <- function(position, dt){
        dt_temp <- dt %>%
          mutate(V3 = str_sub(V2, 1, position)) %>%
          mutate(V4 =  str_sub(V2, position + 1)) %>%
          mutate(Pos = position)
      
        return(dt_temp)
      }
      
      # Process the data
      dt2 <- map_df(1:3, split_df, dt = dt) %>%
        # Remove % in V4
        mutate(V4 = str_replace(V4, "%", "")) %>%
        # Convert V1, V3 and V4 to numeric
        mutate_at(vars(V1, V3, V4), funs(as.numeric)) %>%
        # Calculate possible percentage
        mutate(V5 = V1/V3 * 100) %>%
        # Calculate the difference between V4 and V5
        mutate(V6 = abs(V4 - V5)) %>%
        # Select the smallest difference based on V6 for each group
        group_by(ID) %>%
        arrange(ID, V6) %>%
        slice(1)
      
      # The best split is now in V3
      dt2$V3
      [1]  7  1 10  0 20
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-13
        相关资源
        最近更新 更多