Fatores em R: mais que um aborrecimento?

95

Um dos tipos de dados básicos em R são os fatores. Na minha experiência, os fatores são basicamente uma dor e eu nunca os uso. Sempre me converto em personagens. Eu me sinto estranhamente como se estivesse faltando alguma coisa.

Existem alguns exemplos importantes de funções que usam fatores como variáveis ​​de agrupamento onde o tipo de dados do fator se torna necessário? Existem circunstâncias específicas em que devo usar fatores?

JD Long
fonte
7
Estou adicionando este comentário para usuários R iniciantes que provavelmente encontrarão esta pergunta. Recentemente, escrevi uma postagem no blog que compila muitas das informações das respostas abaixo em um tutorial instrutivo sobre quando, como e por que usar os fatores em R. gormanalysis.com/?p=115
Ben
Sempre presumi que os fatores eram armazenados com mais eficiência do que os caracteres - como se cada entrada fosse um indicador para o nível. Mas ao testar para escrever isso, descobri que não é verdade!
isomorfismos
2
@isomorfismos bem, isso costumava ser verdade, nos primeiros dias de R, mas isso mudou. Veja esta postagem do blog: Simplystatistics.org/2015/07/24/…
MichaelChirico
4
5+ anos depois, este "stringsAsFactors: Uma biografia não autorizada" foi escrito: Simplystatistics.org/2015/07/24/…
JD Long

Respostas:

49

Você deve usar fatores. Sim, eles podem ser uma dor, mas minha teoria é que 90% da razão pela qual eles são uma dor é porque em read.tablee read.csv, o argumento stringsAsFactors = TRUEpor padrão (e a maioria dos usuários não percebe essa sutileza). Eu digo que eles são úteis porque os pacotes de ajuste de modelo como o lme4 usam fatores e fatores ordenados para ajustar os modelos de forma diferenciada e determinar o tipo de contraste a ser usado. E os pacotes de gráficos também os usam para agrupar. ggplote a maioria das funções de ajuste de modelo coage os vetores de caracteres a fatores, de modo que o resultado é o mesmo. No entanto, você acaba com avisos em seu código:

lm(Petal.Length ~ -1 + Species, data=iris)

# Call:
# lm(formula = Petal.Length ~ -1 + Species, data = iris)

# Coefficients:
#     Speciessetosa  Speciesversicolor   Speciesvirginica  
#             1.462              4.260              5.552  

iris.alt <- iris
iris.alt$Species <- as.character(iris.alt$Species)
lm(Petal.Length ~ -1 + Species, data=iris.alt)

# Call:
# lm(formula = Petal.Length ~ -1 + Species, data = iris.alt)

# Coefficients:
#     Speciessetosa  Speciesversicolor   Speciesvirginica  
#             1.462              4.260              5.552  

Mensagem de aviso: Em model.matrix.default(mt, mf, contrasts):

variável Speciesconvertida em umfactor

Uma coisa complicada é a parte inteira drop=TRUE. Em vetores, isso funciona bem para remover níveis de fatores que não estão nos dados. Por exemplo:

s <- iris$Species
s[s == 'setosa', drop=TRUE]
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa
s[s == 'setosa', drop=FALSE]
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa versicolor virginica

