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"

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)

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

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)


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"

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)


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"

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.


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"

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]

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
}

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

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)

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ą.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.