2009-07-28 21 views
406

Ich habe einen Datenrahmen mit einem Faktor. Wenn ich eine Teilmenge dieses Datenrahmens unter Verwendung von subset() oder einer anderen Indizierungsfunktion erstelle, wird ein neuer Datenrahmen erstellt. Die Faktorvariable behält jedoch alle ihre ursprünglichen Ebenen bei - auch wenn sie nicht im neuen Datenrahmen vorhanden sind.Drop-Faktor-Ebenen in einem Teildatenrahmen

Dies verursacht Kopfschmerzen beim facettierten Plotten oder bei der Verwendung von Funktionen, die auf Faktorstufen beruhen.

Was ist der prägnanteste Weg, um Ebenen von 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" 

Antwort

310

Alles, was Sie tun sollten, müssen wieder Faktor(), um Ihre Variable anzuwenden, nachdem subsetting:

> 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 Faktor Seite Beispiel:

factor(ff)  # drops the levels that do not occur 

Zum Löschen 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) 
+17

Das ist in Ordnung für eine einmalige, aber in einem dat.frame mit einer großen Anzahl von Spalten, müssen Sie das für jede Spalte tun, die ein Faktor ist ... was dazu führt, dass eine Funktion wie drop.levels () von gdata. –

+6

Ich sehe ... aber aus einer Benutzerperspektive ist es schnell, etwas wie subdf schreiben [] <- lapply (Subdf, Funktion (x) if (is.factor (x)) Faktor (x) sonst x) ... Ist drop.levels() bei großen Datenmengen viel effizienter oder besser? (Man müsste die obige Zeile in einer For-Schleife für einen riesigen Datenrahmen neu schreiben, nehme ich an.) – hatmatrix

+1

Danke Stephen & Dirk - Ich gebe diesem einen Daumen hoch für die Fehler eines Faktors, aber hoffentlich werden die Leute Lesen Sie diese Kommentare für Ihre Vorschläge zur Bereinigung eines ganzen Datenrahmens von Faktoren. – medriscoll

33

Es ist ein bekanntes Problem und eine mögliche Abhilfe wird durch drop.levels() im gdata Paket zur Verfügung gestellt, wo Ihr Beispiel

> 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 dropUnusedLevels Funktion wird im Paket Hmisc. Es funktioniert jedoch nur, indem der Subset-Operator [ geändert wird und ist hier nicht anwendbar.

Als logische Folge ein direkter Ansatz auf einer Pro-Spalte-Basis ist 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" 
+4

Der 'reorder' Parameter der' drop.levels' Funktion ist erwähnenswert: wenn Sie die ursprüngliche Reihenfolge der Faktoren erhalten , benutze es mit 'FALSE' Wert. – daroczig

7

Dieser widerwärtig ist. Dies ist, wie ich in der Regel tun es, andere Pakete laden zu vermeiden:

levels(subdf$letters)<-c("a","b","c",NA,NA) 

, die Sie bekommt:

> subdf$letters 
[1] a b c 
Levels: a b c 

Beachten Sie, dass die neuen Ebenen ersetzen, was auch immer ihr Index in den alten Ebenen einnimmt (subdf $ Briefe), so etwas wie:

levels(subdf$letters)<-c(NA,"a","c",NA,"b") 

wird nicht funktionieren.

Dies ist offensichtlich nicht ideal, wenn Sie viele Ebenen haben, aber für einige wenige ist es schnell und einfach.

31

Wenn Sie dieses Verhalten nicht möchten, verwenden Sie keine Faktoren, sondern verwenden Sie stattdessen Zeichenvektoren. Ich denke, das macht mehr Sinn, als die Dinge danach zu reparieren. Versuchen Sie Folgendes, bevor Sie Ihre Daten mit read.table oder read.csv Laden:

options(stringsAsFactors = FALSE) 

Der Nachteil ist, dass Sie auf alphabetische Ordnung beschränkt sind.(Nachbestellung ist dein Freund für Plots)

+5

Sie können auch Sie read.csv (file = 'foo.csv', as.is = T). Alternativ – andrewj

9

Hier ist eine andere Art und Weise, die ich glaube, zu dem factor(..) Ansatz 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" 
4

Ich schrieb Utility-Funktionen, dies zu tun. Nun, da ich die Droplevel von gdata kenne, sieht es ziemlich ähnlich aus. Hier sind sie (von here):

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 
} 
441

Da R-Version 2.12 gibt es eine droplevels() Funktion.

levels(droplevels(subdf$letters)) 
+7

, können Sie nur ein wenig nach unten scrollen ... –

+0

@ RomanLuštrik Leider sortiere nach Stimmen macht immer noch die akzeptierte Antwort # 1, obwohl es (jetzt) ​​weniger Stimmen als Ihr :-( – tim

+2

Ein Vorteil dieses Verfahrens hat über mit 'Faktor()' ist, dass es nicht notwendig ist, den ursprünglichen Datenrahmen zu ändern oder einen neuen persistenten Datenrahmen erstellen. ich wickle kann 'droplevels' um einen subsetted Datenrahmen und verwenden sie es als Daten Argument an eine Gitterfunktion und Gruppen behandelt werden richtig. – Mars

6

hier ist ein Weg, das wieder zu tun

varFactor <- factor(letters[1:15]) 
varFactor <- varFactor[1:5] 
varFactor <- varFactor[drop=T] 
3

Sehr interessanter Thread, ich mochte Idee besonders einfach Faktor subselection. Ich hatte das ähnliche Problem vorher und ich konvertierte nur zum Charakter und dann zurück zu Faktor.

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)) 
13

Eine andere Möglichkeit, das gleiche zu tun, sondern mit dplyr

library(dplyr) 
subdf <- df %>% filter(numbers <= 3) %>% droplevels() 
str(subdf) 

Edit:

funktioniert auch! Dank agenis

subdf <- df %>% filter(numbers <= 3) %>% droplevels 
levels(subdf$letters) 
+2

Sie nicht einmal die Klammer nach droplevels brauchen – agenis

5

Mit Blick auf die droplevels Methoden code in the R source you can see es factor Funktion wickelt. Das bedeutet, dass Sie die Spalte mit factor Funktion grundsätzlich neu erstellen können.
Unterhalb der data.table Möglichkeit, Ebenen aus allen Faktorspalten zu löschen.

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" 
+1

ich denke, die 'data.table' Art und Weise so etwas wie' for (j in Namen (DT) [sapply (DT, is.factor)]) würde eingestellt (DT, j = j, Wert = Faktor (DT [[j] ])) ' –

+1

@DavidArenburg ändert sich hier nicht viel, da wir '[.data.table' nur einmal aufrufen – jangorecki

3

Aus Gründen der Vollständigkeit, jetzt gibt es auch fct_drop im forcats Paket http://forcats.tidyverse.org/reference/fct_drop.html.

Aus droplevels in der Art und Weise unterscheidet befasst sie sich 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