在子集数据帧中删除未使用的因子水平

我有一个数据帧包含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"
441374 次浏览

这是一个已知的问题,您的示例所在的gdata包中的drop.levels()提供了一个可能的补救措施

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

Hmisc包中还有dropUnusedLevels函数。但是,它只能通过修改子集操作符[来工作,在这里不适用。

因此,基于每列的直接方法是简单的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"

这是令人讨厌的。我通常是这样做的,以避免加载其他包:

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

不能工作。

当你有很多关卡时,这显然不太理想,但对于少数关卡来说,这是快速而简单的。

你所要做的就是在子集设置后再次应用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)

如果你不想要这种行为,不要使用因子,而是使用字符向量。我觉得这比事后修补要好得多。在使用read.tableread.csv加载数据之前,请尝试以下操作:

options(stringsAsFactors = FALSE)

缺点是你只能按字母排序。(重新排序是你的朋友情节)

下面是另一种方法,我认为它相当于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"

我写了效用函数来做这个。现在我知道了gdata的drop。水平,看起来很相似。以下是它们(来自在这里):

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
}

从R版本2.12开始,就有了droplevels()函数。

levels(droplevels(subdf$letters))

这里有一种方法

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

非常有趣的话题,我特别喜欢因子子选择的想法。我以前遇到过类似的问题,我只是转换成字符,然后再转换回因子。

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

另一种方法是使用dplyr

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

# EYZ0

同样有效!感谢agenis

subdf <- df %>% filter(numbers <= 3) %>% droplevels
levels(subdf$letters)
查看droplevels方法你可以看到R源代码中的代码它包装为factor函数。这意味着您基本上可以使用factor函数重新创建列 下面是数据。从所有因子列中删除级别的表方式。< / p >

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"

为了完整起见,现在在forcatshttp://forcats.tidyverse.org/reference/fct_drop.html中也有fct_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
不幸的是,factor()在使用RevoScaleR的rxDataStep时似乎不工作。我分两步做: 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)
我已经尝试了这里的大多数例子,如果不是全部,但似乎没有一个在我的情况下工作。 在挣扎了相当一段时间后,我尝试使用as.character ()在因子列上将其更改为带有字符串的col,这似乎工作得很好

不确定性能问题。

真正的droplevels函数是collapse::fdroplevels,它比droplevels快得多,并且不执行任何不必要的匹配或数值列表。例子:

library(collapse)
library(microbenchmark)


# wlddev data supplied in collapse, iso3c is a factor
data <- fsubset(wlddev, iso3c %!in% "USA")


microbenchmark(fdroplevels(data), droplevels(data), unit = "relative")
## Unit: relative
##               expr  min       lq     mean   median       uq      max neval cld
##  fdroplevels(data)  1.0  1.00000  1.00000  1.00000  1.00000  1.00000   100  a
##   droplevels(data) 30.2 29.15873 24.54175 24.86147 22.11553 14.23274   100   b

谢谢你提出这个问题。然而,以上的解决方案都不适合我。我为这个问题做了一个变通方案,分享它以防其他人偶然发现这个问题:

对于所有包含零值级别的factor列,您可以首先将这些列转换为character类型,然后再将它们转换回factors类型。

对于上面的问题,只需添加以下代码行:

# Convert into character
subdf$letters = as.character(subdf$letters)


# Convert back into factor
subdf$letters = as.factor(subdf$letters)


# Verify the levels in the subset
levels(subdf$letters)