Spadek nieużywanych poziomów współczynnika w podgrupie danych

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

Powoduje to problemy podczas wykonywania Wykresów lub korzystania z funkcji, które opierają się na poziomach czynników.

Jaki jest najbardziej zwięzły sposób na usunięcie poziomów z czynnika w nowym ramka 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"
Author: Henrik, 2009-07-28

15 answers

Wszystko, co powinieneś zrobić, to zastosować factor() do zmiennej ponownie po podzbiorze:

> 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

Z przykładu strony factor:

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

Do zrzucania poziomów ze wszystkich kolumn czynnika 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)
 438
Author: hatmatrix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-25 17:37:05

Od wersji R 2.12 istnieje funkcja droplevels().

levels(droplevels(subdf$letters))
 502
Author: Roman Luštrik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-26 11:37:26

Jeśli nie chcesz tego zachowania, nie używaj współczynników, zamiast tego użyj wektorów znaków. Myślę, że to ma więcej sensu niż łatanie rzeczy po wszystkim. Spróbuj wykonać następujące czynności przed załadowaniem danych za pomocą read.table lub read.csv:

options(stringsAsFactors = FALSE)
Wadą jest to, że ograniczasz się do kolejności alfabetycznej. (reorder jest twoim przyjacielem dla fabuły)
 47
Author: hadley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-28 23:53:43

Jest to znany problem, a jednym z możliwych rozwiązań jest drop.levels() w pakiecie gdata , gdzie twój przykład staje się

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

Istnieje również funkcja dropUnusedLevels w pakiecie Hmisc . Jednak działa tylko poprzez zmianę operatora podzbioru [ i nie ma tu zastosowania.

Jako następstwo, bezpośrednie podejście na podstawie kolumny 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"
 39
Author: Dirk Eddelbuettel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-28 19:04:18

Inny sposób robienia tego samego, ale z dplyr

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

Edit:

Również Działa ! Dzięki agenis
subdf <- df %>% filter(numbers <= 3) %>% droplevels
levels(subdf$letters)
 25
Author: Prradep,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:18

Ze względu na kompletność, teraz jest również fct_drop w forcats pakiecie http://forcats.tidyverse.org/reference/fct_drop.html .

Różni się od droplevels 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
 17
Author: Aurèle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-12 09:44:29

Oto inny sposób, który moim zdaniem jest odpowiednikiem podejścia factor(..):

> 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"
 15
Author: ars,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-29 03:40:37

To obrzydliwe. Zazwyczaj tak to robię, aby uniknąć ładowania innych pakietów:

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

Co daje:

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

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

levels(subdf$letters)<-c(NA,"a","c",NA,"b")
Nie zadziała.

To oczywiście nie jest idealne, gdy masz wiele poziomów, ale dla kilku, to szybkie i łatwe.

 8
Author: Matt Parker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-28 19:06:07

Patrząc na kod droplevelsmetod w źródle R można zobaczyć zawija się on do factor funkcji. Oznacza to, że możesz odtworzyć kolumnę za pomocą funkcji factor.
Poniżej danych.tabela pozwala na obniżenie poziomów ze wszystkich kolumn współ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"
 8
Author: jangorecki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-11 23:30:39

Oto sposób na to

varFactor <- factor(letters[1:15])
varFactor <- varFactor[1:5]
varFactor <- varFactor[drop=T]
 7
Author: Diogo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-09 10:03:06

Napisałem do tego funkcje użytkowe. Teraz, gdy wiem o spadku gdata.poziomy, wygląda całkiem podobnie. Here they are (from here):

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
}
 6
Author: Brendan OConnor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-01 20:37:36

[1]}bardzo ciekawy wątek, szczególnie spodobał mi się pomysł, aby po prostu ponownie uwzględnić subselekcję. Miałem podobny problem wcześniej 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))
 4
Author: DfAC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-25 12:08:08

Niestety factor () nie wydaje się działać podczas korzystania z Rxdatastep RevoScaleR. Robię to w dwóch krokach: 1) Konwertuj na znak i przechowuj w tymczasowej zewnętrznej ramce danych (.xdf). 2) Konwertuj z powrotem do czynnika i przechowuj w ostatecznej zewnętrznej ramce danych. Eliminuje to Wszelkie niewykorzystane poziomy współ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
Author: Jerome Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-01-28 21:10:38

Wypróbowałem większość przykładów tutaj, jeśli nie wszystkie, ale żaden nie działa w moim przypadku. Po dłuższych zmaganiach próbowałem używać as.character () w kolumnie factor, aby zmienić ją na col z łańcuchami, które wydają się działać dobrze.

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

 1
Author: Naga Pakalapati,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-02 03:20:17

Prawdziwą funkcją droplevels, która jest znacznie szybsza niż droplevels i nie wykonuje żadnego niepotrzebnego dopasowywania lub tabelowania wartości, jest collapse::fdroplevels. Przykład:

library(collapse)
library(microbenchmark)

# wlddev data supplied in collapse, iso3c is a factor
data <- fsubset(wlddev, iso3c %!in% "USA")

microbenchmark(fdroplevels(data), droplevels(data), unit = "relative")
## Unit: relative
##               expr  min       lq     mean   median       uq      max neval cld
##  fdroplevels(data)  1.0  1.00000  1.00000  1.00000  1.00000  1.00000   100  a 
##   droplevels(data) 30.2 29.15873 24.54175 24.86147 22.11553 14.23274   100   b
 0
Author: Sebastian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-09 08:45:18