@iremaydas
Cung cấp hướng dẫn lập trình R cơ bản (base R) bao gồm cấu trúc dữ liệu, xử lý dữ liệu (data wrangling), mô hình hóa thống kê, trực quan hóa và nhập/xuất (I/O), chỉ sử dụng các gói có sẵn trong bản cài đặt R tiêu chuẩn
---
name: base-r
description: Cung cấp hướng dẫn lập trình R cơ bản (base R) bao gồm cấu trúc dữ liệu, xử lý dữ liệu (data wrangling), mô hình hóa thống kê, trực quan hóa và nhập/xuất (I/O), chỉ sử dụng các gói có sẵn trong bản cài đặt R tiêu chuẩn
---
# Kỹ năng Lập trình R Cơ bản (Base R)
Tài liệu tham khảo toàn diện cho lập trình R cơ bản — bao gồm cấu trúc dữ liệu, luồng điều khiển, hàm, nhập/xuất (I/O), tính toán thống kê và vẽ biểu đồ.
## Tham khảo Nhanh
### Cấu trúc Dữ liệu
```r
# Vectors (atomic)
x <- c(1, 2, 3) # numeric
y <- c("a", "b", "c") # character
z <- c(TRUE, FALSE, TRUE) # logical
# Factor
f <- factor(c("low", "med", "high"), levels = c("low", "med", "high"), ordered = TRUE)
# Matrix
m <- matrix(1:6, nrow = 2, ncol = 3)
m[1, ] # first row
m[, 2] # second column
# List
lst <- list(name = "ali", scores = c(90, 85), passed = TRUE)
lst$name # access by name
lst[[2]] # access by position
# Data frame
df <- data.frame(
id = 1:3,
name = c("a", "b", "c"),
value = c(10.5, 20.3, 30.1),
stringsAsFactors = FALSE
)
df[df$value > 15, ] # filter rows
df$new_col <- df$value * 2 # add column
```
### Trích xuất Tập con (Subsetting)
```r
# Vectors
x[1:3] # by position
x[c(TRUE, FALSE)] # by logical
x[x > 5] # by condition
x[-1] # exclude first
# Data frames
df[1:5, ] # first 5 rows
df[, c("name", "value")] # select columns
df[df$value > 10, "name"] # filter + select
subset(df, value > 10, select = c(name, value))
# which() for index positions
idx <- which(df$value == max(df$value))
```
### Luồng Điều khiển (Control Flow)
```r
# if/else
if (x > 0) {
"positive"
} else if (x == 0) {
"zero"
} else {
"negative"
}
# ifelse (vectorized)
ifelse(x > 0, "pos", "neg")
# for loop
for (i in seq_along(x)) {
cat(i, x[i], "\n")
}
# while
while (condition) {
# body
if (stop_cond) break
}
# switch
switch(type,
"a" = do_a(),
"b" = do_b(),
stop("Unknown type")
)
```
### Hàm (Functions)
```r
# Define
my_func <- function(x, y = 1, ...) {
result <- x + y
return(result) # or just: result
}
# Anonymous functions
sapply(1:5, function(x) x^2)
# R 4.1+ shorthand:
sapply(1:5, \(x) x^2)
# Useful: do.call for calling with a list of args
do.call(paste, list("a", "b", sep = "-"))
```
### Họ Hàm Apply (Apply Family)
```r
# sapply — simplify result to vector/matrix
sapply(lst, length)
# lapply — always returns list
lapply(lst, function(x) x[1])
# vapply — like sapply but with type safety
vapply(lst, length, integer(1))
# apply — over matrix margins (1=rows, 2=cols)
apply(m, 2, sum)
# tapply — apply by groups
tapply(df$value, df$group, mean)
# mapply — multivariate
mapply(function(x, y) x + y, 1:3, 4:6)
# aggregate — like tapply for data frames
aggregate(value ~ group, data = df, FUN = mean)
```
### Thao tác Chuỗi (String Operations)
```r
paste("a", "b", sep = "-") # "a-b"
paste0("x", 1:3) # "x1" "x2" "x3"
sprintf("%.2f%%", 3.14159) # "3.14%"
nchar("hello") # 5
substr("hello", 1, 3) # "hel"
gsub("old", "new", text) # replace all
grep("pattern", x) # indices of matches
grepl("pattern", x) # logical vector
strsplit("a,b,c", ",") # list("a","b","c")
trimws(" hi ") # "hi"
tolower("ABC") # "abc"
```
### Nhập/Xuất Dữ liệu (Data I/O)
```r
# CSV
df <- read.csv("data.csv", stringsAsFactors = FALSE)
write.csv(df, "output.csv", row.names = FALSE)
# Tab-delimited
df <- read.delim("data.tsv")
# General
df <- read.table("data.txt", header = TRUE, sep = "\t")
# RDS (single R object, preserves types)
saveRDS(obj, "data.rds")
obj <- readRDS("data.rds")
# RData (multiple objects)
save(df1, df2, file = "data.RData")
load("data.RData")
# Connections
con <- file("big.csv", "r")
chunk <- readLines(con, n = 100)
close(con)
```
### Vẽ Biểu đồ Cơ bản (Base Plotting)
```r
# Scatter
plot(x, y, main = "Title", xlab = "X", ylab = "Y",
pch = 19, col = "steelblue", cex = 1.2)
# Line
plot(x, y, type = "l", lwd = 2, col = "red")
lines(x, y2, col = "blue", lty = 2) # add line
# Bar
barplot(table(df$category), main = "Counts",
col = "lightblue", las = 2)
# Histogram
hist(x, breaks = 30, col = "grey80",
main = "Distribution", xlab = "Value")
# Box plot
boxplot(value ~ group, data = df,
col = "lightyellow", main = "By Group")
# Multiple plots
par(mfrow = c(2, 2)) # 2x2 grid
# ... four plots ...
par(mfrow = c(1, 1)) # reset
# Save to file
png("plot.png", width = 800, height = 600)
plot(x, y)
dev.off()
# Add elements
legend("topright", legend = c("A", "B"),
col = c("red", "blue"), lty = 1)
abline(h = 0, lty = 2, col = "grey")
text(x, y, labels = names, pos = 3, cex = 0.8)
```
### Thống kê (Statistics)
```r
# Descriptive
mean(x); median(x); sd(x); var(x)
quantile(x, probs = c(0.25, 0.5, 0.75))
summary(df)
cor(x, y)
table(df$category) # frequency table
# Linear model
fit <- lm(y ~ x1 + x2, data = df)
summary(fit)
coef(fit)
predict(fit, newdata = new_df)
confint(fit)
# t-test
t.test(x, y) # two-sample
t.test(x, mu = 0) # one-sample
t.test(before, after, paired = TRUE)
# Chi-square
chisq.test(table(df$a, df$b))
# ANOVA
fit <- aov(value ~ group, data = df)
summary(fit)
TukeyHSD(fit)
# Correlation test
cor.test(x, y, method = "pearson")
```
### Thao tác Dữ liệu (Data Manipulation)
```r
# Merge (join)
merged <- merge(df1, df2, by = "id") # inner
merged <- merge(df1, df2, by = "id", all = TRUE) # full outer
merged <- merge(df1, df2, by = "id", all.x = TRUE) # left
# Reshape
wide <- reshape(long, direction = "wide",
idvar = "id", timevar = "time", v.names = "value")
long <- reshape(wide, direction = "long",
varying = list(c("v1", "v2")), v.names = "value")
# Sort
df[order(df$value), ] # ascending
df[order(-df$value), ] # descending
df[order(df$group, -df$value), ] # multi-column
# Remove duplicates
df[!duplicated(df), ]
df[!duplicated(df$id), ]
# Stack / combine
rbind(df1, df2) # stack rows (same columns)
cbind(df1, df2) # bind columns (same rows)
# Transform columns
df$log_val <- log(df$value)
df$category <- cut(df$value, breaks = c(0, 10, 20, Inf),
labels = c("low", "med", "high"))
```
### Môi trường & Gỡ lỗi (Environment & Debugging)
```r
ls() # list objects
rm(x) # remove object
rm(list = ls()) # clear all
str(obj) # structure
class(obj) # class
typeof(obj) # internal type
is.na(x) # check NA
complete.cases(df) # rows without NA
traceback() # after error
debug(my_func) # step through
browser() # breakpoint in code
system.time(expr) # timing
Sys.time() # current time
```
## Các Tệp Tham khảo
Để tìm hiểu sâu hơn, hãy đọc các tệp tham khảo trong thư mục `references/`:
### Những Cạm bẫy của Hàm & Tham khảo Nhanh (được cô đọng từ Tài liệu Tham khảo R 4.5.3)
Các hành vi khó nhận biết, giá trị mặc định gây bất ngờ và những tương tác phức tạp — chỉ tập trung vào những gì Claude chưa biết sẵn:
- **data-wrangling.md** — Đọc khi: việc trích xuất tập con (subsetting) trả về sai kiểu dữ liệu, dùng hàm apply trên data frame gây ép kiểu ngoài ý muốn, merge/split/cbind hoạt động kỳ lạ, các mức (levels) của factor vẫn tồn tại sau khi lọc, các trường hợp biên của table/duplicated.
- **modeling.md** — Đọc khi: cú pháp công thức (formula) gây nhầm lẫn (`I()`, `*` so với `:`, `/`), aov đưa ra sai loại tổng bình phương (SS type), glm âm thầm khớp mô hình OLS, nls không hội tụ, predict trả về sai thang đo, optim/optimize cần tinh chỉnh tham số.
- **statistics.md** — Đọc khi: kiểm định giả thuyết cho kết quả đáng ngạc nhiên, cần chọn đúng phương pháp p.adjust, tham số phân cụm có vẻ sai, cách đặt tên hàm phân phối gây bối rối (các tiền tố `d`/`p`/`q`/`r`).
- **visualization.md** — Đọc khi: thiết lập par bị đặt lại ngoài ý muốn, tương tác giữa layout/mfrow gây khó hiểu, nhãn trục bị cắt khuất, màu sắc hiển thị không chuẩn, cần vẽ các biểu đồ chuyên biệt (contour, persp, mosaic, pairs).
- **io-and-text.md** — Đọc khi: read.table âm thầm làm rơi dữ liệu hoặc phân tích sai cột, biểu thức chính quy (regex) hoạt động khác với dự kiến, định dạng sprintf phức tạp, đầu ra của write.table xuất hiện tên hàng không mong muốn.
- **dates-and-system.md** — Đọc khi: chuyển đổi Date/POSIXct bị lệch ngày, múi giờ gây lỗi lệch một đơn vị (off-by-one), đơn vị của difftime không như dự tính, cần tìm/liệt kê/kiểm tra tệp bằng mã lệnh.
- **misc-utilities.md** — Đọc khi: do.call hoạt động khác với lời gọi hàm trực tiếp, cần dùng Reduce/Filter/Map, trình xử lý tryCatch không kích hoạt, all.equal trả về chuỗi ký tự thay vì giá trị logic, các hàm chuỗi thời gian cần thiết lập ban đầu.
## Mẹo Viết Mã R Tốt
- Sử dụng `vapply()` thay cho `sapply()` trong mã môi trường production — nó bắt buộc kiểm tra kiểu dữ liệu trả về
- Ưu tiên dùng `seq_along(x)` thay vì `1:length(x)` — cách viết sau sẽ bị lỗi khi `x` rỗng
- Sử dụng `stringsAsFactors = FALSE` trong `read.csv()` / `data.frame()` (giá trị mặc định này đã được đổi từ bản R 4.0)
- Vector hóa các phép toán thay vì viết vòng lặp bất cứ khi nào có thể
- Sử dụng `stop()`, `warning()`, `message()` để xử lý lỗi — không dùng `print()`
- Toán tử `<<-` gán giá trị vào môi trường cha — hãy dùng hạn chế và có chủ đích
- Dùng `with(df, expr)` để tránh phải lặp lại `df$` ở khắp mọi nơi
- Dùng `Sys.setenv()` và `.Renviron` cho các biến môi trường
FILE:references/misc-utilities.md
# Tiện ích Hỗn hợp — Tham khảo Nhanh
> Các hành vi khó nhận biết, cạm bẫy thường gặp và giá trị mặc định dễ gây nhầm lẫn của các hàm trong R.
> Chỉ tập trung vào những gì Claude chưa biết sẵn.
---
## do.call
- `do.call(fun, args_list)` — đối số `args` bắt buộc phải là một **danh sách (list)**, ngay cả khi chỉ truyền một đối số duy nhất.
- `quote = TRUE` ngăn việc tính toán giá trị của các đối số trước khi thực hiện lời gọi hàm — rất cần thiết khi truyền các biểu thức (expressions) hoặc ký hiệu (symbols).
- Hành vi của `substitute` bên trong `do.call` khác với các lời gọi trực tiếp. Ngữ nghĩa không được định nghĩa đầy đủ cho trường hợp này.
- Mẫu câu lệnh hữu ích: `do.call(rbind, list_of_dfs)` để gộp một danh sách các data frame lại với nhau.
---
## Reduce / Filter / Map / Find / Position
Các hàm hỗ trợ lập trình hàm (functional programming) có sẵn trong gói base của R — thực sự có nhiều điểm không hiển nhiên.
- `Reduce(f, x)` áp dụng hàm hai ngôi `f` secara tích lũy: `Reduce("+", 1:4)` = `((1+2)+3)+4`. Chiều thực hiện rất quan trọng đối với các phép toán không có tính giao hoán.
- `Reduce(f, x, accumulate = TRUE)` trả về toàn bộ các kết quả trung gian — tương đương với `itertools.accumulate` trong Python.
- `Reduce(f, x, right = TRUE)` gộp từ phải sang trái: `f(x1, f(x2, f(x3, x4)))`.
- `Reduce` kèm tham số `init` sẽ thêm một giá trị khởi đầu: `Reduce(f, x, init = v)` = `f(f(f(v, x1), x2), x3)`.
- `Filter(f, x)` giữ lại các phần tử mà tại đó `f(elem)` trả về `TRUE`. Khác với `x[sapply(x, f)]`, hàm này xử lý chính xác các giá trị `NULL`/rỗng.
- `Map(f, ...)` là một hàm bọc (wrapper) đơn giản cho `mapply(f, ..., SIMPLIFY = FALSE)` — luôn trả về một danh sách (list).
- `Find(f, x)` trả về phần tử **đầu tiên** mà tại đó `f(elem)` là `TRUE`. Dùng `Find(f, x, right = TRUE)` để lấy phần tử cuối cùng.
- `Position(f, x)` trả về **chỉ số vị trí (index)** của phần tử khớp đầu tiên (giống như `Find` nhưng trả về vị trí chứ không phải giá trị).
---
## lengths
- `lengths(x)` trả về độ dài của **từng phần tử** trong một danh sách (list). Tương đương với `sapply(x, length)` nhưng nhanh hơn nhiều (được cài đặt bằng C).
- Hoạt động trên mọi đối tượng dạng danh sách. Trả về một vector số nguyên.
---
## conditions (tryCatch / withCallingHandlers)
- `tryCatch` **tháo gỡ (unwinds)** ngăn xếp lời gọi (call stack) — trình xử lý (handler) chạy trong môi trường gọi hàm chứ không phải nơi phát sinh lỗi. Không thể tiếp tục thực thi từ điểm lỗi.
- `withCallingHandlers` KHÔNG tháo gỡ ngăn xếp — trình xử lý chạy ngay tại nơi tín hiệu điều kiện được phát ra. Có thể kiểm tra/ghi nhật ký rồi để điều kiện đó tiếp tục lan truyền.
- `tryCatch(expr, error = function(e) e)` trả về đối tượng điều kiện lỗi.
- `tryCatch(expr, warning = function(w) {...})` bắt cảnh báo **đầu tiên** rồi thoát luôn. Hãy sử dụng `withCallingHandlers` + `invokeRestart("muffleWarning")` để tắt cảnh báo nhưng vẫn tiếp tục chạy chương trình.
- Mệnh đề `finally` của `tryCatch` luôn luôn được thực thi (giống như try/finally trong Java).
- `globalCallingHandlers()` đăng ký các trình xử lý tồn tại xuyên suốt phiên làm việc (hữu ích cho việc ghi nhật ký log).
- Điều kiện tùy chỉnh: dùng `stop(errorCondition("msg", class = "myError"))` sau đó bắt lỗi bằng `tryCatch(..., myError = function(e) ...)`.
---
## all.equal
- Kiểm tra **sự bằng nhau xấp xỉ** với sai số cho phép (mặc định là `1.5e-8`, tức là `sqrt(.Machine$double.eps)`).
- Trả về `TRUE` hoặc một **chuỗi ký tự** mô tả sự khác biệt — KHÔNG trả về `FALSE`. Hãy sử dụng `isTRUE(all.equal(x, y))` trong các câu lệnh điều kiện.
- Đối số `tolerance` kiểm soát mức sai số số học. Đối số `scale` dùng để chọn giữa so sánh tuyệt đối và so sánh tương đối.
- Kiểm tra cả các thuộc tính (attributes), tên (names), kích thước chiều (dimensions) — kỹ lưỡng hơn toán tử `==`.
---
## combn
- `combn(n, m)` hoặc `combn(x, m)`: tạo tất cả các tổ hợp chập `m` từ `x`.
- Trả về một **ma trận (matrix)** có `m` hàng; mỗi cột là một tổ hợp.
- Đối số `FUN` áp dụng một hàm lên từng tổ hợp: `combn(5, 3, sum)` trả về tổng của tất cả các tập con gồm 3 phần tử.
- `simplify = FALSE` trả về một danh sách (list) thay vì ma trận.
---
## modifyList
- `modifyList(x, val)` thay thế các phần tử của danh sách `x` bằng các phần tử trong `val` dựa theo **tên (name)**.
- Việc gán một giá trị bằng `NULL` sẽ **xóa bỏ** phần tử đó khỏi danh sách.
- Hàm này **có** thêm các tên mới chưa tồn tại trong `x` — bên trong nó sử dụng phép gán `x[names(val)] <- val`, vì vậy bất kỳ tên nào có trong `val` cũng sẽ được thêm mới hoặc ghi đè.
---
## relist
- Ngược lại với `unlist`: từ một vector phẳng và một danh sách khung xương (skeleton list), hàm sẽ tái tạo lại cấu trúc lồng nhau ban đầu.
- `relist(flesh, skeleton)` — `flesh` là dữ liệu phẳng, còn `skeleton` cung cấp hình dáng cấu trúc.
- Hoạt động được với cả factors, ma trận và các danh sách lồng nhau.
---
## txtProgressBar
- `txtProgressBar(min, max, style = 3)` — kiểu 3 hiển thị phần trăm + thanh tiến trình (hữu ích nhất).
- Cập nhật bằng `setTxtProgressBar(pb, value)`. Đóng lại bằng `close(pb)`.
- Kiểu 1: ký tự xoay vòng `|/-\`, kiểu 2: thanh tiến trình đơn giản. Chỉ có kiểu 3 mới hiển thị số phần trăm.
---
## object.size
- Trả về mức **ước tính** dung lượng bộ nhớ mà một đối tượng đang sử dụng. Không phải lúc nào cũng chính xác tuyệt đối đối với các tham chiếu dùng chung.
- Dùng `format(object.size(x), units = "MB")` để xuất kết quả dễ đọc cho con người.
- Không tính kích thước của các môi trường (environments) hoặc các con trỏ ngoại vi (external pointers).
---
## installed.packages / update.packages
- `installed.packages()` có thể chạy chậm (do phải quét toàn bộ các gói). Hãy dùng `find.package()` hoặc `requireNamespace()` để kiểm tra một gói cụ thể.
- `update.packages(ask = FALSE)` cập nhật tất cả các gói mà không hiện lời nhắc hỏi lại.
- `lib.loc` chỉ định thư viện nào cần kiểm tra/cập nhật.
---
## vignette / demo
- `vignette()` liệt kê tất cả các tài liệu hướng dẫn chuyên đề (vignettes); `vignette("name", package = "pkg")` mở một tài liệu cụ thể.
- `demo()` liệt kê tất cả các bản chạy thử (demos); `demo("topic")` chạy tương tác một chủ đề.
- `browseVignettes()` mở trình duyệt tài liệu vignette dưới dạng HTML.
---
## Chuỗi thời gian (Time series): acf / arima / ts / stl / decompose
- `ts(data, start, frequency)`: `frequency` là số quan sát trên mỗi đơn vị thời gian (12 cho dữ liệu hàng tháng, 4 cho dữ liệu hàng quý).
- Mặc định của `acf` là `type = "correlation"`. Sử dụng `type = "partial"` để tính PACF. Dùng `plot = FALSE` để tắt việc tự động vẽ biểu đồ.
- `arima(x, order = c(p,d,q))` dùng cho các mô hình ARIMA. Dùng `seasonal = list(order = c(P,D,Q), period = S)` cho thành phần mùa vụ.
- `arima` xử lý được các giá trị `NA` trong chuỗi thời gian (thông qua bộ lọc Kalman).
- `stl` yêu cầu tham số `s.window` (cửa sổ mùa vụ) — bắt buộc phải chỉ định, không có giá trị mặc định. `s.window = "periodic"` giả định tính mùa vụ cố định.
- `decompose`: đơn giản hơn `stl`, sử dụng trung bình trượt. Tham số `type = "additive"` (cộng tính) hoặc `"multiplicative"` (nhân tính).
- Các thành phần kết quả của `stl`: ma trận `$time.series` với các cột `seasonal`, `trend`, `remainder`.
FILE:references/data-wrangling.md
# Xử lý Dữ liệu (Data Wrangling) — Tham khảo Nhanh
> Các hành vi khó nhận biết, cạm bẫy thường gặp và giá trị mặc định dễ gây nhầm lẫn của các hàm trong R.
> Chỉ tập trung vào những gì Claude chưa biết sẵn.
---
## Extract / Extract.data.frame
Những cạm bẫy khi đánh chỉ mục (indexing) trong R cơ bản.
- `m[j = 2, i = 1]` thực chất là `m[2, 1]` chứ không phải `m[1, 2]` — tên đối số bị **bỏ qua** bên trong toán tử `[`, chỉ khớp theo vị trí. Không bao giờ đặt tên cho các đối số chỉ mục.
- Đánh chỉ mục bằng factor: `x[f]` sử dụng mã số nguyên (integer codes) của factor `f`, chứ không phải nhãn ký tự của nó. Hãy dùng `x[as.character(f)]` nếu muốn đánh chỉ mục theo nhãn.
- `x[[]]` không có chỉ mục bên trong luôn luôn báo lỗi. `x$name` mặc định thực hiện khớp một phần (partial matching); trong khi `x[["name"]]` thì không (mặc định khớp chính xác hoàn toàn).
- Gán `NULL` thông qua `x[[i]] <- NULL` hoặc `x$name <- NULL` sẽ **xóa bỏ** phần tử đó khỏi danh sách (list).
- Dùng toán tử `[` trên data frame với một cột duy nhất: `df[, 1]` trả về một **vector** (mặc định drop=TRUE đối với cột), nhưng `df[1, ]` lại trả về một **data frame** (mặc định drop=FALSE đối với hàng). Hãy luôn chỉ định rõ `drop = FALSE`.
- Đánh chỉ mục một data frame bằng ma trận (`df[cbind(i,j)]`) sẽ ép kiểu sang ma trận trước tiên — hãy tránh dùng cách này.
---
## subset
Chỉ sử dụng khi thao tác tương tác trực tiếp; không an toàn khi viết mã lập trình hàm.
- Đối số `subset` sử dụng cơ chế **đánh giá phi tiêu chuẩn (non-standard evaluation)** — tên cột được phân giải bên trong data frame, điều này có thể âm thầm lấy nhầm biến khi dùng trong hàm lập trình. Hãy dùng toán tử `[` với biểu thức logic tường minh bên trong các hàm.
- Các giá trị `NA` trong điều kiện logic sẽ bị coi là `FALSE` (các hàng đó bị loại bỏ âm thầm).
- Các biến factor có thể vẫn giữ lại những mức (levels) không còn dùng đến sau khi trích xuất tập con; hãy gọi hàm `droplevels()`.
---
## match / %in%
- Toán tử `%in%` **không bao giờ trả về NA** — đặc điểm này giúp nó an toàn khi đưa vào điều kiện `if()`, khác với toán tử `==`.
- `match()` chỉ trả về vị trí của kết quả khớp **đầu tiên**; các giá trị trùng lặp trong `table` sẽ bị bỏ qua.
- Các biến factor, vector thô (raw vectors) và danh sách (lists) đều được chuyển đổi thành chuỗi ký tự trước khi so khớp.
- `NaN` khớp với `NaN` nhưng không khớp với `NA`; `NA` chỉ khớp với `NA`.
---
## apply
- Khi chạy trên một **data frame**, hàm `apply` trước tiên sẽ ép kiểu nó thành ma trận thông qua `as.matrix` — các kiểu dữ liệu hỗn hợp sẽ bị biến thành kiểu ký tự (character).
- Chiều của giá trị trả về bị chuyển vị: nếu hàm FUN trả về một vector có độ dài n, kết quả sẽ có kích thước chiều là `c(n, dim(X)[MARGIN])`. Kết quả tính theo từng hàng sẽ trở thành **các cột**.
- Kết quả kiểu factor sẽ bị ép kiểu thành chuỗi ký tự trong mảng đầu ra.
- Các đối số truyền qua `...` không được phép trùng tên với `X`, `MARGIN` hoặc `FUN` (nguy cơ bị khớp một phần tên đối số).
---
## lapply / sapply / vapply
- `sapply` có thể trả về một vector, ma trận hoặc danh sách một cách khó đoán trước — hãy sử dụng `vapply` trong mã không tương tác cùng với mẫu kiểu dữ liệu `FUN.VALUE` tường minh.
- Việc gọi trực tiếp các hàm nguyên thủy (primitives) bên trong `lapply` có thể gây lỗi điều phối phương thức (dispatch issues); hãy bọc trong `function(x) is.numeric(x)` thay vì truyền trực tiếp `is.numeric`.
- `sapply` với tham số `simplify = "array"` có thể tạo ra các mảng nhiều chiều bậc cao hơn (chứ không chỉ dừng ở ma trận).
---
## tapply
- Trả về một **mảng (array)** (không phải data frame). Thông tin về lớp (class) trên các giá trị trả về sẽ bị **loại bỏ** (ví dụ: các đối tượng Date bị biến thành dạng số numeric).
- Các đối số `...` truyền cho FUN **không** được chia nhỏ theo từng ô — chúng được áp dụng trên phạm vi toàn cục, vì vậy FUN không nên kỳ vọng nhận các đối số bổ sung có cùng độ dài với X.
- `default = NA` dùng để điền vào các ô trống; hãy đặt `default = 0` cho các phép toán dạng tính tổng. Trước phiên bản R 3.4.0, giá trị này bị gắn cứng là `NA`.
- Sử dụng `array2DF()` để chuyển đổi kết quả sang dạng data frame.
---
## mapply
- Tên đối số là `SIMPLIFY` (viết hoa toàn bộ) chứ không phải `simplify` — không nhất quán với `sapply`.
- `MoreArgs` bắt buộc phải là một **danh sách (list)** chứa các đối số không thực hiện vector hóa.
- Tự động lặp lại (recycles) các đối số ngắn hơn cho khớp với độ dài chung; nếu có đối số mang độ dài bằng 0 thì kết quả trả về cũng có độ dài bằng 0.
---
## merge
- Giá trị mặc định của `by` là `intersect(names(x), names(y))` — có thể âm thầm ghép nối trên các cột ngoài ý muốn nếu hai data frame tình cờ có chung tên cột.
- `by = 0` hoặc `by = "row.names"` thực hiện ghép theo tên hàng, đồng thời thêm một cột `"Row.names"` vào kết quả.
- `by = NULL` (hoặc cả `by.x`/`by.y` đều có độ dài bằng 0) sẽ tạo ra **tích Đề-các (Cartesian product)**.
- Kết quả mặc định được sắp xếp theo các cột `by` (`sort = TRUE`). Để giữ nguyên không sắp xếp, hãy dùng `sort = FALSE`.
- Các khóa trùng lặp khi khớp nhau sẽ tạo ra **tất cả các tổ hợp** (mỗi cặp khớp tạo thành một hàng).
---
## split
- Nếu `f` là một danh sách các biến factor, sự tương tác giữa chúng sẽ được sử dụng; các mức (levels) có chứa dấu `"."` có thể gây ra việc chia tách ngoài ý muốn trừ khi thay đổi tham số `sep`.
- `drop = FALSE` (mặc định) vẫn giữ lại các mức factor rỗng dưới dạng các phần tử danh sách trống.
- Hỗ trợ cú pháp công thức (formula): `split(df, ~ Month)`.
---
## cbind / rbind
- Gọi `cbind` trên các data frame sẽ kích hoạt `data.frame(...)` chứ không phải `cbind.matrix`. Việc trộn lẫn ma trận và data frame có thể dẫn đến kết quả bất ngờ.
- Gọi `rbind` trên các data frame sẽ so khớp các cột **theo tên**, không phải theo vị trí. Các cột bị thiếu sẽ nhận giá trị `NA`.
- `cbind(NULL)` trả về `NULL` (không phải ma trận). Để nhất quán, `rbind(NULL)` cũng trả về `NULL`.
---
## table
- Mặc định **loại trừ giá trị NA** (`useNA = "no"`). Hãy sử dụng `useNA = "ifany"` hoặc `exclude = NULL` để đếm cả các giá trị NA.
- Việc thiết lập `exclude` khác rỗng và khác mặc định đồng nghĩa với việc ngầm định `useNA = "ifany"`.
- Kết quả trả về luôn là một **mảng (array)** (ngay cả khi chỉ có 1 chiều), mang lớp `"table"`. Chuyển đổi thành data frame bằng hàm `as.data.frame(tbl)`.
- Hai loại NA (NA nằm trong mức của factor so với giá trị NA thực sự) được xử lý khác nhau tùy thuộc vào thiết lập `useNA`/`exclude`.
---
## duplicated / unique
- `duplicated` đánh dấu các lần xuất hiện **thứ hai trở đi** là TRUE, chứ không đánh dấu lần đầu tiên. Sử dụng `fromLast = TRUE` để đảo ngược chiều kiểm tra.
- Đối với data frame, hàm kiểm tra trên toàn bộ hàng. Đối với danh sách (list), hàm so sánh theo cách đệ quy.
- `unique` giữ lại lần xuất hiện **đầu tiên** của mỗi giá trị.
---
## data.frame (những cạm bẫy cần lưu ý)
- `stringsAsFactors = FALSE` là giá trị mặc định kể từ bản R 4.0.0 (trước đó mặc định là TRUE).
- Các vector nguyên tử (atomic vectors) sẽ tự động lặp lại (recycle) cho khớp với cột dài nhất, nhưng chỉ khi độ dài cột dài nhất là bội số chẵn. Hãy bọc bằng `I()` để ngăn việc chuyển đổi ngoài ý muốn.
- Tên cột trùng lặp chỉ được cho phép khi đặt `check.names = FALSE`, nhưng nhiều thao tác xử lý sau đó sẽ âm thầm khử trùng lặp tên cột.
- Các đối số là ma trận sẽ bị tách rộng thành nhiều cột riêng lẻ trừ khi được bảo vệ bằng hàm `I()`.
---
## factor (những cạm bẫy cần lưu ý)
- `as.numeric(f)` trả về **mã số nguyên nội bộ (integer codes)** chứ không phải giá trị số ban đầu. Hãy sử dụng `as.numeric(levels(f))[f]` hoặc `as.numeric(as.character(f))`.
- Chỉ có toán tử `==` và `!=` hoạt động giữa các biến factor; hai factor phải có tập hợp các mức (level sets) giống hệt nhau. Các factor có thứ tự (ordered factors) mới hỗ trợ thêm `<`, `>`.
- Hàm `c()` áp dụng trên các factor sẽ hợp nhất các tập mức (kể từ bản R 4.1.0), nhưng các phiên bản cũ hơn lại chuyển đổi chúng thành số nguyên.
- Các mức (levels) mặc định được sắp xếp theo thứ tự chữ cái, nhưng thứ tự sắp xếp này **phụ thuộc vào thiết lập vùng ngôn ngữ (locale)** tại thời điểm khởi tạo.
---
## aggregate
- Giao diện công thức (`aggregate(y ~ x, data, FUN)`) mặc định loại bỏ các nhóm có giá trị `NA`.
- Phương thức áp dụng cho data frame yêu cầu đối số `by` phải là một **list** (không phải vector).
- Trả về các cột được đặt tên theo các biến phân nhóm, còn cột kết quả giữ nguyên tên gốc.
- Nếu hàm FUN trả về nhiều giá trị, cột kết quả sẽ là một **cột ma trận (matrix column)** nằm bên trong data frame.
---
## complete.cases
- Trả về một vector logic: nhận giá trị TRUE đối với các hàng **không có** bất kỳ giá trị NA nào trên tất cả các cột/đối số.
- Hoạt động được trên nhiều đối số cùng lúc (ví dụ: `complete.cases(x, y)` kiểm tra cả hai).
---
## order
- Trả về một **vector hoán vị** chứa các chỉ số (indices), chứ không phải các giá trị đã sắp xếp. Hãy dùng `x[order(x)]` để sắp xếp.
- Mặc định là sắp xếp tăng dần; dùng `-x` để sắp xếp giảm dần đối với biến số, hoặc truyền `decreasing = TRUE`.
- Đối với việc sắp xếp chuỗi ký tự, kết quả phụ thuộc vào thiết lập ngôn ngữ hệ thống (locale). Hãy sử dụng `method = "radix"` để sắp xếp nhanh và độc lập với locale.
- Hàm `sort.int()` với `method = "radix"` nhanh hơn rất nhiều đối với các vector số nguyên/ký tự kích thước lớn.
FILE:references/dates-and-system.md
# Ngày tháng và Hệ thống — Tham khảo Nhanh
> Các hành vi không hiển nhiên, những cái bẫy dễ mắc phải và các giá trị mặc định dễ gây nhầm lẫn của các hàm trong R.
> Chỉ tập hợp những điều mà Claude chưa biết sẵn.
---
## Ngày tháng (Lớp Date)
- Các đối tượng `Date` được lưu trữ dưới dạng **số ngày nguyên tính từ mốc 1970-01-01**. Các phép toán số học hoạt động theo đơn vị ngày.
- `Sys.Date()` trả về ngày hiện tại dưới dạng đối tượng Date.
- `seq.Date(from, to, by = "month")` — bước nhảy theo "month" (tháng) có thể tạo ra các khoảng thời gian có độ dài không đều nhau. Cộng thêm 1 tháng vào ngày 31 tháng 1 sẽ cho ra ngày 3 tháng 3 (chứ không phải 28 tháng 2).
- `diff(dates)` trả về một đối tượng `difftime` tính bằng ngày.
- `format(date, "%Y")` cho năm, `"%m"` cho tháng, `"%d"` cho ngày, `"%A"` cho tên thứ trong tuần (phụ thuộc vào locale).
- Các năm trước Công nguyên (trước năm 1 CN) có thể không được xử lý chính xác.
- `length(date_vector) <- n` sẽ đệm thêm các giá trị `NA` nếu vector được kéo dài ra.
---
## Các lớp Ngày-Giờ (POSIXct / POSIXlt)
- `POSIXct`: số giây tính từ mốc 1970-01-01 UTC (nhỏ gọn, bản chất là một vector số).
- `POSIXlt`: một danh sách (list) gồm các thành phần `$sec`, `$min`, `$hour`, `$mday`, `$mon` (từ 0-11!), `$year` (tính từ năm 1900!), `$wday` (0-6, Chủ nhật = 0), `$yday` (0-365).
- Chuyển đổi giữa POSIXct và Date: `as.Date(posixct_obj)` mặc định sử dụng `tz = "UTC"` — có thể cho ra ngày khác với mong muốn nếu thời gian gốc ở một múi giờ khác.
- `Sys.time()` trả về đối tượng POSIXct theo múi giờ hiện tại.
- `strptime` trả về đối tượng POSIXlt; hãy dùng `as.POSIXct(strptime(...))` để lấy kiểu POSIXct.
- Phép toán với `difftime`: trừ hai đối tượng POSIXct sẽ cho ra đối tượng difftime. Đơn vị đo được tự động chọn ("secs", "mins", "hours", "days", "weeks").
---
## difftime
- `difftime(time1, time2, units = "auto")` — tự động chọn đơn vị nhỏ nhất hợp lý.
- Các đơn vị chỉ định tường minh: `"secs"`, `"mins"`, `"hours"`, `"days"`, `"weeks"`. Không có đơn vị "months" (tháng) hoặc "years" (năm) do độ dài thay đổi không cố định.
- Dùng `as.numeric(diff, units = "hours")` để trích xuất giá trị số theo đơn vị cụ thể.
- Gán `units(diff_obj) <- "hours"` sẽ thay đổi trực tiếp đơn vị của đối tượng.
---
## system.time / proc.time
- `system.time(expr)` trả về thời gian `user`, `system` và `elapsed`.
- `gcFirst = TRUE` (mặc định): chạy trình thu gom rác (garbage collection) trước khi bấm giờ để có kết quả nhất quán hơn.
- `proc.time()` trả về thời gian tích lũy kể từ khi phiên R khởi động — hãy lấy hiệu hai lần gọi để đo khoảng thời gian.
- Thời gian `elapsed` (đồng hồ thực tế) có thể nhỏ hơn `user` (khi chạy BLAS đa luồng) hoặc lớn hơn (do chờ I/O).
---
## Sys.sleep
- `Sys.sleep(seconds)` — cho phép truyền số giây thập phân. Thời gian nghỉ thực tế có thể dài hơn (do cơ chế lập lịch của hệ điều hành).
- Tiến trình sẽ **nhường CPU (yields)** cho hệ điều hành trong lúc ngủ (không chiếm dụng CPU theo kiểu busy-wait).
---
## options (các tùy chọn quan trọng)
Một số tùy chọn không hiển nhiên được chọn lọc:
- `options(scipen = n)`: giá trị dương ưu tiên hiển thị số dạng thập phân thông thường, giá trị âm ưu tiên ký hiệu khoa học. Mặc định là 0. Áp dụng cho `print`/`format`/`cat` nhưng không áp dụng cho `sprintf`.
- `options(digits = n)`: số chữ số có nghĩa khi in ra (từ 1-22, mặc định là 7). Chỉ mang tính chất gợi ý.
- `options(digits.secs = n)`: số chữ số thập phân tối đa cho phần giây khi định dạng thời gian (0-6, mặc định là 0).
- `options(warn = n)`: -1 = bỏ qua cảnh báo, 0 = gom lại in sau (mặc định), 1 = in ngay lập tức, 2 = chuyển cảnh báo thành lỗi (error).
- `options(error = recover)`: nhảy vào trình gỡ lỗi (debugger) khi gặp lỗi. `options(error = NULL)` đặt lại về mặc định.
- `options(OutDec = ",")`: thay đổi dấu phân cách thập phân ở đầu ra (ảnh hưởng đến `format`, `print`, KHÔNG ảnh hưởng đến `sprintf`).
- `options(stringsAsFactors = FALSE)`: thiết lập mặc định toàn cục cho `data.frame` (không còn cần thiết kể từ R 4.0.0 vì mặc định đã là FALSE).
- `options(expressions = 5000)`: số lượng biểu thức lồng nhau tối đa được đánh giá. Hãy tăng lên nếu dùng đệ quy sâu.
- `options(max.print = 99999)`: kiểm soát ngưỡng cắt bớt kết quả trong đầu ra của lệnh `print`.
- `options(na.action = "na.omit")`: cách xử lý NA mặc định trong các hàm mô hình hóa.
- `options(contrasts = c("contr.treatment", "contr.poly"))`: ma trận tương phản mặc định cho các nhân tố (factor) không có thứ tự / có thứ tự.
---
## file.path / basename / dirname
- `file.path("a", "b", "c.txt")` → `"a/b/c.txt"` (dùng dấu phân cách phù hợp với nền tảng hệ điều hành).
- `basename("/a/b/c.txt")` → `"c.txt"`. `dirname("/a/b/c.txt")` → `"/a/b"`.
- `file.path` KHÔNG chuẩn hóa đường dẫn (không phân giải `..`); hãy dùng `normalizePath()` cho việc đó.
---
## list.files
- `list.files(pattern = "*.csv")` — đối số `pattern` là một **biểu thức chính quy (regex)**, không phải mẫu glob! Hãy dùng `glob2rx("*.csv")` hoặc `"\\.csv$"`.
- `full.names = FALSE` (mặc định) chỉ trả về tên tệp cơ sở (basename). Hãy dùng `full.names = TRUE` để lấy đường dẫn đầy đủ.
- `recursive = TRUE` để tìm kiếm đệ quy trong các thư mục con.
- `all.files = TRUE` để bao gồm cả các tệp ẩn (bắt đầu bằng dấu `.`).
---
## file.info
- Trả về data frame gồm các cột `size`, `isdir`, `mode`, `mtime`, `ctime`, `atime`, `uid`, `gid`.
- `mtime`: thời gian sửa đổi tệp (kiểu POSIXct). Rất hữu ích khi gọi `file.info(f)$mtime`.
- Trên một số hệ thống tệp, `ctime` là thời điểm thay đổi trạng thái siêu dữ liệu, chứ không phải thời điểm tạo tệp.
---
## file_test
- `file_test("-f", path)`: trả về TRUE nếu tệp thông thường tồn tại.
- `file_test("-d", path)`: trả về TRUE nếu thư mục tồn tại.
- `file_test("-nt", f1, f2)`: trả về TRUE nếu tệp f1 mới hơn tệp f2.
- Đáng tin cậy hơn `file.exists()` khi cần phân biệt giữa tệp và thư mục.
FILE:references/io-and-text.md
# Nhập/Xuất (I/O) và Xử lý Văn bản — Tham khảo Nhanh
> Các hành vi không hiển nhiên, những cái bẫy dễ mắc phải và các giá trị mặc định dễ gây nhầm lẫn của các hàm trong R.
> Chỉ tập hợp những điều mà Claude chưa biết sẵn.
---
## read.table (những cái bẫy cần lưu ý)
- `sep = ""` (mặc định) có nghĩa là **bất kỳ khoảng trắng nào** (dấu cách, tab, xuống dòng) — chứ không phải chuỗi rỗng theo nghĩa đen.
- Mặc định `comment.char = "#"` — các dòng có ký tự `#` sẽ bị cắt bỏ phần sau. Hãy dùng `comment.char = ""` để tắt tính năng này (đồng thời giúp đọc nhanh hơn).
- Tự động phát hiện `header`: được đặt thành TRUE nếu dòng đầu tiên có **ít hơn một trường** so với các dòng tiếp theo (trường bị thiếu được giả định là tên hàng).
- `colClasses = "NULL"` sẽ **bỏ qua** hoàn toàn cột đó — rất hữu ích để tăng tốc độ đọc.
- Các giá trị mặc định của `read.csv` khác với `read.table`: `header = TRUE`, `sep = ","`, `fill = TRUE`, `comment.char = ""`.
- Đối với các tệp lớn: việc chỉ định rõ `colClasses` và `nrows` giúp giảm đáng kể lượng bộ nhớ sử dụng. `read.table` chạy chậm đối với các data frame rộng (hàng trăm cột); hãy dùng `scan` hoặc `data.table::fread` cho dữ liệu dạng ma trận.
- `stringsAsFactors = FALSE` là mặc định kể từ R 4.0.0 (trước đó là TRUE).
---
## write.table (những cái bẫy cần lưu ý)
- Mặc định `row.names = TRUE` — tạo ra một cột đầu tiên không có tên gây nhầm lẫn khi đọc lại tệp. Hãy dùng `row.names = FALSE` hoặc `col.names = NA` để xuất tệp CSV tương thích với Excel.
- `write.csv` cố định cứng `sep = ","`, `dec = "."` và dùng `qmethod = "double"` — không thể ghi đè các tham số này thông qua `...`.
- `quote = TRUE` (mặc định) đặt dấu ngoặc kép quanh các cột kiểu ký tự/factor. Các cột kiểu số không bao giờ được đặt trong dấu ngoặc kép.
- Các cột dạng ma trận bên trong data frame sẽ âm thầm tách rộng thành nhiều cột khi ghi ra.
- Chạy chậm đối với các data frame có nhiều cột (hàng trăm cột trở lên); mỗi cột được xử lý riêng biệt theo lớp (class).
---
## read.fwf
- Đọc các tệp có định dạng độ rộng cố định (fixed-width format). `widths` là một vector chứa độ rộng của từng trường.
- **Độ rộng mang giá trị âm sẽ bỏ qua** bấy nhiêu ký tự tương ứng (hữu ích khi muốn bỏ qua một số trường).
- `buffersize` kiểm soát số lượng dòng được đọc cùng một lúc; hãy tăng giá trị này lên đối với các tệp lớn.
- Bên trong sử dụng `read.table` sau khi đã tách các trường.
---
## count.fields
- Đếm số trường trên mỗi dòng trong một tệp — rất hữu ích để chẩn đoán lỗi khi đọc tệp.
- Các đối số `sep` và `quote` khớp với các đối số tương ứng của `read.table`.
---
## grep / grepl / sub / gsub (những cái bẫy cần lưu ý)
- Có ba chế độ biểu thức chính quy (regex): POSIX mở rộng (mặc định), `perl = TRUE`, `fixed = TRUE`. Chúng hoạt động khác nhau trong các trường hợp biên (edge cases).
- **Hãy đặt tên tường minh cho các đối số** — các đối số không đặt tên đứng sau `x`/`pattern` sẽ được khớp theo vị trí với `ignore.case`, `perl`, v.v. Đây là nguyên nhân phổ biến gây ra các lỗi ngầm.
- `sub` chỉ thay thế kết quả khớp **đầu tiên**; `gsub` thay thế **tất cả** các kết quả khớp.
- Tham chiếu ngược (Backreferences): dùng `"\\1"` trong chuỗi thay thế (hai dấu gạch chéo ngược trong chuỗi của R). Khi bật `perl = TRUE`: dùng `"\\U\\1"` để chuyển thành chữ in hoa.
- `grep(value = TRUE)` trả về **các phần tử** khớp; `grep(value = FALSE)` (mặc định) trả về **các chỉ số vị trí (indices)**.
- `grepl` trả về vector logic — được ưu tiên sử dụng khi lọc dữ liệu.
- `regexpr` trả về vị trí khớp đầu tiên + độ dài (dưới dạng các thuộc tính - attributes); `gregexpr` trả về tất cả các vị trí khớp dưới dạng một danh sách (list).
- `regexec` trả về vị trí khớp + vị trí các nhóm bắt giữ (capture groups); `gregexec` thực hiện điều này cho tất cả các lần khớp.
- Các lớp ký tự như `[:alpha:]` phải được đặt bên trong cặp ngoặc vuông kép `[[:alpha:]]` khi ở chế độ POSIX.
---
## strsplit
- Trả về một **list** (mỗi chuỗi đầu vào tương ứng với một phần tử), ngay cả khi đầu vào chỉ có một chuỗi duy nhất.
- `split = ""` hoặc `split = character(0)` sẽ tách thành từng ký tự riêng lẻ.
- Nếu khớp ngay ở đầu chuỗi: phần tử đầu tiên của kết quả là `""`. Nếu khớp ở cuối chuỗi: không có phần tử `""` ở cuối.
- `fixed = TRUE` chạy nhanh hơn và tránh việc diễn giải mẫu tách như một biểu thức chính quy.
- Lỗi thường gặp: các đối số không đặt tên bị khớp ngầm theo vị trí với `fixed`, `perl`, v.v.
---
## substr / substring
- `substr(x, start, stop)`: trích xuất/thay thế chuỗi con. Đánh chỉ số từ 1, bao gồm cả hai đầu mút.
- `substring(x, first, last)`: tương tự nhưng `last` mặc định là `1000000L` (về cơ bản là "đến hết chuỗi"). Được vector hóa trên cả `first` và `last`.
- Dạng phép gán: `substr(x, 1, 3) <- "abc"` thay thế trực tiếp tại chỗ (chuỗi thay thế phải có cùng độ dài).
---
## trimws
- `which = "both"` (mặc định, cắt cả hai đầu), `"left"` (trái), hoặc `"right"` (phải).
- `whitespace = "[ \\t\\r\\n]"` — biểu thức chính quy có thể tùy chỉnh để quy định những ký tự nào được tính là khoảng trắng.
---
## nchar
- `type = "bytes"` đếm số byte; `type = "chars"` (mặc định) đếm số ký tự; `type = "width"` đếm độ rộng hiển thị.
- `nchar(NA)` trả về `NA` (chứ không phải 2). `nchar(factor)` hoạt động trên nhãn của các mức (level labels).
- `keepNA = TRUE` (mặc định kể từ R 3.3.0); đặt thành `FALSE` để đếm `"NA"` là 2 ký tự.
---
## format / formatC
- `format(x, digits, nsmall)`: `nsmall` ép buộc số chữ số thập phân tối thiểu. `big.mark = ","` thêm dấu phân cách hàng nghìn.
- `formatC(x, format = "f", digits = 2)`: định dạng theo kiểu ngôn ngữ C. `format = "e"` cho ký hiệu khoa học, `"g"` cho định dạng chung.
- `format` trả về vector ký tự; luôn căn lề phải theo mặc định (`justify = "right"`).
---
## type.convert
- Chuyển đổi các vector ký tự sang kiểu dữ liệu phù hợp (logical, integer, double, complex, character).
- `as.is = TRUE` (khuyên dùng): giữ nguyên chuỗi ký tự là kiểu character chứ không chuyển thành factor.
- Áp dụng theo từng cột trên data frame. `tryLogical = TRUE` (từ R 4.3+) chuyển đổi các cột chứa chữ "TRUE"/"FALSE" thành kiểu logic.
---
## Rscript
- `commandArgs(trailingOnly = TRUE)` lấy các đối số truyền cho kịch bản (không tính các cờ của R/Rscript).
- Dòng `#!` trên hệ thống Unix: `/usr/bin/env Rscript` hoặc đường dẫn đầy đủ.
- Dùng `--vanilla` hoặc `--no-init-file` để bỏ qua việc nạp tệp `.Rprofile`.
- Mã thoát (exit code): dùng `quit(status = 1)` để thoát khi có lỗi.
---
## capture.output
- Thu giữ đầu ra từ `cat`, `print`, hoặc bất kỳ biểu thức nào ghi ra luồng đầu ra chuẩn (stdout).
- `file = NULL` (mặc định) trả về vector ký tự. `file = "out.txt"` ghi trực tiếp vào tệp.
- `type = "message"` thu giữ luồng lỗi chuẩn (stderr) thay vì stdout.
---
## URLencode / URLdecode
- `URLencode(url, reserved = FALSE)` theo mặc định KHÔNG mã hóa các ký tự dành riêng (`/`, `?`, `&`, v.v.).
- Hãy đặt `reserved = TRUE` để mã hóa một **thành phần** URL (chẳng hạn như giá trị của tham số truy vấn).
---
## glob2rx
- Chuyển đổi các mẫu glob của shell sang biểu thức chính quy (regex): `glob2rx("*.csv")` → `"^.*\\.csv$"`.
- Rất hữu ích khi kết hợp với `list.files(pattern = glob2rx("data_*.RDS"))`.
FILE:references/modeling.md
# Mô hình hóa — Tham khảo Nhanh
> Các hành vi không hiển nhiên, những cái bẫy dễ mắc phải và các giá trị mặc định dễ gây nhầm lẫn của các hàm trong R.
> Chỉ tập hợp những điều mà Claude chưa biết sẵn.
---
## formula
Những cái bẫy khi đặc tả mô hình bằng ký hiệu công thức.
- Bắt buộc phải dùng `I()` để sử dụng các toán tử số học theo nghĩa đen: `y ~ x + I(x^2)`. Nếu không có `I()`, dấu `^` mang ý nghĩa là tích chéo tương tác (interaction crossing).
- `*` = tác động chính + tương tác: `a*b` được khai triển thành `a + b + a:b`.
- `(a+b+c)^2` = tất cả các tác động chính + tất cả các tương tác bậc 2 (chứ không phải bình phương số học).
- `-` loại bỏ các số hạng: `(a+b+c)^2 - a:b` chỉ lược bỏ tương tác `a:b`.
- `/` mang ý nghĩa lồng nhau (nesting): `a/b` = `a + b %in% a` = `a + a:b`.
- `.` trong công thức có nghĩa là "tất cả các cột còn lại trong dữ liệu" (trong ngữ cảnh `terms.formula`) hoặc "nội dung trước đó" (trong `update.formula`).
- Các đối tượng công thức (formula) mang theo một **môi trường (environment)** dùng để tra cứu biến; `as.formula("y ~ x")` sử dụng `parent.frame()`.
---
## terms / model.matrix
- `model.matrix` tạo ma trận thiết kế (design matrix) bao gồm cả việc mã hóa biến giả (dummy coding). Ma trận tương phản mặc định: `contr.treatment` cho các factor không có thứ tự, `contr.poly` cho các factor có thứ tự.
- Các thuộc tính của đối tượng `terms`: `order` (bậc tương tác của từng số hạng), `intercept`, ma trận `factors`.
- Tên cột sinh ra từ `model.matrix` có thể gây bất ngờ: ví dụ nối liền tên biến factor và tên mức `factorLevelName`.
---
## glm
- Mặc định `family = gaussian(link = "identity")` — gọi `glm()` mà không truyền `family` sẽ âm thầm khớp mô hình bình phương tối thiểu thông thường - OLS (giống như `lm`, nhưng chậm hơn và xuất kết quả dựa trên độ lệch deviance).
- Các họ phân phối phổ biến: `binomial(link = "logit")`, `poisson(link = "log")`, `Gamma(link = "inverse")`, `inverse.gaussian()`.
- Họ `binomial` chấp nhận biến phản hồi dưới dạng: vector 0/1, vector logic, factor (mức thứ hai = thành công), hoặc ma trận 2 cột `cbind(success, failure)`.
- Đối số `weights` trong `glm` có nghĩa là **trọng số tiên nghiệm (prior weights)** (không phải trọng số tần suất) — đối với trọng số tần suất, hãy dùng mẹo cbind hoặc dùng offset.
- Dùng `predict.glm(type = "response")` để lấy xác suất dự đoán; mặc định `type = "link"` trả về giá trị log-odds (đối với hồi quy logistic) hoặc log-rate (đối với hồi quy Poisson).
- Dùng `anova(glm_obj, test = "Chisq")` cho các kiểm định dựa trên độ lệch (deviance); kiểm định `"F"` không hợp lệ đối với các họ phân phối phi chuẩn (non-Gaussian).
- Các họ giả phân phối (`quasibinomial`, `quasipoisson`) cho phép xử lý hiện tượng quá phân tán (overdispersion) — khi đó chỉ số AIC sẽ không được tính.
- Hội tụ: truyền `control = glm.control(maxit = 100)` nếu mức mặc định 25 vòng lặp là chưa đủ.
---
## aov
- `aov` là một hàm bọc quanh `lm` giúp lưu thêm thông tin cho phân tích phương sai (ANOVA) cân bằng. Đối với các thiết kế không cân bằng, hàm tính Tổng bình phương Loại I (Type I SS - tuần tự) — do đó thứ tự đưa các biến vào công thức rất quan trọng.
- Để tính Tổng bình phương Loại III (Type III SS), hãy dùng `car::Anova()` hoặc đặt ma trận tương phản thành `contr.sum`/`contr.helmert`.
- Phân tầng sai số cho phép đo lặp lại (repeated measures): `aov(y ~ A*B + Error(Subject/B))`.
- `summary.aov` trả về bảng ANOVA; `summary.lm(aov_obj)` trả về bảng tóm tắt theo kiểu hồi quy.
---
## nls
- Đòi hỏi phải cung cấp **các giá trị khởi tạo tốt** trong `start = list(...)` nếu không quá trình hội tụ sẽ thất bại.
- Các mô hình tự khởi tạo (`SSlogis`, `SSasymp`, v.v.) tự động tính toán giá trị khởi tạo.
- Thuật toán `"port"` cho phép đặt giới hạn biên cho các tham số (`lower`/`upper`).
- Nếu dữ liệu khớp quá hoàn hảo (không có nhiễu phần dư), bước kiểm tra hội tụ sẽ báo lỗi — hãy dùng `control = list(scaleOffset = 1)` hoặc thêm nhiễu nhỏ (jitter) vào dữ liệu.
- Dùng đối số `weights` cho hồi quy bình phương tối thiểu phi tuyến có trọng số; dùng `na.action` để xử lý giá trị khuyết thiếu.
---
## step / add1
- `step` thực hiện lựa chọn mô hình **từng bước (stepwise)** theo tiêu chuẩn AIC (mặc định). Hãy truyền `k = log(n)` nếu muốn dùng tiêu chuẩn BIC.
- Hướng lựa chọn: `direction = "both"` (mặc định, cả hai chiều), `"forward"` (tiến), hoặc `"backward"` (lùi).
- `add1`/`drop1` đánh giá việc thêm/bớt từng số hạng đơn lẻ; `step` gọi lặp đi lặp lại các hàm này.
- Đối số `scope` xác định giới hạn mô hình trên/dưới cho quá trình tìm kiếm.
- `step` sửa đổi trực tiếp đối tượng mô hình tại chỗ — có thể chạy chậm đối với các mô hình lớn có nhiều biến ứng viên.
---
## predict.lm / predict.glm
- `predict.lm` với `interval = "confidence"` trả về khoảng tin cậy (CI) cho phản hồi **trung bình**; `interval = "prediction"` trả về khoảng dự đoán (PI) cho **quan sát mới** (rộng hơn).
- `newdata` phải có các cột khớp với các biến trong công thức gốc — các biến factor phải có cùng tập mức (levels).
- `predict.glm` với `type = "response"` trả về giá trị dự đoán trên thang đo phản hồi thực tế (ví dụ: xác suất đối với hồi quy logistic); `type = "link"` (mặc định) trả về trên thang đo hàm liên kết (link scale).
- `se.fit = TRUE` trả về sai số chuẩn (standard errors); đối với `predict.glm`, các sai số chuẩn này luôn nằm trên thang đo **link** bất kể giá trị của `type`.
- `predict.lm` với `type = "terms"` trả về phần đóng góp riêng của từng số hạng.
---
## loess
- `span` kiểm soát độ trơn (mặc định là 0.75). Span < 1 sử dụng tỷ lệ điểm dữ liệu tương ứng; span > 1 sử dụng tất cả các điểm với khoảng cách đã điều chỉnh.
- Tối đa **4 biến dự đoán (predictors)**. Lượng bộ nhớ sử dụng tăng xấp xỉ **bậc hai** theo n (1000 điểm tốn ~ 10MB).
- `degree = 0` (hằng số cục bộ) được cho phép nhưng ít được kiểm thử kỹ — hãy thận trọng khi dùng.
- Không hoàn toàn giống hệt hàm `loess` của ngôn ngữ S; tính năng phân điều kiện (conditioning) không được triển khai.
- `normalize = TRUE` (mặc định) chuẩn hóa các biến dự đoán về cùng một thang đo chung; hãy đặt thành `FALSE` đối với các tọa độ không gian.
---
## lowess so với loess
- `lowess` là hàm cũ hơn; trả về `list(x, y)` — không thể dự đoán tại các điểm mới.
- `loess` là giao diện công thức mới hơn có hỗ trợ phương thức `predict`.
- Tham số độ rộng của `lowess` là `f` (span, mặc định 2/3); tham số của `loess` là `span` (mặc định 0.75).
- Giá trị mặc định `iter` của `lowess` là 3 (số vòng lặp kháng nhiễu - robustifying iterations); mặc định của `loess` là `family = "gaussian"` (không kháng nhiễu ngoại lai).
---
## smooth.spline
- Tham số làm trơn mặc định được chọn bằng **GCV** (kiểm định chéo tổng quát hóa - generalized cross-validation).
- `cv = TRUE` chuyển sang dùng kiểm định chéo để-lại-một (leave-one-out CV) thông thường — không sử dụng tùy chọn này khi có các giá trị x trùng lặp.
- `spar` và `lambda` kiểm soát độ trơn; `df` có thể dùng để chỉ định bậc tự do tương đương.
- Trả về đối tượng có các phương thức `predict`, `print`, `plot`. Thành phần `fit` chứa các điểm nút (knots) và hệ số.
---
## optim
- Mặc định thực hiện **tối thiểu hóa (minimizes)**. Để tối đa hóa: đặt `control = list(fnscale = -1)`.
- Phương pháp mặc định là Nelder-Mead (không cần đạo hàm gradient, bền vững nhưng chậm). Hoạt động kém cho bài toán 1 chiều (1D) — hãy dùng `"Brent"` hoặc `optimize()`.
- `"L-BFGS-B"` là phương pháp duy nhất hỗ trợ ràng buộc hình hộp (`lower`/`upper`). Khi truyền giới hạn biên, hàm sẽ tự động chọn phương pháp này kèm theo một cảnh báo.
- `"SANN"` (luyện kim mô phỏng - simulated annealing): mã hội tụ **luôn bằng 0** — nó không bao giờ báo "thất bại". `maxit` = tổng số lần tính hàm mục tiêu (mặc định 10000), không có tiêu chuẩn dừng nào khác.
- `parscale`: co giãn tỷ lệ các tham số sao cho một thay đổi đơn vị ở mỗi tham số tạo ra mức thay đổi tương đương ở hàm mục tiêu. Rất quan trọng đối với các bài toán có nhiều thang đo trộn lẫn.
- `hessian = TRUE`: trả về ma trận Hessian tính bằng phương pháp số của bài toán **không ràng buộc**, ngay cả khi các ràng buộc hình hộp đang được bật.
- Hàm `fn` có thể trả về `NA`/`Inf` (ngoại trừ phương pháp `"L-BFGS-B"` luôn yêu cầu các giá trị hữu hạn). Giá trị khởi tạo bắt buộc phải hữu hạn.
---
## optimize / uniroot
- `optimize`: tối thiểu hóa 1 chiều trên một khoảng bị chặn. Trả về `minimum` và `objective`.
- `uniroot`: tìm nghiệm của hàm `f` trong đoạn `[lower, upper]`. **Bắt buộc** `f(lower)` và `f(upper)` phải trái dấu nhau.
- `uniroot` với `extendInt = "yes"` có thể tự động mở rộng khoảng tìm kiếm để tìm sự đổi dấu — nhưng có thể tìm ra nghiệm giả đối với những hàm thực chất không cắt trục hoành (bằng 0).
- `nlm`: bộ tối thiểu hóa kiểu Newton. Đạo hàm bậc nhất (Gradient) và bậc hai (Hessian) được truyền dưới dạng **các thuộc tính (attributes)** của giá trị trả về từ hàm `fn` (cách thiết kế giao diện khá lạ).
---
## TukeyHSD
- Yêu cầu đầu vào phải là một đối tượng `aov` đã khớp (không nhận đối tượng `lm`).
- Mặc định `conf.level = 0.95`. Trả về các giá trị p đã hiệu chỉnh và khoảng tin cậy cho tất cả các phép so sánh cặp.
- Chỉ thực sự có ý nghĩa đối với các thiết kế **cân bằng** hoặc gần cân bằng; có thể quá lỏng lẻo (liberal) đối với dữ liệu mất cân bằng nghiêm trọng.
---
## anova (dành cho lm)
- `anova(model)`: tính tổng bình phương tuần tự (Loại I - Type I SS) — **thứ tự các số hạng trong mô hình có ảnh hưởng đến kết quả**.
- `anova(model1, model2)`: kiểm định F so sánh các mô hình lồng nhau (nested models).
- Để tính tổng bình phương Loại II hoặc Loại III (Type II / III SS), hãy dùng `car::Anova()`.
FILE:references/statistics.md
# Thống kê — Tham khảo Nhanh
> Các hành vi không hiển nhiên, những cái bẫy dễ mắc phải và các giá trị mặc định dễ gây nhầm lẫn của các hàm trong R.
> Chỉ tập hợp những điều mà Claude chưa biết sẵn.
---
## chisq.test
- `correct = TRUE` (mặc định) áp dụng hiệu chỉnh liên tục Yates **chỉ cho các bảng 2x2**.
- `simulate.p.value = TRUE`: mô phỏng Monte Carlo với `B = 2000` lần lặp (giá trị p nhỏ nhất ~ 0.0005). Mô phỏng này giả định **tổng biên cố định** (lấy mẫu theo kiểu Fisher, chứ không phải giả định của kiểm định khi-bình phương thông thường).
- Đối với kiểm định mức độ phù hợp (goodness-of-fit): hãy truyền vào một vector, không phải ma trận. Tổng của `p` phải bằng 1 (hoặc đặt `rescale.p = TRUE`).
- Đối tượng trả về bao gồm `$expected`, `$residuals` (phần dư Pearson), và `$stdres` (phần dư chuẩn hóa).
---
## wilcox.test
- Mặc định `exact = TRUE` đối với các mẫu nhỏ không có giá trị đồng hạng (ties). Khi có giá trị đồng hạng, xấp xỉ chuẩn sẽ được sử dụng.
- `correct = TRUE` áp dụng hiệu chỉnh liên tục cho xấp xỉ chuẩn.
- `conf.int = TRUE` tính toán ước lượng Hodges-Lehmann và khoảng tin cậy (chứ không chỉ tính mỗi giá trị p).
- Kiểm định ghép cặp: `paired = TRUE` sử dụng kiểm định hạng có dấu (Wilcoxon signed-rank test), chứ không phải kiểm định tổng hạng (Mann-Whitney rank-sum test).
---
## fisher.test
- Đối với các bảng lớn hơn 2x2, hàm sử dụng mô phỏng (`simulate.p.value = TRUE`) hoặc thuật toán mạng (network algorithm).
- `workspace` kiểm soát dung lượng bộ nhớ cấp cho thuật toán mạng; hãy tăng giá trị này lên nếu gặp lỗi bộ nhớ trên các bảng lớn.
- Đối số `or` dùng để kiểm định một tỷ số chênh (odds ratio) cụ thể (mặc định là 1) — chỉ áp dụng cho bảng 2x2.
---
## ks.test
- Kiểm định hai mẫu hoặc kiểm định một mẫu so với một phân phối tham chiếu.
- **Không** xử lý tốt các giá trị đồng hạng (ties) — sẽ phát ra cảnh báo và dùng xấp xỉ tiệm cận.
- Đối với các giả thuyết hợp (composite hypotheses - khi các tham số được ước lượng từ chính dữ liệu), giá trị p sẽ **bảo thủ** (quá lớn). Hãy dùng gói `dgof` hoặc `ks.test` với `exact = NULL` cho các phân phối rời rạc.
---
## p.adjust
- Các phương pháp: `"holm"` (mặc định), `"BH"` (kiểm soát tỷ lệ phát hiện sai Benjamini-Hochberg FDR), `"bonferroni"`, `"BY"`, `"hochberg"`, `"hommel"`, `"fdr"` (bí danh của BH), `"none"`.
- Đối số `n`: tổng số giả thuyết kiểm định (có thể lớn hơn `length(p)` nếu một số giá trị p đã bị loại ra trước đó).
- Xử lý giá trị `NA`: các giá trị p sau hiệu chỉnh sẽ là `NA` tại những vị trí mà đầu vào là `NA`.
---
## pairwise.t.test / pairwise.wilcox.test
- `p.adjust.method` mặc định là `"holm"`. Hãy đổi sang `"BH"` nếu muốn kiểm soát FDR.
- `pool.sd = TRUE` (mặc định cho kiểm định t): sử dụng độ lệch chuẩn gộp (pooled SD) trên tất cả các nhóm (giả định phương sai bằng nhau).
- Trả về một ma trận các giá trị p, chứ không trả về giá trị thống kê kiểm định.
---
## shapiro.test
- Kích thước mẫu phải nằm trong khoảng từ 3 đến 5000.
- Kiểm định tính chuẩn (normality); giá trị p thấp = bằng chứng bác bỏ giả thuyết phân phối chuẩn.
---
## kmeans
- Khuyến nghị đặt `nstart > 1` (ví dụ: `nstart = 25`): chạy thuật toán từ nhiều điểm khởi tạo ngẫu nhiên khác nhau và trả về kết quả tốt nhất.
- Mặc định `iter.max = 10` — có thể quá thấp để đạt sự hội tụ. Hãy tăng lên đối với dữ liệu lớn/phức tạp.
- Thuật toán mặc định là "Hartigan-Wong" (nhìn chung là tốt nhất). Các điểm quá gần nhau có thể gây ra hiện tượng không hội tụ (cảnh báo với `ifault = 4`).
- Việc đánh số thứ tự cụm là tùy ý; thứ tự có thể khác nhau giữa các nền tảng hệ điều hành.
- Luôn trả về đúng k cụm khi k được chỉ định (ngoại trừ thuật toán Lloyd-Forgy có thể trả về ít hơn).
---
## hclust
- `method = "ward.D2"` triển khai đúng tiêu chuẩn của Ward (sử dụng khoảng cách bình phương). Phương pháp `"ward.D"` cũ hơn không bình phương khoảng cách (vẫn được giữ lại để tương thích ngược).
- Đầu vào bắt buộc phải là một đối tượng `dist`. Hãy dùng `as.dist()` để chuyển đổi một ma trận đối xứng.
- Đặt `hang = -1` trong `plot()` sẽ căn thẳng hàng tất cả các nhãn ở sát đáy biểu đồ.
---
## dist
- `method = "euclidean"` (mặc định). Các tùy chọn khác: `"manhattan"`, `"maximum"`, `"canberra"`, `"binary"`, `"minkowski"`.
- Trả về một đối tượng `dist` (chỉ lưu nửa tam giác dưới). Hãy dùng `as.matrix()` để lấy toàn bộ ma trận đầy đủ.
- `"canberra"`: các số hạng có cả tử số và mẫu số đều bằng 0 sẽ bị **bỏ qua** khỏi tổng (chứ không bị coi là 0/0).
- Các giá trị `Inf`: khoảng cách Euclid có chứa `Inf` sẽ bằng `Inf`. Nhiều giá trị `Inf` trong cùng một quan sát sẽ cho ra `NaN` đối với một số phương pháp tính khoảng cách.
---
## prcomp so với princomp
- `prcomp` sử dụng **phân rã giá trị suy biến - SVD** (vượt trội hơn về độ chính xác số học); `princomp` sử dụng `eigen` trên ma trận hiệp phương sai (kém ổn định hơn, khác biệt về mẫu số chuẩn hóa N-1 so với N).
- Đặt `scale. = TRUE` trong `prcomp` giúp chuẩn hóa các biến; điều này rất quan trọng khi các biến có thang đo chênh lệch nhau lớn.
- Độ lệch chuẩn của `princomp` khác với `prcomp` theo hệ số nhân `sqrt((n-1)/n)`.
- Cả hai đều trả về `$rotation` (trọng số tải - loadings) và `$x` (điểm thành phần chính - scores); dấu âm/dương của các thành phần chính có thể khác nhau giữa các lần chạy.
---
## density
- Độ rộng băng thông (bandwidth) mặc định: `bw = "nrd0"` (quy tắc ngón tay cái của Silverman). Đối với dữ liệu đa đỉnh (multimodal), hãy cân nhắc dùng `"SJ"` hoặc `"bcv"`.
- `adjust`: hệ số nhân điều chỉnh băng thông. `adjust = 0.5` làm giảm một nửa băng thông (đường biểu diễn ít trơn hơn).
- Hàm nhân (kernel) mặc định: `"gaussian"`. Khoảng giá trị của mật độ mở rộng vượt ra ngoài phạm vi dữ liệu gốc (được kiểm soát bởi `cut`, mặc định là 3 lần băng thông).
- `n = 512`: số điểm tính giá trị mật độ. Hãy tăng lên nếu muốn vẽ đường cong mượt hơn.
- `from`/`to`: giới hạn rõ ràng khoảng giá trị cần tính mật độ.
---
## quantile
- Có **chín** tùy chọn `type` (từ 1-9). Mặc định là `type = 7` (mặc định của R, nội suy tuyến tính). Type 1 = hàm ngược của hàm phân phối tích lũy thực nghiệm - empirical CDF (mặc định của SAS). Các loại từ 4-9 là liên tục; từ 1-3 là gián đoạn.
- Mặc định `na.rm = FALSE` — sẽ trả về lỗi hoặc NA nếu có bất kỳ giá trị NA nào trong dữ liệu.
- Mặc định `names = TRUE`, tự động gắn thêm tên "0%", "25%", v.v. cho kết quả.
---
## Các hàm Phân phối xác suất (những cái bẫy chung cho mọi phân phối)
Tất cả các hàm phân phối đều tuân theo quy ước tiếp đầu ngữ `d/p/q/r`. Những điểm không hiển nhiên thường gặp:
- **Đối số `n` trong các hàm sinh số ngẫu nhiên `r*()`**: nếu `length(n) > 1`, hàm sẽ lấy `length(n)` làm số lượng cần sinh ra, chứ không lấy giá trị của `n`. Vì vậy `rnorm(c(1,2,3))` sẽ sinh ra 3 giá trị, chứ không phải 1+2+3 giá trị.
- `log = TRUE` / `log.p = TRUE`: tính toán trên thang logarit để đảm bảo độ ổn định số học ở vùng đuôi phân phối.
- `lower.tail = FALSE` tính trực tiếp hàm sống sót P(X > x) (chính xác hơn nhiều so với tính `1 - pnorm()` ở vùng đuôi).
- **Phân phối Gamma**: được tham số hóa bởi `shape` và `rate` (= 1/scale). Mặc định `rate = 1`. Việc chỉ định cả `rate` lẫn `scale` cùng lúc sẽ gây ra lỗi.
- **Phân phối Beta**: dùng `shape1` (alpha), `shape2` (beta) — không có cách tham số hóa trực tiếp theo `mean`/`sd`.
- **Phân phối Poisson `dpois`**: `x` có thể không phải là số nguyên (sẽ trả về 0 kèm theo một cảnh báo đối với các giá trị không nguyên nếu `log = FALSE`).
- **Phân phối Weibull**: dùng `shape` và `scale` (không có tham số `rate`). Công thức tham số hóa của R: `f(x) = (shape/scale)(x/scale)^(shape-1) exp(-(x/scale)^shape)`.
- **Phân phối Lognormal**: `meanlog` và `sdlog` là trung bình và độ lệch chuẩn của **logarit biến ngẫu nhiên**, chứ không phải của bản thân phân phối đó.
---
## cor.test
- Phương pháp mặc định: `"pearson"`. Ngoài ra còn có `"kendall"` và `"spearman"`.
- Trả về `$estimate`, `$p.value`, `$conf.int` (khoảng tin cậy CI chỉ có cho hệ số tương quan Pearson).
- Giao diện công thức: `cor.test(~ x + y, data = df)` — lưu ý dấu `~` không có vế trái (LHS).
---
## ecdf
- Trả về một **hàm số (function)** (hàm bậc thang). Gọi hàm đó trên các giá trị mới: `Fn <- ecdf(x); Fn(3.5)`.
- `plot(ecdf(x))` vẽ biểu đồ hàm phân phối tích lũy thực nghiệm (empirical CDF).
- Hàm trả về liên tục phải và có giới hạn trái (hàm cadlag).
---
## weighted.mean
- Cách xử lý `NA` trong trọng số: quan sát sẽ bị loại bỏ nếu trọng số tương ứng là `NA`.
- Tổng các trọng số không cần phải bằng 1; chúng sẽ được chuẩn hóa tự động bên trong hàm.
FILE:references/visualization.md
# Trực quan hóa dữ liệu — Tham khảo Nhanh
> Các hành vi không hiển nhiên, những cái bẫy dễ mắc phải và các giá trị mặc định dễ gây nhầm lẫn của các hàm trong R.
> Chỉ tập hợp những điều mà Claude chưa biết sẵn.
---
## par (những cái bẫy cần lưu ý)
- Các thiết lập `par()` được áp dụng riêng cho từng thiết bị đồ họa (device). Khi mở một thiết bị mới, mọi thiết lập sẽ quay về mặc định.
- Việc thiết lập `mfrow`/`mfcol` sẽ đặt lại `cex` về 1 và `mex` về 1. Với bố cục 2x2, giá trị `cex` cơ sở bị nhân với 0.83; với bố cục từ 3 hàng/cột trở lên, bị nhân với 0.66.
- Các tham số `mai` (tính bằng inch), `mar` (tính bằng dòng), `pin`, `plt`, `pty` đều tương tác qua lại với nhau. Việc khôi phục toàn bộ các tham số đã lưu sau khi thay đổi kích thước thiết bị có thể tạo ra kết quả không nhất quán — tham số nào đứng sau theo thứ tự bảng chữ cái sẽ ghi đè.
- Đặt `bg` thông qua `par()` cũng đồng thời đặt `new = FALSE`. Đặt `fg` thông qua `par()` cũng đồng thời đặt luôn `col`.
- `xpd = NA` cắt hình vẽ theo vùng thiết bị (cho phép vẽ ra tận lề ngoài cùng); `xpd = TRUE` cắt theo vùng hình (figure region); `xpd = FALSE` (mặc định) cắt theo vùng biểu đồ (plot region).
- `mgp = c(3, 1, 0)`: kiểm soát dòng đặt tiêu đề trục (`mgp[1]`), dòng đặt nhãn số đo trục (`mgp[2]`), và dòng vẽ đường trục (`mgp[3]`). Tất cả đều tính theo đơn vị `mex`.
- `las`: 0 = song song với trục, 1 = nằm ngang, 2 = vuông góc với trục, 3 = thẳng đứng. Tham số này **không** chịu tác động của `srt`.
- `tck = 1` vẽ các đường lưới ngang dọc xuyên qua biểu đồ. `tcl = -0.5` (mặc định) vẽ các vạch chia hướng ra ngoài.
- `usr` khi dùng thang đo logarit: chứa giá trị **log10** của các giới hạn tọa độ, chứ không phải giá trị gốc.
- Các tham số chỉ đọc (không thể gán): `cin`, `cra`, `csi`, `cxy`, `din`, `page`.
---
## layout
- `layout(mat)` trong đó `mat` là một ma trận các số nguyên chỉ định cách sắp xếp các hình vẽ.
- `widths`/`heights` chấp nhận hàm `lcm()` để kết hợp kích thước tuyệt đối (cm) lẫn kích thước tương đối.
- Linh hoạt hơn `mfrow`/`mfcol` nhưng không thể truy vấn lại trạng thái sau khi đã thiết lập (khác với `par("mfrow")`).
- `layout.show(n)` hiển thị trực quan khung bố cục để hỗ trợ gỡ lỗi.
---
## axis / mtext
- `axis(side, at, labels)`: `side` 1=dưới, 2=trái, 3=trên, 4=phải.
- Khoảng cách mặc định giữa các nhãn trục được kiểm soát bởi `par("mgp")`. Các nhãn có thể bị đè lên nhau nếu không được quản lý kỹ.
- `mtext`: đối số `line` định vị văn bản theo các dòng lề (0 = sát mép biểu đồ, số dương = dịch ra phía ngoài). `adj` kiểm soát vị trí căn chỉnh ngang (0-1).
- `mtext` với `outer = TRUE` sẽ viết chữ vào vùng lề **ngoài cùng** (được thiết lập bởi `par(oma = ...)`).
---
## curve
- Đối số đầu tiên có thể là một **biểu thức (expression)** theo biến `x` hoặc một hàm số: `curve(sin, 0, 2*pi)` hoặc `curve(x^2 + 1, 0, 10)`.
- `add = TRUE` để vẽ đè lên biểu đồ hiện có. Mặc định tính tại `n = 101` điểm.
- Mặc định `xname = "x"`; hãy thay đổi tham số này nếu biểu thức của bạn sử dụng tên biến khác.
---
## pairs
- Hàm truyền vào `panel` nhận các đối số `(x, y, ...)` cho từng cặp biến. Dùng `lower.panel`, `upper.panel`, `diag.panel` để tùy chỉnh riêng cho các vùng tam giác dưới, tam giác trên và đường chéo.
- `gap` kiểm soát khoảng cách giữa các ô biểu đồ con (mặc định là 1).
- Giao diện công thức: `pairs(~ var1 + var2 + var3, data = df)`.
---
## coplot
- Biểu đồ có điều kiện: `coplot(y ~ x | a)` hoặc `coplot(y ~ x | a * b)` cho hai biến điều kiện.
- Có thể tùy chỉnh hàm `panel`; dùng `rows`/`columns` để kiểm soát bố cục.
- Hàm panel mặc định chỉ vẽ các điểm; hãy dùng `panel = panel.smooth` để vẽ thêm đường làm trơn loess đè lên.
---
## matplot / matlines / matpoints
- Vẽ các cột của ma trận này theo các cột của ma trận khác. Tự động lặp lại (recycle) các tham số `col`, `lty`, `pch` qua các cột.
- Mặc định `type = "l"` (vẽ đường, khác với `plot` mặc định là `"p"` vẽ điểm).
- Rất hữu ích khi cần vẽ đồng thời nhiều chuỗi thời gian hoặc nhiều đường cong khớp mô hình.
---
## contour / filled.contour / image
- `contour(x, y, z)`: `z` bắt buộc phải là một ma trận có kích thước `dim = c(length(x), length(y))`.
- `filled.contour` sử dụng bố cục phi tiêu chuẩn — nó tự tạo vùng biểu đồ riêng cho thanh chú giải màu. **Không thể dùng chung `par(mfrow)` với hàm này**. Việc thêm các phần tử vẽ đè lên đòi hỏi phải truyền qua đối số `plot.axes`.
- `image`: vẽ các giá trị z dưới dạng lưới các hình chữ nhật tô màu. Bảng màu mặc định có thể gây hiểu nhầm; hãy chỉ định rõ tham số `col`.
- Đối với `image`, `x` và `y` có thể chỉ định **ranh giới ô** hoặc **tâm ô** tùy thuộc vào độ dài vector truyền vào.
---
## persp
- `persp(x, y, z, theta, phi)`: `theta` = góc phương vị (xoay ngang), `phi` = góc độ cao (nghiêng dọc).
- Trả về một **ma trận biến đổi** (ẩn - invisible) dùng để chiếu tọa độ 3D xuống 2D — hãy dùng hàm `trans3d()` cùng ma trận này để vẽ thêm điểm/đường thẳng lên biểu đồ phối cảnh 3D.
- `shade` và `col` kiểm soát việc đổ bóng và màu bề mặt. Đặt `border = NA` để xóa các đường lưới bề mặt.
---
## segments / arrows / rect / polygon
- Tất cả các hàm này đều nhận tọa độ đã vector hóa; tự động lặp lại (recycle) khi cần.
- `arrows`: `code = 1` (mũi tên ở điểm đầu), `code = 2` (mũi tên ở điểm cuối, mặc định), `code = 3` (cả hai đầu).
- `polygon`: điểm cuối cùng tự động nối khép kín về điểm đầu tiên. Tô màu bên trong bằng `col`; `border` kiểm soát đường viền.
- `rect(xleft, ybottom, xright, ytop)` — lưu ý thứ tự đối số không giống như các hệ thống đồ họa khác.
---
## dev / dev.off / dev.copy
- `dev.new()` mở một thiết bị đồ họa mới. `dev.off()` đóng thiết bị hiện tại (và đẩy toàn bộ dữ liệu đệm ra tệp đối với các thiết bị ghi tệp như `pdf`).
- Gọi `dev.off()` trên thiết bị đang mở **cuối cùng** sẽ đưa hệ thống về thiết bị rỗng (null device).
- Gọi `dev.copy(pdf, file = "plot.pdf")` theo sau là `dev.off()` để lưu biểu đồ hiện tại ra tệp PDF.
- `dev.list()` trả về danh sách tất cả các thiết bị đang mở; `dev.cur()` trả về thiết bị đang hoạt động hiện hành.
---
## pdf
- Bắt buộc phải gọi `dev.off()` để hoàn tất việc ghi tệp. Nếu quên gọi, tệp PDF sinh ra có thể bị rỗng hoặc bị hỏng.
- `onefile = TRUE` (mặc định): xuất nhiều trang vào chung một tệp PDF. `onefile = FALSE`: mỗi trang một tệp riêng (sử dụng ký hiệu `%d` trong tên tệp để đánh số thứ tự trang).
- Khuyến nghị đặt `useDingbats = FALSE` để tránh lỗi hiển thị ký hiệu điểm (`pch`) trên một số trình đọc PDF.
- Kích thước mặc định: 7x7 inch. Tham số `family` kiểm soát họ phông chữ.
---
## png / các thiết bị ảnh bitmap
- `res` kiểm soát độ phân giải DPI (mặc định là 72). Để xuất ảnh chất lượng in ấn: đặt `res = 300` cùng với `width`/`height` phù hợp tính bằng pixel hoặc inch (kèm theo `units = "in"`).
- `type = "cairo"` (trên các hệ thống có cài thư viện cairo) cho chất lượng khử răng cưa (antialiasing) tốt hơn nhiều so với mặc định.
- Đặt `bg = "transparent"` để có nền trong suốt (định dạng PNG hỗ trợ kênh alpha).
---
## colors / rgb / hcl / col2rgb
- `colors()` trả về tất cả 657 tên màu có sẵn. `col2rgb("color")` trả về ma trận giá trị RGB.
- `rgb(r, g, b, alpha, maxColorValue = 255)` — lưu ý giá trị mặc định của `maxColorValue` là 1, chứ không phải 255.
- `hcl(h, c, l)`: không gian màu đồng nhất về mặt cảm nhận thị giác. Được khuyên dùng khi tạo các thang màu chuyển sắc.
- `adjustcolor(col, alpha.f = 0.5)`: cách đơn giản nhất để thêm độ trong suốt cho màu sắc bất kỳ.
---
## colorRamp / colorRampPalette
- `colorRamp` trả về một **hàm số** ánh xạ đoạn [0,1] sang ma trận màu RGB.
- `colorRampPalette` trả về một **hàm số** nhận đối số `n` và trả về `n` mã màu nội suy.
- Đặt `space = "Lab"` cho kết quả nội suy màu đồng đều về mặt thị giác hơn so với `"rgb"`.
---
## palette / recordPlot
- `palette()` trả về bảng màu hiện tại (mặc định gồm 8 màu). `palette("Set1")` thiết lập một bảng màu dựng sẵn.
- Khi truyền số nguyên làm màu sắc trong các hàm vẽ, R sẽ dùng số đó làm chỉ số tra vào bảng `palette()` (có xoay vòng lại từ đầu nếu vượt quá). Chỉ số 0 = màu nền (background color).
- `recordPlot()` / `replayPlot()`: lưu và khôi phục toàn bộ một biểu đồ — phụ thuộc chặt vào thiết bị đồ họa và dễ lỗi khi mang sang phiên làm việc khác.
FILE:assets/analysis_template.R
# ============================================================
# Analysis Template — Base R
# Copy this file, rename it, and fill in your details.
# ============================================================
# Author :
# Date :
# Data :
# Purpose :
# ============================================================
# ── 0. Setup ─────────────────────────────────────────────────
# Clear environment (optional — comment out if loading into existing session)
rm(list = ls())
# Set working directory if needed
# setwd("/path/to/your/project")
# Reproducibility
set.seed(42)
# Libraries — uncomment what you need
# library(haven) # read .dta / .sav / .sas
# library(readxl) # read Excel files
# library(openxlsx) # write Excel files
# library(foreign) # older Stata / SPSS formats
# library(survey) # survey-weighted analysis
# library(lmtest) # Breusch-Pagan, Durbin-Watson etc.
# library(sandwich) # robust standard errors
# library(car) # Type II/III ANOVA, VIF
# ── 1. Load Data ─────────────────────────────────────────────
df <- read.csv("your_data.csv", stringsAsFactors = FALSE)
# df <- readRDS("your_data.rds")
# df <- haven::read_dta("your_data.dta")
# First look — always run these
dim(df)
str(df)
head(df, 10)
summary(df)
# ── 2. Data Quality Check ────────────────────────────────────
# Missing values
na_report <- data.frame(
column = names(df),
n_miss = colSums(is.na(df)),
pct_miss = round(colMeans(is.na(df)) * 100, 1),
row.names = NULL
)
print(na_report[na_report$n_miss > 0, ])
# Duplicates
n_dup <- sum(duplicated(df))
cat(sprintf("Duplicate rows: %d\n", n_dup))
# Unique values for categorical columns
cat_cols <- names(df)[sapply(df, function(x) is.character(x) | is.factor(x))]
for (col in cat_cols) {
cat(sprintf("\n%s (%d unique):\n", col, length(unique(df[[col]]))))
print(table(df[[col]], useNA = "ifany"))
}
# ── 3. Clean & Transform ─────────────────────────────────────
# Rename columns (example)
# names(df)[names(df) == "old_name"] <- "new_name"
# Convert types
# df$group <- as.factor(df$group)
# df$date <- as.Date(df$date, format = "%Y-%m-%d")
# Recode values (example)
# df$gender <- ifelse(df$gender == 1, "Male", "Female")
# Create new variables (example)
# df$log_income <- log(df$income + 1)
# df$age_group <- cut(df$age,
# breaks = c(0, 25, 45, 65, Inf),
# labels = c("18-25", "26-45", "46-65", "65+"))
# Filter rows (example)
# df <- df[df$year >= 2010, ]
# df <- df[complete.cases(df[, c("outcome", "predictor")]), ]
# Drop unused factor levels
# df <- droplevels(df)
# ── 4. Descriptive Statistics ────────────────────────────────
# Numeric summary
num_cols <- names(df)[sapply(df, is.numeric)]
round(sapply(df[num_cols], function(x) c(
n = sum(!is.na(x)),
mean = mean(x, na.rm = TRUE),
sd = sd(x, na.rm = TRUE),
median = median(x, na.rm = TRUE),
min = min(x, na.rm = TRUE),
max = max(x, na.rm = TRUE)
)), 3)
# Cross-tabulation
# table(df$group, df$category, useNA = "ifany")
# prop.table(table(df$group, df$category), margin = 1) # row proportions
# ── 5. Visualization (EDA) ───────────────────────────────────
par(mfrow = c(2, 2))
# Histogram of main outcome
hist(df$outcome_var,
main = "Distribution of Outcome",
xlab = "Outcome",
col = "steelblue",
border = "white",
breaks = 30)
# Boxplot by group
boxplot(outcome_var ~ group_var,
data = df,
main = "Outcome by Group",
col = "lightyellow",
las = 2)
# Scatter plot
plot(df$predictor, df$outcome_var,
main = "Predictor vs Outcome",
xlab = "Predictor",
ylab = "Outcome",
pch = 19,
col = adjustcolor("steelblue", alpha.f = 0.5),
cex = 0.8)
abline(lm(outcome_var ~ predictor, data = df),
col = "red", lwd = 2)
# Correlation matrix (numeric columns only)
cor_mat <- cor(df[num_cols], use = "complete.obs")
image(cor_mat,
main = "Correlation Matrix",
col = hcl.colors(20, "RdBu", rev = TRUE))
par(mfrow = c(1, 1))
# ── 6. Analysis ───────────────────────────────────────────────
# ·· 6a. Comparison of means ··
t.test(outcome_var ~ group_var, data = df)
# ·· 6b. Linear regression ··
fit <- lm(outcome_var ~ predictor1 + predictor2 + group_var,
data = df)
summary(fit)
confint(fit)
# Check VIF for multicollinearity (requires car)
# car::vif(fit)
# Robust standard errors (requires lmtest + sandwich)
# lmtest::coeftest(fit, vcov = sandwich::vcovHC(fit, type = "HC3"))
# ·· 6c. ANOVA ··
# fit_aov <- aov(outcome_var ~ group_var, data = df)
# summary(fit_aov)
# TukeyHSD(fit_aov)
# ·· 6d. Logistic regression (binary outcome) ··
# fit_logit <- glm(binary_outcome ~ x1 + x2,
# data = df,
# family = binomial(link = "logit"))
# summary(fit_logit)
# exp(coef(fit_logit)) # odds ratios
# exp(confint(fit_logit)) # OR confidence intervals
# ── 7. Model Diagnostics ─────────────────────────────────────
par(mfrow = c(2, 2))
plot(fit)
par(mfrow = c(1, 1))
# Residual normality
shapiro.test(residuals(fit))
# Homoscedasticity (requires lmtest)
# lmtest::bptest(fit)
# ── 8. Save Output ────────────────────────────────────────────
# Cleaned data
# write.csv(df, "data_clean.csv", row.names = FALSE)
# saveRDS(df, "data_clean.rds")
# Model results to text file
# sink("results.txt")
# cat("=== Linear Model ===\n")
# print(summary(fit))
# cat("\n=== Confidence Intervals ===\n")
# print(confint(fit))
# sink()
# Plots to file
# png("figure1_distributions.png", width = 1200, height = 900, res = 150)
# par(mfrow = c(2, 2))
# # ... your plots ...
# par(mfrow = c(1, 1))
# dev.off()
# ============================================================
# END OF TEMPLATE
# ============================================================
FILE:scripts/check_data.R
# check_data.R — Quick data quality report for any R data frame
# Usage: source("check_data.R") then call check_data(df)
# Or: source("check_data.R"); check_data(read.csv("yourfile.csv"))
check_data <- function(df, top_n_levels = 8) {
if (!is.data.frame(df)) stop("Input must be a data frame.")
n_row <- nrow(df)
n_col <- ncol(df)
cat("══════════════════════════════════════════\n")
cat(" DATA QUALITY REPORT\n")
cat("══════════════════════════════════════════\n")
cat(sprintf(" Rows: %d Columns: %d\n", n_row, n_col))
cat("══════════════════════════════════════════\n\n")
# ── 1. Column overview ──────────────────────
cat("── COLUMN OVERVIEW ────────────────────────\n")
for (col in names(df)) {
x <- df[[col]]
cls <- class(x)[1]
n_na <- sum(is.na(x))
pct <- round(n_na / n_row * 100, 1)
n_uniq <- length(unique(x[!is.na(x)]))
na_flag <- if (n_na == 0) "" else sprintf(" *** %d NAs (%.1f%%)", n_na, pct)
cat(sprintf(" %-20s %-12s %d unique%s\n",
col, cls, n_uniq, na_flag))
}
# ── 2. NA summary ────────────────────────────
cat("\n── NA SUMMARY ─────────────────────────────\n")
na_counts <- sapply(df, function(x) sum(is.na(x)))
cols_with_na <- na_counts[na_counts > 0]
if (length(cols_with_na) == 0) {
cat(" No missing values. \n")
} else {
cat(sprintf(" Columns with NAs: %d of %d\n\n", length(cols_with_na), n_col))
for (col in names(cols_with_na)) {
bar_len <- round(cols_with_na[col] / n_row * 20)
bar <- paste0(rep("█", bar_len), collapse = "")
pct_na <- round(cols_with_na[col] / n_row * 100, 1)
cat(sprintf(" %-20s [%-20s] %d (%.1f%%)\n",
col, bar, cols_with_na[col], pct_na))
}
}
# ── 3. Numeric columns ───────────────────────
num_cols <- names(df)[sapply(df, is.numeric)]
if (length(num_cols) > 0) {
cat("\n── NUMERIC COLUMNS ────────────────────────\n")
cat(sprintf(" %-20s %8s %8s %8s %8s %8s\n",
"Column", "Min", "Mean", "Median", "Max", "SD"))
cat(sprintf(" %-20s %8s %8s %8s %8s %8s\n",
"──────", "───", "────", "──────", "───", "──"))
for (col in num_cols) {
x <- df[[col]][!is.na(df[[col]])]
if (length(x) == 0) next
cat(sprintf(" %-20s %8.3g %8.3g %8.3g %8.3g %8.3g\n",
col,
min(x), mean(x), median(x), max(x), sd(x)))
}
}
# ── 4. Factor / character columns ───────────
cat_cols <- names(df)[sapply(df, function(x) is.factor(x) | is.character(x))]
if (length(cat_cols) > 0) {
cat("\n── CATEGORICAL COLUMNS ────────────────────\n")
for (col in cat_cols) {
x <- df[[col]]
tbl <- sort(table(x, useNA = "no"), decreasing = TRUE)
n_lv <- length(tbl)
cat(sprintf("\n %s (%d unique values)\n", col, n_lv))
show <- min(top_n_levels, n_lv)
for (i in seq_len(show)) {
lbl <- names(tbl)[i]
cnt <- tbl[i]
pct <- round(cnt / n_row * 100, 1)
cat(sprintf(" %-25s %5d (%.1f%%)\n", lbl, cnt, pct))
}
if (n_lv > top_n_levels) {
cat(sprintf(" ... and %d more levels\n", n_lv - top_n_levels))
}
}
}
# ── 5. Duplicate rows ────────────────────────
cat("\n── DUPLICATES ─────────────────────────────\n")
n_dup <- sum(duplicated(df))
if (n_dup == 0) {
cat(" No duplicate rows.\n")
} else {
cat(sprintf(" %d duplicate row(s) found (%.1f%% of data)\n",
n_dup, n_dup / n_row * 100))
}
cat("\n══════════════════════════════════════════\n")
cat(" END OF REPORT\n")
cat("══════════════════════════════════════════\n")
# Return invisibly for programmatic use
invisible(list(
dims = c(rows = n_row, cols = n_col),
na_counts = na_counts,
n_dupes = n_dup
))
}
FILE:scripts/scaffold_analysis.R
#!/usr/bin/env Rscript
# scaffold_analysis.R — Generates a starter analysis script
#
# Usage (from terminal):
# Rscript scaffold_analysis.R myproject
# Rscript scaffold_analysis.R myproject outcome_var group_var
#
# Usage (from R console):
# source("scaffold_analysis.R")
# scaffold_analysis("myproject", outcome = "score", group = "treatment")
#
# Output: myproject_analysis.R (ready to edit)
scaffold_analysis <- function(project_name,
outcome = "outcome",
group = "group",
data_file = NULL) {
if (is.null(data_file)) data_file <- paste0(project_name, ".csv")
out_file <- paste0(project_name, "_analysis.R")
template <- sprintf(
'# ============================================================
# Project : %s
# Created : %s
# ============================================================
# ── 0. Libraries ─────────────────────────────────────────────
# Add packages you need here
# library(ggplot2)
# library(haven) # for .dta files
# library(openxlsx) # for Excel output
# ── 1. Load Data ─────────────────────────────────────────────
df <- read.csv("%s", stringsAsFactors = FALSE)
# Quick check — always do this first
cat("Dimensions:", dim(df), "\\n")
str(df)
head(df)
# ── 2. Explore / EDA ─────────────────────────────────────────
summary(df)
# NA check
na_counts <- colSums(is.na(df))
na_counts[na_counts > 0]
# Key variable distributions
hist(df$%s, main = "Distribution of %s", xlab = "%s")
if ("%s" %%in%% names(df)) {
table(df$%s)
barplot(table(df$%s),
main = "Counts by %s",
col = "steelblue",
las = 2)
}
# ── 3. Clean / Transform ──────────────────────────────────────
# df <- df[complete.cases(df), ] # drop rows with any NA
# df$%s <- as.factor(df$%s) # convert to factor
# ── 4. Analysis ───────────────────────────────────────────────
# Descriptive stats by group
tapply(df$%s, df$%s, mean, na.rm = TRUE)
tapply(df$%s, df$%s, sd, na.rm = TRUE)
# t-test (two groups)
# t.test(%s ~ %s, data = df)
# Linear model
fit <- lm(%s ~ %s, data = df)
summary(fit)
confint(fit)
# ANOVA (multiple groups)
# fit_aov <- aov(%s ~ %s, data = df)
# summary(fit_aov)
# TukeyHSD(fit_aov)
# ── 5. Visualize Results ──────────────────────────────────────
par(mfrow = c(1, 2))
# Boxplot by group
boxplot(%s ~ %s,
data = df,
main = "%s by %s",
xlab = "%s",
ylab = "%s",
col = "lightyellow")
# Model diagnostics
plot(fit, which = 1) # residuals vs fitted
par(mfrow = c(1, 1))
# ── 6. Save Output ────────────────────────────────────────────
# Save cleaned data
# write.csv(df, "%s_clean.csv", row.names = FALSE)
# Save model summary to text
# sink("%s_results.txt")
# summary(fit)
# sink()
# Save plot to file
# png("%s_boxplot.png", width = 800, height = 600, res = 150)
# boxplot(%s ~ %s, data = df, col = "lightyellow")
# dev.off()
',
project_name,
format(Sys.Date(), "%%Y-%%m-%%d"),
data_file,
# Section 2 — EDA
outcome, outcome, outcome,
group, group, group, group,
# Section 3
group, group,
# Section 4
outcome, group,
outcome, group,
outcome, group,
outcome, group,
outcome, group,
outcome, group,
# Section 5
outcome, group,
outcome, group,
group, outcome,
# Section 6
project_name, project_name, project_name,
outcome, group
)
writeLines(template, out_file)
cat(sprintf("Created: %s\n", out_file))
invisible(out_file)
}
# ── Run from command line ─────────────────────────────────────
if (!interactive()) {
args <- commandArgs(trailingOnly = TRUE)
if (length(args) == 0) {
cat("Usage: Rscript scaffold_analysis.R <project_name> [outcome_var] [group_var]\n")
cat("Example: Rscript scaffold_analysis.R myproject score treatment\n")
quit(status = 1)
}
project <- args[1]
outcome <- if (length(args) >= 2) args[2] else "outcome"
group <- if (length(args) >= 3) args[3] else "group"
scaffold_analysis(project, outcome = outcome, group = group)
}
FILE:README.md
# base-r-skill
GitHub: https://github.com/iremaydas/base-r-skill
Một kỹ năng (skill) Claude Code dành cho lập trình R cơ bản (base R).
---
## Câu chuyện ra đời
Tôi là một nghiên cứu sinh Tiến sĩ ngành khoa học chính trị, thường xuyên sử dụng R nhưng chưa bao giờ dám tự nhận mình là *một chuyên gia R*. Tôi cần một kỹ năng Claude Code dành cho R cơ bản — một thứ không dùng tidyverse, không dùng ggplot2, chỉ R thuần túy mà thôi — và tôi tìm khắp nơi đều không thấy.
Thế là tôi tự làm lấy một cái. Vào lúc 11 giờ đêm. Nhờ chính Claude giúp tôi xây dựng một kỹ năng dành cho Claude.
Nếu bạn cũng là người lần nào cũng phải lên Google gõ `how to drop NA rows in R` (cách xóa các hàng NA trong R), thì bộ kỹ năng này chính là dành cho bạn. 🫶
---
## Bên trong có gì
```
base-r/
├── SKILL.md # Tệp kỹ năng chính
├── references/ # Những cái bẫy & hành vi không hiển nhiên
│ ├── data-wrangling.md # Bẫy trích xuất tập con, họ hàm apply, merge, đặc thù của factor
│ ├── modeling.md # Cú pháp công thức, lm/glm/aov/nls, optim
│ ├── statistics.md # Kiểm định giả thuyết, phân phối xác suất, phân cụm
│ ├── visualization.md # par, layout, thiết bị đồ họa, màu sắc
│ ├── io-and-text.md # read.table, grep, biểu thức chính quy, định dạng chuỗi
│ ├── dates-and-system.md # Bẫy Date/POSIXct, options(), thao tác tệp
│ └── misc-utilities.md # tryCatch, do.call, chuỗi thời gian, tiện ích khác
├── scripts/
│ ├── check_data.R # Báo cáo nhanh chất lượng dữ liệu cho mọi data frame
│ └── scaffold_analysis.R # Tạo khung kịch bản phân tích khởi đầu
└── assets/
└── analysis_template.R # Mẫu phân tích dữ liệu dùng ngay (copy-paste)
```
Các tệp tham khảo được cô đọng từ tài liệu hướng dẫn chính thức của R 4.5.3 — **từ 19.518 dòng → còn 945 dòng** (giảm 95%). Chỉ những kiến thức không hiển nhiên mới được giữ lại: những cái bẫy dễ mắc, các giá trị mặc định gây bất ngờ, những tương tác phức tạp giữa các tham số. Những điều mà Claude vốn đã nắm rõ đều được cắt bỏ.
---
## Cách sử dụng
Thêm kỹ năng này vào cấu hình Claude Code của bạn bằng cách trỏ tới kho mã nguồn này. Sau đó, Claude sẽ tự động nạp các tệp tham khảo có liên quan khi bạn làm việc với các tác vụ R.
Hoạt động hiệu quả nhất cho:
- Thao tác dữ liệu bằng R cơ bản (không dùng tidyverse)
- Mô hình hóa thống kê với `lm`, `glm`, `aov`
- Vẽ biểu đồ cơ bản với `plot`, `par`, `barplot`
- Hiểu được lý do tại sao đoạn mã R của bạn lại cư xử kỳ lạ như vậy
Không dành cho: tidyverse, ggplot2, Shiny, hoặc phát triển gói (package) R.
---
## Kịch bản `check_data.R`
Có lẽ là công cụ độc lập hữu ích nhất ở đây. Hãy nạp nó bằng `source()` và chạy `check_data(df)` trên bất kỳ data frame nào để nhận ngay một báo cáo được định dạng rõ ràng về kích thước chiều, số lượng giá trị NA, thống kê tóm tắt biến số và bảng phân tổ biến phân loại.
```r
source("scripts/check_data.R")
check_data(your_df)
```
---
## Được xây dựng với sự trợ giúp từ
- Claude (hiển nhiên rồi)
- Tài liệu hướng dẫn sử dụng R chính thức (trọn bộ 19.518 dòng)
- Một chút bực bội nhẹ và vài tách cà phê
---
## Đóng góp
Nếu bạn phát hiện còn thiếu một cái bẫy nào đó, một giá trị mặc định chưa chuẩn, hoặc điều gì đó xứng đáng có mặt trong bộ tài liệu tham khảo — mọi Pull Request (PR) đều rất được chào đón. Tôi cũng đang vừa làm vừa học mà.
---
*Được thực hiện bởi [@iremaydas](https://github.com/iremaydas) — Nghiên cứu sinh Tiến sĩ, người dùng R không chuyên, và là người tra Google toàn thời gian về những thứ lẽ ra giờ này mình phải thuộc lòng rồi.*