Poziomy współczynnika spadku w podgrupie danych

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

Powoduje to bóle głowy podczas robienia 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 moich nowych danych rama?

Oto mój 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    

## but the levels are still there!
levels(subdf$letters)
## [1] "a" "b" "c" "d" "e"
Author: divibisan, 2009-07-28

12 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)
 343
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))
 457
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)
 37
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"
 33
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)
 15
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

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

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

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.

 6
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

Oto sposób na to

varFactor <- factor(letters[1:15])
varFactor <- varFactor[1:5]
varFactor <- varFactor[drop=T]
 6
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

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

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