Fattori in R: più di un fastidio?


95

Uno dei tipi di dati di base in R sono i fattori. Nella mia esperienza i fattori sono fondamentalmente un dolore e non li uso mai. Mi converto sempre in personaggi. Mi sento stranamente come se mi mancasse qualcosa.

Esistono alcuni esempi importanti di funzioni che utilizzano i fattori come variabili di raggruppamento in cui il tipo di dati del fattore diventa necessario? Ci sono circostanze specifiche in cui dovrei usare i fattori?


7
Sto aggiungendo questo commento per gli utenti R principianti che probabilmente troveranno questa domanda. Recentemente ho scritto un post sul blog che raccoglie molte delle informazioni dalle risposte seguenti in un tutorial didattico su quando, come e perché utilizzare i fattori in R. gormanalysis.com/?p=115
Ben

Avevo sempre pensato che i fattori fossero memorizzati in modo più efficiente dei caratteri, come se ogni voce fosse un puntatore al livello. Ma provandolo per scriverlo, ho scoperto che non è vero!
isomorfismi

2
@isomorfismi bene, questo era vero, nei primi giorni di R, ma è cambiato. Vedi questo post del blog: simplystatistics.org/2015/07/24/...
MichaelChirico

4
Più di 5 anni dopo è stata scritta questa "stringsAsFactors: An unauthorized Biography": simplystatistics.org/2015/07/24/…
JD Long

Risposte:


49

Dovresti usare i fattori. Sì, possono essere un dolore, ma la mia teoria è che il 90% del motivo per cui sono un dolore è perché in read.tablee read.csv, l'argomento stringsAsFactors = TRUEper impostazione predefinita (e la maggior parte degli utenti perde questa sottigliezza). Dico che sono utili perché i pacchetti di adattamento del modello come lme4 utilizzano fattori e fattori ordinati per adattare i modelli in modo differenziale e determinare il tipo di contrasti da utilizzare. E i pacchetti grafici li usano anche per raggruppare. ggplote la maggior parte delle funzioni di adattamento del modello obbliga i vettori di caratteri a fattori, quindi il risultato è lo stesso. Tuttavia, ti ritroverai con avvisi nel codice:

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  

Messaggio di avviso: In model.matrix.default(mt, mf, contrasts):

variabile Speciesconvertita in un filefactor

Una cosa complicata è l'intera parte drop=TRUE. Nei vettori questo funziona bene per rimuovere i livelli di fattori che non sono nei dati. Per esempio:

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

