Mọi thứ trong R đều là vector
Hầu hết các ngôn ngữ có giá trị vô hướng (scalar), tức một giá trị đơn lẻ, rồi đến một kiểu chứa để giữ nhiều giá trị. R bỏ qua giá trị vô hướng. Vector là một tập hợp có thứ tự gồm các giá trị cùng một kiểu, và là đơn vị mà mọi thứ khác được xây dựng trên đó. Ngay cả thứ trông như một con số đơn lẻ cũng là một vector có độ dài 1:
c(), viết tắt của combine, là cách bạn tự tay tạo một vector. length() cho biết nó chứa bao nhiêu phần tử. Và bản thân 5 báo is.vector là TRUE với độ dài 1: R không bao giờ ngừng làm việc với vector, chỉ là đôi khi nó làm việc với những vector rất ngắn.
Thiết kế này là lý do code R trông khác Python hay JavaScript: những thao tác mà ở nơi khác bạn sẽ viết vòng lặp thì ở đây áp dụng cho cả vector trong một biểu thức. Ta sẽ nói đến điều đó ở phần dưới.
Một quy tắc cần ghi nhớ ngay: mọi phần tử của vector có cùng một kiểu. Nếu bạn đưa cho c() một hỗn hợp, nó không phàn nàn mà lặng lẽ chuyển mọi thứ về kiểu linh hoạt nhất có mặt:
Có một chuỗi trong hỗn hợp là mọi thứ thành chuỗi ("1", "TRUE"). Giá trị logic lẫn trong các số trở thành 1 và 0. Việc ép kiểu (coercion) này theo một thứ bậc cố định, logical → integer → double → character, và diễn ra không có cảnh báo, đó là lý do một "7" lạc trong một cột số có thể âm thầm biến cả bộ dữ liệu thành văn bản. Toàn bộ thứ bậc được trình bày trong bài kiểu dữ liệu; nếu bạn cần giữ các giá trị thật sự khác kiểu, đó là việc của list.
Tạo dãy: :, seq() và rep()
Gõ từng phần tử vào c() nhanh chóng trở nên nhàm chán. R có ba lối tắt cho các mẫu có quy luật:
1:10là cách dùng hằng ngày: các số nguyên từ cận này đến cận kia, bước nhảy 1.seq()tổng quát hóa nó: chọn bước nhảy bằngby =, hoặc nói bạn muốn bao nhiêu phần tử bằnglength.out =và để R tự tính khoảng cách.seq_len(n)tạo ra1đếnnvà là cách an toàn để tạo chỉ số cho vòng lặp: khác với1:n, nó cho đúng một vector rỗng khinbằng 0 thay vì đếm ngược1 0.rep()lặp lại:times =lặp cả vector nối đuôi nhau (1 2 1 2 1 2),each =lặp từng phần tử tại chỗ (1 1 1 2 2 2). Hai cách này dễ nhầm; hãy chạy đoạn code và so sánh.
Chỉ số: R đếm từ 1
Dấu ngoặc vuông lấy phần tử ra. Phần tử đầu tiên là [1], không phải [0]. Nếu bạn đến từ Python hay JavaScript, đây là chỗ vấp phổ biến nhất lúc đầu:
Ba điều ở đây đáng dừng lại:
- Bạn có thể đánh chỉ số bằng một vector vị trí:
fruits[c(2, 4)]lấy phần tử thứ hai và thứ tư trong một lần. - Chỉ số âm nghĩa là "mọi thứ trừ".
fruits[-1]là vector không có phần tử đầu tiên. Điều này hoàn toàn khác Python, nơi-1nghĩa là phần tử cuối cùng. Bạn không thể trộn chỉ số dương và âm trong cùng một lệnh gọi. fruits[0]không phải lỗi: nó trả về mộtcharacter(0)rỗng. Một bug lệch một đơn vị trong R thường tạo ra kết quả rỗng lặng lẽ thay vì làm chương trình dừng, nên hãy kiểm tralength()khi thứ gì đó phía sau trống trơn một cách bí ẩn.
Phần tử còn có thể mang tên, cho bạn cách đánh chỉ số thứ ba: theo nhãn:
Vector có tên hoạt động như một bảng tra cứu gọn nhẹ và giữ code dễ đọc: prices["tea"] nói rõ ý nghĩa của nó, còn prices[2] thì không.
Mặt nạ logic và which()
Cách đánh chỉ số mạnh nhất là dùng một vector logic, một mặt nạ gồm các giá trị TRUE/FALSE dài bằng dữ liệu. Mọi phép so sánh trên vector đều tạo ra đúng thứ đó:
temps > 24 không cho một câu trả lời mà cho năm câu, mỗi phần tử một câu. Đặt mặt nạ đó vào trong [ ] sẽ giữ lại các phần tử mà mặt nạ là TRUE. Kết hợp điều kiện bằng & (và) và | (hoặc), được trình bày cùng các toán tử khác.
which() chuyển một mặt nạ thành vị trí: ở đây là 2 3 5, chỉ số của các lần đo nóng. Dùng nó khi bạn cần biết các kết quả khớp nằm ở đâu, để báo cáo hoặc để đánh chỉ số một vector khác tại cùng các vị trí. Để lọc thông thường, temps[temps > 24] trực tiếp hơn temps[which(temps > 24)].
Mẫu lọc bằng mặt nạ này là xương sống của gần như mọi công việc dữ liệu trong R: nó sẽ quay lại ngay khi bạn lọc các hàng của data frame.
Tính toán vector hóa và quy tắc tái sử dụng
Phép tính số học trong R áp dụng theo từng phần tử trên cả vector, không cần vòng lặp:
Dòng đầu tiên là tính năng nổi bật: prices * 2 nhân đôi mọi phần tử. Nơi các ngôn ngữ khác cần một vòng lặp for, R chỉ cần một toán tử, và dạng vector hóa vừa ngắn hơn vừa nhanh hơn, vì việc lặp diễn ra bên dưới bằng code C đã được tối ưu. Hãy viết vòng lặp for khi mỗi bước phụ thuộc vào bước trước; còn với phép tính theo từng phần tử, hãy vector hóa.
Dòng thứ ba cho thấy quy tắc tái sử dụng (recycling rule): khi các vector có độ dài khác nhau, R lặp lại vector ngắn hơn cho khớp. c(10, 20) được tái sử dụng thành 10 20 10 20, cho ra 11 22 13 24. Việc tái sử dụng 2 trên một vector dài 3 chính là điều khiến prices * 2 chạy được: một giá trị vô hướng chỉ là một vector dài 1 đang được tái sử dụng.
Tái sử dụng diễn ra gọn gàng khi độ dài lớn hơn là bội số chính xác của độ dài nhỏ hơn. Khi không phải vậy, R vẫn tính ra kết quả, và phát một cảnh báo:
Bạn nhận được 11 22 13 cùng một cảnh báo rằng độ dài của đối tượng dài hơn không phải bội số của độ dài đối tượng ngắn hơn. Hãy coi cảnh báo đó là một báo cáo bug: tái sử dụng một phần gần như không bao giờ là điều bạn muốn, và nó thường có nghĩa hai vector lẽ ra phải dài bằng nhau lại không bằng nhau.
Sắp xếp, đảo ngược, loại bỏ trùng lặp
Ba tiện ích nhỏ bạn sẽ dùng liên tục:
sort() sắp xếp các giá trị, rev() đảo ngược thứ tự hiện có mà không sắp xếp, còn unique() loại bỏ phần tử trùng lặp và giữ lần xuất hiện đầu tiên. Không hàm nào trong số đó sửa x: như gần như mọi thứ trong R, chúng trả về một vector mới và để nguyên vector gốc.
Điều cần nhớ
- Vector là đơn vị nền tảng của R: có thứ tự, cùng một kiểu từ đầu đến cuối, và ngay cả giá trị đơn lẻ cũng là vector dài 1.
- Tạo bằng
c(), sinh mẫu bằng:,seq()vàrep(), và nhớ rằngc()lặng lẽ ép kiểu khi trộn nhiều kiểu. - Chỉ số bắt đầu từ 1; chỉ số âm bỏ phần tử; tên cho phép đánh chỉ số theo nhãn.
- Mặt nạ logic (
x[x > 5]) là cách lọc chuẩn của cả ngôn ngữ;which()biến mặt nạ thành vị trí. - Phép tính được vector hóa, vector ngắn hơn được tái sử dụng, và cảnh báo tái sử dụng một phần nghĩa là bạn đang có bug.
Tiếp theo: list, kiểu chứa dành cho khi các giá trị của bạn không cùng một kiểu.
Câu hỏi thường gặp
Làm sao để tạo vector trong R?
Dùng hàm c() (viết tắt của combine): x <- c(10, 20, 30). Với dãy có quy luật, dùng 1:10, seq(0, 1, by = 0.25) hoặc rep(0, 5). Mọi phần tử cuối cùng phải cùng một kiểu: nếu trộn nhiều kiểu, R lặng lẽ chuyển tất cả về kiểu linh hoạt nhất.
Chỉ số trong R bắt đầu từ 0 hay 1?
Từ 1. x[1] là phần tử đầu tiên, x[length(x)] là phần tử cuối cùng. Điều này làm vấp ngã mọi người đến từ Python, JavaScript hay C: không có phần tử x[0] (gọi nó sẽ trả về một vector rỗng chứ không báo lỗi, nên bug rất khó thấy).
Quy tắc tái sử dụng (recycling) trong R làm gì?
Khi tính toán trên hai vector có độ dài khác nhau, R lặp lại vector ngắn hơn cho đến khi khớp với vector dài hơn. c(1, 2, 3, 4) + c(10, 20) cho 11 22 13 24. Nếu độ dài lớn hơn không phải bội số của độ dài nhỏ hơn, R vẫn tính ra kết quả nhưng phát cảnh báo: hãy coi cảnh báo đó là một bug.
Hàm which() trong R làm gì?
Nó chuyển một vector logic thành vị trí của các giá trị TRUE. Nếu x > 5 cho FALSE TRUE TRUE, thì which(x > 5) cho 2 3. Dùng nó khi bạn cần chính các vị trí; để lọc thông thường, đánh chỉ số trực tiếp bằng vector logic (x[x > 5]) đơn giản hơn.