You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

rmarkdown渲染NSE函数仅在callr调用场景下运行失败问题

问题描述

我遇到了NSE、rmarkdown和callr三者联动的异常问题,和同类RMarkdown禁用callr的问题类型相似。
当我定义变量用于通过嵌套eval和列表调用、基于NSE实现的类filter逻辑时,仅在callr环境中运行会触发报错,直接运行则正常。

复现用Rmd文件内容

---
title: "Test CC2 Failure Reproducibly"
author: "Robert M Flight"
output: rmarkdown::md_document
editor_options: 
  chunk_output_type: console
---

```{r run_enrichments}
set.seed(1234)
create_go_annotation = function(db, ontology = NULL){
  all_genes = keys(db)
  go_all_gene = AnnotationDbi::select(db, keys = all_genes, columns = c("GOALL", "ONTOLOGYALL"))
  
  if (!is.null(ontology)) {
    go_all_gene = go_all_gene[go_all_gene$ONTOLOGYALL == ontology, ]
    ontology_type = paste0("GO.", ontology)
  } else {
    ontology_type = "GO.all"
  }
  go_2_gene = split(go_all_gene$ENTREZID, go_all_gene$GOALL)
  go_2_gene = lapply(go_2_gene, unique)
  go_desc = AnnotationDbi::select(GO.db::GO.db, keys = names(go_2_gene), columns = "TERM", keytype = "GOID")$TERM
  names(go_desc) = names(go_2_gene)

  go_annotation = categoryCompare2::annotation(annotation_features = go_2_gene,
                                               description = go_desc,
                                               annotation_type = ontology_type,
                                               feature_type = "ENTREZID")
  go_annotation
}


library(org.Hs.eg.db)
library(GO.db)
library(categoryCompare2)
library(methods)

go_mf = create_go_annotation(org.Hs.eg.db, "MF")

all_features = unique(unlist(go_mf@annotation_features))
sig_features = sample(all_features, 500)

enrich = hypergeometric_feature_enrichment(
  new("hypergeom_features", significant = sig_features,
      universe = all_features,
      annotation = go_mf),
  p_adjust = "BH"
)

comb_enrich = combine_enrichments(sig1 = enrich)
sig_cutoff = 0.1
filter_enrich = get_significant_annotations(comb_enrich, padjust <= sig_cutoff)
## callr运行命令与报错信息
```r
r(function() rmarkdown::render(here::here("categoryCompare2_failure.Rmd"), output_file = "cc2_fails_1.md"), show = TRUE)

# Quitting from lines 13-57 (categoryCompare2_failure.Rmd)
# 
# Error in FUN(X[[i]], ...) : object 'sig_cutoff' not found
# Error: callr subprocess failed: object 'sig_cutoff' not found

