Eu tenho uma variável em um dataframe onde um dos campos normalmente tem 7 a 8 valores. Quero agrupar 3 ou 4 novas categorias em uma nova variável dentro do dataframe. Qual é a melhor abordagem?
Eu usaria uma instrução CASE se estivesse em uma ferramenta semelhante a SQL, mas não tivesse certeza de como atacar isso em R.
Qualquer ajuda que você puder fornecer será muito apreciada!
dput()
b) Você quer uma solução em base R, dplyr, data.table, tidyverse ...?Respostas:
case_when()
, que foi adicionado ao dplyr em maio de 2016, resolve esse problema de maneira semelhante amemisc::cases()
.Por exemplo:
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" ) )
Desde 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" ) )
fonte
.$
na frente de cada coluna..$
não é mais necessário. Na época em que essa resposta foi escrita originalmente, ela estava.switch
, permite criar uma sequência de expressões em vez de chaves para os casos.Dê uma olhada na
cases
função domemisc
pacote. Ele implementa a funcionalidade de caso com duas maneiras diferentes de usá-la. Dos exemplos do pacote:z1=cases( "Condition 1"=x<0, "Condition 2"=y<0,# only applies if x >= 0 "Condition 3"=TRUE )
onde
x
ey
são dois vetores.Referências: pacote memisc , exemplo de casos
fonte
Se você conseguiu
factor
, você pode alterar os níveis pelo método padrão: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
Você pode escrever uma função simples como um 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"))
fonte
x
na última linhachangelevels
?Esta é uma maneira de usar a
switch
instrução: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
A única desvantagem disso é que você precisa continuar escrevendo o nome da categoria (
animal
etc.) para cada item. É sintaticamente mais conveniente ser capaz de definir nossas categorias como a seguir (veja a pergunta muito semelhante Como adicionar uma coluna em um quadro de dados em R )myMap <- list(animal = c('cow', 'pig'), bird = c('eagle', 'pigeon'))
e queremos de alguma forma "inverter" esse mapeamento. Eu escrevo minha própria função invMap:
invMap <- function(map) { items <- as.character( unlist(map) ) nams <- unlist(Map(rep, names(map), sapply(map, length))) names(nams) <- items nams }
e, em seguida, inverta o mapa acima da seguinte maneira:
> invMap(myMap) cow pig eagle pigeon "animal" "animal" "bird" "bird"
E então é fácil usar isso para adicionar a
type
coluna no data-frame:df <- transform(df, type = invMap(myMap)[name]) > df name type 1 cow animal 2 pig animal 3 eagle bird 4 pigeon bird
fonte
Não vejo nenhuma proposta para 'troca'. Exemplo de código (execute-o):
x <- "three" y <- 0 switch(x, one = {y <- 5}, two = {y <- 12}, three = {y <- 432}) y
fonte
Imho, código mais simples e universal:
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' })
fonte
y = 'else'
. Os elementos que não atendem a nenhuma outra condição permanecerão inalterados.Existe uma
switch
declaração, mas nunca consigo fazê-la funcionar da maneira que acho que deveria. Como você não forneceu um exemplo, farei um usando uma variável de fator:dft <-data.frame(x = sample(letters[1:8], 20, replace=TRUE)) levels(dft$x) [1] "a" "b" "c" "d" "e" "f" "g" "h"
Se você especificar as categorias que deseja em uma ordem apropriada para a reatribuição, você pode usar o fator ou as variáveis numéricas como um índice:
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" ...
Mais tarde, aprendi que realmente existem duas funções de switch diferentes. Não é uma função genérica, mas você deve pensar nela como
switch.numeric
ouswitch.character
. Se seu primeiro argumento for um 'fator' R, você obterá umswitch.numeric
comportamento, que provavelmente causará problemas, uma vez que a maioria das pessoas vê os fatores exibidos como caracteres e faz a suposição incorreta de que todas as funções os processarão como tais.fonte
Você pode usar recode do pacote do carro:
library(ggplot2) #get data library(car) daimons$new_var <- recode(diamonds$clarity , "'I1' = 'low';'SI2' = 'low';else = 'high';")[1:10]
fonte
sos::findFn("recode")
achadosdoBy::recodeVar
,epicalc::recode
,memisc::recode
, mas eu não olhei para eles em detalhes ...Eu não gosto de nenhum desses, eles não são claros para o leitor ou o usuário potencial. Eu apenas uso uma função anônima, a sintaxe não é tão engenhosa quanto uma declaração de caso, mas a avaliação é semelhante a uma declaração de caso e não é tão dolorosa. isso também pressupõe que você o avalie dentro de onde suas variáveis são definidas.
result <- ( function() { if (x==10 | y< 5) return('foo') if (x==11 & y== 5) return('bar') })()
todos esses () são necessários para incluir e avaliar a função anônima.
fonte
result <- (if (x==10 | y< 5) 'foo' else if (x==11 & y== 5) 'bar' )
. 2) Isso só funciona sex
ey
forem escalares; para vetores, como na pergunta original,ifelse
instruções aninhadas seriam necessárias.Estou usando nesses casos que você está se referindo
switch()
. Parece uma instrução de controle, mas na verdade, é uma função. A expressão é avaliada e com base neste valor, o item correspondente na lista é retornado.O que se segue é um exemplo de string simples que resolve seu problema de transformar categorias antigas em novas.
newCat <- switch(EXPR = category, cat1 = catX, cat2 = catX, cat3 = catY, cat4 = catY, cat5 = catZ, cat6 = catZ, "not available")
fonte
Se você quiser ter uma sintaxe semelhante ao sql, você pode apenas usar o
sqldf
pacote. A função a ser usada também é nomessqldf
e a sintaxe é a seguintesqldf(<your query in quotation marks>)
fonte
Uma declaração de caso, na verdade, pode não ser a abordagem certa aqui. Se este for um fator, o que provavelmente é, apenas defina os níveis do fator de forma adequada.
Digamos que você tenha um fator com as letras de A a E, assim.
> 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
Para ingressar nos níveis B e C e nomeá-lo BC, basta alterar os nomes desses níveis para 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
O resultado é o desejado.
fonte
Mistura
plyr::mutate
edplyr::case_when
funciona para mim e é legível.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]
Pontos de bônus se a coluna puder sair da mutação como um fator ao invés de char! A última linha da instrução case_when, que captura todas as linhas não correspondidas, é muito 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
fonte
Você pode usar a
base
funçãomerge
para tarefas de remapeamento de estilo de caso: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
fonte
A partir de data.table v1.13.0, você pode usar a função
fcase()
(caso rápido) para fazerCASE
operações semelhantes a SQL (também semelhantes adplyr::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') ]
fonte