R'de Case İfadesi Eşdeğeri


92

Alanlardan birinin tipik olarak 7-8 değere sahip olduğu bir veri çerçevesinde bir değişkenim var. Dataframe içindeki yeni bir değişken içinde 3 veya 4 yeni kategoriyi daraltmak istiyorum. En iyi yaklaşım nedir?

SQL benzeri bir araçta olsam, ancak buna R'de nasıl saldıracağımı bilmiyorsam bir CASE deyimi kullanırdım.

Sağlayabileceğiniz herhangi bir yardım çok takdir edilecektir!


a) Tamsayı mı, sayısal mı, kategorik mi yoksa dizi mi? Lütfen dput()b) tabanında R, dplyr, data.table, tidyverse ... kullanarak bir çözüm mü istiyorsunuz?
smci

Yanıtlar:


39

case_when()Mayıs 2016'da dplyr'e eklenen, bu sorunu memisc::cases().

Örneğin:

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

Dplyr 0.7.0 itibariyle,

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
.$Her sütunun önüne ihtiyacınız yok .
kath

1
Evet, dplyr 0.7.0 itibariyle (9 Haziran 2017'de yayınlandı) .$artık gerekli değil. Bu cevap ilk yazıldığı sırada öyleydi.
Evan Cortens

harika çözüm. her iki ifade de doğruysa. İkincisi birincinin üzerine mi yazıyor?
JdP

1
@JdP SQL'deki CASE WHEN gibi çalışır, bu nedenle ifadeler sırayla değerlendirilir ve sonuç ilk TRUE ifadesi olur. (Bu yüzden yukarıdaki örnekte, sonuna varsayılan değer olarak hizmet eden bir DOĞRU
yazdım

Bu cevabı beğendim çünkü aksine switch, vakalar için anahtarlar yerine bir dizi ifade oluşturmanıza izin veriyor.
Dannid

27

Paketteki casesişleve bir göz atın memisc. İki farklı kullanım yolu ile durum işlevselliğini uygular. Paketteki örneklerden:

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

nerede xve yiki vektör.

Referanslar: memisc paketi , vaka örneği


24

Eğer varsa, factorseviyeleri standart yöntemle değiştirebilirsiniz:

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

Bir sarmalayıcı olarak basit bir işlev yazabilirsiniz:

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

2
Güzel cevap. Bunun gibi eski ve yeni isimlerle seviyelere argüman olarak bir liste kullanabileceğinizi unuttum; Benim çözümüm seviyelerin sırasını düz tutmaya bağlı, bu yüzden bu şekilde bu daha iyi.
Aaron Stack Overflow'dan

Ayrıca, xson satırda olması gerekir changelevelsmi?
Aaron Stack Overflow'dan

22

İşte switchifadeyi kullanmanın bir yolu :

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

Bunun bir dezavantajı, animalher bir öğe için kategori adını ( vb.) Yazmaya devam etmeniz gerektiğidir . Kategorilerimizi aşağıdaki gibi tanımlayabilmek sözdizimsel olarak daha uygundur (çok benzer soruya bakın R'de bir veri çerçevesine nasıl sütun eklenir )

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

ve bu haritayı bir şekilde "tersine çevirmek" istiyoruz. Kendi invMap işlevimi yazıyorum:

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

ve sonra yukarıdaki haritayı aşağıdaki gibi ters çevirin:

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

Ve sonra, typesütunu veri çerçevesine eklemek için bunu kullanmak kolaydır :

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

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

19

'Geçiş' için bir teklif göremiyorum. Kod örneği (çalıştırın):

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

15

Imho, en basit ve evrensel kod:

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'
})

Bu yöntemi beğendim. Bununla birlikte, bazı durumlarda bu vazgeçilmez olacağı için 'başka' bir uygulama var mı
T.Fung

2
@ T.Fung İlk satırı olarak değiştirebilirsiniz y = 'else'. Daha fazla koşulu karşılamayan unsurlar değişmeden kalacaktır.
Gregory Demin

7

Bir switchifade var ama onu olması gerektiğini düşündüğüm gibi çalıştıramıyorum. Örnek vermediğin için faktör değişkenini kullanarak bir örnek yapacağım:

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

