Drop-Faktor-Ebenen in einem untergeordneten Datenrahmen


543

Ich habe einen Datenrahmen mit a factor. Wenn ich eine Teilmenge dieses Datenrahmens mit subsetoder einer anderen Indizierungsfunktion erstelle , wird ein neuer Datenrahmen erstellt. Die factorVariable behält jedoch alle ursprünglichen Ebenen bei, auch wenn sie im neuen Datenrahmen nicht vorhanden sind.

Dies führt zu Problemen beim Erstellen von Facetten oder beim Verwenden von Funktionen, die auf Faktorstufen beruhen.

Was ist der prägnanteste Weg, um Ebenen aus einem Faktor im neuen Datenrahmen zu entfernen?

Hier ist ein 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    

# all levels are still there!
levels(subdf$letters)
## [1] "a" "b" "c" "d" "e"

Antworten:


420

Alles, was Sie tun müssen, ist, Faktor () nach der Teilmenge 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

BEARBEITEN

Aus dem Beispiel der Faktorseite:

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)

22
Das ist für ein Einzelstück in Ordnung, aber in einem data.frame mit einer großen Anzahl von Spalten können Sie dies für jede Spalte tun, die ein Faktor ist ... was zur Notwendigkeit einer Funktion wie drop.levels () führt. von gdata.
Dirk Eddelbuettel

