Câu lệnh trường hợp tương đương trong R


87

Tôi có một biến trong khung dữ liệu nơi một trong các trường thường có 7-8 giá trị. Tôi muốn sắp xếp chúng 3 hoặc 4 danh mục mới trong một biến mới trong khung dữ liệu. Đâu là cách tiếp cận lí tưởng nhất?

Tôi sẽ sử dụng câu lệnh CASE nếu tôi đang sử dụng một công cụ giống SQL nhưng không chắc chắn cách tấn công điều này trong R.

Bất kỳ sự giúp đỡ nào bạn có thể cung cấp sẽ được đánh giá cao!


a) Chúng là số nguyên, số, phân loại hay chuỗi? Vui lòng đăng đoạn mã dữ liệu ví dụ, sử dụng dput()b) Bạn có muốn một giải pháp trong cơ sở R, dplyr, data.table, slimverse ... không?
smci

Câu trả lời:


38

case_when(), đã được thêm vào dplyr vào tháng 5 năm 2016, giải quyết vấn đề này theo cách tương tự như memisc::cases().

Ví dụ:

library(dplyr)
mtcars %>% 
  mutate(category = case_when(
    .$cyl == 4 & .$disp < median(.$disp) ~ "4 cylinders, small displacement",
    .$cyl == 8 & .$disp > median(.$disp) ~ "8 cylinders, large displacement",
    TRUE ~ "other"
  )
)

Kể từ dplyr 0.7.0,

mtcars %>% 
  mutate(category = case_when(
    cyl == 4 & disp < median(disp) ~ "4 cylinders, small displacement",
    cyl == 8 & disp > median(disp) ~ "8 cylinders, large displacement",
    TRUE ~ "other"
  )
)

4
Bạn không cần .$ở trước mỗi cột.
kath

1
Có, kể từ dplyr 0.7.0 (phát hành ngày 9 tháng 6 năm 2017), .$không còn cần thiết nữa. Tại thời điểm câu trả lời này ban đầu được viết, nó là.
Evan Cortens

giải pháp tuyệt vời. nếu cả hai câu đều đúng. Cái thứ hai có ghi đè cái thứ nhất không?
JdP

1
@JdP Nó hoạt động giống như CASE WHEN trong SQL, vì vậy các câu lệnh được đánh giá theo thứ tự và kết quả là câu lệnh TRUE đầu tiên. (Vì vậy, trong ví dụ trên, tôi đã đặt TRUE ở cuối, đóng vai trò như một giá trị mặc định.)
Evan Cortens

Tôi thích câu trả lời này bởi vì, không giống như switch, nó cho phép bạn tạo một chuỗi các biểu thức thay vì khóa cho các trường hợp.
Dannid

27

Hãy xem caseschức năng từ memiscgói. Nó thực hiện chức năng trường hợp với hai cách khác nhau để sử dụng. Từ các ví dụ trong gói:

z1=cases(
    "Condition 1"=x<0,
    "Condition 2"=y<0,# only applies if x >= 0
    "Condition 3"=TRUE
    )

ở đâu xylà hai vectơ.

Tham khảo: gói memisc , ví dụ trường hợp


23

Nếu bạn có factorthì bạn có thể thay đổi cấp độ bằng phương pháp tiêu chuẩn:

df <- data.frame(name = c('cow','pig','eagle','pigeon'), 
             stringsAsFactors = FALSE)
df$type <- factor(df$name) # First step: copy vector and make it factor
# Change levels:
levels(df$type) <- list(
    animal = c("cow", "pig"),
    bird = c("eagle", "pigeon")
)
df
#     name   type
# 1    cow animal
# 2    pig animal
# 3  eagle   bird
# 4 pigeon   bird

Bạn có thể viết hàm đơn giản như một trình bao bọc:

changelevels <- function(f, ...) {
    f <- as.factor(f)
    levels(f) <- list(...)
    f
}

df <- data.frame(name = c('cow','pig','eagle','pigeon'), 
                 stringsAsFactors = TRUE)

df$type <- changelevels(df$name, animal=c("cow", "pig"), bird=c("eagle", "pigeon"))

1
Câu trả lời hay đấy. Tôi quên rằng bạn có thể sử dụng một danh sách làm đối số cho các cấp với các tên cũ và mới như vậy; giải pháp của tôi phụ thuộc vào việc giữ thứ tự của các cấp thẳng hàng, vì vậy theo cách này thì tốt hơn.
Aaron rời khỏi Stack Overflow

Ngoài ra, nên xở dòng cuối cùng changelevels?
Aaron rời khỏi Stack Overflow

20

Đây là một cách sử dụng switchcâu lệnh:

df <- data.frame(name = c('cow','pig','eagle','pigeon'), 
                 stringsAsFactors = FALSE)
df$type <- sapply(df$name, switch, 
                  cow = 'animal', 
                  pig = 'animal', 
                  eagle = 'bird', 
                  pigeon = 'bird')

> df
    name   type
1    cow animal
2    pig animal
3  eagle   bird
4 pigeon   bird

