【问题标题】:Using regular expressions to change element names for dataframes in a list使用正则表达式更改列表中数据框的元素名称
【发布时间】:2020-12-17 18:53:05
【问题描述】:

我有许多数据框的列表,我想在其中使用正则表达式更改数据框中的某些元素。这是我的数据的简化模型:

df1 <- data.frame(ID = c("KBS_2015_08_25_A1_P1", "KBS_2015_08_25_A2_P10", "KBS_2015_09_04_A2_P2"),
                  Site = c("KBS","KBS","KBS"))
df2 <- data.frame(ID = c("UMBS_2015_08_12_A1_P1", "UMBS_2015_08_29_D3_P3", "UMBS_2015_08_29_D5_P5"),
                  Site = c("UMBS","UMBS","UMBS"))
df_list <- list(df1=df1,df2=df2)

我试图创建一个函数,获取 ID 列中的信息并将其更改为日期的字符串。

change_id <- function(df){
  df$ID[df$ID == "^KBS_2015_08_25*P\\d"] <- "8/25/2015"
  df$ID[df$ID == "^KBS_2015_09_04*P\\d"] <- "9/4/2015"
  df$ID[df$ID == "^UMBS_2015_08_12*P\\d"] <- "8/12/2015"
  df$ID[df$ID == "^UMBS_2015_08_29*P\\d"] <- "8/29/2015"
  return(df)
}

df_list <- lapply(df_list, change_id)

我没有收到任何错误,但此函数不会更改数据帧中的任何内容。我在尝试字符匹配时一定遗漏了一些东西。

使用 R 版本 4.0.2,Mac OS X 10.13.6

【问题讨论】:

  • 你的正则表达式正确吗? 08_25*P 表示文字 08_2,然后是零个或多个 5s,然后是文字 P。

标签: r dataframe


【解决方案1】:

我们可以使用sub

lapply(df_list, transform, ID = sub(".*_(\\d{4}_\\d{2}_\\d{2})_.*", "\\1", ID))

如果需要特定格式,转换为Date类,然后使用format

df_list1 <- lapply(df_list, transform, 
    ID = format(as.Date(sub(".*_(\\d{4}_\\d{2}_\\d{2})_.*",
               "\\1", ID), "%Y_%m_%d"), "%m/%d/%Y"))

-输出

df_list1
#$df1
#          ID Site
#1 08/25/2015  KBS
#2 08/25/2015  KBS
#3 09/04/2015  KBS

#$df2
#          ID Site
#1 08/12/2015 UMBS
#2 08/29/2015 UMBS
#3 08/29/2015 UMBS

【讨论】:

    【解决方案2】:

    @akrun 出色答案的替代方法是“加入”方法。这很好的原因是模式/替换列表可以保存为单个框架/表格,使维护更容易。

    它使用fuzzyjoin::regex_left_join 操作,类似于merge 和dplyr::left_join,但具有模式匹配。

    ptns <- data.frame(
      ID_ptn = c("^KBS_2015_08_25.*P\\d", "^KBS_2015_09_04.*P\\d",
                 "^UMBS_2015_08_12.*P\\d", "^UMBS_2015_08_29.*P\\d"),
      ID_new = c("8/25/2015", "9/4/2015", "8/12/2015", "8/29/2015")
    )
    
    fuzzyjoin::regex_left_join(df1, ptns, by = c("ID" = "ID_ptn"))
    #                      ID Site                ID_ptn    ID_new
    # 1  KBS_2015_08_25_A1_P1  KBS ^KBS_2015_08_25.*P\\d 8/25/2015
    # 2 KBS_2015_08_25_A2_P10  KBS ^KBS_2015_08_25.*P\\d 8/25/2015
    # 3  KBS_2015_09_04_A2_P2  KBS ^KBS_2015_09_04.*P\\d  9/4/2015
    

    可以通过以下方式将其扩展到更大的列表:

    lapply(df_list, function(df) {
      tmp <- fuzzyjoin::regex_left_join(df, ptns, by = c("ID" = "ID_ptn"))
      tmp$ID <- replace(tmp$ID, !is.na(tmp$ID_new), tmp$ID_new)
      tmp[ names(ptns) ] <- NULL
      tmp
    })
    # $df1
    #          ID Site
    # 1 8/25/2015  KBS
    # 2 8/25/2015  KBS
    # 3  9/4/2015  KBS
    # $df2
    #          ID Site
    # 1 8/12/2015 UMBS
    # 2 8/29/2015 UMBS
    # 3 8/29/2015 UMBS
    

    这是@akrun 更直接(也许更容易看到和理解)的答案的替代方案。我提供它作为看待问题的另一种方式。

    (我会提出一个警告:如果模式可能重叠,其中两个或多个模式可以匹配单个ID,则需要采取更多步骤来确定使用哪一个。这将证明因为某些行重复,并且通过连接增加的行数。考虑到当前的模式,这不太可能,但是......警告购买者。)

    【讨论】:

      猜你喜欢
      • 2021-09-27
      • 2016-09-29
      • 2017-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多