Pernyataan Kasus Setara di R

92

Saya memiliki variabel dalam kerangka data di mana salah satu bidang biasanya memiliki 7-8 nilai. Saya ingin menyusunnya 3 atau 4 kategori baru dalam variabel baru dalam kerangka data. Apa pendekatan terbaik?

Saya akan menggunakan pernyataan CASE jika saya menggunakan alat seperti SQL tetapi tidak yakin bagaimana cara menyerang ini di R.

Bantuan apa pun yang Anda berikan akan sangat dihargai!

Btibert3
sumber
a) Apakah integer, numerik, kategorikal atau string? Silakan posting contoh potongan data, menggunakan dput()b) Apakah Anda menginginkan solusi dalam basis R, dplyr, data.table, tidyverse ...?
smci

Jawaban:

39

case_when(), yang ditambahkan ke dplyr pada Mei 2016, memecahkan masalah ini dengan cara yang mirip dengan memisc::cases().

Sebagai contoh:

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"
  )
)

Pada 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"
  )
)
Evan Cortens
sumber
4
Anda tidak perlu .$di depan setiap kolom.
kath
1
Ya, mulai dplyr 0.7.0 (dirilis 9 Juni 2017), .$tidak lagi diperlukan. Pada saat jawaban ini pertama kali ditulis, memang begitu.
Evan Cortens
solusi yang bagus. jika kedua pernyataan itu benar. Apakah yang kedua menimpa yang pertama?
JdP
1
@JdP Bekerja seperti CASE WHEN di SQL, jadi pernyataan dievaluasi secara berurutan, dan hasilnya adalah pernyataan BENAR pertama. (Jadi dalam contoh di atas, saya telah memasukkan TRUE di bagian akhir, yang berfungsi sebagai nilai default.)
Evan Cortens
Saya suka jawaban ini karena, tidak seperti switch, ini memungkinkan Anda membuat urutan ekspresi alih-alih kunci untuk kasus.
Dannid
27

Lihat casesfungsi dari memiscpaket. Ini mengimplementasikan fungsionalitas kasus dengan dua cara berbeda untuk menggunakannya. Dari contoh di dalam paket:

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

dimana xdan yadalah dua vektor.

Referensi: paket memisc , contoh kasus

Henrico
sumber
24

Jika Anda mendapatkannya, factorAnda dapat mengubah level dengan metode standar:

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

Anda bisa menulis fungsi sederhana sebagai pembungkus:

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"))
Marek
sumber
2
Jawaban bagus. Saya lupa Anda bisa menggunakan daftar sebagai argumen untuk menyamakan dengan nama lama dan baru seperti itu; solusi saya bergantung pada seseorang yang menjaga urutan level tetap lurus, jadi ini lebih baik dengan cara itu.
Aaron meninggalkan Stack Overflow
Juga, haruskah xdi baris terakhir changelevels?
Aaron meninggalkan Stack Overflow
22

Berikut cara menggunakan switchpernyataan tersebut:

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

Satu kelemahan dari ini adalah Anda harus tetap menulis nama kategori ( animal, dll) untuk setiap item. Secara sintaksis lebih mudah untuk dapat mendefinisikan kategori kita seperti di bawah ini (lihat pertanyaan yang sangat mirip Bagaimana cara menambahkan kolom dalam bingkai data di R )

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

dan kami ingin "membalik" pemetaan ini. Saya menulis fungsi invMap saya sendiri:

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

dan kemudian balikkan peta di atas sebagai berikut:

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

Dan kemudian mudah menggunakan ini untuk menambahkan typekolom di bingkai data:

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

> df
    name   type
1    cow animal
2    pig animal
3  eagle   bird
4 pigeon   bird
Prasad Chalasani
sumber
19

Saya tidak melihat proposal untuk 'saklar'. Contoh kode (jalankan):

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

Imho, kode paling lugas dan universal:

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'
})
Gregory Demin
sumber
Saya suka metode ini. Namun, apakah ada penerapan 'lain' karena dalam beberapa keadaan ini akan sangat diperlukan
T.Fung
2
@ T.Fung Anda dapat mengubah baris pertama menjadi y = 'else'. Elemen yang tidak memenuhi ketentuan lebih lanjut akan tetap tidak berubah.
Gregory Demin
7

Ada switchpernyataan tetapi saya tidak pernah bisa membuatnya bekerja seperti yang saya kira seharusnya. Karena Anda belum memberikan contoh, saya akan membuatnya menggunakan variabel faktor:

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