Yeniden atamaya uygun bir sırada istediğiniz kategorileri belirtirseniz, faktör veya sayısal değişkenleri indeks olarak kullanabilirsiniz:

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

Daha sonra gerçekten iki farklı anahtar işlevi olduğunu öğrendim. Bu genel bir işlev değildir, ancak ya ya switch.numericda olarak düşünmelisiniz switch.character. İlk argümanınız bir R 'faktörü' ise, switch.numericsorunlara yol açma olasılığı yüksek bir davranışla karşılaşırsınız, çünkü çoğu insan gösterilen faktörleri karakter olarak görür ve tüm işlevlerin onları bu şekilde işleyeceği konusunda yanlış varsayımda bulunur.


6

Araç paketindeki yeniden kodu kullanabilirsiniz:

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

11

Evet, ama daha iyi bir sürüm yazan var mı biliyor musunuz? sos::findFn("recode")buluntular doBy::recodeVar, epicalc::recode, memisc::recodeama ... detaylı olarak onlara baktım değil
Ben Bolker

5

Bunların hiçbirini sevmiyorum, okuyucu veya potansiyel kullanıcı için net değiller. Ben sadece anonim bir işlev kullanıyorum, sözdizimi bir vaka ifadesi kadar kaygan değil, ancak değerlendirme bir vaka ifadesine benzer ve o kadar da acı verici değil. bu aynı zamanda onu değişkenlerinizin tanımlandığı yerde değerlendirdiğinizi varsayar.

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

anonim işlevi kapsamak ve değerlendirmek için bunların tümü () gereklidir.


6
1) İşlev kısmı gereksizdir; sadece yapabilirsin result <- (if (x==10 | y< 5) 'foo' else if (x==11 & y== 5) 'bar' ). Eğer 2) Bu yalnızca çalışır xve yskalerler vardır; orijinal soruda olduğu gibi vektörler için iç içe geçmiş ifelseifadeler gerekli olacaktır.
Aaron Stack Overflow'dan

4

Bahsettiğin durumlarda kullanıyorum switch(). Bir kontrol ifadesine benziyor ama aslında bir fonksiyon. İfade değerlendirilir ve bu değere göre listedeki karşılık gelen öğe döndürülür.

switch, ilk argümanın bir karakter dizesi veya sayı olarak değerlendirilip değerlendirilmediğine bağlı olarak iki farklı şekilde çalışır.

Aşağıda, eski kategorileri yenileriyle daraltmak için sorununuzu çözen basit bir dizi örneği verilmiştir.

Karakter dizesi formu için, adlandırılmış değerlerden sonra varsayılan olarak tek bir adlandırılmamış bağımsız değişken ekleyin.

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

3

Sql benzeri sözdizimine sahip olmak istiyorsanız, sadece sqldfpaketi kullanabilirsiniz . Kullanılacak fonksiyon aynı zamanda isimlerdir sqldfve sözdizimi aşağıdaki gibidir

sqldf(<your query in quotation marks>)

2

Bir vaka açıklaması aslında burada doğru yaklaşım olmayabilir. Bu bir faktörse, ki muhtemelen öyle, faktörün seviyelerini uygun şekilde ayarlayın.

A'dan E'ye kadar olan harflerde bunun gibi bir faktörünüz olduğunu varsayalım.

> 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

B ve C seviyelerine katılmak ve BC olarak adlandırmak için, sadece bu seviyelerin isimlerini BC olarak değiştirin.

> 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

Sonuç arzu edildiği gibidir.


2

Karıştırma plyr::mutate ve dplyr::case_whenbenim için çalışıyor ve okunabilir.

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]

Sütun mutasyondan char yerine faktör olarak çıkabiliyorsa bonus puan! Tüm eşleşmeyen satırları yakalayan case_when ifadesinin son satırı çok önemlidir.

     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

Sen kullanabilirsiniz baseişlevini mergevaka tarzı remapping'i görevler için:

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

Data.table v1.13.0'dan itibarenfcase() SQL benzeri CASEişlemler yapmak için (hızlı durum) işlevini kullanabilirsiniz (buna benzer 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') ]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.