2009-07-28 2 views
406

У меня есть кадр данных, содержащий множитель. Когда я создаю подмножество этого фрейма данных, используя subset() или другую функцию индексирования, создается новый кадр данных. Однако фактор-переменная сохраняет все свои исходные уровни - даже если они не существуют в новом фрейме данных.Уровни фактора падения в подмножестве данных

Это создает головные боли при графовом построении или использовании функций, которые полагаются на уровни факторов.

Что является самым кратким способом удаления уровней из фактора в моем новом фрейме данных?

Вот мой пример:

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" 

ответ

310

Все, что вы должны сделать, это применить коэффициент() в переменную снова после Подменю:

> 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

На примере фактор страницы:

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

Для сброса уровней из всех столбцов факторов в фрейме данных вы можете использовать:

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

Это нормально для одноразового использования, но в data.frame с большим количеством столбцов вы можете сделать это на каждом столбце, который является фактором ..., что приводит к необходимости в такой функции, как drop.levels () из gdata. –

+6

Я вижу ... но с точки зрения пользователя быстро написать что-то вроде subdf [] <- lapply (subdf, function (x) if (is.factor (x)) factor (x) else x) ... Является ли drop.levels() намного эффективнее вычислительно или лучше с большими наборами данных? (Мне нужно было бы переписать строку выше в for-loop для огромного фрейма данных, я полагаю.) – hatmatrix

+1

Спасибо Стивен и Дирк. Я даю этому один большой палец для подсказок одного фактора, но, надеюсь, люди будут прочитайте эти комментарии для своих предложений по очистке всего кадра данных факторов. – medriscoll

33

Это известная проблема, и один из возможного средства обеспечивается drop.levels() в gdata пакете, где ваш пример становится

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

Существует также функция dropUnusedLevels в пакете Hmisc. Однако он работает только при изменении оператора подмножества [ и здесь не применим.

Как следствие, прямой подход на основе каждого столбца представляет собой простой 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" 
+4

Параметр 'reorder' параметр' drop.levels' функции стоит упомянуть: если у вас есть, чтобы сохранить первоначальный порядок ваших факторов , используйте его с значением FALSE. – daroczig

7

Это неприятно. Это, как я обычно это, чтобы избежать загрузки других пакетов:

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

который получает вас:

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

Обратите внимание, что новые уровни заменит все, что занимает их индекс в старых уровнях (subdf $ буквы), так что-то вроде:

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

не будет работать.

Это, очевидно, не идеально, когда у вас много уровней, но для некоторых это быстро и просто.

31

Если вы не хотите этого поведения, не используйте факторы, используйте вместо него векторы символов. Я думаю, что это имеет больше смысла, чем исправление. Попробуйте следующее: перед загрузкой данных с read.table или read.csv:

options(stringsAsFactors = FALSE) 

Недостаток заключается в том, что вы ограничены алфавитного порядка.(Перезаказ является вашим другом для участков)

+5

Вы также можете прочитать read.csv (file = 'foo.csv', as.is = T). – andrewj

9

Вот еще один способ, который я считаю, равносильно 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" 
4

я писал вспомогательные функции, чтобы сделать это. Теперь, когда я знаю о drop.levels gdata, он выглядит довольно похожим. Вот они (от 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 
} 
441

С R версии 2.12, есть droplevels() функция.

levels(droplevels(subdf$letters)) 
+7

В качестве альтернативы, вы можете просто немного прокрутить вниз ... –

+0

@ RomanLuštrik К сожалению, сортировка по голосам по-прежнему принимает принятый ответ №1, хотя он (сейчас) имеет меньше голосов, чем ваш :-( – tim

+2

Преимущество этого метода использование 'factor()' заключается в том, что нет необходимости изменять исходный фреймворк или создавать новый постоянный фрейм. Я могу обернуть «капли» вокруг подмножества данных и использовать его в качестве аргумента данных для функции решетки, а группы будут обрабатываться правильно. – Mars

6

вот способ сделать это

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

Очень интересная нить, мне особенно понравилась идея просто фактор Subselection снова. Раньше у меня была аналогичная проблема, и я просто обратился к персонажу, а затем вернулся к фактору.

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

Другой способ сделать то же самое, но с dplyr

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

Edit:

Также работает! Благодаря agenis

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

вам даже не нужны скобки после пучков – agenis

5

Глядя на droplevels методы code in the R source you can see она обтекает factor функции. Это означает, что вы можете в основном воссоздать столбец с помощью функции factor.
Ниже таблицы данных.table для снижения уровней из всех столбцов факторов.

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

Я думаю, что способ 'data.table' был бы похож на' для (j в именах (DT) [sapply (DT, is.factor)]) set (DT, j = j, value = factor (DT [[j] ])) ' –

+1

@DavidArenburg здесь не меняется, поскольку мы называем '.data.table' только один раз – jangorecki

3

Для полноты картины, теперь есть и в forcats пакете http://forcats.tidyverse.org/reference/fct_drop.htmlfct_drop.

Он отличается от droplevels в том, как он имеет дело с 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 

 Смежные вопросы

  • Нет связанных вопросов^_^