Рівень коефіцієнта падіння в заданому кадрі даних


543

У мене є кадр даних, що містить 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"

Відповіді:


420

Все, що вам потрібно зробити, це знову застосувати фактори () до вашої змінної після підмножини:

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

22
Це добре для одноразового, але в data.frame з великою кількістю стовпців, ви можете зробити це для кожного стовпця, який є фактором ... що призводить до необхідності функції, такої як drop.levels () від гдата.
Дірк Еддельбуеттель

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

1
Дякую, Стівен та Дірк - я даю цьому великий палець для кеса одного фактора, але, сподіваємось, люди прочитають ці коментарі щодо ваших пропозицій щодо очищення цілого кадру даних факторів.
medriscoll

9
Як побічний ефект функція перетворює кадр даних у список, тому mydf <- droplevels(mydf)рішення, запропоноване Романом Луштриком та Томмі О'Деллом нижче, є кращим.
Йоган

1
Крім того : цей метод робить збереження порядку змінного.
webelo

492

З R версії 2.12 є droplevels()функція.

levels(droplevels(subdf$letters))

7
Перевага цього методу перед використанням factor()полягає в тому, що не потрібно змінювати вихідний кадр даних або створювати новий стійкий кадр даних. Я можу обернутись droplevelsнавколо заданого фрейму даних і використовувати його як аргумент даних для функції решітки, і групи будуть оброблятися правильно.
Марс

Я помітив, що якщо я маю рівень NA у своєму факторі (справжній рівень NA), він знижується на знижений рівень, навіть якщо НА є.
Meep

46

Якщо ви не хочете такої поведінки, не використовуйте факторів, використовуйте натомість символи векторів. Я думаю, що це має більше сенсу, ніж виправлення речей згодом. Спробуйте наступне : перед завантаженням даних з допомогою read.tableабо read.csv:

options(stringsAsFactors = FALSE)

Недоліком є ​​те, що ви обмежені в алфавітному порядку. (упорядкувати ваш друг для сюжетів)


38

Це відома проблема, і один із можливих засобів захисту надається 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
reorderПараметр drop.levelsфункції варто згадати: якщо у вас є , щоб зберегти первісний порядок ваших факторів, використовувати його FALSEзначення.
daroczig

Використання gdata для просто drop.levels дає "gdata: підтримка read.xls для файлів" XLS "(Excel 97-2004) ВИМКНЕНО." "gdata: неможливо завантажити бібліотеки perl, необхідні read.xls ()" "gdata: для підтримки файлів" XLSX "(Excel 2007+)." "gdata: запустіть функцію 'installXLSXsupport ()'" "gdata: автоматично завантажувати та встановлювати perl". Використовуйте droplevels від низинних ( stackoverflow.com/a/17218028/9295807 )
Vrokipal

Речі трапляються з часом. Ви будете коментувати відповідь я написав дев'ять років тому. Тож давайте сприймемо це як підказку, як правило, віддають перевагу базовим R-рішенням, оскільки ті, які використовують функціонал, який поки що буде приблизно через N років.
Дірк Еддельбуеттель

25

Ще один спосіб зробити те ж саме, але з dplyr

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

Редагувати:

Також працює! Завдяки agenis

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

17

Для повноти картини , тепер є і 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

15

Ось ще один спосіб, який я вважаю рівнозначним 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"

Ха, після всіх цих років я не знав, `[.factor`що існує dropаргумент, який має аргументи, і ви розмістили це у 2009 році ...
Девід Аренбург

8

Це неприємно. Так я зазвичай роблю, щоб не завантажувати інші пакунки:

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

не буде працювати.

Це, очевидно, не ідеально, коли у вас багато рівнів, але для кількох це швидко і легко.


8

Дивлячись на кодdroplevels методів у джерелі R, ви можете бачити, як він працює для factorфункціонування. Це означає, що ви можете в основному відтворити стовпчик з factorфункцією.
Нижче наведено спосіб зменшення рівнів з усіх стовпців-факторів.

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лише один раз
jangorecki

7

ось спосіб зробити це

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

2
Це є відповіді на цю відповідь, яку було опубліковано 5 років раніше.
Девід Аренбург

6

Я написав функції утиліти для цього. Тепер, коли я знаю про 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))

Я маю на увазі, factor(as.chracter(...))працює, але лише менш ефективно та лаконічно, ніж factor(...). Здається, суворо гірше, ніж інші відповіді.
Грегор Томас

1

На жаль, фактор (), здається, не працює при використанні 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)

1

Я спробував більшість прикладів тут, якщо не всі, але жоден, здається, не працюють у моєму випадку. Пробуючи довгий час, я намагався використовувати as.character () на стовпчику факторів, щоб змінити його на col із рядками, які, здається, працюють добре.

Не впевнений у питаннях продуктивності.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.