Fallaussage äquivalent in R.

90

Ich habe eine Variable in einem Datenrahmen, in dem eines der Felder normalerweise 7-8 Werte hat. Ich möchte ihnen 3 oder 4 neue Kategorien innerhalb einer neuen Variablen innerhalb des Datenrahmens zusammenfassen. Was ist der beste Ansatz?

Ich würde eine CASE-Anweisung verwenden, wenn ich in einem SQL-ähnlichen Tool wäre, aber nicht sicher, wie ich dies in R angreifen soll.

Jede Hilfe, die Sie leisten können, wird sehr geschätzt!

Btibert3
quelle
a) Sind sie ganzzahlig, numerisch, kategorisch oder string? Bitte posten Sie ein Beispiel-Daten-Snippet mit dput()b) Möchten Sie eine Lösung in Basis R, dplyr, data.table, tidyverse ...?
smci

Antworten:

38

case_when(), das im Mai 2016 zu dplyr hinzugefügt wurde, löst dieses Problem auf ähnliche Weise wie memisc::cases().

Zum Beispiel:

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

Ab 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
quelle
4
Sie brauchen das nicht .$vor jeder Spalte.
Kath
1
Ja, ab dplyr 0.7.0 (veröffentlicht am 9. Juni 2017) ist das .$nicht mehr erforderlich. Zu der Zeit, als diese Antwort ursprünglich geschrieben wurde, war es.
Evan Cortens
tolle Lösung. wenn beide Aussagen wahr sind. Überschreibt der zweite den ersten?
JdP
1
@JdP Es funktioniert genau wie CASE WHEN in SQL, daher werden die Anweisungen der Reihe nach ausgewertet und das Ergebnis ist die erste TRUE-Anweisung. (Also im obigen Beispiel habe ich am Ende einen TRUE
eingegeben
Ich mag diese Antwort, weil Sie im Gegensatz switchdazu eine Folge von Ausdrücken anstelle von Schlüsseln für die Fälle erstellen können.
Dannid
27

Schauen Sie sich die casesFunktion aus dem memiscPaket an. Es implementiert die Case-Funktionalität auf zwei verschiedene Arten. Aus den Beispielen im Paket:

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

wo xund ysind zwei Vektoren.

Referenzen: Memisc-Paket , Fallbeispiel

Henrico
quelle
24

Wenn Sie factordann haben, können Sie die Ebenen nach der Standardmethode ändern:

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

Sie könnten eine einfache Funktion als Wrapper schreiben:

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
quelle
2
Gute Antwort. Ich habe vergessen, dass Sie eine Liste als Argument für Ebenen mit den alten und neuen Namen verwenden können. Meine Lösung hängt davon ab, dass man die Reihenfolge der Ebenen gerade hält, also ist dies auf diese Weise besser.
Aaron verließ Stack Overflow
Sollte das auch xin der letzten Zeile stehen changelevels?
Aaron verließ Stack Overflow
22

Hier ist eine Möglichkeit, die switchAnweisung zu verwenden:

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

Der einzige Nachteil dabei ist, dass Sie den Kategorienamen ( animalusw.) für jedes Element weiter schreiben müssen. Es ist syntaktisch bequemer, unsere Kategorien wie folgt definieren zu können (siehe die sehr ähnliche Frage, wie eine Spalte in einem Datenrahmen in R hinzugefügt wird ).

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

und wir wollen dieses Mapping irgendwie "invertieren". Ich schreibe meine eigene invMap-Funktion:

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

und invertieren Sie dann die obige Karte wie folgt:

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

Und dann ist es einfach, damit die typeSpalte im Datenrahmen hinzuzufügen :

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

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

Ich sehe keinen Vorschlag für einen Wechsel. Codebeispiel (ausführen):

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

Imho, einfachster und universellster Code:

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
quelle
Ich mag diese Methode.
Gibt
2
@ T.Fung Sie können die erste Zeile in ändern y = 'else'. Elemente, die keine weiteren Bedingungen erfüllen, bleiben unverändert.
Gregory Demin
7

Es gibt eine switchAussage, aber ich kann nie scheinen, dass sie so funktioniert, wie ich es mir vorstelle. Da Sie kein Beispiel angegeben haben, werde ich eines mit einer Faktorvariablen erstellen:

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

Wenn Sie die gewünschten Kategorien in einer Reihenfolge angeben, die der Neuzuweisung entspricht, können Sie den Faktor oder die numerischen Variablen als Index verwenden:

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

Ich habe später erfahren, dass es wirklich zwei verschiedene Schalterfunktionen gibt. Es ist keine generische Funktion, aber Sie sollten sie als entweder switch.numericoder betrachten switch.character. Wenn Ihr erstes Argument ein R-Faktor ist, erhalten Sie ein switch.numericVerhalten, das wahrscheinlich Probleme verursacht, da die meisten Leute Faktoren als Zeichen anzeigen und die falsche Annahme treffen, dass alle Funktionen sie als solche verarbeiten.

IRTFM
quelle
6

Sie können recode aus dem Autopaket verwenden:

library(ggplot2) #get data
library(car)
daimons$new_var <- recode(diamonds$clarity , "'I1' = 'low';'SI2' = 'low';else = 'high';")[1:10]
Ian Fellows
quelle
11
Ich kann einfach keine Funktion unterstützen, die ihre Parameter aus Text
analysiert
Ja, aber wissen Sie, ob jemand eine bessere Version geschrieben hat? sos::findFn("recode")Funde doBy::recodeVar, epicalc::recode, memisc::recode, aber ich habe nicht auf sie im Detail ... sah
Ben Bolker
5

Ich mag keine davon, sie sind dem Leser oder dem potenziellen Benutzer nicht klar. Ich benutze nur eine anonyme Funktion, die Syntax ist nicht so schick wie eine case-Anweisung, aber die Auswertung ähnelt einer case-Anweisung und ist nicht so schmerzhaft. Dies setzt auch voraus, dass Sie es dort bewerten, wo Ihre Variablen definiert sind.

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

Alle diese () sind erforderlich, um die anonyme Funktion einzuschließen und auszuwerten.

jamesM
quelle
6
1) Der Funktionsteil ist nicht erforderlich; du könntest es einfach tun result <- (if (x==10 | y< 5) 'foo' else if (x==11 & y== 5) 'bar' ). 2) Dies funktioniert nur, wenn xund ySkalare sind; Für Vektoren ifelsewären wie in der ursprünglichen Frage verschachtelte Anweisungen erforderlich.
Aaron verließ Stack Overflow
4

