使用case_when与正则条件时出现意外警告,疑似多案例匹配
问题:case_when处理混合时间格式时出现未预期警告但结果正确
问题背景
处理包含混合时间格式的数据集,格式涵盖hh:mm类时间字符串与Excel时间序列号(小于1的小数)。将所有数据转为字符串后,通过case_when结合stringr、readr识别不同格式并解析,最终输出结果符合预期,但执行过程中出现了NA强制转换和时间解析失败的警告,这些警告对应的观测值本应被case_when的条件排除,因此对case_when的执行逻辑存在疑问。
示例数据
dummy <- tibble(x = c("13:15:21", "02:03:17+01:00", "12:03", "0.1234"))
正则规则与函数实现
使用的正则匹配规则:
^0\.:识别小于1的小数形式Excel时间序列号\+:识别带夏令时时区标识的时间字符串.+(?=\+):提取时区标识前的时间部分用于解析::匹配通用时间格式,作为兜底规则放在最后
处理函数代码:
convert_times <- function(x){ case_when(str_detect(x, "^0\\.") ~ convert_excel_time(x), str_detect(x, "\\+") ~ parse_time(str_extract(x, ".+(?=\\+)")), str_detect(x, ":") ~ parse_time(x), .default = NA) } convert_excel_time <- function(x){ as.numeric(x) * 24 * 60 * 60 %>% as_datetime() %>% hms::as_hms() }
执行结果与警告
预期输出
> dummy %>% + mutate(new = convert_times(x)) # A tibble: 4 × 2 x new <chr> <time> 1 13:15:21 13:15:21.00 2 02:03:17+01:00 02:03:17.00 3 12:03 12:03:00.00 4 0.1234 02:57:41.76
警告信息
[[1]] <warning/rlang_warning> Warning in `mutate()`: ℹ In argument: `new = convert_times(x)`. Caused by warning in `convert_excel_time()`: ! NAs introduced by coercion --- Backtrace: ▆ 1. ├─dummy %>% mutate(new = convert_times(x)) 2. ├─dplyr::mutate(., new = convert_times(x)) 3. └─dplyr:::mutate.data.frame(., new = convert_times(x)) [[2]] <warning/rlang_warning> Warning in `mutate()`: ℹ In argument: `new = convert_times(x)`. Caused by warning: ! 2 parsing failures. row col expected actual 2 -- time like 02:03:17+01:00 4 -- time like 0.1234 --- Backtrace: ▆ 1. ├─dummy %>% mutate(new = convert_times(x)) 2. ├─dplyr::mutate(., new = convert_times(x)) 3. └─dplyr:::mutate.data.frame(., new = convert_times(x))
原因分析
case_when是向量式执行的函数:它会先计算所有右侧分支的表达式(即对整个向量执行所有分支的函数),再根据左侧的条件选择对应位置的结果,而不是逐行判断条件后再执行对应分支的函数。
这就导致:
- 所有元素都会执行
convert_excel_time(x),非Excel格式的字符串转为numeric时会产生NA强制转换警告; - 所有元素都会执行
parse_time(str_extract(...))和parse_time(x),第2行带时区的字符串直接用parse_time解析会失败,第4行的Excel序列号无法被parse_time识别,因此产生解析失败警告。
这些警告对应的计算结果最终会被case_when的条件过滤掉,所以最终输出是正确的,但执行过程中不必要的函数调用会触发警告。
解决方案
方案1:用purrr::map逐元素处理
通过map遍历每个元素,在单个元素内部执行case_when,这样每个元素只会执行符合条件的分支函数,避免不必要的计算:
library(purrr) library(dplyr) library(readr) library(hms) convert_times <- function(x){ map(x, ~{ case_when( str_detect(.x, "^0\\.") ~ convert_excel_time(.x), str_detect(.x, "\\+") ~ parse_time(str_extract(.x, ".+(?=\\+)")), str_detect(.x, ":") ~ parse_time(.x), .default = NA ) }) %>% list_c() # 将列表转换为向量 } convert_excel_time <- function(x){ as.numeric(x) * 24 * 60 * 60 %>% as_datetime() %>% hms::as_hms() }
方案2:在分支函数内部添加条件过滤
在每个分支的函数中,只处理符合对应条件的元素,其余返回NA,避免对不匹配的元素执行无效计算:
library(dplyr) library(readr) library(hms) library(stringr) convert_excel_time <- function(x){ if_else(str_detect(x, "^0\\."), as.numeric(x) * 24 * 60 * 60 %>% as_datetime() %>% hms::as_hms(), NA) } convert_times <- function(x){ case_when( str_detect(x, "^0\\.") ~ convert_excel_time(x), str_detect(x, "\\+") ~ { idx <- str_detect(x, "\\+") res <- rep(NA, length(x)) res[idx] <- parse_time(str_extract(x[idx], ".+(?=\\+)")) res }, str_detect(x, ":") ~ { idx <- str_detect(x, ":") & !str_detect(x, "\\+") res <- rep(NA, length(x)) res[idx] <- parse_time(x[idx]) res }, .default = NA ) }
两种方案都能消除不必要的函数调用,从而解决警告问题,同时保持最终输出结果正确。
内容的提问来源于stack exchange,提问作者j.hmg
相关产品推荐
相关产品推荐