# > .Last.error.trace
# Stack trace:
#   Process 221313:
#   1. callr:::r(function() rmarkdown::render(here::he ...
#     2. callr:::get_result(output = out, options)
#     3. throw(newerr, parent = remerr[[2]])
#         x callr subprocess failed: object 'sig_cutoff' not found
#   Process 221619:
#   15. (function () ...
#   16. rmarkdown::render(here::here("categoryCompare2_failure.Rmd"))
#   17. knitr::knit(knit_input, knit_output, envir = envir, quiet ...
#   18. knitr:::process_file(text, output)
#   19. base:::withCallingHandlers(if (tangle) process_tangle(gro ...
#   20. knitr:::process_group(group)
#   21. knitr:::process_group.block(group)
#   22. knitr:::call_block(x)
#   23. knitr:::block_exec(params)
#   24. knitr:::eng_r(options)
#   25. knitr:::in_dir(input_dir(), evaluate(code, envir = env, n ...
#   26. knitr:::evaluate(code, envir = env, new_device = FALSE, k ...
#   27. evaluate::evaluate(...)
#   28. evaluate:::evaluate_call(expr, parsed$src[[i]], envir = e ...
#   29. evaluate:::timing_fn(handle(ev <- withCallingHandlers(wit ...
#   30. base:::handle(ev <- withCallingHandlers(withVisible(eval( ...
#   31. base:::withCallingHandlers(withVisible(eval(expr, envir, ...
#   32. base:::withVisible(eval(expr, envir, enclos))
#   33. base:::eval(expr, envir, enclos)
#   34. base:::eval(expr, envir, enclos)
#   35. categoryCompare2:::get_significant_annotations(comb_enric ...
#   36. categoryCompare2:::get_significant_annotations(comb_enric ...
#   37. categoryCompare2:::.get_significant_combined_enrichment(i ...
#   38. base:::lapply(in_results@enriched, function(x) { ...
#   39. categoryCompare2:::FUN(X[[i]], ...)
#   40. categoryCompare2:::get_significant_annotations(x@statisti ...
#   41. categoryCompare2:::get_significant_annotations(x@statisti ...
#   42. categoryCompare2:::.get_significant_stat_results(in_resul ...
#   43. categoryCompare2:::multi_query_list(in_results@statistic_ ...
#   44. base:::lapply(queries, eval, list_to_query)
#   45. base:::FUN(X[[i]], ...)
#   46. base:::FUN(X[[i]], ...)
#   47. base:::.handleSimpleError(function (e) ...
#   48. h(simpleError(msg, call))
#     x object 'sig_cutoff' not found

报错提示找不到sig_cutoff对象,但该变量确实存在于运行环境中,并未被正常向下传递。
直接运行rmarkdown渲染则可正常执行:

rmarkdown::render(here::here("categoryCompare2_failure.Rmd"), output_file = "cc2_works.md")

可确认该问题与callr相关,使用targets包时也会触发相同报错。
涉及版本:callr v 1.0.7, rmarkdown v 2.11, R 4.1.0


问题成因

这个问题是非标准评估(NSE)的环境链查找逻辑和callr独立子进程的环境结构不兼容导致的:

  1. categoryCompare2包的get_significant_annotations函数内部实现类filter的NSE逻辑时,调用eval解析用户传入的padjust <= sig_cutoff表达式,仅指定了数据查询环境(存储padjust等字段的列表),没有显式指定父查找环境,默认会用eval被调用位置的包内部环境,而非用户传入表达式的调用环境。
  2. 直接运行rmarkdown::render时,代码块运行环境属于当前R会话的全局环境子环境,eval的默认查找链能向上找到代码块里定义的sig_cutoff变量,所以运行正常。
  3. callr启动的是完全独立的R子进程,子进程内的环境链结构和主进程不同:callr::r()传入的匿名函数运行环境、rmarkdown渲染的代码块环境、包内部函数运行环境三者的链断开,包内部的eval调用顺着默认环境链查找不到代码块里定义的sig_cutoff,就会报找不到对象的错误。使用targets包时触发同样问题也是这个原因,因为targets默认用callr运行任务。
修复方案

用户端临时修复(无需修改包代码)

任选一种即可:

  1. 用!!运算符直接把变量值注入到表达式里,避免运行时环境查找:
filter_enrich = get_significant_annotations(comb_enrich, padjust <= !!sig_cutoff)
  1. 把变量临时写入子进程的全局环境,保证NSE查找路径通顺:
sig_cutoff = 0.1
assign("sig_cutoff", sig_cutoff, envir = .GlobalEnv)
filter_enrich = get_significant_annotations(comb_enrich, padjust <= sig_cutoff)

包层面永久修复

修改categoryCompare2包内部multi_query_list函数的eval调用,显式指定父查找环境为用户调用函数时的环境:
把原来的实现:

lapply(queries, eval, list_to_query)

修改为:

lapply(queries, function(x) eval(x, list_to_query, enclos = parent.frame()))

内容的提问来源于stack exchange,提问作者rmflight

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 06:57:04