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

Drop unused factor levels in a subsetted data frame

提问人:medriscoll 提问时间:7/29/2009 最后编辑:Henrikmedriscoll 更新时间:12/29/2021 访问量:450142

问:

我有一个包含 .当我使用或其他索引函数创建此数据帧的子集时,将创建一个新的数据框。但是,该变量将保留其所有原始级别,即使它们在新数据帧中不存在。factorsubsetfactor

这在执行分面绘图或使用依赖于因子水平的函数时会导致问题。

从新数据帧中的因子中删除水平的最简洁方法是什么?

下面是一个示例:

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"
数据帧 因子 R-常见问题解答

评论


答:

41赞 Dirk is no longer here 7/29/2009 #1

这是一个已知问题,您的示例变成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"

评论

5赞 daroczig 1/17/2011
该函数的参数值得一提:如果您必须保留因子的原始顺序,请将其与值一起使用。reorderdrop.levelsFALSE
0赞 Vrokipal 6/21/2018
仅将 gdata 用于 drop.levels 会生成“gdata:已启用对'XLS' (Excel 97-2004) 文件的读取.xls 支持”。“gdata:无法加载 read.xls()” “gdata:支持'XLSX' (Excel 2007+) 文件。”“gdata:运行函数 'installXLSXsupport()'” “gdata:自动下载并安装 perl”。使用 baseR (stackoverflow.com/a/17218028/9295807 中的液滴)
0赞 Dirk is no longer here 6/21/2018
事情会随着时间的推移而发生。你正在评论我九年前写的一个答案。因此,让我们以此为契机,通常更喜欢基本 R 解决方案,因为这些解决方案使用的功能在现在后仍将持续 N 年左右。
9赞 Matt Parker 7/29/2009 #2

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

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

行不通。

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

455赞 hatmatrix 7/29/2009 #3

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

评论

24赞 Dirk is no longer here 7/29/2009
这对于一次性来说很好,但是在具有大量列的 data.frame 中,您可以在每个列上执行此操作,这是一个因素......导致需要 gdata 中的 drop.levels() 等函数。
7赞 hatmatrix 7/30/2009
明白了。。。但从用户的角度来看,编写类似 subdf[] <- lapply(subdf,function(x) if(is.factor(x)) factor(x) else x) ...drop.levels() 在计算上更有效率还是在处理大型数据集时更好?(我想,对于一个巨大的数据帧,人们将不得不在for循环中重写上面的行。
1赞 medriscoll 7/30/2009
谢谢 Stephen & Dirk - 我为一个因素的 caes 竖起大拇指,但希望人们能阅读这些评论,了解您对清理整个因素数据帧的建议。
10赞 Johan 5/9/2014
作为副作用,该函数将数据框转换为列表,因此下面 Roman Luštrik 和 Tommy O'Dell 建议的解决方案更可取。mydf <- droplevels(mydf)
1赞 webelo 7/1/2016
另外:此方法确实保留了变量的顺序。
47赞 hadley 7/29/2009 #4

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

options(stringsAsFactors = FALSE)

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

16赞 ars 7/29/2009 #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"

评论

0赞 David Arenburg 2/13/2019
呵呵,这么多年了,我不知道有一种方法可以争论,而你在 2009 年发布了这个......`[.factor`drop
6赞 Brendan OConnor 9/2/2009 #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
}
530赞 Roman Luštrik 11/26/2010 #7

从 R 版本 2.12 开始,有一个函数。droplevels()

levels(droplevels(subdf$letters))

评论

9赞 Mars 11/21/2015
与使用方法相比,此方法的一个优点是无需修改原始数据帧或创建新的持久性数据帧。我可以包装一个子集的 DataFrame,并将其用作格函数的数据参数,并且组将被正确处理。factor()droplevels
0赞 Meep 7/5/2016
我注意到,如果我的因子中有 NA 水平(真正的 NA 水平),即使存在 NA,它也会下降。
7赞 Diogo 1/31/2014 #8

这是一种方法

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

评论

2赞 David Arenburg 2/13/2019
这是 5 年前发布的这个答案的骗局。
4赞 DfAC 5/25/2015 #9

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

   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赞 Gregor Thomas 2/13/2019
我的意思是,有效,但效率和简洁程度不如.严格来说,似乎比其他答案更糟糕。factor(as.chracter(...))factor(...)
28赞 Prradep 7/15/2015 #10

做同样事情的另一种方法,但dplyr

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

编辑:

也有效!感谢 agenis

subdf <- df %>% filter(numbers <= 3) %>% droplevels
levels(subdf$letters)
8赞 jangorecki 12/9/2015 #11

查看 R 源代码中的方法代码,可以看到它包装到函数。这意味着您基本上可以使用函数重新创建列。
在data.table方法下,从所有因子列中删除水平。
droplevelsfactorfactor

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赞 David Arenburg 1/24/2016
我认为方式是这样的data.tablefor (j in names(DT)[sapply(DT, is.factor)]) set(DT, j = j, value = factor(DT[[j]]))
1赞 jangorecki 11/30/2016
@DavidArenburg它在这里没有太大变化,因为我们只调用一次[.data.table
22赞 Aurèle 6/12/2017 #12

为了完整起见,现在包里也有 http://forcats.tidyverse.org/reference/fct_drop.htmlfct_dropforcats

它的不同之处在于它处理的方式:droplevelsNA

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
1赞 Jerome Smith 1/29/2019 #13

不幸的是,使用 RevoScaleR 的 rxDataStep 时,factor() 似乎不起作用。我分两步完成: 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赞 Naga Pakalapati 9/2/2019 #14

尝试了这里的大多数示例,如果不是全部,但似乎没有一个在我的情况下有效。 经过很长一段时间的挣扎,我尝试在因子列上使用 as.character() 将其更改为带有字符串的列,这似乎工作得很好。

不确定性能问题。

-1赞 Sebastian 1/9/2021 #15

一个真正的 droplevels 函数比 快得多,并且不执行任何不必要的值匹配或制表,是 。例:droplevelscollapse::fdroplevels

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
3赞 Sandy 12/29/2021 #16

感谢您发布此问题。但是,上述解决方案都不适合我。我为这个问题做了一个解决方法,分享它,以防其他人偶然发现这个问题:

对于包含具有零值的级别的所有列,可以先将这些列转换为类型,然后再将它们转换回 .factorcharacterfactors

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

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