Các toán tử R cung cấp
Toán tử (operator) của R chia thành bốn nhóm: số học (+ - * / ^ %% %/%), so sánh (== != < <= > >=), logic (& | && || ! xor) và phép kiểm tra thuộc tập hợp %in%. Có hai điều khiến chúng khác với hầu hết các ngôn ngữ: gần như tất cả đều được vector hóa, tức là xử lý cả vector cùng lúc, theo từng phần tử; và R có hai toán tử AND cùng hai toán tử OR không thể dùng thay cho nhau. Trang này trình bày tất cả, kèm những bẫy về thứ tự ưu tiên khiến kết quả sai mà không báo gì.
Số học: +, -, *, /, ^, %% và %/%
Năm toán tử đầu làm đúng như bạn nghĩ. Hai toán tử bọc trong dấu phần trăm, %% (chia lấy dư, cho số dư) và %/% (chia lấy phần nguyên, cho thương), là những toán tử mọi người hay tìm kiếm:
Lưu ý rằng / luôn thực hiện phép chia số thực: 17 / 5 là 3.4, không bao giờ là 3. Nếu bạn muốn phần nguyên, đó chính là việc của %/%.
Công việc hằng ngày của %% là kiểm tra tính chia hết. Một số là số chẵn khi chia cho 2 không còn dư:
Dòng thứ hai đáng để dừng lại: với toán hạng âm, %% của R trả về kết quả mang dấu của số chia (-7 %% 3 là 2, vì -7 = -3 * 3 + 2). Một số ngôn ngữ làm theo cách ngược lại; nếu bạn chuyển code từ ngôn ngữ khác sang, hãy kiểm tra kỹ.
Toán tử so sánh được vector hóa
==, !=, <, <=, >, >= so sánh theo từng phần tử và trả về một vector logic, không phải một đáp án duy nhất:
Đây là cỗ máy đứng sau hầu như mọi thao tác lọc trong R: bạn so sánh cả một vector với một giá trị, nhận về một mặt nạ logic, rồi dùng mặt nạ đó để lấy tập con. Một lưu ý khi dùng == với số thập phân: số thực dấu phẩy động được lưu xấp xỉ, nên 0.1 + 0.2 == 0.3 là FALSE. Khi so sánh số thập phân, hãy ưu tiên all.equal() hoặc kiểm tra abs(x - y) < 1e-9.
Toán tử logic: & và && (nhầm lẫn kinh điển)
R có hai toán tử AND và hai toán tử OR, và chọn nhầm là chuyện ai học R cũng từng trải qua.
& và | được vector hóa. Chúng kết hợp hai vector logic theo từng phần tử:
Dùng chúng mỗi khi bạn xây dựng bộ lọc trên dữ liệu: df[df$age > 30 & df$city == "Lisbon", ].
&& và || làm việc với giá trị đơn và có cơ chế đoản mạch. Chúng nhận giá trị đơn, trả về một TRUE/FALSE duy nhất, và bỏ qua hoàn toàn toán hạng thứ hai nếu toán hạng thứ nhất đã quyết định được đáp án:
Dòng thứ hai minh họa cơ chế đoản mạch: vì x < 0 là FALSE, R không bao giờ thực thi lệnh gọi stop(). Đây chính là lý do && thuộc về điều kiện if(): bạn có thể viết if (!is.na(x) && x > 10) và phần x > 10 sẽ được bỏ qua an toàn khi x là NA.
Quy tắc chung: &/| cho vector và lọc dữ liệu, &&/|| cho điều kiện if(). Và từ R 4.3, quy tắc này được áp dụng nghiêm ngặt: đưa cho && hoặc || một vector có độ dài lớn hơn 1 là lỗi (các phiên bản R cũ âm thầm chỉ dùng phần tử đầu tiên, điều đã che giấu nhiều bug thật trong nhiều năm).
xor(a, b) hoàn thiện bộ toán tử: TRUE khi đúng một trong hai giá trị là TRUE:
%in%: kiểm tra thuộc tập hợp
x %in% table hỏi, với từng phần tử của x, "phần tử này có xuất hiện ở đâu đó trong table không?" Đây là một trong những toán tử được dùng nhiều nhất trong code R thực tế:
Vế được kiểm tra có thể là cả một vector, khiến %in% trở thành công cụ tự nhiên để lọc các hàng theo một tập giá trị cho phép.
Nó cũng tốt hơn cách bạn sẽ phải viết nếu không có nó: một chuỗi == nối với nhau bằng |:
Với năm giá trị cho phép, chuỗi == trở thành một bức tường lặp lại; còn phiên bản %in% chỉ cần thêm phần tử vào vector. Có một lợi thế thứ hai tinh tế hơn: %in% không bao giờ trả về NA:
NA == 1 là NA vì R không thể biết một giá trị không xác định có bằng 1 hay không, hành vi này được trình bày chi tiết trong giá trị thiếu. Giá trị NA đó sẽ làm hỏng mọi if() mà nó đi tới. %in% tránh được toàn bộ vấn đề bằng cách luôn trả lời TRUE hoặc FALSE, đó là lý do nó là lựa chọn an toàn hơn cho các điều kiện trên dữ liệu có thể bị thiếu.
Bẫy thứ tự ưu tiên
Hai bẫy về thứ tự ưu tiên toán tử gây ra phần lớn những khoảnh khắc "R bị hỏng rồi". Thứ nhất, ^ có độ ưu tiên cao hơn dấu trừ một ngôi:
Thứ hai, dấu hai chấm có độ ưu tiên cao hơn các phép số học hai ngôi:
Khi không chắc, hãy thêm dấu ngoặc. Chúng không tốn gì, và mọi người đọc (kể cả chính bạn sau này) sẽ hiểu chúng giống hệt cách trình thông dịch hiểu.
$ và [ ]: các toán tử truy cập
Bạn cũng sẽ thường xuyên gặp $, [ ] và [[ ]], về mặt kỹ thuật cũng là toán tử, dùng để lấy giá trị ra khỏi cấu trúc dữ liệu chứ không phải để tính toán:
Chúng được trình bày đầy đủ trong các trang về vector, list và data frame; ở đây chỉ cần nhận ra chúng thuộc cùng họ toán tử.
Những điều cần nhớ
%%cho số dư,%/%cho thương nguyên;x %% 2 == 0kiểm tra số chẵn.- Phép so sánh được vector hóa: chúng trả về một vector logic mà bạn dùng để lọc.
&/|kết hợp vector theo từng phần tử;&&/||dành cho giá trị đơn trongif()và có cơ chế đoản mạch. Từ R 4.3, dùng&&với vector dài hơn là lỗi.x %in% tablethay cho chuỗi==và không bao giờ trả vềNA.-2^2là-4và1:n - 1bắt đầu từ 0: thêm dấu ngoặc khi không chắc.
Tiếp theo: đưa các toán tử này vào làm việc bên trong if, else và ifelse(), các câu lệnh điều kiện của R.
Câu hỏi thường gặp
%% trong R dùng để làm gì?
%% là toán tử chia lấy dư (modulo): nó trả về số dư của phép chia. 17 %% 5 là 2. Toán tử đi kèm %/% thực hiện phép chia lấy phần nguyên và trả về thương không có số dư: 17 %/% 5 là 3. Cách dùng kinh điển của %% là kiểm tra số chẵn: x %% 2 == 0.
& và && trong R khác nhau thế nào?
& được vector hóa: nó so sánh hai vector theo từng phần tử và trả về một vector cùng độ dài. && chỉ làm việc với giá trị đơn, có cơ chế đoản mạch (bỏ qua toán hạng thứ hai nếu toán hạng thứ nhất đã quyết định kết quả) và trả về đúng một TRUE hoặc FALSE. Dùng & khi lọc vector, && bên trong điều kiện if(). Từ R 4.3, đưa cho && hoặc || một vector dài hơn 1 là lỗi, không còn là cảnh báo.
%in% trong R dùng để làm gì?
x %in% table kiểm tra xem từng phần tử của x có xuất hiện ở đâu đó trong table không, trả về TRUE hoặc FALSE cho mỗi phần tử. "mango" %in% basket thay cho một chuỗi phép so sánh == nối bằng |, và khác với ==, nó không bao giờ trả về NA: một giá trị thiếu ở vế được kiểm tra chỉ đơn giản cho ra FALSE.
Tại sao -2^2 trả về -4 trong R?
Vì ^ có độ ưu tiên cao hơn dấu trừ một ngôi, R đọc -2^2 thành -(2^2), tức là -4. Nếu bạn muốn bình phương của âm hai, hãy viết (-2)^2, kết quả là 4. Dấu hai chấm cũng có bẫy tương tự: 1:n - 1 nghĩa là (1:n) - 1, không phải 1:(n - 1).