Livelli di fattore di caduta in un frame di dati sotto settato


543

Ho un frame di dati contenente a factor. Quando creo un sottoinsieme di questo frame di dati utilizzando subseto un'altra funzione di indicizzazione, viene creato un nuovo frame di dati. Tuttavia, la factorvariabile mantiene tutti i suoi livelli originali, anche quando / se non esistono nel nuovo frame di dati.

Ciò causa problemi quando si esegue la stampa sfaccettata o si utilizzano funzioni che si basano sui livelli dei fattori.

Qual è il modo più conciso per rimuovere i livelli da un fattore nel nuovo frame di dati?

Ecco un esempio:

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"

Risposte:


420

Tutto quello che dovresti fare è applicare nuovamente factor () alla tua variabile dopo aver effettuato il 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

MODIFICARE

Dall'esempio della pagina del fattore:

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

Per eliminare i livelli da tutte le colonne dei fattori in un frame di dati, puoi utilizzare:

subdf <- subset(df, numbers <= 3)
subdf[] <- lapply(subdf, function(x) if(is.factor(x)) factor(x) else x)

22
Va bene per una tantum, ma in un data.frame con un gran numero di colonne, puoi farlo su ogni colonna che è un fattore ... portando alla necessità di una funzione come drop.levels () da gdata.
Dirk Eddelbuettel,

6
Vedo ... ma dal punto di vista dell'utente è veloce scrivere qualcosa come subdf [] <- lapply (subdf, function (x) if (is.factor (x)) factor (x) else x) ... È drop.levels () molto più efficiente dal punto di vista computazionale o migliore con grandi set di dati? (Uno dovrebbe riscrivere la riga sopra in un ciclo for per un enorme frame di dati, suppongo.)
hatmatrix

1
Grazie Stephen & Dirk - Sto dando a questo il pollice in alto per i motivi di un fattore, ma spero che la gente leggerà questi commenti per i tuoi suggerimenti su come ripulire un intero frame di dati di fattori.
medriscoll,

9
Come effetto collaterale, la funzione converte il frame di dati in un elenco, quindi mydf <- droplevels(mydf)è preferibile la soluzione suggerita da Roman Luštrik e Tommy O'Dell di seguito.
Johan,

1
Inoltre: questo metodo non preservare l'ordinamento della variabile.
webelo,

492

Dalla versione R 2.12, c'è una droplevels()funzione.

levels(droplevels(subdf$letters))

7
Un vantaggio di questo metodo rispetto all'uso factor()è che non è necessario modificare il frame di dati originale o creare un nuovo frame di dati persistente. Posso avvolgere droplevelsun frame di dati sotto settato e usarlo come argomento di dati per una funzione reticolare e i gruppi verranno gestiti correttamente.
Marte,

Ho notato che se ho un livello NA nel mio fattore (un livello NA autentico), viene abbassato di livelli scartati, anche se sono presenti NA.
Meep

46

Se non si desidera questo comportamento, non utilizzare i fattori, utilizzare invece i vettori di caratteri. Penso che questo abbia più senso che aggiustare le cose in seguito. Prova quanto segue prima di caricare i tuoi dati con read.tableo read.csv:

options(stringsAsFactors = FALSE)

Lo svantaggio è che sei limitato all'ordinamento alfabetico. (riordina è tuo amico per le trame)


38

Si tratta di un problema noto, ed un possibile rimedio è fornita da drop.levels()nel GData pacchetto in cui il tuo esempio diventa

> drop.levels(subdf)
  letters numbers
1       a       1
2       b       2
3       c       3
> levels(drop.levels(subdf)$letters)
[1] "a" "b" "c"

