Bài 1: Quy trình làm việc tái lập với R

Tổ chức một dự án mô hình sao cho mọi con số đều chạy lại được từ mã

Vì sao bạn nên quan tâm?

Cuối năm, Hội đồng thuốc và điều trị của một bệnh viện tỉnh họp để sửa phác đồ kháng sinh kinh nghiệm cho ICU, dựa trên bản tổng kết kháng sinh đồ do khoa vi sinh gửi lên. Một ngày colistin cho một người bệnh nhiễm CRE tốn khoảng 3,641,873 VNĐ theo giá trúng thầu quy về năm 2025 (Bệnh viện Trung ương Thái Nguyên 2023), gấp 23 lần một ngày meropenem generic (Bệnh viện Đa khoa khu vực Củ Chi 2024) và bằng 15 ngày lương tối thiểu vùng I theo mức từ 1/1/2026 (Chính phủ 2025). Nếu tỷ lệ K. pneumoniae kháng carbapenem trong báo cáo bị gõ tay sai, hội đồng có thể đẩy cả khoa sang phác đồ đắt gấp nhiều lần, hoặc giữ một phác đồ đã mất tác dụng, mà không ai phát hiện. Bài này giúp bạn tổ chức dự án sao cho mọi con số trong báo cáo đều chạy lại được từ dữ liệu thô và truy được về nguồn, để hội đồng có thể tin và kiểm tra.

Mục tiêu học tập

Sau bài này, bạn có thể:

  1. Chạy một script R từ thư mục gốc của dự án, bằng IDE hoặc bằng Rscript.
  2. Sắp xếp một dự án mô hình thành các thư mục code/, params/, data/, figs/ và giải thích vai trò của từng thư mục.
  3. Lưu tham số trong bảng CSV có cột nguồn và cờ giá trị minh họa, đọc bằng read_params(), và truy một tham số về số liệu công bố gốc.
  4. Chọn đúng công cụ: script .R cho phần tính toán, Quarto .qmd cho phần báo cáo.
  5. Viết con số trong văn bản bằng mã R nội dòng thay vì gõ tay.

Ý chính

  • Tính tái lập (reproducibility) nghĩa là người khác, hoặc chính bạn sáu tháng sau, chạy lại mã trên cùng dữ liệu và ra đúng các con số đã báo cáo.
  • Thư mục dự án là đơn vị làm việc. Mọi đường dẫn tính tương đối từ thư mục module (module dùng chung _shared/ đặt ở thư mục cha, xem bên dưới); không dùng setwd() với đường dẫn tuyệt đối.
  • Tách ba lớp: đầu vào (params/, data/), mô hình (hàm trong code/model.R), phân tích (một script cho mỗi câu hỏi). Kết quả đi vào figs/ và báo cáo.
  • Dữ liệu thô chỉ để đọc. Mọi bước làm sạch được viết thành mã.
  • Mỗi tham số là một dòng trong bảng tham số (parameter table), kèm nguồn, hoặc gắn cờ illustrative = TRUE nếu là giá trị minh họa. Tham số suy ra từ số liệu công bố thì phép suy ra cũng phải nằm trong mã.
  • Giá trị dùng chung cho cả khóa học (giá dịch vụ, giá thuốc, lương, CPI, bảng sống) chỉ nằm ở _shared/; module không chép lại.
  • Mọi con số trong văn bản sinh ra từ mã R nội dòng (inline R code). Số gõ tay sẽ lỗi thời ngay khi dữ liệu hoặc mã thay đổi.
  • Ghi lại môi trường chạy: phiên bản R và gói, cùng set.seed() cho mọi bước ngẫu nhiên.

Nội dung

Vì sao tái lập là yêu cầu bắt buộc

Một mô hình kinh tế y tế là một lập luận định lượng dùng để hỗ trợ quyết định, ví dụ có nên đưa một xét nghiệm nhanh vào danh mục chi trả hay không. Hội đồng đánh giá công nghệ y tế, người phản biện và đồng nghiệp đều cần kiểm tra lập luận đó. Mô hình dựng trong bảng tính thường khó kiểm tra: công thức nằm rải rác trong các ô, khó so sánh phiên bản, và khó chạy lại hàng nghìn lần cho phân tích bất định. Đó là nhận xét thực tế của người viết. Các tác giả đánh giá công nghệ y tế cũng nêu rằng bảng tính hạn chế tính minh bạch, khả năng tái lập và việc định lượng bất định, còn các ngôn ngữ lập trình như R cho phép xây mô hình minh bạch, tái lập, dùng lại được và biểu diễn được bất định (Incerti và c.s. 2019).