6
Ich verstehe ... aber aus Anwendersicht ist es schnell, so etwas wie subdf [] <- lapply (subdf, Funktion (x) zu schreiben, wenn (is.factor (x)) Faktor (x) sonst x) ... ist drop.levels () viel rechnerisch effizienter oder besser mit großen Datenmengen? (Man müsste die obige Zeile in einer for-Schleife für einen riesigen
Datenrahmen

1
Vielen Dank, Stephen & Dirk. Ich gebe diesem einen die Daumen hoch für die Faktoren eines Faktors, aber hoffentlich lesen die Leute diese Kommentare für Ihre Vorschläge zur Bereinigung eines gesamten Datenrahmens von Faktoren.
Medriscoll

9
Als Nebeneffekt konvertiert die Funktion den mydf <- droplevels(mydf)Datenrahmen in eine Liste, weshalb die von Roman Luštrik und Tommy O'Dell unten vorgeschlagene Lösung vorzuziehen ist.
Johan

1
Außerdem: Diese Methode behält die Reihenfolge der Variablen bei.
Webelo

492

Seit R Version 2.12 gibt es eine droplevels()Funktion.

levels(droplevels(subdf$letters))

7
Ein Vorteil dieser Methode gegenüber der Verwendung factor()besteht darin, dass der ursprüngliche Datenrahmen nicht geändert oder ein neuer persistenter Datenrahmen erstellt werden muss. Ich kann droplevelseinen untergeordneten Datenrahmen umschließen und ihn als Datenargument für eine Gitterfunktion verwenden, und Gruppen werden korrekt behandelt.
Mars

Ich habe festgestellt, dass wenn ich einen NA-Level in meinem Faktor habe (einen echten NA-Level), dieser um gesunkene Levels sinkt, selbst wenn die NAs vorhanden sind.
Meep

46

Wenn Sie dieses Verhalten nicht möchten, verwenden Sie keine Faktoren, sondern stattdessen Zeichenvektoren. Ich denke, das ist sinnvoller, als die Dinge danach zu reparieren. Versuchen Sie Folgendes, bevor Sie Ihre Daten mit read.tableoder laden read.csv:

options(stringsAsFactors = FALSE)

Der Nachteil ist, dass Sie sich auf die alphabetische Reihenfolge beschränken. (Nachbestellung ist dein Freund für Grundstücke)


38

Es ist ein bekanntes Problem, und eine mögliche Lösung finden Sie drop.levels()im gdata- Paket, in dem Ihr Beispiel angezeigt 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 dropUnusedLevelsFunktion im Hmisc- Paket. Es funktioniert jedoch nur durch Ändern des Teilmengenoperators [und ist hier nicht anwendbar.

Als Konsequenz ist ein direkter Ansatz auf Spaltenbasis einfach 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"

5
Der reorderParameter der drop.levelsFunktion ist erwähnenswert: Wenn Sie die ursprüngliche Reihenfolge Ihrer Faktoren beibehalten müssen, verwenden Sie sie mit FALSEWert.
Daroczig

Wenn Sie gdata nur für drop.levels verwenden, erhalten Sie "gdata: read.xls-Unterstützung für 'XLS'-Dateien (Excel 97-2004) AKTIVIERT." "gdata: Perl-Bibliotheken, die von read.xls () benötigt werden, können nicht geladen werden." "gdata: Zur Unterstützung von 'XLSX'-Dateien (Excel 2007+)." "gdata: Führen Sie die Funktion 'installXLSXsupport ()'" "gdata: um das Perl automatisch herunterzuladen und zu installieren". Verwenden Sie Droplevels von baseR ( stackoverflow.com/a/17218028/9295807 )
Vrokipal

Sachen passieren im Laufe der Zeit. Sie sind zu kommentieren eine Antwort , die ich vor neun Jahren schrieb. Nehmen wir dies als Hinweis, um im Allgemeinen Basis-R-Lösungen zu bevorzugen, da diese Funktionen verwenden, die in etwa N Jahren noch verfügbar sein werden .
Dirk Eddelbuettel

25

Ein anderer Weg, dasselbe zu tun, aber mit dplyr

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

Bearbeiten:

Funktioniert auch! Danke an agenis

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

17

Der Vollständigkeit halber gibt es jetzt auch fct_dropdas forcatsPaket http://forcats.tidyverse.org/reference/fct_drop.html .

Es unterscheidet sich von droplevelsder Art und Weise, wie es behandelt wird 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

15

Hier ist ein anderer Weg, der meiner Meinung nach 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"

Ha, nach all den Jahren wusste ich nicht, dass es eine `[.factor`Methode gibt, die ein dropArgument hat, und Sie haben dies 2009 gepostet ...
David Arenburg

8

Das ist widerlich. So mache ich es 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 alles ersetzen, was ihren Index in den alten Ebenen belegt (subdf $ Buchstaben), also so etwas wie:

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

wird nicht funktionieren.

Dies ist natürlich nicht ideal, wenn Sie viele Level haben, aber für einige ist es schnell und einfach.


8

Wenn Sie sich den droplevelsMethodencode in der R-Quelle ansehen , sehen Sie, dass er factorfunktioniert. Das heißt, Sie können die Spalte grundsätzlich mit factorFunktion neu erstellen .
Unterhalb der data.table-Methode können Ebenen aus allen Faktorspalten gelöscht werden.

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, der data.tableWeg wäre so etwas wiefor (j in names(DT)[sapply(DT, is.factor)]) set(DT, j = j, value = factor(DT[[j]]))
David Arenburg

1
@ DavidArenburg es ändert sich hier nicht viel, da wir [.data.tablenur einmal
anrufen

7

Hier ist ein Weg, das zu tun

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

2
Dies ist ein Betrug dieser Antwort, die 5 Jahre zuvor veröffentlicht wurde.
David Arenburg

6

Ich habe dazu Dienstprogrammfunktionen geschrieben. Jetzt, wo ich über gdatas drop.levels Bescheid 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
}

4

Sehr interessanter Thread, mir hat besonders die Idee gefallen, die Unterauswahl einfach wieder zu faktorisieren. Ich hatte vorher ein ähnliches Problem und bin einfach zum Charakter und dann zurück zum Faktor konvertiert.

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

Ich meine, factor(as.chracter(...))funktioniert, aber nur weniger effizient und prägnant als factor(...). Scheint streng schlechter als die anderen Antworten.
Gregor Thomas

1

Leider scheint factor () bei Verwendung von rxDataStep von RevoScaleR nicht zu funktionieren. Ich mache es in zwei Schritten: 1) In Zeichen konvertieren und in einem temporären externen Datenrahmen (.xdf) speichern. 2) Zurück zum Faktor konvertieren und im endgültigen externen Datenrahmen speichern. Dadurch werden nicht verwendete Faktorstufen eliminiert, ohne dass alle Daten in den Speicher geladen werden müssen.

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

1

Habe die meisten Beispiele hier ausprobiert, wenn in meinem Fall nicht alle, aber keine zu funktionieren scheinen. Nachdem ich einige Zeit gekämpft habe, habe ich versucht, as.character () für die Faktorspalte zu verwenden, um sie in eine Spalte mit Zeichenfolgen zu ändern, die anscheinend einwandfrei funktioniert.

Nicht sicher für Leistungsprobleme.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.