提问



我有一个包含因子的数据框。当我使用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"

最佳参考


您需要做的就是在子集化后再次将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)

其它参考1


自R版本2.12以来,有一个droplevels()函数。


levels(droplevels(subdf$letters))

其它参考2


如果你不想要这种行为,不要使用因素,而是使用字符向量。我认为这比后来修补更有意义。在使用read.tableread.csv加载数据之前,请尝试以下操作:


options(stringsAsFactors = FALSE)


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

其它参考3


这是一个已知问题,drop.levels()在gdata包中提供了一种可能的补救措施,其中您的示例变为[37]


> 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函数。但是,它只能通过改变子集运算符[来实现,并且在这里不适用。[38]


作为必然结果,基于每列的直接方法很简单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


dplyr做同样的另一种方式


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


 


还有效!感谢agenis


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

其它参考5


这是另一种方式,我认为这相当于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"

其它参考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")


不会工作。


当你有很多级别时,这显然不是理想的,但对于少数,它是快速和简单的。

其它参考7


这是一种方法


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

其它参考8


查看R源代码中的droplevels方法代码,您可以看到它包装到factor函数。这意味着您基本上可以使用factor函数重新创建列。

在data.table方式下面,从所有因子列中删除级别。 [40]





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


为了完整起见,现在forcats包中也有fct_drop http://forcats.tidyverse.org/reference/fct_drop.html。[41]


它与处理NA的方式不同droplevels:


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

其它参考10


我写了实用程序函数来执行此操作。现在我知道gdata的drop.levels,看起来非常相似。这里是(从这里):[42]


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


非常有趣的线程,我特别喜欢只想再次选择子选择的想法。之前我遇到过类似的问题,我只是转换为角色然后回到因素。


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