C'è anche la dropUnusedLevelsfunzione nel pacchetto Hmisc . Tuttavia, funziona solo modificando l'operatore del sottoinsieme [e non è applicabile qui.

Come corollario, un approccio diretto su una base per colonna è un semplice 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
Il reorderparametro della drop.levelsfunzione della pena ricordare: se si dispone di preservare l'ordine originale dei vostri fattori, utilizzarlo con FALSEil valore.
daroczig,

L'uso di gdata solo per drop.levels produce "gdata: supporto read.xls per i file 'XLS' (Excel 97-2004) ABILITATI." "gdata: impossibile caricare le librerie perl necessarie per read.xls ()" "gdata: per supportare i file 'XLSX' (Excel 2007+)." "gdata: esegui la funzione 'installXLSXsupport ()'" "gdata: per scaricare e installare automaticamente il perl". Utilizzare droplevels da Baser ( stackoverflow.com/a/17218028/9295807 )
Vrokipal

Le cose accadono nel tempo. Si sta commentando una risposta che ho scritto nove anni fa. Quindi prendiamo questo come suggerimento per preferire generalmente le soluzioni di base R poiché quelle sono quelle che utilizzano funzionalità che saranno ancora tra N anni.
Dirk Eddelbuettel,

25

Un altro modo di fare lo stesso ma con dplyr

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

Modificare:

Funziona anche! Grazie ad Agenis

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

17

Per completezza, ora c'è anche fct_dropnel forcatspacchetto http://forcats.tidyverse.org/reference/fct_drop.html .

Si differenzia dal droplevelsmodo in cui tratta 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

Ecco un altro modo, che credo sia equivalente factor(..)all'approccio:

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

Ah, dopo tutti questi anni non sapevo che esistesse un `[.factor`metodo che avesse un dropargomento e tu l'hai pubblicato nel 2009 ...
David Arenburg,

8

Questo è odioso. Ecco come lo faccio di solito, per evitare di caricare altri pacchetti:

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

che ti dà:

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

Nota che i nuovi livelli sostituiranno qualunque cosa occupi il loro indice nei vecchi livelli (subdf $ lettere), quindi qualcosa di simile:

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

non funzionerà.

Questo ovviamente non è l'ideale quando si hanno molti livelli, ma per alcuni è semplice e veloce.


8

Osservando il codice deidroplevels metodi nell'origine R, puoi vedere che si avvolge per factorfunzionare. Ciò significa che puoi fondamentalmente ricreare la colonna con la factorfunzione.
Sotto il modo data.table per eliminare i livelli da tutte le colonne dei fattori.

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
Penso che il data.tablemodo sarebbe qualcosa di similefor (j in names(DT)[sapply(DT, is.factor)]) set(DT, j = j, value = factor(DT[[j]]))
David Arenburg,

1
@DavidArenburg qui non cambia molto come chiamiamo [.data.tablesolo una volta
jangorecki

7

ecco un modo per farlo

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

2
Questo è un inganno di questa risposta che è stata pubblicata 5 anni prima.
David Arenburg,

6

Ho scritto funzioni di utilità per farlo. Ora che conosco i drop.level di gdata, sembra abbastanza simile. Eccoli (da qui ):

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

Discussione molto interessante, mi è piaciuta soprattutto l'idea di considerare nuovamente la sottoselezione. Ho avuto il problema simile prima e mi sono appena convertito in personaggio e poi di nuovo in fattore.

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

Voglio dire, factor(as.chracter(...))funziona, ma solo in modo meno efficiente e succinto factor(...). Sembra strettamente peggio delle altre risposte.
Gregor Thomas,

1

Sfortunatamente factor () non sembra funzionare quando si utilizza rxDataStep di RevoScaleR. Lo faccio in due passaggi: 1) Converti in carattere e memorizza in un frame di dati esterno temporaneo (.xdf). 2) Convertire nuovamente in fattore e archiviare in un frame di dati esterno definitivo. Ciò elimina qualsiasi livello di fattore inutilizzato, senza caricare tutti i dati in memoria.

# 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

Ho provato la maggior parte degli esempi qui, se non tutti, ma nessuno sembra funzionare nel mio caso. Dopo aver lottato per un po 'di tempo, ho provato ad usare as.character () sulla colonna dei fattori per cambiarlo in un passo con stringhe che sembra funzionare bene.

Non sono sicuro per problemi di prestazioni.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.