Menu

If else trong R: if, else if, ifelse() và switch()

Logic điều kiện trong R: cú pháp if/else if/else, cái bẫy dấu ngoặc nhọn làm hỏng script, ifelse() vector hóa cho cả vector, và switch() cho lựa chọn nhiều nhánh.

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.

Câu lệnh if

Câu lệnh if trong R chỉ chạy một khối lệnh khi điều kiện là TRUE. Điều kiện nằm trong ngoặc tròn, code nằm trong ngoặc nhọn, và một else không bắt buộc cho biết điều gì xảy ra trong trường hợp còn lại:

Điều kiện score >= 60 cho ra một giá trị TRUE duy nhất, nên nhánh thứ nhất chạy. Các toán tử so sánh và logic (==, >=, &&, %in% và những toán tử khác) là nguyên liệu để bạn xây dựng các điều kiện này.

Khi có nhiều hơn hai kết quả, hãy nối các phép kiểm tra bằng else if. R đi dọc chuỗi từ điều kiện đầu tiên xuống và chạy nhánh đầu tiên có điều kiện là TRUE, bỏ qua phần còn lại:

Thứ tự rất quan trọng: vì 85 >= 80 được kiểm tra trước 85 >= 70, điểm xếp loại là "B". Nếu bạn sắp xếp các phép kiểm tra theo chiều ngược lại, mọi điểm trên 70 đều nhận "C".

Bẫy dấu ngoặc nhọn: else phải đặt ở đâu

Đây là lỗi mà gần như ai cũng gặp trong script R đầu tiên. Trong một script (hoặc bất cứ thứ gì chạy bằng Rscript), else phải nằm cùng dòng với dấu ngoặc nhọn đóng của nhánh trước nó. Đoạn sau sẽ lỗi:

if (score >= 60) {
  print("You passed")
}
else {              # Error: unexpected 'else'
  print("Try again")
}

Lý do: R phân tích từng câu lệnh một. Khi gặp } ở đầu dòng, câu lệnh if đã hoàn chỉnh về cú pháp, và R tính nó ngay lúc đó. else ở dòng tiếp theo bị xem là phần mở đầu của một câu lệnh mới, mà một câu lệnh không thể bắt đầu bằng else. Cách sửa là mẫu được dùng xuyên suốt trang này:

if (score >= 60) {
  print("You passed")
} else {            # brace and else on the same line
  print("Try again")
}