Hai bài viết ngắn đáng đọc trước khi bắt đầu tóm tắt các thói quen tối thiểu: ghi lại cách tạo ra mọi kết quả, tránh thao tác tay, lưu dữ liệu gốc nguyên vẹn, đặt seed cho mọi bước ngẫu nhiên và quản lý phiên bản (Sandve và c.s. 2013; Wilson và c.s. 2017). Khóa học này áp dụng các thói quen đó ngay từ bài đầu.

R và trình soạn mã

R là môi trường tính toán thống kê miễn phí, chạy trên Windows, macOS và Linux (R Core Team không ngày). Bạn viết mã trong một trình soạn mã tích hợp (IDE) như RStudio, Positron hoặc VS Code. Có hai cách chạy mã. Gõ từng lệnh vào console để thử nhanh. Lưu chuỗi lệnh vào một script .R rồi chạy từ đầu đến cuối để có kết quả chính thức. Kết quả chính thức chỉ do script tạo ra, không phải những gì bạn đã gõ vào console.

Gói (package) mở rộng R. Cài một lần bằng install.packages("ggplot2"), nạp ở mỗi phiên bằng library(ggplot2). Khóa học dùng base R cho cơ chế mô hình vì từng bước đều nhìn thấy được, và dùng ggplot2 để vẽ biểu đồ.

Cấu trúc một dự án mô hình

Mỗi module của khóa học là một dự án nhỏ với cùng một cấu trúc:

