【问题标题】:Extracting a date from a column and adding the year if missing in R从列中提取日期并在 R 中缺少年份时添加年份
【发布时间】:2021-12-31 05:09:44
【问题描述】:

我正在尝试从文本中提取日期并在数据集中创建一个新列。在 A1 列中以不同的格式输入日期(mm-dd-yy 或 mm-dd)。我需要找到一种方法来识别 A1 列中的日期,然后在缺少年份时添加年份。到目前为止,无论格式如何,我都能够提取日期;但是,当我在新列 A2 上使用 as.Date 时,mm-dd 格式的日期变为<NA>。我知道对于这种情况可能没有直接的解决方案,但是解决方法(可推广到更大的数据集)会很棒。这一年将从 2019 年 9 月到 2020 年 8 月。此外,我不确定为什么我在 as.Date 函数中使用的格式无法控制日期的显示方式。后一个问题并不那么重要,但我对 R 函数的行为感到惊讶。非常感谢 tidyverse 中的解决方案。

library(tidyverse)
library(stringr)
    
db <- data.frame(A1 = c("review 11/18", "begins 12/4/19", "3/5/20", NA, "deadline 09/5/19", "9/3")) 

db %>% mutate(A2 = str_extract(A1, "[0-9/0-9]+")) 
#                A1      A2
#1     review 11/18   11/18
#2   begins 12/4/19 12/4/19
#3           3/5/20  3/5/20
#4             <NA>    <NA>
#5 deadline 09/5/19 09/5/19
#6              9/3     9/3
    
db %>% mutate(A2 = str_extract(A1, "[0-9/0-9]+")) %>% 
       mutate(A2 = A2 %>% as.Date(., "%m/%d/%y"))

 #               A1         A2
 #   1     review 11/18       <NA>
 #   2   begins 12/4/19 2019-12-04
 #   3           3/5/20 2020-03-05
 #   4             <NA>       <NA>
 #   5 deadline 09/5/19 2019-09-05
 #   6              9/3       <NA>

【问题讨论】:

    标签: r date tidyverse stringr


    【解决方案1】:

    也许:

    library(tidyverse)
    
    db <- data.frame(A1 = c("review 11/18", "begins 12/4/19", "3/5/20", NA, "deadline 09/5/19", "9/3")) 
    
    #year from september to august 2019
    
    (db <- 
     db %>% 
      mutate(A2 = str_extract(A1, '[\\d\\d/]+'),
             A2 = if_else(str_count(A2, '/') == 1 & as.numeric(str_extract(A2, '\\d+')) > 8, paste0(A2, '/19'), A2),
             A2 = if_else(str_count(A2, '/') == 1 & as.numeric(str_extract(A2, '\\d+')) <= 8, paste0(A2, '/20'), A2),
             A2 = as.Date(A2, "%m/%d/%y")) )             
    #>                 A1         A2
    #> 1     review 11/18 2019-11-18
    #> 2   begins 12/4/19 2019-12-04
    #> 3           3/5/20 2020-03-05
    #> 4             <NA>       <NA>
    #> 5 deadline 09/5/19 2019-09-05
    #> 6              9/3 2019-09-03
    

    reprex package (v2.0.1) 于 2021 年 11 月 21 日创建

    【讨论】:

    • 非常好!没想到string_count()"/"。允许更短的表达式
    【解决方案2】:

    我喜欢 rematch2 包用于许多正则表达式场景。

    第一个模式尝试匹配完整的 m/d/y 值。第二种模式尝试匹配部分 m/d 值(此外,它将月份与日期分开,因此可以确定应该是 2019 年还是 2020 年)。

    一旦这些部分被分离出来,剩下的只是一系列小步骤。

    db |> 
      rematch2::bind_re_match(from = A1, "^.*?(?<mdy>\\d{1,2}/\\d{1,2}/\\d{2})$") |> 
      rematch2::bind_re_match(from = A1, "^.*?(?<md_m>\\d{1,2})/(?<md_d>\\d{1,2})$") |> 
      dplyr::mutate(
        md_m  = as.integer(md_m),
        md_y  = dplyr::if_else(9L <= md_m, "19", "20"), # It's 2019 if the month is Sept or later
        md    = sprintf("%i/%s/%s", md_m, md_d, md_y),  # Assemble components
        md    = as.Date(md , "%m/%d/%y"),               # Convert data type
        mdy   = as.Date(mdy, "%m/%d/%y"),               # Convert data type
        
        date = dplyr::coalesce(mdy, md),                # Prefer the mdy if it's not missing
      )
    

    输出:

                    A1        mdy md_m md_d md_y         md       date
    1     review 11/18       <NA>   11   18   19 2019-11-18 2019-11-18
    2   begins 12/4/19 2019-12-04    4   19   20 2020-04-19 2019-12-04
    3           3/5/20 2020-03-05    5   20   20 2020-05-20 2020-03-05
    4             <NA>       <NA>   NA <NA> <NA>       <NA>       <NA>
    5 deadline 09/5/19 2019-09-05    5   19   20 2020-05-19 2019-09-05
    6              9/3       <NA>    9    3   19 2019-09-03 2019-09-03
    

    【讨论】:

      【解决方案3】:

      嗯,这既不是一个漂亮、简洁或整洁的解决方案,但它确实有效,并且在模块化方面应该是灵活的。

      library(tidyverse)
      
      db <- data.frame(A1 = c("review 11/18", "begins 12/4/19", "3/5/20", NA, "deadline 09/5/19", "9/3")) 
      db <- db %>% mutate(A2 = str_extract(A1, "[0-9/0-9]+"), A2 = str_extract(A1, "[0-9/0-9]+"))
      
      test1 <- unlist(lapply(str_split(db$A2, "/", n = 3), function(x) length(x)))
      test2 <- lapply(str_split(db$A2, "/", n = 3), function(x) as.numeric(x))
      
      if(test1 == 2){
        if(test2[[1]] >= 9){
          db$A2 <- ifelse(test = between(nchar(db$A2), 3, 5) & !is.na(db$A2), yes = paste0(db$A2, "/19"), no = db$A2)
        }
        if(test2[[1]] < 9){
          db$A2 <- ifelse(test = between(nchar(db$A2), 3, 5) & !is.na(db$A2), yes = paste0(db$A2, "/20"), no = db$A2)
        }
      }
      
      db <- db %>% mutate(A2 = A2 %>% as.Date(., "%m/%d/%y"))
      db
      
                      A1         A2
      1     review 11/18 2019-11-18
      2   begins 12/4/19 2019-12-04
      3           3/5/20 2020-03-05
      4             <NA>       <NA>
      5 deadline 09/5/19 2019-09-05
      6              9/3 2019-09-03
      

      【讨论】:

        猜你喜欢
        • 2021-10-02
        • 1970-01-01
        • 2016-08-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-04
        相关资源
        最近更新 更多