Tuttavia , con data.frames, il comportamento di [.data.frame()è diverso: vedi questa email o ?"[.data.frame". Utilizzando drop=TRUEil data.frames non funziona come ci si immagina:

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

Fortunatamente puoi eliminare facilmente i fattori droplevels()per eliminare i livelli di fattore inutilizzati per un singolo fattore o per ogni fattore in a data.frame(da R 2.12):

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

Ecco come mantenere i livelli che hai selezionato per entrare nelle ggplotleggende.

Internamente, factors sono numeri interi con un vettore di caratteri a livello di attributo (vedere attributes(iris$Species)e class(attributes(iris$Species)$levels)), che è pulito. Se dovessi cambiare il nome di un livello (e stavi usando stringhe di caratteri), questa sarebbe un'operazione molto meno efficiente. E cambio molto i nomi dei livelli, soprattutto per le ggplotleggende. Se fingi fattori con vettori di caratteri, c'è il rischio che cambierai solo un elemento e crei accidentalmente un nuovo livello separato.


1
stringsAsFactorsnon è una funzione.
IRTFM

30

i fattori ordinati sono fantastici, se mi capita di amare le arance e odiare le mele ma non mi importa dell'uva, non ho bisogno di gestire qualche strano indice per dirlo:

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", ]

questa è una bella applicazione. Non ci ho mai pensato.
JD Long

Cosa hai d$f <- ordered(d$f, c("apples", "grapes", "oranges"))fatto? Avrei immaginato che li avesse ordinati nel data frame, ma dopo aver eseguito quella riga e stampato il data frame, non cambia nulla. Impone solo un ordine interno anche se l'ordine stampato non cambia?
Addem

... Sì, penso che quello che ho scritto fosse qualcosa come una frase corretta. Se capisco il tuo punto, ci stai dimostrando che puoi assegnare un ordine sui fattori, cosa che non puoi fare per le stringhe.
Addem

4
ordinato () crea un ordinamento arbitrario da qualsiasi valore, nell'ordine in cui dici che sono ordinati. È un peccato che abbia usato valori ordinati lessicograficamente, è una coincidenza. Ad esempio, lo uso per i dati in cui "Z" è errato, "3" è buono ma le etichette non sono numeriche o alfabetiche, quindi ordino (dati, c ("Z", "B", "A", " 0 "," 1 "," 2 "," 3 ")) e quindi posso semplicemente fare dati>" A "ed è un giorno felice.
mdsumner

19

A factorè molto simile a un tipo enumerato in altre lingue. Il suo uso appropriato è per una variabile che può assumere solo uno dei set di valori prescritti. In questi casi, non tutti i possibili valori consentiti possono essere presenti in un particolare insieme di dati ei livelli "vuoti" lo riflettono accuratamente.

Considera alcuni esempi. Per alcuni dati raccolti in tutti gli Stati Uniti, lo stato dovrebbe essere registrato come fattore. In questo caso, il fatto che non siano stati raccolti casi da un particolare stato è rilevante. Potevano esserci dati da quello stato, ma è accaduto (per qualsiasi motivo, che potrebbe essere un motivo di interesse) di non esserlo. Se la città natale fosse raccolta, non sarebbe un fattore. Non esiste una serie prestabilita di possibili città natale. Se i dati fossero raccolti da tre città piuttosto che a livello nazionale, la città sarebbe un fattore: ci sono tre scelte che sono state date all'inizio e se non sono stati trovati casi / dati rilevanti in una di queste tre città, ciò è rilevante.

Altri aspetti di factors, come fornire un modo per dare un ordinamento arbitrario a un insieme di stringhe, sono utili caratteristiche secondarie di factors, ma non sono la ragione della loro esistenza.


3
+1. Brian, penso che tu abbia colto nel segno con l'acquisizione di livelli non presenti nei dati.
Ricardo Saporta

13

I fattori sono fantastici quando si eseguono analisi statistiche e si esplorano effettivamente i dati. Tuttavia, prima che si leggano, si puliscono, si risolvono i problemi, si fondono e generalmente si manipolano i dati, i fattori sono un dolore totale. Più recentemente, come negli ultimi anni, molte funzioni sono migliorate per gestire meglio i fattori. Ad esempio, rbind suona bene con loro. Trovo ancora che sia un fastidio totale aver lasciato livelli vuoti dopo una funzione di sottoinsieme.

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

So che è semplice ricodificare i livelli di un fattore e modificare le etichette e ci sono anche modi meravigliosi per riordinare i livelli. Il mio cervello non riesce a ricordarli e devo impararlo di nuovo ogni volta che lo uso. La ricodifica dovrebbe essere molto più semplice di quanto non sia.

Le funzioni di stringa di R sono abbastanza facili e logiche da usare. Quindi quando manipolo di solito preferisco i personaggi ai fattori.


1
Hai esempi di analisi delle statistiche che utilizzano fattori?
JD Long

3
ora c'è una funzione base-R droplevels(). E non riordina i fattori per impostazione predefinita.
Ben Bolker

6

Che titolo irriverente!

Credo che molte funzioni di stima ti consentano di utilizzare fattori per definire facilmente variabili fittizie ... ma non le uso per questo.

Li uso quando ho vettori di caratteri molto grandi con poche osservazioni uniche. Ciò può ridurre il consumo di memoria, soprattutto se le stringhe nel vettore di caratteri sono più lunghe.

PS - Scherzo sul titolo. Ho visto il tuo tweet. ;-)


1
Quindi li usi davvero solo per risparmiare spazio di archiviazione. Questo ha senso.
JD Long

13
Beh, almeno lo era ;-). Ma alcune versioni R fa l'archiviazione dei caratteri è stata riscritta per essere hash internamente, quindi parte di questo argomento storico è ora vuoto. I fattori ancora sono molto utili per raggruppare e modellare.
Dirk Eddelbuettel

1
Secondo ?factoresso era R-2.6.0 e dice, "I valori interi sono memorizzati in 4 byte mentre ogni riferimento a una stringa di caratteri necessita di un puntatore di 4 o 8 byte." Risparmieresti spazio nella conversione in factor se la stringa di caratteri avesse bisogno di 8 byte?
Joshua Ulrich

2
N <- 1000; a <- campione (c ("a", "b", "c"), N, sostituisci = TRUE); print (object.size (a), units = "Kb"); print (object.size (factor (a)), units = "Kb"); 8 Kb 4,5 Kb quindi sembra ancora risparmiare un po 'di spazio.
Eduardo Leoni

2
@Eduardo ho 4Kb vs 4.2Kb. Perché N=100000ho 391,5 Kb contro 391,8 Kb. Quindi il fattore richiede poca memoria in più.
Marek

1

I fattori sono un eccellente motore per badge "casi unici". L'ho ricreato male molte volte e, nonostante un paio di rughe occasionalmente, sono estremamente potenti.

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 c'è un modo migliore per svolgere questo compito mi piacerebbe vederlo, non vedo questa capacità di factordiscussione.


-2

tapply (e aggregate ) si basano su fattori. Il rapporto tra informazione e impegno di queste funzioni è molto alto.

Ad esempio, in una singola riga di codice (la chiamata a tapply di seguito) puoi ottenere il prezzo medio dei diamanti per Taglio e Colore:

> 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

7
Questo non è un buon esempio, perché tutti quegli esempi funzionerebbero anche con le stringhe.
Hadley
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.