Menu

Ghi dữ liệu ra tệp trong R (write.csv, saveRDS)

Xuất dữ liệu từ R: write.csv với row.names = FALSE (luôn luôn), write.table cho các dấu phân cách khác và saveRDS để lưu rồi đọc lại mà không mất kiểu dữ liệu.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

write.csv() xuất một data frame

Đưa một data frame ra khỏi R thành một tệp mà các chương trình khác mở được chỉ cần một lệnh gọi:

write.csv(df, "results.csv", row.names = FALSE)

Đó là dòng cần ghi nhớ, kể cả phần row.names = FALSE mà lát nữa chúng ta sẽ giải thích. Đối số đầu tiên là data frame, đối số thứ hai là tên tệp, và hàm này không trả về gì: toàn bộ việc của nó là tạo ra tệp.

Bạn có thể xem chính xác write.csv() tạo ra gì mà không cần ghi xuống ổ đĩa, vì ở chỗ cần tên tệp, hàm nhận bất kỳ connection nào, kể cả stdout(), thứ chỉ đơn giản in ra:

CSV của bạn đây: một dòng tiêu đề, mỗi hàng một dòng, giá trị văn bản được bọc trong dấu ngoặc kép, số thì để nguyên. (Dấu ngoặc kép là do mặc định quote = TRUE; mọi trình đọc CSV phổ biến đều xử lý được, nên cứ để nguyên.) Ghi ra tệp thật là cùng lệnh gọi đó với "results.csv" thay cho stdout().

Vì sao row.names = FALSE nên là mặc định của bạn

Mỗi data frame đều mang theo tên hàng, thường chỉ là các số 1, 2, 3..., và mặc định của write.csv() là ghi chúng vào tệp thành một cột đầu tiên thừa với tiêu đề trống. Hãy xem đầu ra mặc định trông thế nào, và điều gì xảy ra khi tệp đó được đọc ngược lại vào R:

Tệp xuất ra mọc thêm một cột đầu tiên không tên chứa "1", "2", và khi nhập lại, read.csv() buộc phải gọi nó bằng một cái tên nào đó, nên nó tự đặt là X. Đó chính là cột X bí ẩn khiến mọi người mới học bối rối: nó không phải dữ liệu, mà là cách đánh số hàng của R rò rỉ vào tệp. Lặp lại chu trình xuất rồi nhập vài lần, bạn sẽ tích lũy dần X, X.1, X.2...

Tên hàng gần như không bao giờ mang thông tin thật, và khi có thì thông tin đó vốn nên nằm trong một cột đàng hoàng. Vì vậy hãy biến row.names = FALSE thành phản xạ. Lẽ ra nó nên là mặc định; nhưng vì lý do tương thích ngược thì không, nên thói quen này là việc của bạn.

Tệp được lưu ở đâu

write.csv(df, "results.csv") ghi vào thư mục làm việc của R, và nếu đó không phải chỗ bạn nghĩ, tệp sẽ có vẻ như biến mất. Lệnh không hề thất bại; tệp chỉ đang nằm trong thư mục mà getwd() in ra. Đây là cùng một cái bẫy như khi đọc tệp, chỉ là phiên bản phản chiếu, và cùng hai công cụ đó giải quyết được:

getwd()                          # the folder your file went to
write.csv(df, "C:/Users/ada/results/out.csv", row.names = FALSE)   # or: remove all doubt

Đường dẫn đầy đủ (dấu gạch chéo xuôi chạy được trên mọi nền tảng, kể cả Windows) khiến script không còn nhập nhằng. Cách đặt và quản lý thư mục làm việc được trình bày trong thư mục làm việc.

write.table() cho các dấu phân cách khác, và chuyện ghi nối thêm

write.csv() thực ra là một lớp bọc mỏng quanh hàm đa dụng write.table(), được cấu hình sẵn cho dấu phẩy. Hãy gọi thẳng write.table() khi bạn cần định dạng khác, phổ biến nhất là phân cách bằng tab:

Phân cách bằng tab, không có dấu ngoặc kép: định dạng mà nhiều công cụ phía sau ưa dùng. Lưu ý rằng với write.table() bạn phải đặt mọi thứ một cách tường minh; nó không thừa hưởng dấu phẩy hay cách đặt ngoặc kép mặc định của write.csv.

Một việc write.csv() từ chối làm là ghi nối thêm (append): truyền append = TRUE sẽ nhận về cảnh báo rằng thiết lập này bị bỏ qua, vì ghi nối thêm sẽ chèn lại dòng tiêu đề vào giữa tệp. Nếu bạn thật sự cần thêm hàng vào một tệp CSV có sẵn, chẳng hạn ghi log kết quả qua nhiều lần chạy, hãy chuyển xuống write.table() và tự tắt dòng tiêu đề:

write.table(new_rows, "log.csv", sep = ",",
            append = TRUE, col.names = FALSE, row.names = FALSE)

Còn nếu bạn dùng tidyverse, readr::write_csv(df, "out.csv") là bản đối ứng của read_csv(): cùng đầu ra, nhanh hơn với các bảng lớn, và nó vốn không bao giờ ghi tên hàng, nên bớt được một đối số phải nhớ.

saveRDS() và readRDS(): lưu rồi đọc lại chính xác

CSV là định dạng làm mất thông tin. Nó chỉ lưu văn bản, hết. Vì vậy một cột Date trở thành chuỗi "2026-08-07", một factor chỉ còn nhãn và mất thứ tự level, thuộc tính biến mất, và mọi kiểu dữ liệu phải được đoán lại khi nhập (thường đúng, đôi khi không, ví dụ các số 0 ở đầu). Hơn nữa CSV chỉ chứa được một bảng hình chữ nhật: một mô hình đã fit, một list hay một ma trận có dimnames hoàn toàn không có dạng biểu diễn CSV.

