У меня есть фрейм данных, содержащий factor
. Когда я создаю подмножество этого фрейма данных, используя subset
или другую функцию индексации, создается новый фрейм данных. Однако переменная factor
сохраняет все свои исходные уровни, даже если/если они не существуют в новом кадре данных.
Это вызывает проблемы при выполнении граненых графиков или при использовании функций, основанных на факторных уровнях.
Какой самый короткий способ удалить уровни из фактора в новом фрейме данных?
Вот пример:
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"
Ответ 1
Все, что вам нужно сделать, это снова применить 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)
Ответ 2
Так как R версия 2.12, там есть функция droplevels()
.
levels(droplevels(subdf$letters))
Ответ 3
Если вы не хотите этого поведения, не используйте факторы, используйте вместо него векторы символов. Я думаю, что это имеет больше смысла, чем исправление. Перед загрузкой данных выполните следующие действия: read.table
или read.csv
:
options(stringsAsFactors = FALSE)
Недостатком является то, что вы ограничены алфавитным порядком. (переупорядочить ваш друг для сюжетов)
Ответ 4
Это известная проблема, и одно возможное решение предоставляется 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"
Ответ 5
Другой способ сделать то же самое, но с dplyr
library(dplyr)
subdf <- df %>% filter(numbers <= 3) %>% droplevels()
str(subdf)
Изменить:
Также работает! Благодаря agenis
subdf <- df %>% filter(numbers <= 3) %>% droplevels
levels(subdf$letters)
Ответ 6
Для полноты картины теперь в пакете 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
Здесь другой способ, который, я считаю, эквивалентен подходу 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"
Ответ 8
Взглянув на 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"
Ответ 9
Это неприятно. Так я обычно это делаю, чтобы не загружать другие пакеты:
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")
не будет работать.
Это, очевидно, не идеально, когда у вас много уровней, но для некоторых это легко и быстро.
Ответ 10
Я написал вспомогательные функции для этого. Теперь, когда я знаю о 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
}
Ответ 11
вот способ сделать это
varFactor <- factor(letters[1:15])
varFactor <- varFactor[1:5]
varFactor <- varFactor[drop=T]
Ответ 12
Очень интересная тема, мне особенно понравилась идея просто повторить подзаголовок. Раньше у меня была аналогичная проблема, и я просто перешел к символу, а затем вернулся к коэффициенту.
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
Когда я работаю с data.frame
, я теперь использую options(stringsAsFactors = FALSE)
в начале скрипта. Следовательно, персонажи остаются персонажами. Так как у меня больше нет проблем с факторами :)
Ответ 14
К сожалению, 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)
Ответ 15
Попробовал большинство примеров здесь, если не все, но ни один, кажется, не работает в моем случае.
После долгого времени я пытался использовать as.character() в столбце фактора, чтобы изменить его на столбец со строками, который, кажется, работает нормально.
Не уверен в проблемах с производительностью.