Từ một thư mục script đến gói mdrtbvn cài được, có tài liệu và vượt qua R CMD check
Vì sao bạn nên quan tâm?
Một nhóm ở bệnh viện phổi tỉnh muốn chạy lại mô hình với giá khám của bệnh viện mình, thay cho mức 50,600 VNĐ một lượt khám theo bảng giá BHYT năm 2025 của bệnh viện hạng đặc biệt (Bộ Y tế 2024), tức khoảng 1.3 bát phở (Nhadautu.vn 2025). Mô hình M02 chỉ chạy khi có đúng thư mục khóa học bên cạnh: muốn dùng, họ phải chép nhiều tệp, sửa đường dẫn, mà vẫn không chắc đang có bản nào. Trong khi đó, M03 chạy chính mô hình ấy hàng nghìn lần, còn M10 dùng nó để lập ngân sách cho người bệnh lao kháng rifampicin, nhóm mà WHO ước tính có khoảng 9,400 người mắc mới mỗi năm ở Việt Nam (World Health Organization 2026). Bài này giúp bạn quyết định khi nào nên đóng gói một mô hình và phải đưa những gì vào gói, để ai cài gói cũng nhận được đúng mô hình và đúng bảng tham số đã tạo ra kết quả.
Mục tiêu học tập
Sau bài này, bạn có thể:
Giải thích đóng gói mô hình thành gói R (package) lợi gì so với để nguyên thư mục script.
Viết tài liệu bằng chú thích roxygen2, và phân biệt hàm công khai (exported) với hàm nội bộ.
Dựng, kiểm tra bằng R CMD check, cài và dùng gói; tăng số phiên bản đúng quy ước.
Ý chính
Gói R là đơn vị cài được: có tên, phiên bản, khai báo sẵn những gói khác nó cần (phụ thuộc), kèm tài liệu và kiểm thử, nên ai cũng cài và gọi gói ấy theo cùng một cách.
R CMD check, lệnh kiểm tra gói của R, chạy hàng chục kiểm tra tự động, gồm cả bộ kiểm thử testthat. Kết luận “Status: OK” là mức tối thiểu phải đạt trước khi chia sẻ mô hình.
Tệp NAMESPACE ghi những hàm mà gói đưa ra cho người dùng (hàm công khai), mọi hàm khác là hàm nội bộ. Người dùng gọi run_model(), còn build_P() là chi tiết bên trong, sửa được mà không làm hỏng mã của người khác.
Gói roxygen2 đọc các dòng chú thích #' ngay trên mỗi hàm công khai, rồi biến chúng thành trang trợ giúp và dòng export() trong NAMESPACE.
Mã trong gói không dựa vào môi trường làm việc: không source(), không library(), không đường dẫn tương đối, không phụ thuộc ẩn. Muốn dùng hàm của gói khác thì gọi kèm tên gói, dạng pkg::fun(), và khai báo gói ấy trong trường Imports.
Dữ liệu đi kèm nằm trong inst/extdata/, và hàm system.file() tìm ra tệp ấy. Gói đã cài không với tới kb/_shared, nên phải mang theo ảnh chụp, tức bản sao cố định, của bảng tham số đầy đủ (Bài 1).
Mỗi thứ chỉ có một nơi gốc: gói dựng tự động từ code/model.R và tests/testthat/, còn số phiên bản lấy từ MODEL_VERSION.
Nội dung
Vì sao đóng gói một mô hình
Ở M02, muốn dùng mô hình thì phải nạp bằng source() đúng tệp, theo đúng thứ tự, từ đúng thư mục, và thư mục dữ liệu chung kb/_shared phải nằm ngay bên cạnh. Mỗi module dùng lại mô hình (M03, M10) lại lặp các đường dẫn đó, còn gói R thay tất cả bằng một dòng library(mdrtbvn). Marwick và cộng sự đề xuất dùng chính cấu trúc gói R để tổ chức công trình phân tích tái lập được, vì cấu trúc này đã quen thuộc với cộng đồng R và có sẵn công cụ kiểm tra tự động (Marwick, Boettiger, và Mullen 2018). Riêng mô hình kinh tế y tế còn thêm hai lợi ích: số phiên bản của gói chính là phiên bản của mô hình (Bài 3), và người duyệt có thể chạy R CMD check để biết mô hình ít nhất cài được, có tài liệu và vượt qua kiểm thử của chính nó.
Cấu trúc tối thiểu
flowchart TB P["mdrtbvn/"] --> D["DESCRIPTION<br/>tên, phiên bản, tác giả,<br/>giấy phép, phụ thuộc"] P --> N["NAMESPACE<br/>hàm công khai<br/>(roxygen2 viết)"] P --> R["R/model.R<br/>= code/model.R"] P --> M["man/*.Rd<br/>trang trợ giúp<br/>(roxygen2 viết)"] P --> T["tests/testthat/<br/>= tests/testthat/ của module"] P --> I["inst/extdata/mdrtbvn-params.csv<br/>ảnh chụp bảng tham số đầy đủ"] P --> L["LICENSE, NEWS.md"]
flowchart TB
P["mdrtbvn/"] --> D["DESCRIPTION<br/>tên, phiên bản, tác giả,<br/>giấy phép, phụ thuộc"]
P --> N["NAMESPACE<br/>hàm công khai<br/>(roxygen2 viết)"]
P --> R["R/model.R<br/>= code/model.R"]
P --> M["man/*.Rd<br/>trang trợ giúp<br/>(roxygen2 viết)"]
P --> T["tests/testthat/<br/>= tests/testthat/ của module"]
P --> I["inst/extdata/mdrtbvn-params.csv<br/>ảnh chụp bảng tham số đầy đủ"]
P --> L["LICENSE, NEWS.md"]
DESCRIPTION là tệp duy nhất bắt buộc phải tự viết, gồm tên gói, tiêu đề, phiên bản, tác giả và người duy trì, mô tả, giấy phép, và các gói phụ thuộc (Imports cho gói bắt buộc, Suggests cho gói chỉ dùng khi kiểm thử). Tài liệu chính thức Writing R Extensions mô tả đầy đủ mọi tệp (R Core Team 2026); sách R Packages trình bày chính nội dung ấy theo lối thực hành (Wickham và Bryan 2023).
Tài liệu bằng roxygen2
roxygen2 đọc các dòng chú thích bắt đầu bằng #' ngay trên một hàm: dòng đầu là tiêu đề, @param mô tả đối số, @return mô tả giá trị trả về, @export đưa hàm vào NAMESPACE(Wickham và c.s. 2025). Tài liệu nằm cạnh mã nên dễ sửa cùng lúc; R CMD check báo lỗi nếu một đối số có trong hàm mà không có trong tài liệu, hoặc ngược lại. Chỉ các hàm công khai cần khối roxygen; hàm nội bộ dùng chú thích # thường.
Những gì phải đổi khi đưa mô hình vào gói
Phần lớn đã làm ở Bài 1: mô hình chỉ dùng base R, không có phụ thuộc ẩn, mã chỉ có ký tự ASCII. Còn lại hai điểm: dữ liệu và phiên bản. Gói đã cài nằm trong thư viện R của người dùng, tức thư mục chứa các gói đã cài, và không biết thư mục khóa học ở đâu, nên các giá trị chung (ngưỡng, giá, bảng sống) phải đi theo gói. Ta ghi bảng tham số đầy đủ mà assemble_params() ghép ra thành tệp inst/extdata/mdrtbvn-params.csv; khi cài, R chép nội dung inst/ vào thư mục gốc của gói, và default_params() tìm tệp bằng system.file(). Đây là ảnh chụp có chủ đích: kết quả của gói không đổi khi kb/_shared đổi; muốn dùng giá mới, người dùng gọi assemble_params() trên tệp mới hoặc chờ phiên bản gói mới, và mã tham số cho biết họ đang dùng bảng nào. Trong module (chưa cài gói), tùy chọn mdrtbvn.params trỏ tới bảng ghép ở thư mục tạm. Số phiên bản trong DESCRIPTION lấy từ hằng MODEL_VERSION trong code/model.R, để không bao giờ có hai số phiên bản mâu thuẫn.
Ngoài bài học, người ta thường nhờ hai gói usethis và devtools làm các bước này ngay trong phiên R tương tác:
usethis::create_package("mdrtbvn") # skeleton: DESCRIPTION, R/, NAMESPACEusethis::use_mit_license()usethis::use_testthat() # tests/testthat/ and the testthat editiondevtools::document() # roxygen2: man/ and NAMESPACEdevtools::check() # R CMD build + R CMD checkdevtools::install()
Bài này làm từng bước bằng base R và dòng lệnh R CMD để bạn thấy rõ mỗi lệnh trên làm gì; kết quả giống nhau.
Thực hành
Đoạn mã đầu chuẩn bị thư mục dựng gói (trong thư mục tạm) và viết hai hàm trợ giúp. Hàm r_cmd() chạy R CMD ...; document() chạy roxygen2 trong một phiên R riêng: khi đọc mã, công cụ ấy nạp bản đang dựng dở của gói, và ta không muốn bản này lẫn vào phiên đang làm việc. Kiểm tra: không có lỗi.
# Vietnamese text in tables needs a UTF-8 locale (Rscript may start in "C")if (!isTRUE(l10n_info()[["UTF-8"]])) invisible(Sys.setlocale("LC_CTYPE", "en_US.UTF-8"))source("../_shared/R/helpers.R")source("code/model.R")# The complete parameter table, written once; the module and the package read the same fileparam_file <-write_param_table(assemble_params("../m02-cohort-markov/params/mdrtb.csv", "../_shared"),file.path(tempdir(), "mdrtbvn-params.csv"))pkg_name <-"mdrtbvn"work <-file.path(tempdir(), "pkg-build")unlink(work, recursive =TRUE)dir.create(work)pkg <-file.path(work, pkg_name)R_bin <-file.path(R.home("bin"), "R")# Run `R CMD <args>` inside the build folder and return its output linesr_cmd <-function(args) { old <-setwd(work)on.exit(setwd(old))system2(R_bin, c("CMD", args), stdout =TRUE, stderr =TRUE)}# Run roxygen2 in a separate R process, so this session stays clean (roxygen2 loads a# development copy of the package while it reads the code)document <-function(path) { rscript <-file.path(R.home("bin"), "Rscript")system2(rscript, c("-e", shQuote(paste0("roxygen2::roxygenise('", path, "')"))),stdout =TRUE, stderr =TRUE)}
Đoạn mã sau dựng khung gói từ các tệp nguồn của module: mã, kiểm thử, ảnh chụp bảng tham số, DESCRIPTION, LICENSE, NEWS.md (nhật ký thay đổi, ghi cả mã bảng tham số). Tác giả trong DESCRIPTION là tên giữ chỗ, không phải người thật. Kiểm tra: cây thư mục khớp với sơ đồ ở trên.
# The minimal anatomy: DESCRIPTION, R/, tests/, inst/extdata/ (+ LICENSE, NEWS.md)for (d inc("R", "tests/testthat", "inst/extdata")) dir.create(file.path(pkg, d), recursive =TRUE)invisible(file.copy("code/model.R", file.path(pkg, "R", "model.R")))invisible(file.copy(list.files("tests/testthat", full.names =TRUE), file.path(pkg, "tests", "testthat")))# The shared inputs travel WITH the package as a frozen snapshot: an installed package cannot# reach kb/_shared, and its results must not change when that folder changesinvisible(file.copy(param_file, file.path(pkg, "inst", "extdata", "mdrtbvn-params.csv")))writeLines(c("library(testthat)", "library(mdrtbvn)", "", "test_check(\"mdrtbvn\")"),file.path(pkg, "tests", "testthat.R"))desc <-c(Package = pkg_name,Title ="Cohort Model of MDR/RR-TB Treatment Regimens in Viet Nam",Version = MODEL_VERSION, # single source of truth: code/model.R`Authors@R`='person("HEM", "Course", email = "[email protected]", role = c("aut", "cre"))',Description =paste("A cohort state-transition (Markov) model comparing the longer regimen,","the 9-month short regimen and the 6-month BPaLM regimen for MDR/RR-TB","in Viet Nam, with retreatment after failure or loss to follow-up.","Ships a snapshot of its complete parameter table (model parameters and","shared Vietnamese course inputs). Teaching package of course module M11."),License ="MIT + file LICENSE",Encoding ="UTF-8",Imports ="stats, tools, utils",Suggests ="testthat (>= 3.0.0)",`Config/testthat/edition`="3",Roxygen ="list(markdown = TRUE)",RoxygenNote =as.character(utils::packageVersion("roxygen2")))write.dcf(t(desc), file.path(pkg, "DESCRIPTION"), keep.white =names(desc))writeLines(c("YEAR: 2026", "COPYRIGHT HOLDER: mdrtbvn authors"), file.path(pkg, "LICENSE"))n_tests <-sum(vapply(list.files("tests/testthat", "^test-", full.names =TRUE),function(f) sum(grepl("^test_that\\(", readLines(f))), numeric(1)))p <-read_param_file(param_file)writeLines(c(paste("# mdrtbvn", MODEL_VERSION), "",paste0("* First release: the M02 model refactored into inputs, model and outputs, ","with ", n_tests, " automated tests."),paste0("* Parameter table ", param_fingerprint(p), " (inst/extdata/mdrtbvn-params.csv).")),file.path(pkg, "NEWS.md"))cat(paste0(pkg_name, "/", sort(list.files(pkg, recursive =TRUE))), sep ="\n")
Package: mdrtbvn
Title: Cohort Model of MDR/RR-TB Treatment Regimens in Viet Nam
Version: 1.0.0
Authors@R: person("HEM", "Course", email = "[email protected]", role = c("aut", "cre"))
Description: A cohort state-transition (Markov) model comparing the longer regimen, the 9-month short regimen and the 6-month BPaLM regimen for MDR/RR-TB in Viet Nam, with retreatment after failure or loss to follow-up. Ships a snapshot of its complete parameter table (model parameters and shared Vietnamese course inputs). Teaching package of course module M11.
License: MIT + file LICENSE
Encoding: UTF-8
Imports: stats, tools, utils
Suggests: testthat (>= 3.0.0)
Config/testthat/edition: 3
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.3
Đoạn mã sau in khối roxygen2 ngay trên run_model() trong code/model.R. Kiểm tra: có tiêu đề, @param cho mọi đối số, @return và @export.
# A roxygen2 block: #' comments right above the function they documentsrc <-readLines("code/model.R")fun_line <-grep("^run_model <- function", src)doc_start <-max(grep("^#' Run the whole model", src))cat(src[doc_start:(fun_line +1)], " ...", sep ="\n")
#' Run the whole model: validate inputs, simulate, summarise, compare
#'
#' @param p Named list of parameter values (see `default_params()`).
#' @param new,ref Regimens of the pairwise comparison; by default BPaLM versus the 9-month
#' short regimen, the regimen it would replace in Viet Nam.
#' @return List with `version` (model version), `param_id` (identifier of the parameter
#' set, see `param_fingerprint()`), `results` (see `run_cea()`), `frontier` (see
#' `cea_frontier()`) and `comparison` (see `compare_regimens()`), all at `p$wtp`.
#' @export
run_model <- function(p, new = "bpalm", ref = "short") {
results <- run_cea(p)
...
Chạy roxygen2 để viết NAMESPACE và các trang trợ giúp. Kiểm tra: mỗi hàm công khai có đúng một tệp .Rd.
# roxygen2 turns the #' comments in R/model.R into help pages (man/*.Rd) and NAMESPACEdoc_out <-document(pkg)cat(readLines(file.path(pkg, "NAMESPACE")), sep ="\n")
# Generated by roxygen2: do not edit by hand
export(apply_updates)
export(assemble_params)
export(cea_frontier)
export(compare_regimens)
export(default_params)
export(nmb_by_wtp)
export(param_fingerprint)
export(params_from_table)
export(read_param_file)
export(run_cea)
export(run_model)
export(validate_params)
export(write_param_table)
man_files <-list.files(file.path(pkg, "man"))exports <-sub("^export\\((.*)\\)$", "\\1", grep("^export", readLines(file.path(pkg, "NAMESPACE")), value =TRUE))stopifnot(length(man_files) ==length(exports)) # every exported function is documented
Gói công khai 13 hàm. Các hàm còn lại của model.R (dựng ma trận, chạy vết, tóm tắt) là hàm nội bộ: kiểm thử vẫn gọi được chúng, người dùng gói thì không cần biết đến.
Dựng gói thành tệp nguồn .tar.gz, tức tệp dùng để chia sẻ và cài gói, rồi chạy R CMD check trên tệp ấy. Kiểm tra: dòng cuối là “Status: OK”.
# R CMD build makes the source tarball; R CMD check runs dozens of automatic checks,# including our tests, exactly as a reviewer or CRAN wouldbuild_out <-r_cmd(c("build", "--no-build-vignettes", "--no-manual", pkg_name))tarball <-list.files(work, pattern ="\\.tar\\.gz$")Sys.setenv("_R_CHECK_SYSTEM_CLOCK_"="0") # do not ask an internet time servert_check <-system.time(check_out <-r_cmd(c("check", "--no-manual", tarball)))[["elapsed"]]n_checks <-sum(grepl("^\\* checking", check_out))check_status <-sub("^Status: ", "", check_out[grepl("^Status:", check_out)])show <-grepl("R code for possible problems|missing documentation|code/documentation mismatches|for non-ASCII|Running .testthat|^Status", check_out)cat(check_out[show], sep ="\n")
* checking code files for non-ASCII characters ... OK
* checking R code for possible problems ... OK
* checking for missing documentation entries ... OK
* checking for code/documentation mismatches ... OK
Running ‘testthat.R’
Status: OK
R CMD check chạy 41 nhóm kiểm tra trong khoảng 19 giây, gồm cả 54 kiểm thử của Bài 2, và kết luận “OK”. Mục “R code for possible problems” chính là phép tìm phụ thuộc ẩn bằng codetools ở Bài 1: codetools::checkUsageEnv(env) duyệt mọi hàm trong môi trường env và in các lời gọi tới hàm không nhìn thấy được từ đó (bài tập 3 dùng hàm này). Còn mục “non-ASCII characters” là lý do mã mô hình chỉ dùng ký tự ASCII, tức không có chữ có dấu hay ký hiệu như Δ.
Đoạn mã sau cài gói vào một thư viện tạm rồi dùng như mọi gói khác. Kiểm tra: kết quả của gói đúng bằng kết quả của module, và hai bên có chung mã bảng tham số.
# Install into a temporary library and use the package like any otherlib <-file.path(work, "lib")dir.create(lib)install_out <-r_cmd(c("INSTALL", "-l", shQuote(lib), tarball))invisible(loadNamespace(pkg_name, lib.loc = lib))sort(getNamespaceExports(pkg_name))
Đoạn mã cuối in trang trợ giúp mà roxygen2 viết cho run_model(), đúng thứ bạn thấy khi gõ ?run_model. Kiểm tra: phần Arguments và Value lấy đúng từ khối #' ở trên. Đến đây, bạn đã tự dựng, kiểm tra và cài được gói R của mô hình.
# The help page that roxygen2 wrote from the #' comments above run_model()help_txt <-capture.output(tools::Rd2txt(file.path(pkg, "man", "run_model.Rd"),options =list(underline_titles =FALSE)))cat(help_txt[nzchar(trimws(help_txt))], sep ="\n")
Run the whole model: validate inputs, simulate, summarise, compare
Description:
Run the whole model: validate inputs, simulate, summarise, compare
Usage:
run_model(p, new = "bpalm", ref = "short")
Arguments:
p: Named list of parameter values (see 'default_params()').
new, ref: Regimens of the pairwise comparison; by default BPaLM versus
the 9-month short regimen, the regimen it would replace in
Viet Nam.
Value:
List with 'version' (model version), 'param_id' (identifier of the
parameter set, see 'param_fingerprint()'), 'results' (see
'run_cea()'), 'frontier' (see 'cea_frontier()') and 'comparison'
(see 'compare_regimens()'), all at 'p$wtp'.
Mẹo thực hành
Chạy R CMD check (hoặc devtools::check()) trước mỗi lần gắn nhãn phiên bản. Một gói “Status: OK” trên máy bạn là điều kiện cần, chưa phải điều kiện đủ.
Giữ ít hàm công khai, vì mỗi hàm công khai là lời hứa với người dùng: đổi đối số của hàm ấy là thay đổi MAJOR.
Viết NEWS.md mỗi lần đổi phiên bản: một dòng cho mỗi thay đổi người dùng cần biết, đặc biệt là thay đổi làm kết quả khác đi.
Gói mô hình không cần lên CRAN, kho gói chính thức của R: với một nhóm nghiên cứu, cài thẳng từ GitHub (ví dụ bằng remotes::install_github()) là đủ.
Lỗi thường gặp
Gọi library() hay source() trong R/: gói phải khai báo phụ thuộc trong DESCRIPTION và gọi pkg::fun(). Dấu hiệu: R CMD check báo NOTE hoặc WARNING ở phần mã.
Đường dẫn tương đối tới dữ liệu như "../params/mdrtb.csv": chạy được trong thư mục của bạn, hỏng khi gói đã cài. Dùng system.file().
Sửa tay NAMESPACE hay man/ khi đã dùng roxygen2: lần chạy roxygen2 sau sẽ ghi đè. Sửa khối #' rồi chạy lại.
Quên tăng phiên bản khi thay đổi mô hình: hai bản khác nhau mang cùng số phiên bản, và không ai biết kết quả nào do bản nào tạo ra.
Bài tập
(Dễ) Bạn sửa một lỗi nhỏ không đổi thiết kế mô hình. Phiên bản mới là gì? Viết hàm tăng số phiên bản theo quy ước MAJOR.MINOR.PATCH, sửa MODEL_VERSION trong R/model.R của gói và ghi lại Version của DESCRIPTION từ đó.
Lời giải
# Exercise 1: semantic versioning. A bug fix bumps PATCH, a new function MINOR,# a change that breaks existing code MAJOR (and resets the parts to its right)bump_version <-function(v, part =c("patch", "minor", "major")) { part <-match.arg(part) x <-as.integer(strsplit(v, ".", fixed =TRUE)[[1]])if (part =="major") x <-c(x[1] +1, 0, 0)if (part =="minor") x <-c(x[1], x[2] +1, 0)if (part =="patch") x <-c(x[1], x[2], x[3] +1)paste(x, collapse =".")}c(bug_fix =bump_version("1.0.0", "patch"), new_function =bump_version("1.0.0", "minor"),breaking =bump_version("1.2.3", "major"))
# The number lives in ONE place, MODEL_VERSION in the package's R/model.R; DESCRIPTION# is regenerated from it, so the two can never disagreemodel_file <-file.path(pkg, "R", "model.R")model_lines <-readLines(model_file)i_ver <-grep("^MODEL_VERSION <- ", model_lines)old_version <-gsub("^MODEL_VERSION <- \"|\"$", "", model_lines[i_ver])new_version <-bump_version(old_version, "patch")model_lines[i_ver] <-sprintf("MODEL_VERSION <- \"%s\"", new_version)writeLines(model_lines, model_file)d <-read.dcf(file.path(pkg, "DESCRIPTION"))d[, "Version"] <- new_versionwrite.dcf(d, file.path(pkg, "DESCRIPTION"), keep.white =colnames(d))env_ver <-new.env()sys.source(model_file, envir = env_ver)c(model = env_ver$MODEL_VERSION, description =read.dcf(file.path(pkg, "DESCRIPTION"), fields ="Version")[1, 1])
model description.Version
"1.0.1" "1.0.1"
stopifnot(env_ver$MODEL_VERSION == new_version,read.dcf(file.path(pkg, "DESCRIPTION"), fields ="Version")[1, 1] == new_version)# Restore the original number, so that the exercises below build on the version of Lesson 4model_lines[i_ver] <-sprintf("MODEL_VERSION <- \"%s\"", old_version)writeLines(model_lines, model_file)d[, "Version"] <- old_versionwrite.dcf(d, file.path(pkg, "DESCRIPTION"), keep.white =colnames(d))
Sửa lỗi tăng PATCH (1.0.0 → 1.0.1); thêm hàm mới tăng MINOR; thay đổi làm mã cũ không chạy được tăng MAJOR và đưa hai số sau về 0 (Preston-Werner 2013). Con số chỉ có một nơi gốc, MODEL_VERSION; DESCRIPTION được ghi từ đó nên hai nơi không thể lệch nhau (script trả gói về 1.0.0 sau bài tập).
(Vừa) Thêm vào gói một hàm công khai threshold_price(p) tìm giá thuốc BPaLM mỗi tháng sao cho lợi ích ròng quy ra tiền gia tăng (ΔNMB) so với phác đồ ngắn bằng 0. Đặt nó trong một tệp riêng ở R/, có khối roxygen, chạy lại roxygen2 và kiểm tra NAMESPACE.
Lời giải
# Exercise 2: add and export a new function. Write it with a roxygen block in its own# file under R/, run roxygen again, and check that NAMESPACE now exports it.writeLines(c("#' Monthly BPaLM drug price at which BPaLM stops paying off against the short regimen","#'","#' @param p Named list of parameter values (see `default_params()`).","#' @param upper Upper end of the search interval, in VND per month.","#' @return Threshold price in VND per month (incremental NMB = 0).","#' @export","threshold_price <- function(p, upper = 100 * p$c_drug_bpalm) {"," gap <- function(price) {"," p$c_drug_bpalm <- price"," run_model(p)$comparison$d_nmb"," }"," stats::uniroot(gap, lower = 0, upper = upper)$root","}"), file.path(pkg, "R", "threshold.R"))doc_out <-document(pkg)stopifnot("export(threshold_price)"%in%readLines(file.path(pkg, "NAMESPACE")))source(file.path(pkg, "R", "threshold.R"))price_zero <-threshold_price(p)q <- pq$c_drug_bpalm <- price_zero# At the threshold price the incremental NMB is (numerically) zero, and above the base pricec(d_nmb_at_threshold =run_model(q)$comparison$d_nmb)
Giá ngưỡng là 12.87 triệu VNĐ mỗi tháng, gấp 10.9 lần giá GDF hiện tại; tại giá đó ΔNMB bằng 0 đến sai số làm tròn. Đồ thị đầy đủ nằm ở Bài 5. Hàm mới chỉ gọi run_model(), một hàm công khai, nên không phụ thuộc vào chi tiết bên trong mô hình. Thêm một hàm công khai là thay đổi MINOR.
(Khó) Cho summarise_regimen() gọi discount_weights() của tệp tiện ích khóa học thay vì discount_factor(). Trong phiên của bạn mô hình vẫn chạy (tệp tiện ích đã nạp). Hãy cho thấy R CMD check sẽ báo lỗi này: nạp mã vào một môi trường chỉ thấy base R (new.env(parent = baseenv())) rồi chạy codetools::checkUsageEnv() trên môi trường đó.
Lời giải
# Exercise 3: why R CMD check catches hidden dependencies. Make summarise_regimen() call# the course helper discount_weights(), then check the code the way R CMD check does:# against base R only, not against whatever happens to be in your workspace.src <-readLines("code/model.R")target <-"disc <- discount_factor((0:n) / 12, p$disc_rate)"stopifnot(sum(grepl(target, src, fixed =TRUE)) ==1)src_bad <-sub(target, "disc <- discount_weights((0:n) / 12, p$disc_rate)", src, fixed =TRUE)check_usage <-function(lines) { env <-new.env(parent =baseenv()) # sees base R, not the workspaceeval(parse(text = lines), envir = env)capture.output(codetools::checkUsageEnv(env))}check_usage(src) # the real model: no complaints
character(0)
check_usage(src_bad) # the hidden dependency is reported
[1] "summarise_regimen: no visible global function definition for 'discount_weights'"
codetools::checkUsageEnv() kiểm tra mỗi hàm so với những gì nhìn thấy được từ môi trường của nó. Đặt mã vào một môi trường chỉ thấy base R, ta có đúng thông báo “no visible global function definition” mà R CMD check sẽ in ra. Đây chính là phụ thuộc ẩn của Bài 1, nay phát hiện tự động.
Marwick, Ben, Carl Boettiger, và Lincoln Mullen. 2018. “Packaging data analytical work reproducibly using R (and friends)”. The American Statistician 72 (1): 80–88. https://doi.org/10.1080/00031305.2017.1375986.