Porém , com data.frames, o comportamento de [.data.frame()é diferente: consulte este e-mail ou ?"[.data.frame". Usar drop=TRUEno data.frames não funciona como você imagina:

x <- subset(iris, Species == 'setosa', drop=TRUE)  # susbetting with [ behaves the same way
x$Species
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa versicolor virginica

Felizmente, você pode eliminar fatores facilmente com droplevels()a redução dos níveis de fator não utilizados para um fator individual ou para cada fator em a data.frame(desde R 2.12):

x <- subset(iris, Species == 'setosa')
levels(x$Species)
# [1] "setosa"     "versicolor" "virginica" 
x <- droplevels(x)
levels(x$Species)
# [1] "setosa"

É assim que você evita que os níveis selecionados cheguem às ggplotlendas.

Internamente, factors são inteiros com um vetor de caracteres de nível de atributo (consulte attributes(iris$Species)e class(attributes(iris$Species)$levels)), que é limpo. Se você tivesse que alterar o nome de um nível (e estivesse usando cadeias de caracteres), esta seria uma operação muito menos eficiente. E eu mudo muito os nomes dos níveis, especialmente para ggplotlendas. Se você falsificar fatores com vetores de personagens, existe o risco de alterar apenas um elemento e, acidentalmente, criar um novo nível separado.

Vince
fonte
1
stringsAsFactorsnão é uma função.
IRTFM
30

fatores ordenados são fantásticos, se por acaso eu amo laranjas e odeio maçãs, mas não me importo com uvas, não preciso gerenciar algum índice estranho para dizer isso:

d <- data.frame(x = rnorm(20), f = sample(c("apples", "oranges", "grapes"), 20, replace = TRUE, prob = c(0.5, 0.25, 0.25)))
d$f <- ordered(d$f, c("apples", "grapes", "oranges"))
d[d$f >= "grapes", ]
mdsumner
fonte
esse é um aplicativo bacana. Nunca pensei nisso.
JD Long de
O que você fez d$f <- ordered(d$f, c("apples", "grapes", "oranges"))? Eu teria imaginado que ele ordenou isso no quadro de dados, mas depois que executo essa linha e imprimo o quadro de dados, nada muda. Ele apenas impõe uma ordem interna, embora a ordem impressa não mude?
Addem
... Sim, acho que o que escrevi foi algo como uma frase correta. Se eu entendi seu ponto, você está nos mostrando que pode atribuir uma ordem aos fatores, o que é algo que você não pode fazer para as cordas.
Addem
4
order () cria uma ordem arbitrária de quaisquer valores - na ordem em que você diz que eles estão ordenados. É uma pena que usei valores ordenados lexicograficamente, isso é uma coincidência. Por exemplo, eu uso isso para dados onde "Z" é ruim, "3" é bom, mas os rótulos não são numéricos ou alfabéticos - então eu ordeno (dados, c ("Z", "B", "A", " 0 "," 1 "," 2 "," 3 ")) e então eu posso apenas fazer dados>" A "e são dias felizes.
mdsumner
19

A factoré mais análogo a um tipo enumerado em outras línguas. Seu uso apropriado é para uma variável que só pode assumir um dos conjuntos de valores prescritos. Nesses casos, nem todos os valores permitidos possíveis podem estar presentes em qualquer conjunto específico de dados e os níveis "vazios" refletem isso com precisão.

Considere alguns exemplos. Para alguns dados coletados em todos os Estados Unidos, o estado deve ser registrado como um fator. Nesse caso, o fato de nenhum caso ter sido coletado de um determinado estado é relevante. Poderia haver dados daquele estado, mas aconteceu (por qualquer motivo, que pode ser um motivo de interesse) não haver. Se a cidade natal fosse coletada, não seria um fator. Não há um conjunto pré-estabelecido de possíveis cidades natais. Se os dados fossem coletados de três cidades ao invés de nacionalmente, a cidade seria um fator: há três escolhas que foram dadas no início e se nenhum caso / dado relevante foi encontrado em uma dessas três cidades, isso é relevante.

Outros aspectos de factors, como fornecer uma maneira de dar uma ordem de classificação arbitrária a um conjunto de strings, são características secundárias úteis de factors, mas não são a razão de sua existência.

Brian Diggs
fonte
3
+1. Brian, acho que você acertou em cheio ao capturar níveis não presentes nos dados.
Ricardo Saporta,
13

Os fatores são fantásticos quando se está fazendo análises estatísticas e realmente explorando os dados. No entanto, antes disso, quando alguém está lendo, limpando, solucionando problemas, mesclando e geralmente manipulando os dados, os fatores são uma dor total. Mais recentemente, como nos últimos anos, muitas funções foram aprimoradas para lidar melhor com os fatores. Por exemplo, rbind funciona bem com eles. Ainda acho um aborrecimento total ter deixado níveis vazios após uma função de subconjunto.

#drop a whole bunch of unused levels from a whole bunch of columns that are factors using gdata
require(gdata)
drop.levels(dataframe)

Eu sei que é simples recodificar os níveis de um fator e reorganizar os rótulos, e também há maneiras maravilhosas de reorganizar os níveis. Meu cérebro simplesmente não consegue se lembrar deles e tenho que reaprendê-lo toda vez que o uso. A recodificação deve ser muito mais fácil do que é.

As funções string de R são muito fáceis e lógicas de usar. Portanto, quando estou manipulando, geralmente prefiro personagens a fatores.

Farrel
fonte
1
Você tem exemplos de análises estatísticas que usam fatores?
JD Long de
3
existe agora uma função de base-R droplevels(). E não reordena os fatores por padrão.
Ben Bolker,
6

Que título sarcástico!

Acredito que muitas funções de estimativa permitem que você use fatores para definir facilmente variáveis ​​dummy ... mas não os uso para isso.

Eu os uso quando tenho vetores de caracteres muito grandes com poucas observações exclusivas. Isso pode reduzir o consumo de memória, especialmente se as strings no vetor de caracteres forem mais longas.

PS - Estou brincando sobre o título. Eu vi seu tweet. ;-)

