Poziomy współczynnika upuszczenia w podzestawie danych

543

Mam ramkę danych zawierającą factor. Kiedy tworzę podzbiór tej ramki danych za pomocą subsetlub innej funkcji indeksowania, tworzona jest nowa ramka danych. Jednak factorzmienna zachowuje wszystkie swoje pierwotne poziomy, nawet jeśli / jeśli nie istnieją w nowej ramce danych.

Powoduje to problemy podczas wykonywania rysowania fasetowego lub korzystania z funkcji zależnych od poziomów czynników.

Jaki jest najbardziej zwięzły sposób usunięcia poziomów z czynnika w nowej ramce danych?

Oto przykład:

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
źródło

Odpowiedzi:

420

Wszystko, co powinieneś zrobić, to ponownie zastosować współczynnik () do zmiennej po podzestawie:

> 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

EDYTOWAĆ

Z przykładu strony czynnikowej:

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

Do usuwania poziomów ze wszystkich kolumn czynników w ramce danych można użyć:

subdf <- subset(df, numbers <= 3)
subdf[] <- lapply(subdf, function(x) if(is.factor(x)) factor(x) else x)
hatmatrix
źródło
22
Jest to w porządku jednorazowe, ale w ramce data.frame z dużą liczbą kolumn, możesz to zrobić w każdej kolumnie, która jest czynnikiem ... co prowadzi do potrzeby użycia funkcji takiej jak drop.levels () z gdata.
Dirk Eddelbuettel
6
Rozumiem ... ale z perspektywy użytkownika szybko napisać coś w rodzaju subdf [] <- lapply (subdf, funkcja (x) if (is.factor (x)) factor (x) else x) ... Is drop.levels () znacznie bardziej wydajny obliczeniowo czy lepiej z dużymi zestawami danych? (Przypuszczam, że należałoby przepisać powyższą linię w pętli for, aby uzyskać ogromną ramkę danych.)
hatmatrix
1
Dzięki Stephen i Dirk - daję temu aprobatę w sprawie jednego czynnika, ale mam nadzieję, że ludzie przeczytają te komentarze, aby uzyskać sugestie dotyczące czyszczenia całej ramki danych czynników.
medriscoll
9
Jako efekt uboczny funkcja przekształca ramkę danych w listę, więc mydf <- droplevels(mydf)preferowane jest rozwiązanie sugerowane przez Romana Luštrika i Tommy'ego O'Dell poniżej.
Johan
1
Również: metoda ta ma zachować kolejność zmiennej.
webelo
492

Od wersji R 2.12 dostępna jest droplevels()funkcja.

levels(droplevels(subdf$letters))
Roman Luštrik
źródło
7
Zaletą tej metody w porównaniu z użyciem factor()jest to, że nie trzeba modyfikować oryginalnej ramki danych ani tworzyć nowej trwałej ramki danych. Mogę owinąć droplevelspodzbiór ramki danych i użyć jej jako argumentu danych funkcji kratowej, a grupy będą obsługiwane poprawnie.
Mars,
Zauważyłem, że jeśli mam poziom NA w swoim czynniku (prawdziwy poziom NA), spada on o pomijane poziomy, nawet jeśli NA są obecne.
Meep,
46

Jeśli nie chcesz tego zachowania, nie używaj czynników, zamiast tego użyj wektorów znaków. Myślę, że ma to większy sens niż załatanie później. Spróbuj wykonać następujące czynności przed załadowaniem danych za pomocą read.tablelub read.csv:

options(stringsAsFactors = FALSE)

Wadą jest to, że jesteś ograniczony do porządku alfabetycznego. (zmiana kolejności jest twoim przyjacielem dla działek)

Hadley
źródło
38

Jest to znany problem, a jeden możliwy środek zapewnia drop.levels()w GData pakietu, gdzie staje się przykładem

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

