Menu

List trong R: tạo, truy cập ([[ và $ và [) và chỉnh sửa

List là vật chứa đa năng của R: kiểu dữ liệu hỗn hợp, cấu trúc lồng nhau, cả data frame. Kỹ năng then chốt là biết khi nào [ trả về một list nhỏ hơn và khi nào [[ trả về chính phần tử đó.

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.

List chứa được mọi thứ

Vector có một quy tắc cứng: mọi phần tử phải cùng kiểu. List không có quy tắc đó. Mỗi phần tử của list có thể khác kiểu, khác độ dài, thậm chí là một list khác. Đây là vật chứa đa năng của R:

length() báo 4: bốn phần tử, dù một trong số đó (scores) tự nó chứa ba con số. List đếm số ngăn của nó, không đếm thứ bên trong. str() (structure, cấu trúc) là cách nhanh nhất để xem một list thật sự chứa gì: mỗi phần tử một dòng, kèm tên, kiểu và bản xem trước. Hãy biến str() thành phản xạ: bất cứ khi nào một hàm trả cho bạn thứ gì đó mà bạn không chắc nó là gì, cứ str() nó.

Tên là không bắt buộc (list(1, "a", TRUE) vẫn hợp lệ) nhưng gần như luôn đáng có: một list có tên tự giải thích chính nó.

Ba cách truy cập: [, [[ và $

Đây là phần mà ai cũng sai một lần với list, nên hãy nắm thật chính xác. Có ba toán tử truy cập, và chúng làm hai việc khác nhau:

  • [ trả về một list nhỏ hơn: một vật chứa cùng loại, chứa những gì bạn đã chọn.
  • [[ trả về chính phần tử đó: giá trị thật sự nằm trong ngăn.
  • $ là cách viết tắt của [[ với một tên cụ thể: person$age chính là person[["age"]].

Khác biệt này vô hình khi bạn in ra xem qua, nhưng lộ rõ ngay khi bạn cố tính toán:

person["scores"] có lớp "list": một list độ dài 1 chứa vector điểm bên trong. person[["scores"]] có lớp "numeric": chính vector đó, sẵn sàng cho mean(). Gọi mean() với phiên bản ngoặc đơn, bạn sẽ nhận lỗi rằng đối số không phải là số; thông báo lỗi đó gần như luôn là dấu hiệu bạn đã dùng [ trong khi ý định là [[.

Một phép ẩn dụ dễ nhớ: list là một đoàn tàu. [ cho bạn một đoàn tàu ngắn hơn, vẫn là tàu. [[ mở một toa và trao cho bạn hàng hóa bên trong.

Vậy khi nào [ là công cụ đúng? Khi bạn muốn nhiều phần tử và muốn giữ chúng trong một gói:

Bạn không thể lấy "chính" hai phần tử cùng lúc, vì hai giá trị cần một vật chứa, nên chọn nhiều phần tử luôn dùng ngoặc đơn, và luôn cho ra một list.

Thêm một khác biệt nữa: $ dùng khớp một phần (person$sc tìm thấy scores nếu không có gì mơ hồ), tiện ở console nhưng là rủi ro trong script. Trong code cần chạy ổn định lâu dài, hãy ưu tiên [[ với tên đầy đủ; nó còn nhận được tên lưu trong một biến, điều mà $ không làm được.

Thêm, sửa, xóa

List dài ra và ngắn lại bằng phép gán thông thường, không cần phương thức append đặc biệt nào:

  • Gán cho một tên chưa tồn tại sẽ thêm phần tử.
  • Gán cho một tên đã có sẽ thay thế nó.
  • Gán vào vị trí length(x) + 1 sẽ thêm một phần tử không tên vào cuối.
  • Gán NULL sẽ xóa hẳn phần tử, list ngắn lại. (Đây là chỗ duy nhất NULL đóng vai trò lệnh xóa; nếu bạn thật sự cần lưu "không có gì" vào một ngăn, hãy dùng x["k"] <- list(NULL).)

Để nối hai list liền nhau, c() dùng được với list y như với vector: c(list_a, list_b) trả về một list dài hơn.

List lồng nhau

Vì một phần tử của list có thể là một list khác, list lồng nhau được đến độ sâu bất kỳ, nên đây là hình dạng tự nhiên của R cho dữ liệu có cấu trúc như JSON đã phân tích hay kết quả được nhóm:

Đi sâu vào chỉ là nối các toán tử truy cập: mỗi $ hoặc [[ đi xuống thêm một cấp. Khi một cấu trúc lồng nhau trở nên rối, str(company) hiện toàn bộ cây cùng lúc, còn str(company, max.level = 1) chỉ hiện lớp trên cùng.

Làm phẳng bằng unlist()

unlist() gộp một list, dù lồng sâu đến đâu, thành một vector duy nhất:

Có hai điều cần để ý. Thứ nhất, unlist() tự tạo tên cho bạn (math1, math2, art) để bạn vẫn biết mỗi giá trị đến từ đâu. Thứ hai, và đây là cái bẫy, vector chỉ chứa một kiểu, nên unlist một list hỗn hợp sẽ ép mọi thứ về kiểu linh hoạt nhất có mặt. Số 1 quay về thành chuỗi "1". Nếu bạn unlist và các con số biến thành văn bản, nghĩa là list không đồng nhất như bạn tưởng.

Vì sao list quan trọng

List có thể trông như một chủ đề cho người mới mà rồi bạn sẽ vượt qua. Thực tế ngược lại: chúng là trụ cột của cả ngôn ngữ:

  • Hàm trả về nhiều thứ thì trả về một list. Hàm R trả về một đối tượng; list giúp đối tượng đó mang theo một giá trị đã khớp ở chỗ này, một bảng hệ số ở chỗ kia. Khi bạn chạy một mô hình hồi quy, đối tượng mô hình nhận về là một list (lớn, có lớp riêng); str() trên nó sẽ chứng minh điều đó.
  • Data frame là một list các cột: các vector cùng độ dài kèm thêm một số hành vi. Mọi thứ bạn vừa học ($, [[, gán NULL để bỏ một phần tử) áp dụng nguyên vẹn cho cột của data frame.
  • Họ apply (lapply() và các hàm cùng họ) nhận một list, áp dụng một hàm lên từng phần tử, rồi trả về một list. List vào, list ra: đó là hình dạng chuẩn của tính toán lặp lại trong R.

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

  • List chứa được mọi thứ: kiểu hỗn hợp, độ dài khác nhau, list khác, cả data frame.
  • [ trả về một list nhỏ hơn; [[ trả về chính phần tử; $ là [[ với tên cụ thể. Định tính toán? Bạn cần [[ hoặc $.
  • Thêm bằng cách gán cho tên mới, xóa bằng cách gán NULL, kết hợp bằng c().
  • unlist() làm phẳng thành vector và ép các kiểu hỗn hợp trên đường đi: hãy kiểm tra lớp của kết quả.
  • Data frame là list các cột, nên kiến thức này áp dụng trực tiếp.

Tiếp theo: ma trận, người anh em toàn một kiểu, có hàng và cột, của list.

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

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

Ngoặc vuông đơn [ luôn trả về một list chứa các phần tử được chọn, tức một vật chứa nhỏ hơn cùng loại. Ngoặc vuông kép [[ đi vào bên trong và trả về chính phần tử đó. Nếu x$scores chứa một vector số, x["scores"] là một list độ dài 1 còn x[["scores"]] là vector số. Dùng [[ (hoặc $) khi bạn muốn thật sự làm việc với giá trị.

Thêm phần tử vào list trong R như thế nào?

Gán cho một tên chưa tồn tại: x$email <- "rosa@example.com" hoặc x[["email"]] <- .... List tự động dài ra. Để thêm vào cuối mà không đặt tên, gán vào vị trí kế tiếp: x[[length(x) + 1]] <- value. Để xóa một phần tử, gán NULL cho nó: x$email <- NULL.

Chuyển list thành vector trong R như thế nào?

unlist(x) làm phẳng một list (kể cả list lồng nhau) thành một vector duy nhất. Vì vector chỉ chứa một kiểu, mọi thứ bị ép về kiểu linh hoạt nhất có mặt: một list gồm số và chuỗi sẽ thành vector toàn ký tự. Hãy kiểm tra class() của kết quả sau khi unlist.

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