Faktoren in R: mehr als ein Ärger?

95

Einer der grundlegenden Datentypen in R sind Faktoren. Nach meiner Erfahrung sind Faktoren im Grunde genommen ein Schmerz und ich benutze sie nie. Ich konvertiere immer in Zeichen. Ich fühle mich seltsam, als würde mir etwas fehlen.

Gibt es einige wichtige Beispiele für Funktionen, die Faktoren als Gruppierungsvariablen verwenden, bei denen der Faktordatentyp erforderlich wird? Gibt es bestimmte Umstände , als ich sollte Faktoren verwenden?

JD Long
quelle
7
Ich füge diesen Kommentar für Anfänger-R-Benutzer hinzu, die diese Frage wahrscheinlich finden. Ich habe kürzlich einen Blog-Beitrag geschrieben, der einen Großteil der Informationen aus den folgenden Antworten in einem Tutorial zusammenfasst, in dem erläutert wird,
Ben
Ich war immer davon ausgegangen, dass Faktoren effizienter als Zeichen gespeichert wurden - als wäre jeder Eintrag ein Zeiger auf die Ebene. Aber als ich es testete, um es aufzuschreiben, fand ich heraus, dass das nicht stimmt!
Isomorphismen
2
@isomorphismes gut, das war früher wahr, in den früheren Tagen von R, aber das hat sich geändert. Siehe diesen Blog-Beitrag: simplystatistics.org/2015/07/24/…
MichaelChirico
4
5+ Jahre später wurde diese "stringsAsFactors: Eine nicht autorisierte Biographie" geschrieben: simplystatistics.org/2015/07/24/…
JD Long

Antworten:

49

Sie sollten Faktoren verwenden. Ja, sie können ein Schmerz sein, aber meine Theorie ist, dass 90% der Gründe, warum sie ein Schmerz sind , auf das Argument in read.tableund zurückzuführen read.csvsind stringsAsFactors = TRUE(und die meisten Benutzer vermissen diese Subtilität). Ich sage, sie sind nützlich, weil Modellanpassungspakete wie lme4 Faktoren und geordnete Faktoren verwenden, um Modelle unterschiedlich anzupassen und die Art der zu verwendenden Kontraste zu bestimmen. Grafikpakete verwenden sie auch zum Gruppieren nach. ggplotund die meisten Modellanpassungsfunktionen zwingen Zeichenvektoren zu Faktoren, sodass das Ergebnis dasselbe ist. Am Ende werden jedoch Warnungen in Ihrem Code angezeigt:

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  

Warnmeldung: In model.matrix.default(mt, mf, contrasts) :

Variable Specieskonvertiert in afactor

Eine schwierige Sache ist das ganze drop=TRUEStück. In Vektoren funktioniert dies gut, um Ebenen von Faktoren zu entfernen, die nicht in den Daten enthalten sind. Beispielsweise:

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

