Menu

Ma trận trong R: matrix(), cbind, rbind và phép toán ma trận

Cách tạo ma trận bằng matrix(), cbind() và rbind(), truy cập hàng và cột, và phân biệt phép nhân từng phần tử * với phép nhân ma trận thật 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.

Ma trận là vector có số chiều

Ma trận (matrix) trong R là một hình chữ nhật các giá trị, gồm hàng và cột, trong đó mọi ô chứa cùng một kiểu, thường là số. Bên dưới, nó đúng là một vector được gắn thêm thuộc tính dim, và điều đó giải thích phần lớn hành vi của nó: một kiểu duy nhất xuyên suốt, phép toán vector hóa ở mọi nơi.

Bạn tạo ma trận bằng cách định hình lại một vector với matrix():

Sáu giá trị, hai hàng: R tự tính ra cần ba cột. dim() báo cả hai chiều cùng lúc là 2 3; nrow() và ncol() cho từng chiều riêng.

Nhìn kỹ ma trận được in ra: các giá trị chạy 1 2 xuống cột đầu tiên, rồi 3 4 xuống cột tiếp theo. Mặc định R điền ma trận theo từng cột. Nếu dữ liệu của bạn được đọc theo từng hàng, cách con người thường viết, hãy nói rõ bằng byrow = TRUE:

Giờ hàng đầu tiên là 1 2 3. Quên byrow = TRUE không gây lỗi: nó lặng lẽ cho bạn một cách sắp xếp trông như bị chuyển vị của cùng những con số đó, nên hãy tập thói quen kiểm tra bố cục được in ra mỗi khi bạn tạo ma trận từ giá trị thô.

Truy cập: m[hàng, cột]

Truy cập ma trận dùng hai vị trí bên trong một cặp ngoặc vuông, hàng trước dấu phẩy, cột sau dấu phẩy, cả hai đều đếm từ 1:

Để trống một vị trí nghĩa là "tất cả": m[1, ] là cả hàng đầu tiên, m[, 2] là cả cột thứ hai. Để ý cả hai đều trả về vector thường: R bỏ đi chiều đã thu lại còn kích thước 1. Điều này tiện khi làm việc tương tác nhưng là cái bẫy trong code, vì một hàm cần ma trận sẽ gặp lỗi khi nhận vector. Hãy yêu cầu R giữ nguyên hình dạng bằng drop = FALSE:

Giờ dim() báo 1 3: vẫn là ma trận. Bất cứ khi nào bạn cắt một hàng hoặc một cột bên trong hàm, hãy viết drop = FALSE; bug mà nó ngăn chặn (code chạy tốt với dữ liệu nhiều cột nhưng hỏng với dữ liệu một cột) là loại bug rất khổ sở khi phải truy tìm.

Mặt nạ logic cũng dùng được ở đây: m[m > 3] trả về mọi ô lớn hơn 3, dưới dạng vector.

Ghép bằng cbind() và rbind()

Thay vì định hình lại một vector dài, bạn có thể ghép ma trận từ các mảnh: cbind() nối các vector thành cột, rbind() nối thành hàng. Cùng các hàm này cũng mở rộng được một ma trận có sẵn:

Tên đi theo vector: cbind() tự động dùng heights và weights làm tên cột, giúp ma trận in ra dễ đọc. Cả hai hàm đều đòi hỏi độ dài phải khớp (với cơ chế tái sử dụng cho giá trị độ dài 1); nối một vector độ dài 3 vào ma trận 4 hàng sẽ nhận cảnh báo.

Tên cột và tên hàng cũng có thể đặt trực tiếp qua colnames(m) <- ... và rownames(m) <- ..., sau đó bạn có thể truy cập theo tên: people[, "weights"].

Nhân từng phần tử * và nhân ma trận thật sự %*%

Đây là sự phân biệt quan trọng nhất trong cả bài. R có hai toán tử nhân cho ma trận, và chúng tính những thứ hoàn toàn khác nhau:

  • a * a là phép nhân từng phần tử: mỗi ô nhân với ô tương ứng. 1 2 3 4 thành 1 4 9 16, giữ nguyên hình dạng. Đây chỉ là phép tính số học vector hóa, cùng toán tử * bạn dùng với vector.
  • a %*% a là phép nhân ma trận trong đại số tuyến tính: mỗi ô kết quả là một hàng của ma trận thứ nhất nhân với một cột của ma trận thứ hai, rồi cộng lại. Cùng đầu vào cho ra 7 10 15 22: những con số hoàn toàn khác.