Một nhược điểm của điều này là bạn phải tiếp tục viết tên danh mục ( animal, v.v.) cho mỗi mục. Sẽ thuận tiện hơn về mặt cú pháp khi có thể xác định các danh mục của chúng tôi như bên dưới (xem câu hỏi tương tự Làm cách nào để thêm một cột trong khung dữ liệu trong R )

myMap <- list(animal = c('cow', 'pig'), bird = c('eagle', 'pigeon'))

và chúng tôi muốn bằng cách nào đó "đảo ngược" ánh xạ này. Tôi viết hàm invMap của riêng mình:

invMap <- function(map) {
  items <- as.character( unlist(map) )
  nams <- unlist(Map(rep, names(map), sapply(map, length)))
  names(nams) <- items
  nams
}

và sau đó đảo ngược bản đồ trên như sau:

> invMap(myMap)
     cow      pig    eagle   pigeon 
"animal" "animal"   "bird"   "bird" 

Và sau đó, thật dễ dàng sử dụng điều này để thêm typecột vào khung dữ liệu:

df <- transform(df, type = invMap(myMap)[name])

> df
    name   type
1    cow animal
2    pig animal
3  eagle   bird
4 pigeon   bird

16

Tôi không thấy đề xuất nào cho 'chuyển đổi'. Ví dụ về mã (chạy nó):

x <- "three"
y <- 0
switch(x,
       one = {y <- 5},
       two = {y <- 12},
       three = {y <- 432})
y

14

Imho, mã đơn giản và phổ biến nhất:

dft=data.frame(x = sample(letters[1:8], 20, replace=TRUE))
dft=within(dft,{
    y=NA
    y[x %in% c('a','b','c')]='abc'
    y[x %in% c('d','e','f')]='def'
    y[x %in% 'g']='g'
    y[x %in% 'h']='h'
})

Tôi thích phương pháp này. Tuy nhiên, có một thực hiện 'khác' như trong một số trường hợp này sẽ là không thể thiếu
T.Fung

2
@ T.Fung Bạn có thể thay đổi dòng đầu tiên thành y = 'else'. Các phần tử không thỏa mãn bất kỳ điều kiện nào khác sẽ không thay đổi.
Gregory Demin

7

Có một switchtuyên bố nhưng tôi dường như không bao giờ có thể làm cho nó hoạt động theo cách mà tôi nghĩ. Vì bạn chưa cung cấp ví dụ nên tôi sẽ tạo một ví dụ bằng cách sử dụng biến nhân tố:

 dft <-data.frame(x = sample(letters[1:8], 20, replace=TRUE))
 levels(dft$x)
[1] "a" "b" "c" "d" "e" "f" "g" "h"

Nếu bạn chỉ định các danh mục bạn muốn theo thứ tự phù hợp với việc gán lại, bạn có thể sử dụng biến nhân tố hoặc biến số làm chỉ mục:

c("abc", "abc", "abc", "def", "def", "def", "g", "h")[dft$x]
 [1] "def" "h"   "g"   "def" "def" "abc" "h"   "h"   "def" "abc" "abc" "abc" "h"   "h"   "abc"
[16] "def" "abc" "abc" "def" "def"

dft$y <- c("abc", "abc", "abc", "def", "def", "def", "g", "h")[dft$x] str(dft)
'data.frame':   20 obs. of  2 variables:
 $ x: Factor w/ 8 levels "a","b","c","d",..: 4 8 7 4 6 1 8 8 5 2 ...
 $ y: chr  "def" "h" "g" "def" ...

Sau đó tôi biết được rằng thực sự có hai chức năng chuyển đổi khác nhau. Nó không phải là chức năng chung chung nhưng bạn nên nghĩ về nó như một switch.numerichoặc switch.character. Nếu đối số đầu tiên của bạn là R 'factor', bạn sẽ nhận được switch.numerichành vi, có khả năng gây ra vấn đề, vì hầu hết mọi người xem các yếu tố được hiển thị dưới dạng ký tự và đưa ra giả định không chính xác rằng tất cả các hàm sẽ xử lý chúng như vậy.


6

Bạn có thể sử dụng mã hóa từ gói ô tô:

library(ggplot2) #get data
library(car)
daimons$new_var <- recode(diamonds$clarity , "'I1' = 'low';'SI2' = 'low';else = 'high';")[1:10]

11
Tôi chỉ không thể hỗ trợ một chức năng phân tích các thông số của nó từ văn bản
hadley

Có, nhưng bạn có biết nếu ai đã viết một phiên bản tốt hơn? sos::findFn("recode")phát hiện doBy::recodeVar, epicalc::recode, memisc::recode, nhưng tôi đã không nhìn họ một cách chi tiết ...
Bến Bolker

5

Tôi không thích bất kỳ điều nào trong số này, chúng không rõ ràng đối với người đọc hoặc người dùng tiềm năng. Tôi chỉ sử dụng một chức năng ẩn danh, cú pháp không bóng bẩy như một câu lệnh tình huống, nhưng việc đánh giá tương tự như một câu lệnh trường hợp và không quá đau đớn. điều này cũng giả định rằng bạn đánh giá nó ở nơi các biến của bạn được xác định.