Jedoch , mit data.frames, der das Verhalten [.data.frame()ist anders: sehen Sie diese E - Mail oder ?"[.data.frame". Die Verwendung von drop=TRUEon data.frames funktioniert nicht so, wie Sie es sich vorstellen können:

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

Glücklicherweise können Sie Faktoren leicht löschen, indem Sie droplevels()nicht verwendete Faktorstufen für einen einzelnen Faktor oder für jeden Faktor in a data.frame(seit R 2.12) löschen:

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

Auf diese Weise verhindern Sie, dass von Ihnen ausgewählte Ebenen betreten werden ggplot Legenden geraten.

Intern sind factors Ganzzahlen mit einem Zeichenvektor auf Attributebene (siehe attributes(iris$Species)und class(attributes(iris$Species)$levels)), der sauber ist. Wenn Sie einen Ebenennamen ändern müssten (und Zeichenfolgen verwenden würden), wäre dies eine viel weniger effiziente Operation. Und ich ändere Level-Namen sehr, besonders für ggplotLegenden. Wenn Sie Faktoren mit Zeichenvektoren vortäuschen, besteht das Risiko, dass Sie nur ein Element ändern und versehentlich eine separate neue Ebene erstellen.

Vince
quelle
1
stringsAsFactorsist keine Funktion.
IRTFM
30

geordnete Faktoren sind fantastisch, wenn ich Orangen liebe und Äpfel hasse, aber nichts gegen Trauben habe, muss ich keinen seltsamen Index verwalten, um dies zu sagen:

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
quelle
Das ist eine nette Anwendung. Daran habe ich nie gedacht.
JD Long
Was haben die gemacht d$f <- ordered(d$f, c("apples", "grapes", "oranges"))? Ich hätte vermutet, dass es diese im Datenrahmen bestellt hat, aber nachdem ich diese Zeile ausgeführt und den Datenrahmen gedruckt habe, ändert sich nichts. Erzwingt es nur eine interne Bestellung, obwohl sich die gedruckte Bestellung nicht ändert?
Addem
... Ja, ich denke, was ich geschrieben habe, war so etwas wie ein korrekter Satz. Wenn ich Ihren Standpunkt verstehe, zeigen Sie uns, dass Sie Faktoren eine Reihenfolge zuweisen können, was Sie für Zeichenfolgen nicht tun können.
Addem
4
order () erstellt aus beliebigen Werten eine beliebige Reihenfolge - in der Reihenfolge, in der Sie sagen, dass sie geordnet sind. Es ist bedauerlich, dass ich lexikografisch sortierte Werte verwendet habe, das ist ein Zufall. Zum Beispiel verwende ich dies für Daten, bei denen "Z" schlecht ist, "3" gut ist, die Beschriftungen jedoch nicht numerisch oder alphabetisch sind - also ordne ich (Daten, c ("Z", "B", "A", ") 0 "," 1 "," 2 "," 3 ")) und dann kann ich einfach Daten>" A "machen und es sind glückliche Tage.
Mdsumner
19

A factorist am analogsten zu einem Aufzählungstyp in anderen Sprachen. Die geeignete Verwendung ist für eine Variable, die nur einen der vorgeschriebenen Werte annehmen kann. In diesen Fällen ist möglicherweise nicht jeder mögliche zulässige Wert in einem bestimmten Datensatz vorhanden, und die "leeren" Ebenen spiegeln dies genau wider.

Betrachten Sie einige Beispiele. Für einige Daten, die in den gesamten Vereinigten Staaten gesammelt wurden, sollte der Staat als Faktor erfasst werden. In diesem Fall ist die Tatsache relevant, dass keine Fälle aus einem bestimmten Staat gesammelt wurden. Es hätte Daten aus diesem Zustand geben können, aber es gab (aus welchem ​​Grund auch immer, was ein Grund von Interesse sein könnte) nicht. Wenn die Heimatstadt gesammelt würde, wäre dies kein Faktor. Es gibt keine vorgegebenen möglichen Heimatstädte. Wenn Daten nicht national, sondern aus drei Städten erhoben würden, wäre die Stadt ein Faktor: Zu Beginn wurden drei Auswahlmöglichkeiten getroffen, und wenn in einer dieser drei Städte keine relevanten Fälle / Daten gefunden wurden, ist dies relevant.

Andere Aspekte von factors, wie die Bereitstellung einer Möglichkeit, einer Reihe von Zeichenfolgen eine beliebige Sortierreihenfolge zuzuweisen, sind nützliche sekundäre Merkmale von factors, aber nicht der Grund für ihre Existenz.

Brian Diggs
quelle
3
+1. Brian, ich denke, Sie haben den Nagel auf den Kopf getroffen, indem Sie Ebenen erfasst haben, die nicht in den Daten enthalten sind.
Ricardo Saporta
13

Faktoren sind fantastisch, wenn man statistische Analysen durchführt und die Daten tatsächlich untersucht. Zuvor sind jedoch Faktoren beim Lesen, Bereinigen, Beheben von Problemen, Zusammenführen und allgemeinen Bearbeiten der Daten ein totaler Schmerz. In jüngerer Zeit, wie in den letzten Jahren, haben sich viele Funktionen verbessert, um die Faktoren besser handhaben zu können. Zum Beispiel spielt rbind gut mit ihnen. Ich finde es immer noch ein totales Ärgernis, nach einer Teilmengenfunktion leere Ebenen übrig zu haben.

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

Ich weiß, dass es einfach ist, die Ebenen eines Faktors neu zu codieren und die Beschriftungen neu auszurichten, und es gibt auch wunderbare Möglichkeiten, die Ebenen neu zu ordnen. Mein Gehirn kann sich einfach nicht an sie erinnern und ich muss es jedes Mal neu lernen, wenn ich es benutze. Die Neukodierung sollte viel einfacher sein als sie ist.

Die String-Funktionen von R sind recht einfach und logisch zu bedienen. Bei der Manipulation bevorzuge ich im Allgemeinen Zeichen gegenüber Faktoren.

Farrel
quelle
1
Haben Sie Beispiele für Statistikanalysen, bei denen Faktoren verwendet werden?
JD Long
3
Es gibt jetzt eine Base-R-Funktion droplevels(). Und die Faktoren werden standardmäßig nicht neu geordnet.
Ben Bolker
6

Was für ein bissiger Titel!

Ich glaube, viele Schätzfunktionen ermöglichen es Ihnen, Faktoren zu verwenden, um Dummy-Variablen einfach zu definieren ... aber ich verwende sie dafür nicht.

Ich benutze sie, wenn ich sehr große Zeichenvektoren mit wenigen einzigartigen Beobachtungen habe. Dies kann den Speicherverbrauch verringern, insbesondere wenn die Zeichenfolgen im Zeichenvektor länger sind.

PS: Ich scherze über den Titel. Ich habe deinen Tweet gesehen. ;-);

