R中janitor包tabyl()生成累积交叉表adorn_ns报错问题
问题说明
我近期重启数月前的旧项目做调整,项目中使用janitor包生成带累积百分比的交叉表,此前可正常运行的相同代码,如今执行直接返回错误。
复现代码如下:
library(tidyverse) library(janitor) library(scales) tb <- tibble( survey = c(1, 3, 4, 4, 2, 1, 4, 1, 2, 2), diagnosis = factor(c("yes", "yes", "yes", "no", "yes", "yes", "no", "no", "yes", "yes")) ) tb %>% tabyl(survey, diagnosis) %>% mutate(cum_no = percent(cumsum(no)/sum(no), accuracy = 0.1)) %>% mutate(cum_yes = percent(cumsum(yes)/sum(yes), accuracy = 0.1)) %>% adorn_totals(where = "both", ... = c(yes, no)) %>% adorn_percentages(denominator = "col", na.rm = TRUE, ... = c(yes, no, Total)) %>% adorn_pct_formatting() %>% adorn_ns(position = "front", ... = c(yes, no, Total)) #> Error in adorn_ns(., position = "front", ... = c(yes, no, Total)): argument "ns" cannot be null; if not calling adorn_ns() on a data.frame of class "tabyl", pass your own value for ns
以上复现片段生成于2022-06-03,基于reprex包v2.0.1制作
经初步排查,问题出在计算cum_no、cum_yes两个累积百分比变量的mutate步骤:删除这两行mutate代码后,整个表处理流程可正常运行。
判断adorn_ns会从tabyl类的core属性中读取频数N值,初始tabyl对象结构包含完整的类属性:
> str(tabyl(tb, survey, diagnosis)) Classes ‘tabyl’ and 'data.frame': 4 obs. of 3 variables: $ survey: num 1 2 3 4 $ no : num 1 0 0 2 $ yes : num 2 3 1 1 - attr(*, "core")='data.frame': 4 obs. of 3 variables: ..$ survey: num [1:4] 1 2 3 4 ..$ no : num [1:4] 1 0 0 2 ..$ yes : num [1:4] 2 3 1 1 - attr(*, "tabyl_type")= chr "two_way" - attr(*, "var_names")=List of 2 ..$ row: chr "survey" ..$ col: chr "diagnosis"
但执行mutate新增累积百分比列后,对象的core属性已丢失:
Classes ‘tabyl’ and 'data.frame': 4 obs. of 5 variables: $ survey : num 1 2 3 4 $ no : num 1 0 0 2 $ yes : num 2 3 1 1 $ cum_no : chr "33.3%" "33.3%" "33.3%" "100.0%" $ cum_yes: chr "28.6%" "71.4%" "85.7%" "100.0%"
目前不清楚该问题的正确处理方式,也疑惑为何此前代码可正常运行、现在却失效,怀疑是janitor包版本更新导致相关逻辑变动。
问题原因与解决方案
你的判断是对的,报错确实来自janitor包版本迭代的逻辑变动:
- janitor 2.0.0及之后版本调整了tabyl对象的属性保留规则,用
dplyr::mutate()给tabyl新增列时,不会自动同步更新存储原始频数的core属性,后续adorn_*系列函数检测到core属性和当前数据结构不匹配,会直接丢弃不合法的core属性,最终adorn_ns()找不到原始频数存储位置,就会抛出对应的报错。 - 旧版本janitor对tabyl属性的校验逻辑更宽松,mutate新增列时不会丢失
core属性,因此旧代码可以正常运行。
最稳妥的处理方式是调整代码执行顺序,把自定义累积百分比列的计算放到所有adorn_*标准处理流程结束之后,避免自定义列干扰tabyl的核心属性:
tb %>% # 先走janitor标准交叉表处理流程 tabyl(survey, diagnosis) %>% adorn_totals(where = "both", ... = c(yes, no)) %>% adorn_percentages(denominator = "col", na.rm = TRUE, ... = c(yes, no, Total)) %>% adorn_pct_formatting() %>% adorn_ns(position = "front", ... = c(yes, no, Total)) %>% # 所有内置格式处理完成后,再计算自定义累积列 mutate( # 从"频数 (百分比)"格式的单元格中提取原始频数值计算累积占比 cum_no = percent(cumsum(as.numeric(str_extract(no, "^\\d+")))/sum(as.numeric(str_extract(no, "^\\d+"))), accuracy = 0.1), cum_yes = percent(cumsum(as.numeric(str_extract(yes, "^\\d+")))/sum(as.numeric(str_extract(yes, "^\\d+"))), accuracy = 0.1) )
如果坚持要在流程早期添加累积列,也可以在mutate操作后手动重写core属性,但这种方式容易在后续adorn步骤触发其他属性不匹配问题,不推荐使用。
内容的提问来源于stack exchange,提问作者Ntgllr
相关产品推荐
相关产品推荐