Jika Anda menentukan kategori yang Anda inginkan dalam urutan yang sesuai dengan penugasan ulang, Anda dapat menggunakan faktor atau variabel numerik sebagai indeks:

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" ...

Saya kemudian mengetahui bahwa sebenarnya ada dua fungsi sakelar yang berbeda. Ini bukan fungsi umum tetapi Anda harus memikirkannya sebagai salah satu switch.numericatau switch.character. Jika argumen pertama Anda adalah 'faktor' R, Anda mendapatkan switch.numericperilaku, yang mungkin menyebabkan masalah, karena kebanyakan orang melihat faktor ditampilkan sebagai karakter dan membuat asumsi yang salah bahwa semua fungsi akan memprosesnya seperti itu.

IRTFM
sumber
6

Anda dapat menggunakan kode ulang dari paket mobil:

library(ggplot2) #get data
library(car)
daimons$new_var <- recode(diamonds$clarity , "'I1' = 'low';'SI2' = 'low';else = 'high';")[1:10]
Ian Fellows
sumber
11
Saya hanya tidak dapat mendukung fungsi yang mem-parsing parameternya dari teks
hadley
Ya, tetapi apakah Anda tahu jika ada yang menulis versi yang lebih baik? sos::findFn("recode")temuan doBy::recodeVar, epicalc::recode, memisc::recode, tapi saya belum melihat mereka secara rinci ...
Ben Bolker
5

saya tidak suka semua ini, mereka tidak jelas bagi pembaca atau pengguna potensial. Saya hanya menggunakan fungsi anonim, sintaksnya tidak semulus pernyataan kasus, tetapi evaluasinya mirip dengan pernyataan kasus dan tidak terlalu menyakitkan. ini juga mengasumsikan Anda mengevaluasinya di tempat variabel Anda ditentukan.

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

semua itu () diperlukan untuk menyertakan dan mengevaluasi fungsi anonim.

james
sumber
6
1) Bagian fungsi tidak diperlukan; Anda bisa melakukannya result <- (if (x==10 | y< 5) 'foo' else if (x==11 & y== 5) 'bar' ). 2) Ini hanya berfungsi jika xdan ymerupakan skalar; untuk vektor, seperti pada pertanyaan awal, ifelsepernyataan bertingkat akan diperlukan.
Aaron meninggalkan Stack Overflow
4

Saya menggunakan dalam kasus-kasus yang Anda maksud switch(). Ini terlihat seperti pernyataan kontrol tetapi sebenarnya, ini adalah fungsi. Ekspresi dievaluasi dan berdasarkan nilai ini, item yang sesuai dalam daftar dikembalikan.

switch bekerja dalam dua cara berbeda tergantung apakah argumen pertama mengevaluasi ke string karakter atau angka.

Berikut ini adalah contoh string sederhana yang memecahkan masalah Anda untuk menciutkan kategori lama ke kategori baru.

Untuk bentuk karakter-string, miliki satu argumen tanpa nama sebagai default setelah nilai yang dinamai.

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

Jika Anda ingin memiliki sintaks seperti sql, Anda dapat menggunakan sqldfpaket. Tfungsi yang akan digunakan juga nama sqldfdan sintaksnya adalah sebagai berikut

sqldf(<your query in quotation marks>)
kuba
sumber
2

Pernyataan kasus sebenarnya mungkin bukan pendekatan yang tepat di sini. Jika ini adalah sebuah faktor, yang kemungkinan besar terjadi, atur saja tingkat faktornya dengan tepat.

Katakanlah Anda memiliki faktor dengan huruf A sampai E, seperti ini.

> 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

Untuk menggabungkan level B dan C dan menamakannya BC, cukup ubah nama level tersebut menjadi 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

Hasilnya sesuai keinginan.

Aaron meninggalkan Stack Overflow
sumber
2

Mencampur plyr::mutate dan dplyr::case_whenbekerja untuk saya dan dapat dibaca.

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]

Poin bonus jika kolom dapat keluar dari mutasi sebagai faktor, bukan karakter! Baris terakhir dari pernyataan case_when, yang menangkap semua baris yang tidak cocok sangatlah penting.

     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
בנימן הגלילי
sumber
2

Anda dapat menggunakan basefungsi tersebut mergeuntuk tugas pemetaan ulang gaya huruf:

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
patrickmdnet.dll
sumber
1

Pada data.table v1.13.0 Anda dapat menggunakan fungsi fcase()(kasus cepat) untuk melakukan CASEoperasi seperti SQL (juga mirip dengan 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') ]
andschar
sumber