Menu

Factor trong R: dữ liệu phân loại, level và bẫy thường gặp

Factor là cách R lưu dữ liệu phân loại: mã số nguyên mang nhãn văn bản. Cách tạo, sắp thứ tự, đặt mức tham chiếu và tránh cái bẫy chuyển factor sang số.

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.

Factor thực chất là gì

Factor là câu trả lời của R cho dữ liệu phân loại (categorical data): các giá trị lấy từ một danh sách lựa chọn cố định như nhóm điều trị, câu trả lời khảo sát, cỡ áo. Khi in ra nó trông giống vector character, nhưng không phải. Bên trong, factor là một vector mã số nguyên cộng với một bảng tra nhãn gọi là level:

Kết quả in ra hiện các nhãn, rồi một dòng Levels: liệt kê danh sách lựa chọn. as.integer() để lộ cơ chế bên trong: các mã trả về là 3 1 3 2, vì mỗi giá trị thực chất là một chỉ số trỏ vào bảng level, và các level mặc định được sắp theo bảng chữ cái (large, medium, small), không theo thứ tự dữ liệu đến. Vậy small là mã 3 và large là mã 1, trái với trực giác của mọi người. Hãy nhớ mặc định theo bảng chữ cái này; nó là gốc rễ của hai cái bẫy bên dưới.

Sao phải bận tâm với thiết kế hai lớp này thay vì dùng chuỗi thông thường? Vì thống kê cần nó. Mô hình không thể nhân "small" với một hệ số, nhưng nó có thể mã hóa ba level đã biết thành các cột 0 và 1. Những hàm như lm(), glm(), table() và cơ chế đằng sau ANOVA đều dựa vào factor để biết một biến là biến phân loại, tập danh mục đầy đủ là gì (kể cả những danh mục vắng mặt trong dữ liệu) và danh mục nào là mốc so sánh. Vector character thông thường không mang theo thông tin nào trong số đó.

Tạo factor: levels, labels và table()

Mặc định factor() lấy các giá trị khác nhau nó tìm thấy và sắp chúng theo bảng chữ cái thành các level. Bạn thường muốn kiểm soát cả tập hợp lẫn thứ tự, hãy truyền levels =:

Giờ các level chạy theo thứ tự kích cỡ tự nhiên, và table(), phép đếm tần số một dòng mà bạn sẽ dùng liên tục với factor, cũng báo số đếm theo thứ tự đó. nlevels() đếm số danh mục.

levels = còn mang lại hai điều nữa. Giá trị trong dữ liệu không có trong danh sách level sẽ thành NA (tốt: lỗi gõ sẽ lộ ra thay vì trở thành một danh mục riêng). Và các level không xuất hiện lần nào vẫn tồn tại, nên bản tóm tắt câu trả lời khảo sát sẽ hiện "strongly disagree: 0" thay vì giả vờ như lựa chọn đó chưa từng có.

labels = đổi tên các level ngay khi tạo, rất tiện khi dữ liệu thô dùng mã:

Còn as.factor(x) là cách chuyển đổi nhanh một vector có sẵn khi các mặc định đã ổn.

Ordered factor cho dữ liệu thứ bậc

Factor thông thường coi các danh mục là không có thứ tự: "red" không nhỏ hơn "blue". Nhưng một số thang phân loại có thứ hạng thật sự: thấp/trung bình/cao, không đồng ý/trung lập/đồng ý. Hãy khai báo điều đó bằng ordered = TRUE:

Kết quả in ra giờ hiện Levels: low < medium < high, và các toán tử so sánh hoạt động: bạn có thể hỏi một mức đánh giá có cao hơn mức khác không, hoặc lọc mọi giá trị từ "medium" trở lên. Cả hai đều gây lỗi (nói đúng hơn là cảnh báo và NA) trên một factor không có thứ tự. Ordered factor cũng thay đổi cách mô hình mã hóa biến (tương phản đa thức thay vì biến giả), và đó thường là điều bạn muốn với các biến dự báo có thứ bậc.

Chỉ dùng ordered = TRUE khi thứ hạng là có thật. Mã hóa các nhóm thông thường thành có thứ tự sẽ thay đổi đầu ra của mô hình theo những cách rất dễ đọc nhầm.

Mức tham chiếu và relevel()

Level đầu tiên của factor là đặc biệt: các hàm mô hình coi nó là danh mục tham chiếu, mốc mà hệ số của mọi level khác được đo so với. Vì thứ tự level mặc định là theo bảng chữ cái, mốc của bạn do bảng chữ cái chọn trừ khi bạn can thiệp, mà "control" thua "aspirin" theo bảng chữ cái thì không phải một quyết định khoa học.

relevel() đưa một level lên vị trí đầu:

Trước đó: control đứng đầu chỉ nhờ may mắn của bảng chữ cái. Sau relevel(..., ref = "control"), nó đứng đầu có chủ đích. Trong một mô hình hồi quy tuyến tính với biến dự báo này, hệ số treatment giờ trả lời câu hỏi "điều trị khác đối chứng thế nào?", đúng câu bạn thực sự muốn hỏi. Mỗi khi hệ số phân loại của một mô hình trông khó hiểu, hãy kiểm tra mức tham chiếu trước tiên.

