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ức nhóm vi khuẩn đường ruột kháng carbapenem, 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ữ 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ể:
Chạy một script R từ thư mục gốc của dự án, bằng IDE hoặc bằng Rscript.
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.
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.
Chọn đúng công cụ: script .R cho phần tính toán, Quarto .qmd cho phần báo cáo.
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 đều tính tương đối, tức tính 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, loại đường dẫn đầy đủ chỉ đúng trên máy bạn.
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, còn mọi bước làm sạch đều viết thành mã.
Mỗi tham số, tức mỗi con số đầu vào của mô hình, chiếm 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, chỉ số giá tiêu dù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ô hình kinh tế y tế là lập luận định lượng dùng để hỗ trợ ra quyết định, chẳng hạn 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 với các bộ tham số khác nhau để 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à khả năng đị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).
Trước khi bắt đầu, có hai bài viết ngắn đáng đọc, 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 (giá trị khởi tạo bộ sinh số ngẫu nhiên) 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 trình soạn mã tích hợp (IDE) như RStudio, Positron hoặc VS Code. Có hai cách chạy mã: muốn thử nhanh thì gõ từng lệnh vào console, khung mà R chạy ngay lệnh vừa gõ; muốn có kết quả chính thức thì lưu chuỗi lệnh vào một script.R rồi chạy từ đầu đến cuối. Kết quả chính thức chỉ do script tạo ra, chứ không phải những gì bạn đã gõ vào console.
Gói (package) là bộ hàm viết sẵn để mở rộng R; hàm ở đây là lệnh có tên, nhận đầu vào rồi trả về kết quả. Mỗi gói cài một lần bằng install.packages("ggplot2"), nhưng mỗi lần mở R lại phải nạp bằng library(ggplot2). Khóa học dùng base R, tức phần R có sẵn khi cài, 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 đều là dự án nhỏ, chung 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ụ (không chạy phân tích, không ghi tệp)
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 viết mã đề xuất cho mô hình quyết định trong R, vốn 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 chung cho biểu đồ) 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, ta sửa đúng một dòng trong _shared/ là 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, tức nơi R tìm và ghi tệp.
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à mỗi lần xuất thì 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ỉ nằm ở 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(), để mỗi lần chạy lại ra đúng dãy số cũ (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 $, phép lấy phần tử theo tên, tự khớp với tên viết tắt (xem Bài 2). Kiểm tra: R không báo lỗi nào.
# Fail early, before anything is sourced, if the script is not run from the module folderif (!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 themesource("../_shared/R/vn_data.R") # shared Vietnamese inputs: prices, wages, life table, CPIsource("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 missingstopifnot(dir.exists("code"), dir.exists("params"))for (f inc("data", "figs")) dir.create(f, showWarnings =FALSE)basename(getwd())
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 ta gọi mỗi tham số 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, tệp tài liệu tham khảo.
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 làm hàng rào an toàn: nếu bảng khai một tham số là số liệu thật (illustrative = FALSE) mà không ghi nguồn, hàm dừng lại và nêu tên tham số đó. Đoạn mã sau cố ý tạo một bảng lỗi như vậy để xem thông báo. Kiểm tra: thông báo nêu đúng tên tham số thiếu nguồn.
# A "real" parameter without a source is refused by read_params()bad <- tab_sim[1:3, ]bad$illustrative[2] <-FALSEtmp <-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ó sẵn trong bài báo nào, mà suy ra từ hai mốc công bố (Tran và c.s. 2019). Gọ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\). Nếu mỗi ngày có 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)\). Phép suy ra này là 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; đoạn mã sau dùng hàm icu_probs_from_anchors() làm lại mọi phép suy ra rồi 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)
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. Ai đọc báo cáo mà muốn kiểm tra con số này thì mở bài báo gốc và chạy lại một dòng mã là đủ.
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 (hàm này dừng script khi có điều kiện sai).
iso <-simulate_isolates(p_sim, seed =2025)str(iso)
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 plannedstopifnot(!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, rồi ta ghi tệp ấy vào data/. Tệp này do mã tạo ra hoàn toàn, 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 handraw <-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 mã sau cho thấy vì sao không được gõ số bằng tay: ta tính tỷ lệ A. baumannii đề kháng meropenem hai lần, 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 các chủng lặp lại ấy. 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 luôn đúng.
Cuối cùng, ta ghi lại môi trường đã tạo ra kết quả: phiên bản R, phiên bản gói vẽ biểu đồ và nền tảng máy.
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, tức cửa sổ dòng lệnh của máy. Lệnh này khởi động R từ đầu, nên chạy được nghĩa là 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ử. R hiểu x %in% a + 0:4 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)?
Lời giải
table() đếm theo hai chiều. Trung bình của một vectơ logic, tức dãy giá trị đúng/sai, chính là tỷ lệ phần tử TRUE, vì R coi đúng là một, sai là không; 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 ICUtable(iso$organism, iso$hospital)
A B C
A. baumannii 539 304 201
E. coli 1047 655 410
K. pneumoniae 432 289 203
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.
Lời giải
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ở, tức kết quả với bộ tham số gốc, vẫn nguyên vẹn.
# Exercise 2: double the size of hospital C, re-simulate, compare total isolatesp_big <- p_simp_big$n_pat_c <-2* p_sim$n_pat_ciso_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?
Lời giải
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)
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 rightstopifnot(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 giá vào CSV của module thì khi bảng giá chung 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
Alarid-Escudero, Fernando, Eline M. Krijkamp, Petros Pechlivanoglou, Hawre Jalal, Szu-Yu Zoe Kao, Alan Yang, và Eva A. Enns. 2019. “A need for change! A coding framework for improving transparency in decision modeling”. PharmacoEconomics 37 (11): 1329–39. https://doi.org/10.1007/s40273-019-00837-x.
Incerti, Devin, Howard Thom, Gianluca Baio, và Jeroen P. Jansen. 2019. “R you still using Excel? The advantages of modern software tools for health technology assessment”. Value in Health 22 (5): 575–79. https://doi.org/10.1016/j.jval.2019.01.003.
Posit. không ngày. “Quarto: an open-source scientific and technical publishing system”. Truy cập 8 Tháng Mười 2026. https://quarto.org/.
R Core Team. không ngày. “The R Project for Statistical Computing”. R Foundation for Statistical Computing. Truy cập 8 Tháng Mười 2026. https://www.r-project.org/.
Sandve, Geir Kjetil, Anton Nekrutenko, James Taylor, và Eivind Hovig. 2013. “Ten simple rules for reproducible computational research”. PLoS Computational Biology 9 (10): e1003285. https://doi.org/10.1371/journal.pcbi.1003285.
Tran, Dien M., Mattias Larsson, Linus Olson, Ngoc T. B. Hoang, Ngai K. Le, Dung T. K. Khu, và c.s. 2019. “High prevalence of colonisation with carbapenem-resistant Enterobacteriaceae among patients admitted to Vietnamese hospitals: risk factors and burden of disease”. Journal of Infection 79 (2): 115–22. https://doi.org/10.1016/j.jinf.2019.05.013.
Ushey, Kevin, và Hadley Wickham. không ngày. “renv: project environments for R”. Truy cập 8 Tháng Mười 2026. https://rstudio.github.io/renv/.
Wilson, Greg, Jennifer Bryan, Karen Cranston, Justin Kitzes, Lex Nederbragt, và Tracy K. Teal. 2017. “Good enough practices in scientific computing”. PLoS Computational Biology 13 (6): e1005510. https://doi.org/10.1371/journal.pcbi.1005510.
Kịch bản video (~12 phút)
#
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ử