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!
dput()
b) Möchten Sie eine Lösung in Basis R, dplyr, data.table, tidyverse ...?Antworten:
case_when()
, das im Mai 2016 zu dplyr hinzugefügt wurde, löst dieses Problem auf ähnliche Weise wiememisc::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" ) )
quelle
.$
vor jeder Spalte..$
nicht mehr erforderlich. Zu der Zeit, als diese Antwort ursprünglich geschrieben wurde, war es.switch
dazu eine Folge von Ausdrücken anstelle von Schlüsseln für die Fälle erstellen können.Schauen Sie sich die
cases
Funktion aus demmemisc
Paket 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
x
undy
sind zwei Vektoren.Referenzen: Memisc-Paket , Fallbeispiel
quelle
Wenn Sie
factor
dann 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"))
quelle
x
in der letzten Zeile stehenchangelevels
?Hier ist eine Möglichkeit, die
switch
Anweisung 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 (
animal
usw.) 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
type
Spalte 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
quelle
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
quelle
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' })
quelle
y = 'else'
. Elemente, die keine weiteren Bedingungen erfüllen, bleiben unverändert.Es gibt eine
switch
Aussage, 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.numeric
oder betrachtenswitch.character
. Wenn Ihr erstes Argument ein R-Faktor ist, erhalten Sie einswitch.numeric
Verhalten, das wahrscheinlich Probleme verursacht, da die meisten Leute Faktoren als Zeichen anzeigen und die falsche Annahme treffen, dass alle Funktionen sie als solche verarbeiten.quelle
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]
quelle
sos::findFn("recode")
FundedoBy::recodeVar
,epicalc::recode
,memisc::recode
, aber ich habe nicht auf sie im Detail ... sahIch 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.
quelle
result <- (if (x==10 | y< 5) 'foo' else if (x==11 & y== 5) 'bar' )
. 2) Dies funktioniert nur, wennx
undy
Skalare sind; Für Vektorenifelse
wären wie in der ursprünglichen Frage verschachtelte Anweisungen erforderlich.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.Was folgt, ist ein einfaches Beispiel für eine Zeichenfolge, das Ihr Problem löst, alte Kategorien in neue zu reduzieren.
newCat <- switch(EXPR = category, cat1 = catX, cat2 = catX, cat3 = catY, cat4 = catY, cat5 = catZ, cat6 = catZ, "not available")
quelle
Wenn Sie eine SQL-ähnliche Syntax wünschen, können Sie einfach das
sqldf
Paket verwenden. Die zu verwendende Funktion sind auch Namensqldf
und die Syntax lautet wie folgtsqldf(<your query in quotation marks>)
quelle
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.
quelle
Mischen
plyr::mutate
unddplyr::case_when
funktioniert 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
Sie können die
base
Funktionmerge
fü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
quelle
Ab data.table v1.13.0 können Sie die Funktion
fcase()
(Fast-Case) verwenden, um SQL-ähnlicheCASE
Operationen auszuführen (auch ähnlich wiedplyr::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') ]
quelle