result <- ( function() { if (x==10 | y< 5) return('foo') 
                         if (x==11 & y== 5) return('bar')
                        })()

tất cả những cái đó () là cần thiết để bao bọc và đánh giá hàm ẩn danh.


6
1) Phần chức năng là không cần thiết; bạn chỉ có thể làm result <- (if (x==10 | y< 5) 'foo' else if (x==11 & y== 5) 'bar' ). 2) Điều này chỉ hoạt động nếu xylà vô hướng; đối với vectơ, như trong câu hỏi ban đầu, các ifelsecâu lệnh lồng nhau sẽ là cần thiết.
Aaron trái Stack Overflow

4

Tôi đang sử dụng trong những trường hợp bạn đang giới thiệu switch(). Nó trông giống như một câu lệnh điều khiển nhưng thực ra, nó là một hàm. Biểu thức được đánh giá và dựa trên giá trị này, mục tương ứng trong danh sách được trả về.

switch hoạt động theo hai cách riêng biệt tùy thuộc vào việc đối số đầu tiên đánh giá thành một chuỗi ký tự hay một số.

Sau đây là một ví dụ chuỗi đơn giản giải quyết vấn đề của bạn để thu gọn các danh mục cũ thành các danh mục mới.

Đối với biểu mẫu chuỗi ký tự, có một đối số không tên duy nhất làm đối số mặc định sau các giá trị được đặt tên.

newCat <- switch(EXPR = category,
       cat1   = catX,
       cat2   = catX,
       cat3   = catY,
       cat4   = catY,
       cat5   = catZ,
       cat6   = catZ,
       "not available")

3

Nếu bạn muốn có cú pháp giống sql, bạn có thể sử dụng sqldfgói. Hàm được sử dụng cũng là tên sqldfvà cú pháp như sau

sqldf(<your query in quotation marks>)

2

Một tuyên bố tình huống thực sự có thể không phải là cách tiếp cận đúng ở đây. Nếu đây là một yếu tố, có khả năng xảy ra, chỉ cần đặt các mức của yếu tố một cách thích hợp.

Giả sử bạn có một thừa số với các chữ cái từ A đến E, như thế này.

> a <- factor(rep(LETTERS[1:5],2))
> a
 [1] A B C D E A B C D E
Levels: A B C D E

Để tham gia các cấp B và C và đặt tên là BC, chỉ cần đổi tên của các cấp đó thành BC.

> levels(a) <- c("A","BC","BC","D","E")
> a
 [1] A  BC BC D  E  A  BC BC D  E 
Levels: A BC D E

Kết quả như mong muốn.


2

Trộn plyr::mutatedplyr::case_whenlàm việc cho tôi và có thể đọc được.

iris %>%
plyr::mutate(coolness =
     dplyr::case_when(Species  == "setosa"     ~ "not cool",
                      Species  == "versicolor" ~ "not cool",
                      Species  == "virginica"  ~ "super awesome",
                      TRUE                     ~ "undetermined"
       )) -> testIris
head(testIris)
levels(testIris$coolness)  ## NULL
testIris$coolness <- as.factor(testIris$coolness)
levels(testIris$coolness)  ## ok now
testIris[97:103,4:6]

Điểm thưởng nếu cột có thể đột biến như một yếu tố thay vì ký tự! Dòng cuối cùng của câu lệnh case_when, bắt tất cả các hàng chưa khớp là rất quan trọng.

     Petal.Width    Species      coolness
 97         1.3  versicolor      not cool
 98         1.3  versicolor      not cool  
 99         1.1  versicolor      not cool
100         1.3  versicolor      not cool
101         2.5  virginica     super awesome
102         1.9  virginica     super awesome
103         2.1  virginica     super awesome

2

Bạn có thể sử dụng basehàm mergecho các tác vụ ánh xạ lại kiểu chữ hoa chữ thường:

df <- data.frame(name = c('cow','pig','eagle','pigeon','cow','eagle'), 
                 stringsAsFactors = FALSE)

mapping <- data.frame(
  name=c('cow','pig','eagle','pigeon'),
  category=c('mammal','mammal','bird','bird')
)

merge(df,mapping)
# name category
# 1    cow   mammal
# 2    cow   mammal
# 3  eagle     bird
# 4  eagle     bird
# 5    pig   mammal
# 6 pigeon     bird

1

Kể từ data.table v1.13.0, bạn có thể sử dụng hàm fcase()(fast-case) để thực hiện các CASEhoạt động giống SQL (cũng tương tự như dplyr::case_when()):

require(data.table)

dt <- data.table(name = c('cow','pig','eagle','pigeon','cow','eagle'))
dt[ , category := fcase(name %in% c('cow', 'pig'), 'mammal',
                        name %in% c('eagle', 'pigeon'), 'bird') ]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.