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"
Medriscoll
quelle

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)
hatmatrix
quelle
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))
Roman Luštrik
quelle
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)

Hadley
quelle
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"
Dirk Eddelbuettel
quelle
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)
Prradep
quelle
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
Aurèle
quelle
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"
ars
quelle
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.

Matt Parker
quelle
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"
jangorecki
quelle
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]
Diogo
quelle
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
}
Brendan OConnor
quelle
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))
DfAC
quelle
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)
Jerome Smith
quelle
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.

Naga Pakalapati
quelle