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ể:
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 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 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 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.
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] <-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ó 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)
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)
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, 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 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 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.
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)?
Lời giải
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 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ở 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 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
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ử