web-dev-qa-db-de.com

Drop-Faktor-Ebenen in einem subsettierten Datenrahmen

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"
461
medriscoll

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

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

levels(droplevels(subdf$letters))
465
Roman Luštrik

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)

38
hadley

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"
34

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

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"
12
ars

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
8
Aurèle

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"
7
jangorecki

hier ist ein Weg, dies zu tun

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

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.

6
Matt Parker

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
}
5
Brendan OConnor

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

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)
0
Jerome Smith