Ich verwende in den Fällen, auf die Sie sich beziehen switch(). Es sieht aus wie eine Steueranweisung, ist aber tatsächlich eine Funktion. Der Ausdruck wird ausgewertet und basierend auf diesem Wert wird das entsprechende Element in der Liste zurückgegeben.

switch funktioniert auf zwei verschiedene Arten, je nachdem, ob das erste Argument eine Zeichenfolge oder eine Zahl ergibt.

Was folgt, ist ein einfaches Beispiel für eine Zeichenfolge, das Ihr Problem löst, alte Kategorien in neue zu reduzieren.

Geben Sie für das Zeichenfolgenformular ein einzelnes unbenanntes Argument als Standard nach den benannten Werten an.

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

Wenn Sie eine SQL-ähnliche Syntax wünschen, können Sie einfach das sqldfPaket verwenden. Die zu verwendende Funktion sind auch Namen sqldfund die Syntax lautet wie folgt

sqldf(<your query in quotation marks>)
Kuba
quelle
2

Eine case-Anweisung ist hier möglicherweise nicht der richtige Ansatz. Wenn dies ein Faktor ist, der wahrscheinlich ist, stellen Sie einfach die Pegel des Faktors entsprechend ein.

Angenommen, Sie haben einen Faktor mit den Buchstaben A bis E, wie folgt.

> 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

Um die Ebenen B und C zu verbinden und sie BC zu nennen, ändern Sie einfach die Namen dieser Ebenen in 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

Das Ergebnis ist wie gewünscht.

Aaron verließ Stack Overflow
quelle
2

Mischen plyr::mutate und dplyr::case_whenfunktioniert für mich und ist lesbar.

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]

Bonuspunkte, wenn die Spalte als Faktor anstelle von char mutiert werden kann! Die letzte Zeile der case_when-Anweisung, die alle nicht übereinstimmenden Zeilen abfängt, ist sehr wichtig.

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

Sie können die baseFunktion mergefür Remapping-Aufgaben im Fallstil verwenden:

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
quelle
1

Ab data.table v1.13.0 können Sie die Funktion fcase()(Fast-Case) verwenden, um SQL-ähnliche CASEOperationen auszuführen (auch ähnlich wie 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
quelle