Opis przypadku odpowiednik w R.

87

Mam zmienną w ramce danych, w której jedno z pól ma zwykle wartości 7-8. Chcę połączyć je z 3 lub 4 nowymi kategoriami w nowej zmiennej w ramce danych. Jakie jest najlepsze podejście?

Użyłbym instrukcji CASE, gdybym był w narzędziu podobnym do SQL, ale nie byłbym pewien, jak zaatakować to w R.

Każda pomoc, której możesz udzielić, będzie bardzo mile widziana!

Btibert3
źródło
a) Czy są to liczby całkowite, numeryczne, kategorialne czy łańcuchowe? Proszę zamieścić przykładowy fragment danych, używając dput()b) Czy potrzebujesz rozwiązania w bazie R, dplyr, data.table, tidyverse ...?
smci

Odpowiedzi:

38

case_when(), który został dodany do dplyr w maju 2016 r., rozwiązuje ten problem w sposób podobny do memisc::cases().

Na przykład:

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

Od 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
źródło
4
Nie potrzebujesz .$przed każdą kolumną.
kath
1
Tak, od dplyr 0.7.0 (wydany 9 czerwca 2017), .$nie jest już potrzebny. W czasie, gdy pierwotnie pisano tę odpowiedź, tak było.
Evan Cortens,
świetne rozwiązanie. jeśli oba stwierdzenia są prawdziwe. Czy druga nadpisuje pierwszą?
JdP,
1
@JdP Działa tak jak CASE WHEN w SQL, więc instrukcje są oceniane w kolejności, a wynikiem jest pierwsza instrukcja TRUE. (Tak więc w powyższym przykładzie
wstawiłem
Podoba mi się ta odpowiedź, ponieważ w przeciwieństwie do switchniej pozwala na tworzenie sekwencji wyrażeń zamiast kluczy dla spraw.
Dannid
27

Spójrz na casesfunkcję z memiscpakietu. Implementuje funkcjonalność wielkości liter na dwa różne sposoby. Z przykładów w pakiecie:

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

gdzie xi ysą dwoma wektorami.

Referencje: pakiet memisc , przykłady przypadków

Henrico
źródło
23

Jeśli tak, factormożesz zmienić poziomy za pomocą standardowej metody:

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

Możesz napisać prostą funkcję jako opakowanie:

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
źródło
1
Niezła odpowiedź. Zapomniałem, że możesz użyć listy jako argumentu do poziomów ze starymi i nowymi nazwami w ten sposób; moje rozwiązanie polega na tym, aby zachować prostą kolejność poziomów, więc tak jest lepiej.
Aaron opuścił Stack Overflow
Czy powinien xznajdować się w ostatniej linii changelevels?
Aaron opuścił Stack Overflow
20

Oto sposób użycia tego switchoświadczenia:

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

Jedyną wadą jest to, że musisz ciągle wpisywać nazwę kategorii ( animalitp.) Dla każdego elementu. Składniowo wygodniej jest móc zdefiniować nasze kategorie, jak poniżej (zobacz bardzo podobne pytanie Jak dodać kolumnę w ramce danych w R )

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

i chcemy jakoś „odwrócić” to mapowanie. Piszę własną funkcję invMap:

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

a następnie odwróć powyższą mapę w następujący sposób:

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

A potem łatwo jest użyć tego, aby dodać typekolumnę w ramce danych:

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

> df
    name   type
1    cow animal
2    pig animal
3  eagle   bird
4 pigeon   bird
Prasad Chalasani
źródło
16

Nie widzę propozycji „przełącznika”. Przykład kodu (uruchom go):

x <- "three"
y <- 0
switch(x,
       one = {y <- 5},
       two = {y <- 12},
       three = {y <- 432})
y
adamsss6
źródło
14

Imho, najprostszy i najbardziej uniwersalny 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'
})
Gregory Demin
źródło
Podoba mi się ta metoda. Czy jest jednak implementacja
``
2
@ T.Fung Możesz zmienić pierwszą linię na y = 'else'. Elementy, które nie spełniają dalszych warunków, pozostaną niezmienione.
Gregory Demin
7

Jest takie switchstwierdzenie, ale nigdy nie wydaje mi się, żeby działało tak, jak powinno. Ponieważ nie podałeś przykładu, utworzę go za pomocą zmiennej czynnika:

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

Jeśli określisz żądane kategorie w kolejności odpowiedniej do ponownego przypisania, możesz użyć współczynnika lub zmiennych numerycznych jako indeksu:

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

