Ich habe einen Datenrahmen, der einen Faktor enthält. Wenn ich mit subset()
oder einer anderen Indizierungsfunktion eine Teilmenge dieses Datenrahmens erstelle, wird ein neuer Datenrahmen erstellt. Die Faktorvariable behält jedoch alle ursprünglichen Werte bei, selbst wenn sie im neuen Datenrahmen nicht vorhanden sind.
Dies bereitet Kopfschmerzen beim Facetten-Plotten oder beim Verwenden von Funktionen, die auf Faktorstufen basieren.
Was ist der präziseste Weg, um Ebenen aus einem Faktor in meinem neuen Datenrahmen zu entfernen?
Hier ist mein Beispiel:
df <- data.frame(letters=letters[1:5],
numbers=seq(1:5))
levels(df$letters)
## [1] "a" "b" "c" "d" "e"
subdf <- subset(df, numbers <= 3)
## letters numbers
## 1 a 1
## 2 b 2
## 3 c 3
## but the levels are still there!
levels(subdf$letters)
## [1] "a" "b" "c" "d" "e"
Alles, was Sie tun müssen, ist, factor () nach dem Subsetting erneut auf Ihre Variable anzuwenden:
> subdf$letters
[1] a b c
Levels: a b c d e
subdf$letters <- factor(subdf$letters)
> subdf$letters
[1] a b c
Levels: a b c
EDIT
Aus dem Beispiel für die Faktorseite:
factor(ff) # drops the levels that do not occur
Zum Entfernen von Ebenen aus allen Faktorspalten in einem Datenrahmen können Sie Folgendes verwenden:
subdf <- subset(df, numbers <= 3)
subdf[] <- lapply(subdf, function(x) if(is.factor(x)) factor(x) else x)
Seit der R-Version 2.12 gibt es eine droplevels()
-Funktion.
levels(droplevels(subdf$letters))
Wenn Sie dieses Verhalten nicht wünschen, verwenden Sie keine Faktoren, sondern verwenden Sie stattdessen Zeichenvektoren. Ich denke, das macht mehr Sinn, als die Sachen nachträglich zu patchen. Versuchen Sie Folgendes, bevor Sie Ihre Daten mit read.table
oder read.csv
laden:
options(stringsAsFactors = FALSE)
Der Nachteil ist, dass Sie auf die alphabetische Reihenfolge beschränkt sind. (Nachbestellung ist dein Freund für Handlungen)
Dies ist ein bekanntes Problem, und eine mögliche Abhilfe bietet drop.levels()
im Paket gdata , aus dem Ihr Beispiel wird
> drop.levels(subdf)
letters numbers
1 a 1
2 b 2
3 c 3
> levels(drop.levels(subdf)$letters)
[1] "a" "b" "c"
Es gibt auch die Funktion dropUnusedLevels
im Paket Hmisc . Es funktioniert jedoch nur durch Ändern des Subsetoperators [
und ist hier nicht anwendbar.
Als Folgesatz ist ein direkter Ansatz auf Spaltenbasis eine einfache as.factor(as.character(data))
:
> levels(subdf$letters)
[1] "a" "b" "c" "d" "e"
> subdf$letters <- as.factor(as.character(subdf$letters))
> levels(subdf$letters)
[1] "a" "b" "c"
Eine andere Möglichkeit, das gleiche zu tun, jedoch mit dplyr
library(dplyr)
subdf <- df %>% filter(numbers <= 3) %>% droplevels()
str(subdf)
Bearbeiten:
Funktioniert auch! Dank an agenis
subdf <- df %>% filter(numbers <= 3) %>% droplevels
levels(subdf$letters)
Hier ist ein anderer Weg, der meiner Meinung nach dem Ansatz von factor(..)
entspricht:
> df <- data.frame(let=letters[1:5], num=1:5)
> subdf <- df[df$num <= 3, ]
> subdf$let <- subdf$let[ , drop=TRUE]
> levels(subdf$let)
[1] "a" "b" "c"
Der Vollständigkeit halber gibt es jetzt auch fct_drop
im forcats
-Paket http://forcats.tidyverse.org/reference/fct_drop.html .
Es unterscheidet sich von droplevels
im Umgang mit NA
:
f <- factor(c("a", "b", NA), exclude = NULL)
droplevels(f)
# [1] a b <NA>
# Levels: a b <NA>
forcats::fct_drop(f)
# [1] a b <NA>
# Levels: a b
Wenn Sie den droplevels
Methods code in der R-Quelle betrachten, sehen Sie es wird in die factor
-Funktion umgebrochen. Das bedeutet, dass Sie die Spalte grundsätzlich mit der Funktion factor
neu erstellen können.
Unter dem data.table-Weg können Sie die Ebenen aus allen Faktorspalten entfernen.
library(data.table)
dt = data.table(letters=factor(letters[1:5]), numbers=seq(1:5))
levels(dt$letters)
#[1] "a" "b" "c" "d" "e"
subdt = dt[numbers <= 3]
levels(subdt$letters)
#[1] "a" "b" "c" "d" "e"
upd.cols = sapply(subdt, is.factor)
subdt[, names(subdt)[upd.cols] := lapply(.SD, factor), .SDcols = upd.cols]
levels(subdt$letters)
#[1] "a" "b" "c"
hier ist ein Weg, dies zu tun
varFactor <- factor(letters[1:15])
varFactor <- varFactor[1:5]
varFactor <- varFactor[drop=T]
Das ist abscheulich. So mache ich das normalerweise, um das Laden anderer Pakete zu vermeiden:
levels(subdf$letters)<-c("a","b","c",NA,NA)
was bringt dich:
> subdf$letters
[1] a b c
Levels: a b c
Beachten Sie, dass die neuen Ebenen den Index der alten Ebenen (subdf $ letters) ersetzen werden, also etwa Folgendes:
levels(subdf$letters)<-c(NA,"a","c",NA,"b")
funktioniert nicht.
Dies ist offensichtlich nicht ideal, wenn Sie viele Level haben, aber für einige ist es schnell und einfach.
Ich habe dazu Hilfsfunktionen geschrieben. Jetzt, wo ich etwas über gdata's drop.levels weiß, sieht es ziemlich ähnlich aus. Hier sind sie (von hier ):
present_levels <- function(x) intersect(levels(x), x)
trim_levels <- function(...) UseMethod("trim_levels")
trim_levels.factor <- function(x) factor(x, levels=present_levels(x))
trim_levels.data.frame <- function(x) {
for (n in names(x))
if (is.factor(x[,n]))
x[,n] = trim_levels(x[,n])
x
}
Sehr interessanter Thread, mir gefiel vor allem die Idee, die Subselection einfach wieder zu berücksichtigen. Ich hatte vorher ein ähnliches Problem und bin gerade zum Charakter und dann wieder zum Faktor gewechselt.
df <- data.frame(letters=letters[1:5],numbers=seq(1:5))
levels(df$letters)
## [1] "a" "b" "c" "d" "e"
subdf <- df[df$numbers <= 3]
subdf$letters<-factor(as.character(subdf$letters))
Leider scheint factor () nicht zu funktionieren, wenn Sie rxDataStep von RevoScaleR verwenden. Ich mache es in zwei Schritten: 1) In Zeichen konvertieren und in temporären externen Datenrahmen (.xdf) . 2) Zurück in Faktor konvertieren und in endgültigen externen Datenrahmen speichern. Dadurch werden ungenutzte Faktorstufen eliminiert, ohne dass alle Daten in den Speicher geladen werden.
# Step 1) Converts to character, in temporary xdf file:
rxDataStep(inData = "input.xdf", outFile = "temp.xdf", transforms = list(VAR_X = as.character(VAR_X)), overwrite = T)
# Step 2) Converts back to factor:
rxDataStep(inData = "temp.xdf", outFile = "output.xdf", transforms = list(VAR_X = as.factor(VAR_X)), overwrite = T)