Jest również dropUnusedLevelsfunkcja w pakiecie Hmisc . Działa to jednak tylko poprzez zmianę operatora podzbioru [i nie ma tutaj zastosowania.

W następstwie bezpośrednie podejście dla poszczególnych kolumn jest proste 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
źródło
5
reorderParametr drop.levelsfunkcji warto wspomnieć: jeśli trzeba zachować oryginalną kolejność czynników, używać go z FALSEwartością.
daroczig
Użycie gdata tylko do drop.levels daje „gdata: read.xls obsługa plików„ XLS ”(Excel 97-2004) WŁĄCZONYCH.” „gdata: Nie można załadować bibliotek perlowych wymaganych przez read.xls ()” „gdata: do obsługi plików„ XLSX ”(Excel 2007+).” „gdata: Uruchom funkcję„ installXLSXsupport () ”„ „gdata: aby automatycznie pobrać i zainstalować perl”. Użyj poziomów upuszczania z baseR ( stackoverflow.com/a/17218028/9295807 )
Vrokipal
Rzeczy zdarzają się z czasem. Ci komentując odpowiedź pisałem dziewięć lat temu. Weźmy to za podpowiedź, aby ogólnie preferować podstawowe rozwiązania R, ponieważ są to te, które wykorzystują funkcjonalność, która nadal będzie dostępna za około N lat.
Dirk Eddelbuettel,
25

Inny sposób robienia tego samego, ale z dplyr

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

Edytować:

Działa również! Dzięki agenis

subdf <- df %>% filter(numbers <= 3) %>% droplevels
levels(subdf$letters)
Prradep
źródło
17

Dla kompletności, teraz jest również fct_dropw forcatspakiecie http://forceats.tidyverse.org/reference/fct_drop.html .

Różni droplevelssię sposobem, w jaki zajmuje się 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
źródło
15

Oto inny sposób, który moim zdaniem jest równoważny factor(..)podejściu:

> 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
źródło
Ha, po tylu latach nie wiedziałem, że istnieje `[.factor`metoda, która ma dropargument, a ty opublikowałeś to w 2009 roku ...
David Arenburg,
8

To jest wstrętne. Tak zazwyczaj to robię, aby uniknąć ładowania innych pakietów:

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

co daje ci:

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

Zauważ, że nowe poziomy zastąpią wszystko, co zajmuje ich indeks na starych poziomach (litery subdf $), więc coś takiego:

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

nie zadziała.

Nie jest to oczywiście idealne, gdy masz wiele poziomów, ale dla niektórych jest to szybkie i łatwe.

Matt Parker
źródło
8

Patrząc na koddroplevels metod w źródle R, możesz zobaczyć, jak się zawija factor. Oznacza to, że można w zasadzie odtworzyć kolumnę z factorfunkcją.
Poniżej metody data.table, aby upuścić poziomy ze wszystkich kolumn czynników.

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
źródło
1
Myślę, że data.tablesposób byłby podobnyfor (j in names(DT)[sapply(DT, is.factor)]) set(DT, j = j, value = factor(DT[[j]]))
David Arenburg
1
@DavidArenburg, niewiele się tu zmienia, bo dzwonimy [.data.tabletylko raz
jangorecki
7

oto sposób na zrobienie tego

varFactor <- factor(letters[1:15])
varFactor <- varFactor[1:5]
varFactor <- varFactor[drop=T]
Diogo
źródło
2
To jest duplikat tej odpowiedzi, która została opublikowana 5 lat wcześniej.
David Arenburg,
6

W tym celu napisałem funkcje narzędziowe. Teraz, gdy wiem o drop.levels gdata, wygląda całkiem podobnie. Oto one ( stąd ):

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
źródło
4

Bardzo interesujący wątek, szczególnie podoba mi się pomysł ponownego uwzględnienia podselekcji. Miałem wcześniej podobny problem i po prostu przekształciłem się w postać, a potem z powrotem w czynnik.

   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
źródło
Mam na myśli, factor(as.chracter(...))działa, ale tylko mniej wydajnie i zwięźle niż factor(...). Wydaje się, że jest gorzej niż inne odpowiedzi.
Gregor Thomas
1

Niestety czynnik () wydaje się nie działać przy użyciu rxDataStep z RevoScaleR. Robię to w dwóch krokach: 1) Konwertuj na znak i przechowuj w tymczasowej zewnętrznej ramce danych (.xdf). 2) Konwertuj z powrotem na współczynnik i przechowuj w ostatecznej zewnętrznej ramce danych. Eliminuje to wszelkie nieużywane poziomy czynników, bez ładowania wszystkich danych do pamięci.

# 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
źródło
1

Próbowałem tutaj większości przykładów, jeśli nie wszystkie, ale wydaje się, że żaden nie działa w moim przypadku. Po dłuższym okresie zmagania próbowałem użyć as.character () w kolumnie czynnik, aby zmienić ją na kolumnę z ciągami, które wydają się działać dobrze.

Nie jestem pewien problemów z wydajnością.

Naga Pakalapati
źródło