Joshua Ulrich
quelle
1
Sie verwenden sie also wirklich nur, um Speicherplatz zu sparen. Das macht Sinn.
JD Long
13
Na zumindest war es früher so ;-). Vor einigen R-Versionen wurde die Zeichenspeicherung jedoch neu geschrieben, um intern gehasht zu werden, sodass ein Teil dieses historischen Arguments jetzt nichtig ist. Dennoch sind Faktoren für die Gruppierung und Modellierung sehr nützlich.
Dirk Eddelbuettel
1
Demnach war ?factores R-2.6.0 und es heißt: "Ganzzahlige Werte werden in 4 Bytes gespeichert, während jeder Verweis auf eine Zeichenfolge einen Zeiger von 4 oder 8 Bytes benötigt." Würden Sie beim Konvertieren in Faktor Platz sparen, wenn die Zeichenfolge 8 Byte benötigt?
Joshua Ulrich
2
N <- 1000; a <- Probe (c ("a", "b", "c"), N, Ersetzen = WAHR); print (Objektgröße (a), Einheiten = "Kb"); print (object.size (Faktor (a)), Einheiten = "Kb"); 8 Kb 4,5 Kb, also scheint es immer noch etwas Platz zu sparen.
Eduardo Leoni
2
@ Eduardo Ich habe 4Kb vs 4.2Kb. Denn N=100000ich habe 391,5 Kb gegen 391,8 Kb. Der Faktor benötigt also wenig mehr Speicher.
Marek
1

Faktoren sind ein ausgezeichneter "Einzelfälle" -Ausweismotor. Ich habe dies viele Male schlecht nachgebildet, und trotz einiger Falten sind sie extrem stark.

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

Wenn es einen besseren Weg gibt, diese Aufgabe zu erledigen, würde ich sie gerne sehen, ich sehe diese Fähigkeit der factorDiskussion nicht.

mdsumner
quelle
-2

tapply (und aggregieren ) stützen sich auf Faktoren. Das Informations-Aufwand-Verhältnis dieser Funktionen ist sehr hoch.

Zum Beispiel können Sie in einer einzelnen Codezeile (der Aufruf, unten zu tippen ) den Durchschnittspreis für Diamanten nach Schnitt und Farbe ermitteln:

> 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
quelle
7
Dies ist kein gutes Beispiel, da all diese Beispiele auch mit Zeichenfolgen funktionieren würden.
Hadley