(Để sắp xếp lại toàn bộ, không chỉ vị trí đầu, hãy truyền lại một vector levels = đầy đủ vào factor().)

Cái bẫy kinh điển: chuyển factor sang số

Đôi khi số đến dưới dạng factor, thường là một cột CSV có lẫn một giá trị không phải số. Chuyển ngược lại trông có vẻ hiển nhiên nhưng sai một cách đáng nhớ:

as.numeric(f) trả về 2 1 3. Không phải 20, 10, 30 mà là mã level. Các level được sắp theo bảng chữ cái thành "10", "20", "30", nên "20" là level 2 và được chuyển thành... 2. Không lỗi, không cảnh báo, những số nguyên nhỏ trông hợp lý lặng lẽ thay thế dữ liệu của bạn. Đã có những công trình phân tích bị rút lại chỉ vì lỗi này.

Cách đúng là đi qua character: as.numeric(as.character(f)) trước tiên lấy lại nhãn dưới dạng văn bản, rồi đọc văn bản đó thành số: 20 10 30. Hãy khắc ghi cách làm này: chuyển factor sang số luôn phải qua as.character().

droplevels() và câu chuyện stringsAsFactors

Lấy tập con của một factor vẫn giữ nguyên toàn bộ tập level, kể cả những danh mục không còn xuất hiện:

Sau khi lọc bỏ large, table() vẫn báo nó, với số đếm 0. Đôi khi đó chính là điều đúng (bạn muốn thấy cả danh mục rỗng). Khi không phải vậy, vì các nhóm có số đếm 0 làm rối biểu đồ và có thể làm hỏng các phân tích phân tầng, droplevels() sẽ loại bỏ các level không có quan sát nào.

Một ghi chú lịch sử bạn sẽ cần khi đọc code cũ hoặc câu trả lời trên Stack Overflow: trước R 4.0 (2020), data.frame() và read.csv() tự động chuyển mọi cột character thành factor, vì stringsAsFactors = TRUE là mặc định. Cả chục năm bài hướng dẫn đầy rẫy cách lách cho những factor chẳng ai yêu cầu. Từ R 4.0, mặc định là FALSE: chuỗi vẫn là chuỗi, và bạn tạo factor có chủ đích, ở những chỗ một biến trong data frame thực sự là biến phân loại. Đó là thói quen đúng: factor tường minh, có chủ đích, với các level do bạn chọn.

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

  • Factor = mã số nguyên + nhãn level; đó là thứ báo cho các hàm thống kê biết một biến là biến phân loại.
  • Kiểm soát tập danh mục và thứ tự bằng levels =, đổi tên bằng labels =, đếm bằng table().
  • ordered = TRUE cho phép so sánh với các thang đo thực sự có thứ bậc.
  • Level đầu tiên là mốc của mô hình; hãy đặt nó có chủ đích bằng relevel(f, ref = ...).
  • Đừng bao giờ gọi thẳng as.numeric(f); luôn dùng as.numeric(as.character(f)).
  • droplevels() dọn các level không dùng sau khi lấy tập con; từ R 4.0, chuỗi vẫn là chuỗi trừ khi bạn tự tạo factor.

Tiếp theo: data frame, nơi factor, số và văn bản cùng sống thành các cột của một bảng.

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

Factor trong R là gì?

Factor là kiểu của R dành cho dữ liệu phân loại: các giá trị lấy từ một tập lựa chọn cố định gọi là level. Bên trong, nó là một vector mã số nguyên cộng với một bảng nhãn level, nhờ vậy các hàm thống kê xử lý danh mục đúng cách (đếm chúng, mã hóa thành biến giả trong mô hình) thay vì coi chúng là văn bản tự do.

Làm sao để chuyển factor sang số trong R?

Đi qua character: as.numeric(as.character(f)). Gọi thẳng as.numeric(f) sẽ trả về mã level bên trong (1, 2, 3, ...), không phải giá trị mà nhãn hiển thị, nên một factor hiện "20" có thể trả về 2. Đây là một trong những bug âm thầm phổ biến nhất trong R.

relevel() trong R làm gì?

Nó đưa một level được chọn lên vị trí đầu tiên: relevel(group, ref = "control"). Level đầu tiên là danh mục tham chiếu (mốc so sánh) mà các hàm mô hình như lm() và glm() dùng để so mọi level khác, nên chọn nó có chủ đích sẽ khiến hệ số hồi quy mang đúng ý nghĩa bạn muốn.

Vì sao factor của mình vẫn hiện các level đã bị loại bỏ?

Lấy tập con của một factor vẫn giữ nguyên toàn bộ tập level, kể cả khi một số level không còn xuất hiện, nên table() hiện các danh mục có số đếm bằng 0 và mô hình vẫn dành chỗ cho chúng. Chạy droplevels() trên tập con để loại bỏ các level không dù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