Chạy đoạn code và so sánh hai đầu ra cạnh nhau; thấy chúng khác nhau trên cùng đầu vào là điều giúp bạn nhớ mãi sự phân biệt này. Nếu bạn viết * ở chỗ phép toán cần %*%, R sẽ không cảnh báo (với ma trận vuông, hình dạng đều tương thích theo cả hai cách), bạn chỉ nhận được những con số sai. Trong code thống kê (ma trận hiệp phương sai, đại số của mô hình tuyến tính), đây là một trong những bug âm thầm kinh điển.

t() chuyển vị, tức đổi hàng thành cột, và xuất hiện liên tục cạnh %*% vì phép nhân ma trận cần các chiều bên trong khớp nhau:

Để đầy đủ: solve(m) tính ma trận nghịch đảo, và %*% với một vector sẽ xem vector đó như ma trận một cột. Phần lớn công việc dữ liệu chỉ cần đi sâu đến đó.

Tổng hợp theo hàng và cột

Tính tổng hoặc trung bình theo hàng và cột phổ biến đến mức R có sẵn các hàm chuyên dụng, chạy nhanh:

rowSums() gộp mỗi hàng thành một con số (6 15 ở đây), colSums() gộp mỗi cột (5 7 9), còn các biến thể Means thì tính trung bình. Hãy ưu tiên chúng thay vì vòng lặp tự viết hay thậm chí apply(m, 1, sum): rõ ràng hơn và nhanh hơn. Với những phép tổng hợp mà bốn hàm này không làm được (chẳng hạn giá trị lớn nhất của mỗi cột), họ apply là công cụ tổng quát: apply(m, 2, max).

Ma trận hay data frame?

Cả hai đều có dạng hình chữ nhật, vậy nên dùng cái nào?

  • Ma trận: mọi ô cùng một kiểu, và phép toán là điều quan trọng. Tính toán số, đại số tuyến tính, tính khoảng cách, lưới dạng ảnh. Ma trận gọn nhẹ hơn và các phép toán nhanh hơn chính nhờ bảo đảm một kiểu duy nhất.
  • Data frame: các cột có kiểu khác nhau, tên đứng cạnh tuổi, cạnh cờ logic. Đây là dữ liệu dạng bảng ngoài đời thực, và là thứ gần như mọi hàm phân tích dữ liệu đều chờ đợi.

Một quy tắc tốt: nếu bạn sẽ tự nhiên mở nó trong bảng tính với các cột có tên và kiểu hỗn hợp, đó là data frame. Nếu đó là một lưới số mà bạn định làm phép toán đại số, đó là ma trận. Chuyển đổi giữa hai loại rất dễ (as.matrix(), as.data.frame()), nhưng as.matrix() trên một data frame có bất kỳ cột văn bản nào sẽ ép mọi thứ thành ký tự, nên chỉ chuyển các cột số.

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

  • Ma trận là vector có số chiều: một kiểu duy nhất xuyên suốt, tạo bằng matrix(data, nrow, ncol), và điền theo từng cột trừ khi bạn truyền byrow = TRUE.
  • Truy cập bằng m[row, col]; vị trí để trống nghĩa là "tất cả"; thêm drop = FALSE khi cắt một hàng hoặc một cột bên trong code.
  • cbind() và rbind() ghép ma trận từ các vector hoặc mở rộng ma trận có sẵn.
  • * nhân từng phần tử, %*% là phép nhân ma trận thật sự: cùng đầu vào, kết quả khác nhau, không có cảnh báo.
  • rowSums() / colSums() / rowMeans() / colMeans() lo các phép tổng hợp thường ngày.

Tiếp theo: factor, cách R biểu diễn dữ liệu phân loại, và những cái bẫy đi kèm.

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

Tạo ma trận trong R như thế nào?

matrix(1:6, nrow = 2) định hình lại một vector thành 2 hàng và 3 cột, điền theo từng cột. Thêm byrow = TRUE để điền theo từng hàng. Bạn cũng có thể ghép ma trận từ các vector: cbind() nối chúng thành cột, rbind() nối thành hàng.

* và %*% trong R khác nhau thế nào?

* nhân từng phần tử: mỗi ô nhân với ô tương ứng, hai ma trận phải cùng hình dạng. %*% là phép nhân ma trận thật sự trong đại số tuyến tính (hàng nhân cột, nên các chiều bên trong phải khớp nhau). Trên cùng các ma trận, chúng cho kết quả hoàn toàn khác nhau, và dùng * khi ý định là %*% là một bug âm thầm kinh điển.

Lấy một hàng hoặc một cột của ma trận trong R như thế nào?

Để trống vị trí còn lại: m[1, ] là hàng đầu tiên, m[, 2] là cột thứ hai. Mặc định cả hai đều trả về vector thường. Thêm drop = FALSE, như trong m[1, , drop = FALSE], để giữ kết quả là ma trận một hàng hoặc một cột, điều quan trọng khi code phía sau cần hai chiều.

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