Quy trình R cho bài báo lâm sàng mình dùng hiện tại là kết quả của rất nhiều lần làm lại từ đầu vì thiếu một bước nào đó ở giữa chừng. Những năm đầu, mình viết code phân tích rời rạc theo từng yêu cầu của cố vấn hoặc phản biện, không theo một trình tự cố định, và mỗi lần dữ liệu thay đổi là phải sửa lại gần như toàn bộ. Sau nhiều bài báo lâm sàng, mình gom lại thành một quy trình xuyên suốt, từ lúc nhận file CSV thô đến khi có bảng 1 bằng gtsummary, kết quả hồi quy, và biểu đồ sẵn sàng đưa vào bản thảo. Bài này mình trình bày từng bước của quy trình đó, kèm những điểm hay gây lỗi nhất.
Vì sao cần một quy trình R rõ ràng thay vì viết code tuỳ hứng
Một bài báo lâm sàng thường trải qua nhiều vòng chỉnh sửa: phản biện yêu cầu thêm phân tích phụ, đồng tác giả muốn xem lại cách xử lý dữ liệu thiếu, hoặc tự mình phát hiện một biến cần mã hoá lại. Nếu code phân tích không theo một trình tự rõ ràng, mỗi lần chỉnh sửa như vậy dễ kéo theo sai sót ở bước khác mà không nhận ra ngay.
Quy trình mình dùng hiện tại chia thành năm bước cố định: làm sạch dữ liệu, mô tả bằng gtsummary, phân tích hồi quy, trực quan hoá, và đóng gói để tái lập. Mỗi bước là một file hoặc một chunk code riêng, chạy tuần tự và không phụ thuộc ngược vào bước sau. Cách chia này giúp mình sửa một bước mà không phải chạy lại từ đầu toàn bộ phân tích.
Một lợi ích khác ít ai nhắc đến là quy trình chuẩn hoá giúp mình tái sử dụng gần như nguyên vẹn khung code cho bài báo tiếp theo, chỉ cần đổi tên biến và đường dẫn dữ liệu. Thời gian tiết kiệm được ở những bài sau lớn hơn nhiều so với công sức bỏ ra để chuẩn hoá lần đầu.
Bước 1: nhập và làm sạch dữ liệu thô từ CSV
Dữ liệu lâm sàng khi xuất ra từ hồ sơ bệnh án hoặc form thu thập gần như luôn cần làm sạch trước khi phân tích. Các lỗi thường gặp mình hay thấy là tên biến không nhất quán giữa các lần nhập liệu, giá trị thiếu được mã hoá bằng nhiều ký hiệu khác nhau trong cùng một cột, và định dạng ngày tháng không đồng nhất giữa các dòng.
Bước đầu tiên trong quy trình của mình luôn là đọc file CSV, kiểm tra kiểu dữ liệu của từng cột, và chuẩn hoá tên biến về một quy ước duy nhất bằng các hàm của tidyverse. Mình cũng luôn thêm một lớp kiểm tra logic: tuổi phải lớn hơn 0, ngày xuất viện phải sau ngày nhập viện, cân nặng phải nằm trong khoảng hợp lý theo tuổi. Những kiểm tra này bắt được không ít lỗi nhập liệu mà mắt thường dễ bỏ sót khi nhìn cả bảng dữ liệu lớn.
Mình cũng luôn tạo một bản ghi riêng liệt kê những dòng bị loại và lý do loại, để khi phản biện hỏi về số lượng mẫu phân tích, mình có sẵn câu trả lời thay vì phải dò lại dữ liệu gốc. Làm sạch dữ liệu kỹ ở bước này giúp tất cả các bước sau chạy trơn tru, vì phần lớn lỗi phân tích thống kê mình từng gặp thực ra bắt nguồn từ dữ liệu chưa sạch chứ không phải từ mô hình sai.
Bước 2: mô tả dữ liệu và tạo bảng 1 bằng gtsummary
Sau khi dữ liệu sạch, bước tiếp theo là mô tả đặc điểm mẫu nghiên cứu, thường gọi là bảng 1 trong hầu hết bài báo lâm sàng. Gói gtsummary trong R giúp bước này nhanh hơn nhiều so với việc tự tính từng con số rồi gõ tay vào bảng Word.
Mình dùng gtsummary để tính trung bình, độ lệch chuẩn hoặc trung vị tuỳ theo phân phối của biến, tỷ lệ phần trăm cho biến phân loại, và so sánh giữa các nhóm kèm giá trị p nếu nghiên cứu có so sánh nhóm. Gói này cũng xử lý giá trị thiếu tự động, cho phép đổi theme bảng theo định dạng của từng tạp chí, và xuất trực tiếp sang Word hoặc HTML mà không cần định dạng lại thủ công.
Một bảng 1 chuẩn không chỉ để liệt kê số liệu, mà còn giúp mình phát hiện sớm những bất thường trong dữ liệu, ví dụ một biến có độ lệch chuẩn quá lớn so với kỳ vọng lâm sàng, trước khi bước vào phân tích sâu hơn. Nếu một con số ở bảng 1 trông bất thường, gần như chắc chắn có vấn đề ở bước làm sạch dữ liệu cần quay lại kiểm tra.
Bước 3: phân tích hồi quy và diễn giải kết quả
Tuỳ loại thiết kế nghiên cứu, bước này có thể là hồi quy logistic cho biến kết quả nhị phân, hồi quy Cox cho dữ liệu sống còn, hoặc hồi quy tuyến tính cho biến liên tục. Mình luôn kiểm tra giả định của mô hình trước khi diễn giải hệ số, một bước dễ bị bỏ qua khi đang vội hoàn thành bản thảo.
Với hồi quy logistic, mình kiểm tra đa cộng tuyến giữa các biến độc lập và độ phù hợp tổng thể của mô hình trước khi báo cáo tỷ số chênh. Với hồi quy Cox, mình kiểm tra giả định nguy cơ tỷ lệ không đổi theo thời gian, vì vi phạm giả định này có thể làm sai lệch toàn bộ cách diễn giải kết quả sống còn.
Sau khi mô hình chạy xong, mình dùng gtsummary để xuất bảng kết quả hồi quy với tỷ số chênh hoặc tỷ số nguy cơ kèm khoảng tin cậy 95%, đúng định dạng cần cho phần Kết quả của bài báo. Việc giữ nguyên một package cho cả bảng mô tả và bảng hồi quy giúp định dạng bảng nhất quán từ đầu đến cuối bản thảo, thay vì phải ghép nhiều nguồn bảng khác nhau.
Bước 4: trực quan hoá bằng biểu đồ Kaplan-Meier hoặc forest plot
Với dữ liệu sống còn, biểu đồ Kaplan-Meier là hình gần như bắt buộc phải có trong bài báo. Mình dùng gói survminer kết hợp với package survival để vẽ đường cong, kèm bảng số lượng nguy cơ theo thời gian và giá trị p của kiểm định log-rank ngay trên hình.
Với các bài báo dạng tổng quan hệ thống hoặc phân tích gộp, forest plot là hình tương đương, thể hiện kích thước hiệu quả và khoảng tin cậy của từng nghiên cứu đưa vào. Cả hai loại biểu đồ này mình đều cố gắng vẽ bằng code thay vì phần mềm giao diện kéo thả, vì code cho phép chỉnh sửa nhanh khi phản biện yêu cầu đổi định dạng, thay vì phải dựng lại hình từ đầu.
Mình luôn xuất hình ở định dạng và độ phân giải theo đúng yêu cầu của tạp chí ngay từ lần vẽ đầu tiên, thường là file tiff hoặc eps với độ phân giải tối thiểu 300dpi, để tránh phải quay lại chỉnh hình vào phút cuối trước khi nộp bản thảo.
Bước 5: đóng gói quy trình để reviewer chạy lại được
Bước cuối cùng, và theo mình là bước quan trọng nhất mà ít người làm đủ kỹ, là đóng gói toàn bộ quy trình thành một file R Markdown hoặc Quarto duy nhất, chạy được từ đầu đến cuối mà không cần can thiệp tay. Khi phản biện hỏi về cách xử lý dữ liệu hoặc muốn tự chạy lại phân tích, mình chỉ cần gửi file này kèm dữ liệu đã được ẩn danh.
Mình cũng cố định phiên bản các package đang dùng, thường bằng renv, để đảm bảo kết quả không thay đổi khi package được cập nhật sau này. Việc này không chỉ phục vụ tái lập khi công bố, mà còn giúp chính mình quay lại phân tích sau vài tháng mà không mất công nhớ lại đã làm gì ở từng bước, vì toàn bộ logic đã nằm sẵn trong file, có chú thích rõ ràng.
Cấu trúc thư mục dự án mình dùng cho mỗi bài báo
Mỗi dự án mình đều tổ chức theo cùng một cấu trúc thư mục: một thư mục chứa dữ liệu thô không bao giờ chỉnh sửa trực tiếp, một thư mục chứa dữ liệu đã làm sạch, một thư mục chứa các file code được đánh số theo đúng thứ tự chạy, và một thư mục chứa kết quả xuất ra gồm bảng và hình.
Cách đặt tên file theo số thứ tự, ví dụ file đầu tiên bắt đầu bằng số 01, file thứ hai bắt đầu bằng số 02, giúp bất kỳ ai mở dự án lần đầu cũng biết ngay cần chạy theo trình tự nào mà không phải hỏi lại mình. Cấu trúc này nhỏ nhưng tiết kiệm rất nhiều thời gian khi làm việc với đồng tác giả hoặc khi bàn giao lại dự án cho học viên.
Kiểm tra trước khi nộp bản thảo
Trước khi gửi bản thảo, mình luôn chạy lại toàn bộ quy trình từ đầu trên một máy sạch, không dùng biến hay kết quả còn lưu trong bộ nhớ từ những lần chạy trước. Cách này bắt được không ít lỗi ẩn, ví dụ một đoạn code vô tình phụ thuộc vào một biến tạm đã bị xoá nhưng vẫn chạy được nhờ còn lưu trong bộ nhớ.
Mình cũng đối chiếu từng con số xuất hiện trong phần Kết quả và Bàn luận của bản thảo với bảng xuất ra từ gtsummary, đảm bảo không có con số nào được gõ tay sai lệch so với bảng gốc. Bước đối chiếu này tốn thời gian nhưng là lần kiểm tra cuối cùng trước khi một con số sai lọt vào bài báo đã công bố.
Những lỗi mình từng gặp khi mới xây quy trình này
Lỗi phổ biến nhất mình từng mắc là không cố định phiên bản package, khiến kết quả thay đổi nhẹ giữa các lần chạy lại sau khi package được cập nhật. Lỗi thứ hai là gộp bước làm sạch dữ liệu và bước phân tích vào cùng một file, khiến mỗi lần sửa một biến là phải chạy lại toàn bộ từ đầu, rất mất thời gian với dữ liệu lớn.
Nếu bạn mới bắt đầu học R từ số 0, mình có bài viết riêng về lộ trình tự học R cho bác sĩ từ zero đến publication-ready, trong đó có phần nền tảng cần nắm trước khi xây quy trình phức tạp như trên. Nếu bạn đã quen SPSS và đang cân nhắc chuyển sang R, bài SPSS hay R cho bác sĩ lâm sàng, chọn cái nào có thể giúp bạn quyết định trước khi đầu tư thời gian học quy trình này.
Bắt đầu xây quy trình R cho bài báo đầu tiên của bạn
Quy trình mình chia sẻ trong bài này là kết quả tích luỹ qua một số bài báo lâm sàng mình đã và đang thực hiện, bao gồm cả một bài tổng quan hệ thống và phân tích gộp hiện đang trong quá trình bình duyệt. Bạn không cần xây đủ cả năm bước ngay từ bài báo đầu tiên, nhưng càng chuẩn hoá sớm, càng đỡ phải làm lại khi dữ liệu hoặc yêu cầu phản biện thay đổi.
Nếu bạn muốn có sẵn code mẫu cho từng bước, từ làm sạch dữ liệu, gtsummary, hồi quy, đến biểu đồ Kaplan-Meier, khoá học Làm chủ R cùng AI của mình có 13 bài học với 5 bài đầu miễn phí, dùng dữ liệu lâm sàng thật và code mẫu paste vào là chạy được ngay.