Bên trong thân hàm hoặc bất kỳ cặp ngoặc nhọn bao ngoài nào, quy tắc chặt này được nới lỏng (R tiếp tục đọc cho đến khi ngoặc ngoài đóng lại), nhưng đừng dựa vào điều đó: kiểu } else { chạy ở mọi nơi và là cách các hướng dẫn phong cách R quy định.

Điều kiện phải là một TRUE hoặc FALSE

if() đòi hỏi đúng một giá trị logic. Có hai thứ vi phạm điều đó, và cả hai đều là lỗi:

if (NA) print("hi")
# Error: missing value where TRUE/FALSE needed

if (c(TRUE, FALSE)) print("hi")
# Error: the condition has length > 1

Trường hợp NA quan trọng trên thực tế vì phép so sánh với dữ liệu bị thiếu tạo ra NA: nếu x là NA thì x > 10 là NA, và một if nhận điều kiện đó sẽ làm script dừng lại. Mẫu phòng thủ dùng && có đoản mạch:

Vì !is.na(x) là FALSE, phần x > 10 không bao giờ được tính và không có NA nào lọt vào điều kiện. Cách NA lan truyền qua các phép so sánh được trình bày trong giá trị bị thiếu.

Lỗi độ dài lớn hơn 1 (áp dụng từ R 4.2; trước đó R lặng lẽ dùng phần tử đầu tiên) thường có nghĩa là bạn thật ra muốn một trong những cách sau: any(v > 5) nếu chỉ cần một phần tử khớp, all(v > 5) nếu mọi phần tử đều phải thỏa, hoặc ifelse() nếu bạn muốn câu trả lời cho từng phần tử, chính là phần tiếp theo.

ifelse(): if else vector hóa

if quyết định một lần. ifelse(test, yes, no) quyết định cho từng phần tử của một vector và trả về một vector cùng độ dài:

Mỗi phần tử của temps được kiểm tra độc lập: "hot" "mild" "mild" "hot" "mild". Đây là công cụ để tạo một vector hoặc cột data frame mới từ cái đã có, không cần vòng lặp.

Khác với if, ifelse() xử lý giá trị bị thiếu một cách êm thấm: một NA trong phép kiểm tra cho ra NA trong kết quả thay vì báo lỗi:

Bạn có thể lồng các lệnh gọi ifelse() để chia thành ba nhánh, và ở độ sâu đó code vẫn dễ đọc:

Nhưng hãy dừng ở đó. Từ ba lệnh gọi ifelse() lồng nhau trở lên, code biến thành trò đố khớp ngoặc; lúc ấy hãy dùng cut() (cho khoảng giá trị số) hoặc dplyr::case_when(), nơi mỗi điều kiện nằm trên một dòng riêng.

switch() cho lựa chọn nhiều nhánh

Khi bạn so khớp một giá trị với một danh sách tên đã biết, chứ không kiểm tra khoảng giá trị, một chuỗi else if dài là công cụ vụng về. switch() nói thẳng điều đó:

Ba hành vi cần biết:

  • So khớp: switch() so sánh giá trị chuỗi với tên các đối số và trả về kết quả khớp.
  • Rơi xuống (fall-through): một tên có vế phải trống (sat = ,) rơi xuống kết quả của trường hợp kế tiếp; đó là cách "sat" và "sun" dùng chung "weekend" mà không phải lặp lại.
  • Mặc định: đối số không tên cuối cùng ("weekday") được trả về khi không có gì khớp. Nếu không có mặc định, giá trị không khớp khiến switch() trả về NULL một cách vô hình: hàm của bạn lặng lẽ không trả về gì, một bug rất khó hiểu. Luôn cung cấp giá trị mặc định (hoặc biến nó thành một stop() rõ ràng).

switch() phát huy tốt nhất bên trong các hàm phân nhánh theo một đối số chế độ hoặc phương pháp; với mọi thứ dựa trên ngưỡng số, hãy dùng else if hoặc cut().

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

  • if (condition) { } else { }: điều kiện trong ngoặc tròn, một nhánh được chạy; nối bằng else if, nhánh khớp đầu tiên thắng.
  • Trong script, viết } else { trên cùng một dòng: else ở đầu dòng là lỗi cú pháp.
  • if() cần đúng một TRUE/FALSE: NA là lỗi, vector dài hơn là lỗi (từ R 4.2). Phòng thủ bằng !is.na(x) &&, hoặc rút gọn bằng any()/all().
  • ifelse(test, yes, no) là phiên bản vector hóa: dùng nó để tính vector mới, và đừng lồng quá hai lần.
  • switch() xử lý phân nhánh nhiều hướng theo tên, rơi xuống bằng các trường hợp trống; luôn cho nó một giá trị mặc định.

Tiếp theo: làm một việc nhiều lần, với vòng lặp for, và khi nào vòng lặp là (hoặc không phải là) công cụ phù hợp.

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

Viết câu lệnh if else trong R như thế nào?

if (condition) { ... } else { ... }: điều kiện nằm trong ngoặc tròn và mỗi nhánh nằm trong ngoặc nhọn. Nối thêm các phép kiểm tra bằng else if (condition). Trong script, else phải nằm cùng dòng với dấu ngoặc nhọn đóng của nhánh trước (} else {), nếu không R sẽ báo unexpected 'else'.

if và ifelse() trong R khác nhau thế nào?

if là câu lệnh điều khiển luồng, kiểm tra đúng một giá trị TRUE/FALSE và chạy một nhánh code. ifelse(test, yes, no) là hàm vector hóa: nó kiểm tra mọi phần tử của một vector cùng lúc và trả về một vector kết quả. Dùng if để quyết định chương trình làm gì; dùng ifelse() để tính một cột hoặc vector mới từ cái đã có.

Vì sao R báo 'the condition has length > 1'?

Bạn đã truyền cả một vector vào if(), trong khi nó cần một giá trị TRUE hoặc FALSE duy nhất. Từ R 4.2, đây là lỗi chứ không còn lặng lẽ dùng phần tử đầu tiên. Cách sửa là rút vector về một giá trị (any(x > 5), all(x > 5)), hoặc chuyển sang ifelse() vector hóa nếu bạn muốn kết quả cho từng phần tử.

switch() trong R hoạt động thế nào?

switch(value, name1 = result1, name2 = result2, default) so sánh một giá trị chuỗi với tên các đối số và trả về kết quả khớp. Một tên có vế phải để trống sẽ rơi xuống kết quả kế tiếp, nên nhiều trường hợp có thể dùng chung một kết quả. Đối số không tên cuối cùng đóng vai trò mặc định; nếu không có, giá trị không khớp sẽ trả về NULL một cách vô hình.

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