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!


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

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


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

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

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

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

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.


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]

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.


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

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

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.


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

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

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') ]
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.