web-dev-qa-db-fra.com

Affirmation de cas équivalente en R

J'ai une variable dans un cadre de données où l'un des champs a généralement 7-8 valeurs. Je souhaite les regrouper en 3 ou 4 nouvelles catégories dans une nouvelle variable du cadre de données. Quelle est la meilleure approche?

J'utiliserais une instruction CASE si j'étais dans un outil semblable à SQL mais je ne savais pas comment attaquer cela dans R.

Toute aide que vous pouvez fournir sera très appréciée!

69
Btibert3

Jetez un coup d'œil à la fonction cases du paquetage memisc. Il implémente la fonctionnalité de cas avec deux manières différentes de l’utiliser. Parmi les exemples dans le paquet:

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

x et y sont deux vecteurs.

Références: paquet memisc , exemple de cas

26
Henrico

case_when(), ajouté à dplyr en mai 2016, résout ce problème d'une manière similaire à memisc::cases().

Par exemple:

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

À partir de 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"
  )
)
25
Evan Cortens

Si vous avez factor alors vous pouvez changer les niveaux par la méthode standard:

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

Vous pouvez écrire une fonction simple en tant que wrapper:

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

Voici un moyen d'utiliser l'instruction switch:

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

Le seul inconvénient est que vous devez continuer à écrire le nom de la catégorie (animal, etc.) pour chaque élément. Il est syntaxiquement plus pratique de pouvoir définir nos catégories comme ci-dessous (voir la question très similaire Comment ajouter une colonne dans un bloc de données dans R )

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

et nous voulons en quelque sorte "inverser" cette cartographie. J'écris ma propre fonction invMap:

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

puis inversez la carte ci-dessus comme suit:

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

Et ensuite, il est facile d'utiliser ceci pour ajouter la colonne type dans le cadre de données:

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

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

À mon humble avis, code le plus simple et le plus universel:

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'
})
14
Gregory Demin

Je ne vois aucune proposition de "commutateur". Exemple de code (exécutez-le):

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

Il y a une déclaration switch mais je n'arrive jamais à la faire fonctionner comme je le pense. Puisque vous n'avez pas fourni d'exemple, je vais en créer un en utilisant une variable factorielle:

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

Si vous spécifiez les catégories souhaitées dans un ordre approprié à la réaffectation, vous pouvez utiliser les variables factorielles ou numériques comme index:

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

J'ai appris par la suite qu'il existe vraiment deux fonctions de commutation différentes. Ce n'est pas une fonction générique, mais vous devriez y penser soit switch.numeric ou switch.character. Si votre premier argument est un "facteur R", vous obtenez switch.numeric comportement, ce qui est susceptible de poser problème, car la plupart des gens voient les facteurs affichés sous forme de caractère et supposent de manière incorrecte que toutes les fonctions les traiteront comme tels.

7
42-

Vous pouvez utiliser recode à partir du package voiture:

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

je n'aime aucun de ceux-ci, ils ne sont pas clairs pour le lecteur ou l'utilisateur potentiel. Je viens d'utiliser une fonction anonyme, la syntaxe n'est pas aussi lisse qu'une déclaration de cas, mais l'évaluation est similaire à une déclaration de cas et pas si pénible. cela suppose également que vous l’évaluiez dans l’endroit où vos variables sont définies.

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

tous ces éléments () sont nécessaires pour inclure et évaluer la fonction anonyme.

5
jamesM

Mélange plyr::mutate et dplyr::case_when fonctionne pour moi et est lisible.

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]

Des points bonus si la colonne peut sortir de la mutation en tant que facteur au lieu de char! La dernière ligne de l'instruction case_when, qui capture toutes les lignes sans correspondance, est très importante.

     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

J'utilise dans les cas où vous vous référez switch(). Cela ressemble à une déclaration de contrôle, mais en réalité, c'est une fonction. L'expression est évaluée et, en fonction de cette valeur, l'élément correspondant dans la liste est renvoyé.

switch fonctionne de deux manières différentes selon que le premier argument est évalué à une chaîne de caractères ou à un nombre.

Ce qui suit est un exemple de chaîne simple qui résout votre problème de réduire les anciennes catégories à de nouvelles.

Pour la forme chaîne de caractères, utilisez un seul argument non nommé après les valeurs nommées.

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

Vous pouvez utiliser la fonction basemerge pour les tâches de remappage de type cas:

df <- data.frame(name = c('cow','pig','eagle','pigeon','cow','eagle'), 
                 stringsAsFactors = FALSE)

mapping <- data.frame(
  name=c('cow','pig','eagle','pigeon'),
  category=c('animal','animal','bird','bird')
)

merge(df,mapping)
# name category
# 1    cow   animal
# 2    cow   animal
# 3  eagle     bird
# 4  eagle     bird
# 5    pig   animal
# 6 pigeon     bird
2
patrickmdnet

Si vous voulez avoir une syntaxe de type SQL, vous pouvez simplement utiliser le paquetage sqldf. La fonction à utiliser porte également le nom sqldf et sa syntaxe est la suivante

sqldf(<your query in quotation marks>)
2
kuba

Une déclaration de cas pourrait en fait ne pas être la bonne approche ici. S'il s'agit d'un facteur probable, définissez simplement les niveaux du facteur de manière appropriée.

Supposons que vous ayez un facteur avec les lettres A à E, comme ceci.

> 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

Pour rejoindre les niveaux B et C et le nommer BC, il suffit de changer le nom de ces niveaux en 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

Le résultat est comme souhaité.

2
Aaron