Thư mục/tệp Chứa gì Quy tắc
params/*.csv Tham số: giá trị, khoảng, phân phối, nguồn Không có số gõ cứng trong mã
data/ Dữ liệu đầu vào Chỉ đọc; tạo ra bằng mã hoặc nhận nguyên trạng
code/model.R Định nghĩa hàm Nạp vào không gây tác dụng phụ
code/lNN-*.R Một phân tích hoàn chỉnh Chạy độc lập từ thư mục gốc
figs/ Hình do mã tạo ra Không sửa tay
lNN-*.qmd Bài viết, kéo mã từ script Mọi con số là mã R nội dòng

Cách tách này giống khung mã hóa được đề xuất cho mô hình quyết định trong R. Khung đó chia công việc thành đầu vào, triển khai mô hình, hiệu chuẩn, thẩm định và phân tích (Alarid-Escudero và c.s. 2019). Lợi ích rất thực tế: muốn đổi một giá thuốc, bạn sửa một dòng CSV và chạy lại, không phải lục tìm trong mã.

Mọi module cùng dùng hai tệp đặt ở thư mục cha: _shared/R/helpers.R (hàm định dạng số, hàm kiểm tra, giao diện đồ họa) và _shared/R/vn_data.R (giá trị dùng chung của Việt Nam: giá dịch vụ BHYT, giá thuốc trúng thầu, lương tối thiểu, CPI, bảng sống). Nhờ vậy, khi Bộ Y tế đổi giá ngày giường, chỉ cần sửa một dòng trong _shared/ và mọi module cập nhật ở lần chạy sau. “Dự án” của khóa học là thư mục gốc kb/, trong đó các module nằm cạnh _shared/. Khi tải về, hãy tải cả _shared/ lẫn module bạn học; mỗi script chạy với thư mục module làm thư mục làm việc.

flowchart LR
  A["params/*.csv<br/>data/ (chỉ đọc)"] --> B["code/model.R<br/>chỉ định nghĩa hàm"]
  B --> C["code/lNN-*.R<br/>phân tích"]
  A --> C
  C --> D["figs/*.png<br/>con số"]
  D --> E["lNN-*.qmd<br/>mã R nội dòng"]
  E --> F["HTML / DOCX"]

flowchart LR
  A["params/*.csv<br/>data/ (chỉ đọc)"] --> B["code/model.R<br/>chỉ định nghĩa hàm"]
  B --> C["code/lNN-*.R<br/>phân tích"]
  A --> C
  C --> D["figs/*.png<br/>con số"]
  D --> E["lNN-*.qmd<br/>mã R nội dòng"]
  E --> F["HTML / DOCX"]

Script hay Quarto?

Script .R là nơi tính toán: chạy từ trên xuống, không phụ thuộc thứ tự bạn bấm nút. Quarto (Posit không ngày) trộn văn bản với mã và xuất ra HTML, Word hoặc PDF. Mỗi lần xuất, toàn bộ mã chạy lại từ đầu. Khóa học dùng cả hai mà không chép mã hai lần: script đánh dấu từng đoạn bằng ## ---- nhãn ----, và bài học kéo đoạn đó vào bằng knitr::read_chunk(). Mã chỉ tồn tại ở một nơi, nên bài viết không bao giờ lệch khỏi mã.

Mọi con số do mã tạo ra

Đây là nguyên tắc 9 trong mười quy tắc của Sandve và cộng sự: mỗi câu nêu kết quả phải truy được về kết quả nền bên dưới (Sandve và c.s. 2013). Trong tệp .qmd, câu nêu tỷ lệ đề kháng không chứa con số gõ tay mà chứa `r fmt_pct(res_2025)` (biểu thức R đặt giữa hai dấu backtick, bắt đầu bằng chữ r). Khi xuất, Quarto chạy biểu thức này và chèn kết quả vào câu. Nếu dữ liệu, tham số hoặc seed thay đổi, con số tự cập nhật. Các hàm fmt_num(), fmt_pct(), fmt_vnd() và fmt_ci() trong _shared/R/helpers.R thống nhất cách viết số cho cả khóa học: dấu chấm thập phân, dấu phẩy phân cách hàng nghìn.

Ghi lại môi trường chạy

Kết quả có thể đổi khi phiên bản R hoặc phiên bản gói thay đổi. Tối thiểu, hãy in phiên bản R và các gói chính ở cuối báo cáo. Với dự án dài hạn, gói renv lưu đúng phiên bản của từng gói vào một tệp khóa để người khác cài lại y hệt (Ushey và Wickham không ngày). Mọi bước ngẫu nhiên đều bắt đầu bằng set.seed() (xem Bài 6).

Thực hành

Mọi script trong khóa học mở đầu như nhau: dừng ngay nếu chạy sai thư mục, bật mã hóa UTF-8 để hiển thị tiếng Việt, nạp các hàm dùng chung (helpers.R, vn_data.R) và hàm của module, rồi bật cảnh báo khi $ tự khớp với tên viết tắt (xem Bài 2). Kiểm tra: không có thông báo lỗi nào.

# Fail early, before anything is sourced, if the script is not run from the module folder
if (!file.exists("code/model.R") || !file.exists("../_shared/R/helpers.R")) {
  stop("Run this script from the module folder (the one that contains code/ and params/); ",
       "the course root must also contain _shared/.")
}
# Vietnamese text needs a UTF-8 locale (Rscript may start in the "C" locale)
if (!isTRUE(l10n_info()[["UTF-8"]])) invisible(Sys.setlocale("LC_CTYPE", "en_US.UTF-8"))
source("../_shared/R/helpers.R")   # shared helpers: formatting, checks, plot theme
source("../_shared/R/vn_data.R")   # shared Vietnamese inputs: prices, wages, life table, CPI
source("code/model.R")             # this module's functions (definitions only)
options(warnPartialMatchDollar = TRUE)   # warn when `$` silently matches a shortened name (Lesson 2)

Lỗi hay gặp nhất của người mới là chạy script từ sai thư mục; các dòng đầu của đoạn mã trên đã chặn lỗi đó. Đoạn mã sau liệt kê thư mục hiện hành và các thư mục của dự án, tạo data/ và figs/ nếu chưa có. Kiểm tra: tên thư mục hiện hành là m00-r-foundations và cả bốn thư mục đều tồn tại.

# Where are we? code/ and params/ must exist; data/ and figs/ are created when missing
stopifnot(dir.exists("code"), dir.exists("params"))
for (f in c("data", "figs")) dir.create(f, showWarnings = FALSE)
basename(getwd())
[1] "m00-r-foundations"
folders <- c("code", "params", "data", "figs")
data.frame(folder = folders, exists = dir.exists(folders))
  folder exists
1   code   TRUE
2 params   TRUE
3   data   TRUE
4   figs   TRUE

Tiếp theo, ta đọc bảng tham số của bộ dữ liệu mô phỏng. read_params() trả về một danh sách có tên, nên mỗi tham số được gọi bằng p_sim$ten_tham_so. Bảng trộn hai loại dòng: mốc đã công bố, có khóa tài liệu trong cột source (tỷ lệ đề kháng, tỷ lệ loài, xu hướng), và giá trị minh họa cho những gì không có số liệu công khai (lỗi nhập liệu, số lần cấy lại). Kiểm tra: bảng đếm theo nguồn cho thấy mỗi dòng có nguồn trỏ tới một khóa trong refs.bib.

p_sim <- read_params("params/amr_sim.csv")
tab_sim <- attr(p_sim, "table")
tab_sim[tab_sim$name %in% c("n_pat_a", "target_kpn_mem", "trend_kpn"),
        c("name", "value", "source", "illustrative")]
             name   value              source illustrative
3         n_pat_a 300.000                             TRUE
21 target_kpn_mem   0.370   larsson2025burden        FALSE
24      trend_kpn   0.146 vu2021antimicrobial        FALSE
n_params <- nrow(tab_sim)
n_sourced <- sum(!as.logical(tab_sim$illustrative))
n_illustrative <- n_params - n_sourced
c(n_params = n_params, n_sourced = n_sourced, n_illustrative = n_illustrative)
      n_params      n_sourced n_illustrative 
            41             24             17 
table(source = ifelse(as.logical(tab_sim$illustrative), "(minh họa)", tab_sim$source))
source
         (minh họa)   larsson2025burden vu2021antimicrobial 
                 17                   3                  21 
stopifnot(n_sourced + n_illustrative == n_params, n_sourced > 0)
p_sim$target_kpn_mem   # access one value by name
[1] 0.37

Bảng có 41 tham số: 24 có nguồn và 17 là giá trị minh họa.

read_params() còn đóng vai trò hàng rào an toàn. Nếu một tham số được khai là số liệu thật (illustrative = FALSE) mà không có nguồn, hàm dừng lại và nêu tên tham số đó. Ta cố ý tạo một bảng lỗi để xem thông báo.

# A "real" parameter without a source is refused by read_params()
bad <- tab_sim[1:3, ]
bad$illustrative[2] <- FALSE
tmp <- tempfile(fileext = ".csv")
write.csv(bad, tmp, row.names = FALSE)
msg <- tryCatch(read_params(tmp), error = function(e) conditionMessage(e))
msg
[1] "Non-illustrative parameters need a source: n_years"

Có nguồn chưa đủ: phải truy được con số về số liệu gốc. Xác suất mắc mới CRE mỗi ngày trong params/icu_markov.csv không có trong bài báo nào; nó được suy ra từ hai mốc công bố (Tran và c.s. 2019). Nếu mỗi ngày một tỷ lệ \(p\) người chưa mang khuẩn bị lây, phần chưa mang khuẩn giảm theo cấp số nhân: \((1 - p)^T = (1 - \pi_T)/(1 - \pi_0)\), với \(\pi_0\) và \(\pi_T\) là tỷ lệ mang khuẩn vào ngày nhập viện và ngày thứ \(T\). Phép suy ra này là một xấp xỉ: điều tra gốc là cắt ngang, gộp cả sơ sinh, trẻ em và người lớn, và ta đọc đường cong theo ngày nằm viện như thể đó là đường theo dõi một đoàn hệ. Các mốc nằm trong params/reference_data.csv; hàm icu_probs_from_anchors() làm lại mọi phép suy ra, và ta so với CSV. Kiểm tra: hai cột khớp nhau đến sai số làm tròn.

# Every number must trace back to its source. The daily probabilities of the ICU model
# (params/icu_markov.csv) were derived from published figures kept in params/reference_data.csv.
# Recompute them in code and compare with the CSV.
ref <- read_params("params/reference_data.csv")
q_csv <- read_params("params/icu_markov.csv")
derived <- icu_probs_from_anchors(ref)
in_csv <- c(unlist(q_csv[setdiff(names(derived), "p_i_alive_exit")]),
            p_i_alive_exit = q_csv$p_i_c + q_csv$p_i_x)
trace_tab <- cbind(csv = in_csv, derived = derived[names(in_csv)])
round(trace_tab, 5)
                  csv derived
p_u_c          0.1288 0.12879
p_u_x          0.0897 0.08966
p_u_d          0.0227 0.02270
p_c_u          0.0088 0.00880
p_c_i          0.0197 0.01972
p_c_x          0.0897 0.08966
p_c_d          0.0227 0.02270
p_i_d          0.0305 0.03048
p_i_alive_exit 0.0657 0.06567
stopifnot(all(abs(trace_tab[, "csv"] - trace_tab[, "derived"]) < 1e-4))   # equal up to rounding

Với \(\pi_0\) = 13%, \(\pi_T\) = 89% và \(T\) = 15 ngày, xác suất mắc mới là 0.1288 mỗi ngày, đúng giá trị 0.1288 trong CSV. Người đọc báo cáo muốn kiểm tra con số này chỉ cần mở bài báo gốc và chạy lại một dòng mã.

Bây giờ ta tạo bộ dữ liệu của module. simulate_isolates() sinh từng chủng phân lập (isolate) theo các tham số trên, quanh các mốc đã công bố; str() cho thấy mỗi cột có kiểu gì. Kiểm tra: bảng chéo bệnh viện × năm không có ô trống, và ba điều kiện trong stopifnot() đều đúng.

iso <- simulate_isolates(p_sim, seed = 2025)
str(iso)
'data.frame':   4080 obs. of  12 variables:
 $ isolate_id: chr  "I00001" "I00002" "I00003" "I00004" ...
 $ patient_id: chr  "P00005" "P00073" "P00190" "P00218" ...
 $ hospital  : chr  "A" "A" "A" "A" ...
 $ ward      : chr  "non-ICU" "ICU" "non-ICU" "ICU" ...
 $ date      : Date, format: "2021-01-01" "2021-01-01" ...
 $ year      : int  2021 2021 2021 2021 2021 2021 2021 2021 2021 2021 ...
 $ organism  : chr  "A. baumannii" "E. coli" "E. coli" "E. coli" ...
 $ CRO       : chr  NA "R" "R" "R" ...
 $ CIP       : chr  "R" "R" "R" "R" ...
 $ GEN       : chr  "R" "R" "R" "R" ...
 $ SXT       : chr  "R" "R" "R" "R" ...
 $ MEM       : chr  "R" "S" "S" "R" ...
table(iso$hospital, iso$year)
   
    2021 2022 2023 2024 2025
  A  426  412  414  383  383
  B  260  264  250  222  252
  C  174  160  150  176  154
# Sanity checks: unique IDs, every antibiotic column present, years as planned
stopifnot(!anyDuplicated(iso$isolate_id),
          all(names(ANTIBIOTICS) %in% names(iso)),
          all(iso$year %in% (p_sim$year_start + 0:(p_sim$n_years - 1))))
n_iso <- nrow(iso)
n_pat <- length(unique(iso$patient_id))

Bộ dữ liệu có 4,080 chủng từ 3,094 người bệnh. Số chủng nhiều hơn số người bệnh vì một số người được cấy nhiều lần.

Phòng xét nghiệm không gửi dữ liệu sạch. make_messy() biến dữ liệu sạch thành một tệp trích xuất từ phòng xét nghiệm (laboratory export) có lỗi nhập liệu điển hình, và ta ghi nó vào data/. Tệp này được tạo ra hoàn toàn bằng mã, nên xóa đi rồi chạy lại vẫn ra đúng tệp cũ. Kiểm tra: số dòng đọc lại phải khớp với số dòng đã ghi.

# The "laboratory export": written by code, never edited by hand
raw <- make_messy(iso, p_sim, seed = 2026)
dir.create("data", showWarnings = FALSE)
write.csv(raw, "data/amr_isolates_raw.csv", row.names = FALSE, na = "")
raw_back <- read.csv("data/amr_isolates_raw.csv", na.strings = "", encoding = "UTF-8")
stopifnot(nrow(raw_back) == nrow(raw), ncol(raw_back) == ncol(raw))
dim(raw_back)
[1] 4100   11

Tệp data/amr_isolates_raw.csv có 4,100 dòng, nhiều hơn dữ liệu sạch 20 dòng do các dòng bị nhập trùng.

Đoạn sau cho thấy vì sao không được gõ số bằng tay. Ta tính cùng một con số, tỷ lệ A. baumannii đề kháng meropenem, trên hai bộ dữ liệu sinh từ hai seed khác nhau. Tỷ lệ này tính trên mọi chủng, kể cả chủng lặp lại; Bài 4 sẽ loại chúng. Kiểm tra: hai kết quả khác nhau.

# One result computed twice: with the seed used in the lesson and with another seed.
# (Computed on all isolates, repeats included; Lesson 4 removes the repeats.)
aba_mem_r <- function(d) mean(d$MEM[d$organism == "A. baumannii"] == "R", na.rm = TRUE)
res_2025 <- aba_mem_r(iso)
res_2026 <- aba_mem_r(simulate_isolates(p_sim, seed = 2026))
c(seed_2025 = fmt_pct(res_2025), seed_2026 = fmt_pct(res_2026))
seed_2025 seed_2026 
  "72.4%"   "73.8%" 
stopifnot(res_2025 != res_2026)   # the lesson says the two results differ

Với seed của bài học, tỷ lệ là 72.4%; với seed khác, 73.8%. Nếu bạn gõ tay con số thứ nhất vào báo cáo rồi sau đó đổi seed hay sửa dữ liệu, báo cáo sẽ sai mà không ai biết. Câu văn bạn vừa đọc dùng mã R nội dòng, nên nó luôn đúng.

Cuối cùng, ghi lại môi trường đã tạo ra kết quả.

c(R = R.version.string,
  ggplot2 = as.character(utils::packageVersion("ggplot2")),
  platform = R.version$platform)
                             R                        ggplot2 
"R version 4.5.3 (2026-03-11)"                        "4.0.2" 
                      platform 
      "aarch64-apple-darwin20" 
Mẹo thực hành
  • Trong RStudio hoặc Positron, tạo một Project cho mỗi dự án. Khi mở project, thư mục làm việc tự động là thư mục gốc, nên mọi đường dẫn tương đối đều đúng.
  • Thỉnh thoảng khởi động lại R (Session → Restart R) rồi chạy script từ đầu. Nếu script chỉ chạy được khi console còn “nhớ” một biến cũ, đó là lỗi cần sửa ngay.
  • Đặt tên tham số ngắn, không dấu, có quy ước (p_ cho xác suất, c_ cho chi phí); dùng cột label_vi để mô tả bằng tiếng Việt.
  • Chạy cả script bằng Rscript code/ten-script.R trong terminal. Đó là phép thử rằng script không phụ thuộc vào bất cứ thứ gì bạn đã làm bằng tay.
Lỗi thường gặp
  • Dùng setwd("C:/Users/..."). Đường dẫn tuyệt đối chỉ đúng trên máy bạn. Hãy dùng đường dẫn tương đối từ thư mục gốc của project.
  • Sửa dữ liệu thô bằng Excel. Thao tác tay không để lại dấu vết. Mọi chỉnh sửa phải nằm trong mã; tệp gốc giữ nguyên.
  • Gõ số vào văn bản. Dấu hiệu nhận biết: trong tệp .qmd có một con số kết quả không nằm trong mã R nội dòng.
  • Nhầm thứ tự ưu tiên toán tử. x %in% a + 0:4 được hiểu là (x %in% a) + 0:4. Khi viết kiểm tra, hãy thêm ngoặc: x %in% (a + 0:4), rồi chạy thử kiểm tra trên một ví dụ đã biết đáp án để chắc rằng stopifnot() thực sự bắt được lỗi.

Bài tập

Bài tập 1 (dễ). Đếm số chủng theo loài vi khuẩn và bệnh viện. Ở mỗi bệnh viện, bao nhiêu phần trăm chủng lấy từ khoa hồi sức tích cực (ICU)?

table() đếm theo hai chiều. Trung bình của một vectơ logic chính là tỷ lệ phần tử TRUE, và tapply() tính trung bình đó cho từng bệnh viện.

# Exercise 1: isolates per organism and hospital, and share of isolates from ICU
table(iso$organism, iso$hospital)
               
                   A    B    C
  A. baumannii   539  304  201
  E. coli       1047  655  410
  K. pneumoniae  432  289  203
round(tapply(iso$ward == "ICU", iso$hospital, mean), 3)
    A     B     C 
0.200 0.194 0.216 

Bài tập 2 (vừa). Tăng gấp đôi quy mô bệnh viện C bằng cách sửa tham số trong R, không sửa CSV. Chạy lại mô phỏng với cùng seed và so sánh số chủng của bệnh viện C.

Sao chép danh sách tham số, đổi một phần tử, rồi chạy lại. Tệp CSV không thay đổi, nên phân tích cơ sở vẫn nguyên vẹn.

# Exercise 2: double the size of hospital C, re-simulate, compare total isolates
p_big <- p_sim
p_big$n_pat_c <- 2 * p_sim$n_pat_c
iso_big <- simulate_isolates(p_big, seed = 2025)
c(base = sum(iso$hospital == "C"), doubled = sum(iso_big$hospital == "C"))
   base doubled 
    814    1548 

Bài tập 3 (khó hơn). Nạp tham số của mô hình ICU bằng load_icu_params(). Có bao nhiêu tham số được gắn một phân phối xác suất, bao nhiêu có nguồn và bao nhiêu là giá trị minh họa? Hai chi phí lấy từ bảng giá dùng chung nằm ở đâu, và vì sao không được chép chúng vào params/icu_markov.csv?

Bảng đầy đủ nằm trong thuộc tính "table" của kết quả.

# Exercise 3: which ICU parameters are sourced and which illustrative, and which have a
# probability distribution attached? load_icu_params() adds the two shared prices.
tab_icu <- attr(load_icu_params(), "table")
table(dist = tab_icu$dist, illustrative = tab_icu$illustrative)
       illustrative
dist    FALSE TRUE
  beta      8    2
  fixed     0    5
  gamma     2    1
tab_icu[tab_icu$name %in% c("c_icu_day", "c_infection"), c("name", "value", "source")]
                   name   value                            source
c_icu_day     c_icu_day 1017300                     byt2024qd3220
c_infection c_infection 3801137 thainguyen2023thau; cuchi2024thau
n_icu_sourced <- sum(!as.logical(tab_icu$illustrative))
n_icu_illustrative <- sum(as.logical(tab_icu$illustrative))
# The lesson text lists the illustrative ones: check the list is still right
stopifnot(setequal(tab_icu$name[as.logical(tab_icu$illustrative)],
                   c("n_days", "outbreak_rr", "outbreak_start", "outbreak_end", "c_contact",
                     "cost_gamma_shape", "p_i_c", "p_i_x")))

Mô hình ICU có 10 tham số có nguồn và 8 giá trị minh họa (số ngày theo dõi, ba tham số của đợt bùng phát, chi phí phòng ngừa tiếp xúc, độ rộng phân phối của chi phí, và hai xác suất chia lối ra của người đang nhiễm khuẩn). Giá ngày giường và giá thuốc nằm trong _shared/params/vn_prices.csv; load_icu_params() đọc chúng qua vn_prices(). Nếu chép vào CSV của module, khi bảng giá chung được cập nhật, module này sẽ âm thầm dùng giá cũ, và hai module có thể báo hai giá khác nhau cho cùng một ngày giường. Ngay cả khi mọi tham số có nguồn, mô hình vẫn đơn giản hóa thực tế (một ICU chung, không phân tầng bệnh nặng), nên kết quả là con số gần đúng để học, chưa đủ để ra quyết định cho một bệnh viện cụ thể.

Tài liệu tham khảo

# Slide Lời giảng chính Hình/Code
1 Vì sao tái lập Mô hình là lập luận hỗ trợ quyết định; ai cũng phải chạy lại được; bảng tính khó kiểm tra Trích Incerti 2019
2 R và IDE Console để thử, script để chạy thật; cài gói một lần, nạp mỗi phiên Màn hình RStudio/Positron
3 Cấu trúc dự án Bốn thư mục; ba lớp đầu vào, mô hình, phân tích Bảng thư mục, sơ đồ Mermaid
4 Chạy đúng thư mục stop() nếu thiếu code/model.R; _shared/ đặt cạnh module; Project trong IDE l01-setup, l01-check-wd
5 Bảng tham số Một dòng một tham số; nguồn hoặc cờ minh họa; _shared/ cho giá trị chung l01-params
6 Hàng rào an toàn và truy nguồn read_params() từ chối số liệu “thật” không có nguồn; tính lại xác suất mắc mới CRE từ 13% và 89% l01-guardrail, l01-trace-source
7 Dữ liệu do mã tạo Mô phỏng rồi tạo tệp trích xuất có lỗi; dữ liệu thô chỉ đọc l01-simulate, l01-save-raw
8 Script và Quarto Một nơi chứa mã; read_chunk() kéo đoạn mã vào bài Nhãn ## ---- l01-setup ----
9 Mọi con số do mã tạo ra Hai seed, hai con số khác nhau; số gõ tay sẽ lỗi thời l01-every-number
10 Ghi lại môi trường Phiên bản R và gói; renv; seed l01-session
11 Lỗi thường gặp setwd() tuyệt đối; sửa dữ liệu bằng Excel; thứ tự toán tử Callout cảnh báo