Joshua Ulrich
fonte
1
Então, você realmente só os usa para economizar espaço de armazenamento. Isso faz sentido.
JD Long de
13
Bem, pelo menos costumava ;-). Mas algumas versões R atrás, o armazenamento de caracteres foi reescrito para ter um hash interno, então parte desse argumento histórico agora é nulo. Fatores ainda são muito úteis para agrupamento e modelagem.
Dirk Eddelbuettel de
1
De acordo com ?factorele, era R-2.6.0 e diz: "Os valores inteiros são armazenados em 4 bytes, ao passo que cada referência a uma string de caracteres precisa de um ponteiro de 4 ou 8 bytes." Você economizaria espaço convertendo em fator se a string de caracteres precisasse de 8 bytes?
Joshua Ulrich
2
N <- 1000; a <- amostra (c ("a", "b", "c"), N, substituir = TRUE); imprimir (object.size (a), unidades = "Kb"); imprimir (tamanho do objeto (fator (a)), unidades = "Kb"); 8 Kb 4,5 Kb, então ainda parece economizar espaço.
Eduardo Leoni
2
@Eduardo eu tenho 4Kb vs 4,2Kb. Pois N=100000eu tenho 391,5 Kb vs 391,8 Kb. Portanto, o fator ocupa um pouco mais de memória.
Marek de
1

Fatores são um excelente mecanismo de crachá de "caixas exclusivas". Eu recriei mal isso muitas vezes e, apesar de algumas rugas ocasionalmente, elas são extremamente poderosas.

library(dplyr)
d <- tibble(x = sample(letters[1:10], 20, replace = TRUE))

## normalize this table into an indexed value across two tables
id <- tibble(x_u = sort(unique(d$x))) %>% mutate(x_i = row_number())
di <- tibble(x_i = as.integer(factor(d$x)))


## reconstruct d$x when needed
d2 <- inner_join(di, id) %>% transmute(x = x_u)
identical(d, d2)
## [1] TRUE

Se houver uma maneira melhor de fazer essa tarefa, eu adoraria ver, não vejo essa capacidade de factor discutida.

mdsumner
fonte
-2

tapply (e agregar ) dependem de fatores. A relação informação-esforço dessas funções é muito alta.

Por exemplo, em uma única linha de código (a chamada para tocar abaixo), você pode obter o preço médio dos diamantes por corte e cor:

> data(diamonds, package="ggplot2")

> head(dm)

   Carat     Cut    Clarity Price Color
1  0.23     Ideal     SI2   326     E
2  0.21   Premium     SI1   326     E
3  0.23      Good     VS1   327     E


> tx = with(diamonds, tapply(X=Price, INDEX=list(Cut=Cut, Color=Color), FUN=mean))

> a = sort(1:diamonds(tx)[2], decreasing=T)  # reverse columns for readability

> tx[,a]

         Color
Cut         J    I    H    G    F    E    D
Fair      4976 4685 5136 4239 3827 3682 4291
Good      4574 5079 4276 4123 3496 3424 3405
Very Good 5104 5256 4535 3873 3779 3215 3470
Premium   6295 5946 5217 4501 4325 3539 3631
Ideal     4918 4452 3889 3721 3375 2598 2629
doug
fonte
7
Este não é um bom exemplo, porque todos esses exemplos funcionariam com strings também.
hadley