Później dowiedziałem się, że tak naprawdę są dwie różne funkcje przełączników. To nie jest funkcja ogólna, ale powinieneś o niej myśleć jako albo switch.numericalbo switch.character. Jeśli pierwszym argumentem jest „czynnik” R, otrzymujesz switch.numericzachowanie, które może powodować problemy, ponieważ większość ludzi postrzega czynniki jako znaki i przyjmuje błędne założenie, że wszystkie funkcje będą je przetwarzać jako takie.

IRTFM
źródło
6

Możesz użyć recode z pakietu samochodowego:

library(ggplot2) #get data
library(car)
daimons$new_var <- recode(diamonds$clarity , "'I1' = 'low';'SI2' = 'low';else = 'high';")[1:10]
Ian Fellows
źródło
11
Po prostu nie mogę obsługiwać funkcji, która analizuje parametry z tekstu
Hadley,
Tak, ale czy wiesz, czy ktoś napisał lepszą wersję? sos::findFn("recode")Znaleziska doBy::recodeVar, epicalc::recode, memisc::recode, ale nie spojrzał na nich w szczegółach ...
Ben Bolker
5

nie podoba mi się żadna z nich, nie są one jasne dla czytelnika lub potencjalnego użytkownika. Po prostu używam funkcji anonimowej, składnia nie jest tak zgrabna jak instrukcja case, ale ocena jest podobna do instrukcji case i nie jest tak bolesna. zakłada to również, że oceniasz go w miejscu, w którym zdefiniowane są twoje zmienne.

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

wszystkie te () są niezbędne do zawarcia i oceny funkcji anonimowej.

jamesM
źródło
6
1) Część funkcyjna jest niepotrzebna; możesz po prostu zrobić result <- (if (x==10 | y< 5) 'foo' else if (x==11 & y== 5) 'bar' ). 2) Działa to tylko wtedy, gdy xi ysą skalarami; w przypadku wektorów, tak jak w pierwotnym pytaniu, ifelsekonieczne byłyby zagnieżdżone stwierdzenia.
Aaron opuścił Stack Overflow
4

Używam w tych przypadkach, o których mówisz switch(). Wygląda jak instrukcja sterująca, ale w rzeczywistości jest funkcją. Wyrażenie jest oceniane i na podstawie tej wartości zwracany jest odpowiedni element z listy.

przełącznik działa na dwa różne sposoby, w zależności od tego, czy wartość pierwszego argumentu jest ciągiem znaków, czy liczbą.

Poniżej znajduje się prosty przykład łańcucha, który rozwiązuje problem polegający na zwinięciu starych kategorii na nowe.

W przypadku postaci ciągu znaków należy ustawić jeden nienazwany argument jako domyślny po nazwanych wartościach.

newCat <- switch(EXPR = category,
       cat1   = catX,
       cat2   = catX,
       cat3   = catY,
       cat4   = catY,
       cat5   = catZ,
       cat6   = catZ,
       "not available")
petzi
źródło
3

Jeśli chcesz mieć składnię podobną do sql, możesz po prostu użyć sqldfpakietu. Funkcją, której należy użyć, są również nazwy, sqldfa składnia jest następująca

sqldf(<your query in quotation marks>)
kuba
źródło
2

W rzeczywistości stwierdzenie przypadku może nie być tutaj właściwym podejściem. Jeśli jest to czynnik, który prawdopodobnie ma miejsce, wystarczy odpowiednio ustawić jego poziomy.

Powiedzmy, że masz czynnik z literami od A do E, w ten sposób.

> 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

Aby dołączyć do poziomów B i C i nazwać je BC, wystarczy zmienić nazwy tych poziomów na 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

Wynik jest zgodny z oczekiwaniami.

Aaron opuścił Stack Overflow
źródło
2

Miksowanie plyr::mutate i dplyr::case_whendziała dla mnie i jest czytelne.

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]

Dodatkowe punkty, jeśli kolumna może wyjść z mutacji jako czynnika zamiast znaku! Ostatni wiersz instrukcji case_when, który wyłapuje wszystkie niedopasowane wiersze, jest bardzo ważny.

     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
בנימן הגלילי
źródło
2

Możesz użyć tej basefunkcji mergedo zadań ponownego odwzorowania wielkości liter:

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
źródło
1

Od wersji data.table v1.13.0 możesz używać funkcji fcase()(fast-case) do wykonywania CASEoperacji podobnych do SQL (również podobnych do 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
źródło