Khi tệp là dành cho R, chẳng hạn lưu dữ liệu đã làm sạch hôm nay cho phiên làm việc ngày mai, hãy bỏ qua hẳn các định dạng văn bản:

saveRDS(df, "clean_data.rds")        # today

df <- readRDS("clean_data.rds")      # tomorrow: identical object, types intact

saveRDS() tuần tự hóa một đối tượng R, bất kỳ đối tượng nào, một cách chính xác. readRDS() trả nó về nguyên vẹn từng byte: factor giữ level, ngày vẫn là ngày, không có gì phải đoán lại. Nó dùng được cho mọi thứ: một data frame, một list các data frame, một mô hình hồi quy đã fit. Tệp .rds là dạng nhị phân nén, nên thường còn nhỏ hơn tệp CSV. Cái giá duy nhất là ngoài R ra không gì mở được nó, và đó chính là lý do của quy tắc kinh nghiệm: CSV để chia sẻ với người khác và các công cụ khác, RDS để lưu cho chính bạn.

save() và load(), và vì sao RDS thường thắng

Bạn cũng sẽ gặp một cặp hàm cũ hơn. save() ghi nhiều đối tượng có tên vào một tệp .RData, và load() khôi phục chúng vào workspace của bạn, dưới đúng tên gốc:

save(df, model, params, file = "session.RData")
load("session.RData")    # df, model, params silently appear

Chính sự tiện lợi đó là vấn đề. load() quyết định tên biến chứ không phải bạn: nó thả các đối tượng vào environment của bạn mà bạn không nhìn thấy trước, âm thầm ghi đè bất cứ thứ gì đã có sẵn những cái tên đó. Sáu tháng sau, không ai còn nhớ session.RData chứa gì nếu không nạp nó ra để xem.

readRDS() có giao diện minh bạch: bạn chọn tên (df <- readRDS(...)), không có gì bị ghi đè sau lưng bạn, và một tệp nghĩa là một đối tượng. Trừ khi bạn thật sự cần gói nhiều đối tượng vào một tệp, hãy ưu tiên RDS. (Nếu cần gói nhiều đối tượng, hãy đặt chúng vào một list có tên rồi saveRDS() list đó: hiệu quả như nhau mà không có bất ngờ nào.)

Những điều cần nhớ

  • write.csv(df, "out.csv", row.names = FALSE): đó là toàn bộ việc xuất dữ liệu, và đúng vậy, luôn kèm row.names = FALSE.
  • Cột X bí ẩn khi nhập lại là tên hàng rò rỉ vào tệp; mặc định đã ghi chúng ra, còn bạn thì không nên.
  • Tệp được lưu trong thư mục làm việc: getwd() tìm ra các tệp xuất "bị lạc", còn đường dẫn đầy đủ thì ngăn chuyện đó xảy ra.
  • write.table() xử lý các dấu phân cách khác và (với col.names = FALSE) ghi nối thêm thật sự; write.csv() cố ý không cho ghi nối thêm.
  • saveRDS() / readRDS() lưu và đọc lại mọi đối tượng R chính xác, giữ nguyên kiểu, factor và thuộc tính. CSV để chia sẻ, RDS để dùng cho chính bạn.
  • Ưu tiên RDS hơn save()/load(): tên được đặt tường minh, không có ghi đè ngầm.

Tiếp theo: một kiểu dữ liệu xứng đáng có riêng một trang trước khi nó gây rắc rối cho bạn lúc nhập dữ liệu, đó là kiểu ngày tháng.

Câu hỏi thường gặp

Làm sao để xuất một data frame ra tệp CSV trong R?

write.csv(df, "output.csv", row.names = FALSE). Phần row.names = FALSE rất quan trọng: thiếu nó, R ghi số thứ tự hàng nội bộ thành một cột đầu tiên thừa, và cột này hiện ra thành một cột bí ẩn tên X khi có ai đó nhập lại tệp.

Vì sao tệp CSV tôi xuất ra lại có thêm cột X khi đọc lại?

Vì mặc định của write.csv là row.names = TRUE: nó ghi số thứ tự hàng thành một cột đầu tiên không có tên, và read.csv đặt tên cột đó là X khi đọc vào. Hãy xuất với row.names = FALSE và cột ma này sẽ biến mất.

write.csv và saveRDS trong R khác nhau thế nào?

write.csv tạo ra một bảng văn bản thuần mà ai cũng mở được, nhưng nó chỉ lưu văn bản: thứ tự level của factor, kiểu Date, thuộc tính và các đối tượng không phải dạng bảng đều bị làm phẳng hoặc mất đi, và kiểu dữ liệu phải được đoán lại khi nhập. saveRDS lưu một đối tượng R chính xác từng byte; readRDS trả nó về y như cũ. Dùng CSV để chia sẻ với người khác, dùng RDS để lưu cho các phiên R của chính bạn.

write.csv lưu tệp ở đâu?

Trong thư mục làm việc hiện tại của R, trừ khi bạn đưa vào một đường dẫn đầy đủ. Chạy getwd() để xem đó là thư mục nào: nếu tệp của bạn có vẻ đã biến mất, nó đang nằm trong thư mục mà getwd() in ra. Truyền một đường dẫn đầy đủ như "C:/Users/ada/results/out.csv" sẽ xóa bỏ mọi nhập nhằng.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu