Уровни опускания в заданном кадре данных

459

У меня есть кадр данных, содержащий фактор. Когда я создаю подмножество этого фрейма данных с помощью 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"
Теги:
dataframe
r-factor
r-faq

14 ответов

343
Лучший ответ

Все, что вам нужно сделать, это снова применить factor() к вашей переменной после подмножества:

> 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

ИЗМЕНИТЬ

В примере с примерами факторов:

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)
  • 18
    Это хорошо для одноразового использования, но в data.frame с большим количеством столбцов вы можете сделать это для каждого столбца, который является фактором ... что приводит к необходимости использования такой функции, как drop.levels () из гдата.
  • 6
    Я вижу ... но с точки зрения пользователя это быстро написать что-то вроде subdf [] <- lapply (subdf, function (x) if (is.factor (x)) factor (x) else x) ... Is drop.levels () намного эффективнее в вычислительном отношении или лучше с большими наборами данных? (Полагаю, что для огромного фрейма данных пришлось бы переписать приведенную выше строку в цикле for.)
Показать ещё 4 комментария
439

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

levels(droplevels(subdf$letters))
  • 7
    Кроме того, вы можете просто немного прокрутить вниз ...
  • 0
    @ RomanLuštrik К сожалению, сортировка по голосам все еще делает принятый ответ № 1, хотя у него (сейчас) меньше голосов, чем у вас :-(
Показать ещё 2 комментария
36

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

options(stringsAsFactors = FALSE)

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

  • 6
    Вы также можете сделать read.csv (file = 'foo.csv', as.is = T).
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 значения.
  • 0
    Использование gdata только для drop.levels дает «gdata: read.xls поддержка« XLS »(Excel 97-2004) файлов ВКЛЮЧЕНА». "gdata: невозможно загрузить библиотеки perl, необходимые для read.xls ()" "gdata: для поддержки файлов 'XLSX' (Excel 2007+)." "gdata: запустить функцию 'installXLSXsupport ()'" "gdata: для автоматической загрузки и установки perl". Использовать уровни уровней от baseR ( stackoverflow.com/a/17218028/9295807 )
Показать ещё 1 комментарий
18

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

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

Изменить:

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

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

Здесь другой способ, который, я считаю, эквивалентен подходу 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"
  • 0
    Ха, после всех этих лет я не знал, что существует метод `[.factor` с аргументом drop и вы опубликовали это в 2009 году ...
7

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

Он отличается от 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
7

Взглянув на droplevels методов droplevels в droplevels коде R, вы увидите, что он переносится в factor функцию. Это означает, что вы можете воссоздать столбец с помощью factor функции.
Ниже data.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 путь будет что-то вроде for (j in names(DT)[sapply(DT, is.factor)]) set(DT, j = j, value = factor(DT[[j]]))
  • 1
    @DavidArenburg, здесь ничего не меняется, так как мы называем [.data.table только один раз
6

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

varFactor <- factor(letters[1:15])
varFactor <- varFactor[1:5]
varFactor <- varFactor[drop=T]
  • 1
    Это обман этого ответа, который был размещен 5 лет назад.
6

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

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

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

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

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

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

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

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

5

Я написал вспомогательные функции для этого. Теперь, когда я знаю о gdata drop.levels, он выглядит примерно так же. Вот они (отсюда):

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

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

   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))
  • 0
    Я имею в виду, что factor(as.chracter(...)) работает, но менее эффективно и лаконично, чем factor(...) . Кажется, строго хуже, чем другие ответы.
0

К сожалению, factor() не работает при использовании rxDataStep из RevoScaleR. Я делаю это в два этапа: 1) Преобразовать в символ и сохранить во временном внешнем фрейме данных (.xdf). 2) Преобразовать обратно в фактор и сохранить в определенном внешнем фрейме данных. Это исключает любые неиспользуемые уровни факторов без загрузки всех данных в память.

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

Когда я работаю с data.frame, я теперь использую options(stringsAsFactors = FALSE) в начале скрипта. Следовательно, персонажи остаются персонажами. Так как у меня больше нет проблем с факторами :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню