ChatGPT解决这个技术问题 Extra ChatGPT

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

我有一个包含 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"

R
Roman Luštrik

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

levels(droplevels(subdf$letters))

与使用 factor() 相比,此方法的一个优点是无需修改原始数据帧或创建新的持久数据帧。我可以将 droplevels 包裹在一个子集数据帧周围,并将其用作格函数的数据参数,并且组将得到正确处理。
我注意到,如果我的因子中有一个 NA 水平(真正的 NA 水平),即使存在 NA,它也会随着下降的水平而下降。
h
hatmatrix

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

这对于一次性来说很好,但是在具有大量列的 data.frame 中,您可以对作为一个因素的每一列执行此操作......导致需要诸如 drop.levels() 之类的函数来自 gdata。
我明白了...但是从用户的角度来看,编写类似 subdf[] <- lapply(subdf,function(x) if(is.factor(x)) factor(x) else x) ...Is 之类的东西很快drop.levels() 计算效率更高还是处理大型数据集更好? (我想,对于一个巨大的数据帧,必须在 for 循环中重写上面的行。)
谢谢 Stephen & Dirk - 我对这个因素表示赞同,但希望人们会阅读这些评论,以获取有关清理整个因素数据框架的建议。
作为副作用,该函数将数据框转换为列表,因此下面 Roman Luštrik 和 Tommy O'Dell 建议的 mydf <- droplevels(mydf) 解决方案更可取。
另外:此方法确实保留了变量的顺序。
h
hadley

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

options(stringsAsFactors = FALSE)

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


D
Dirk Eddelbuettel

这是一个已知问题,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"

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"

drop.levels 函数的 reorder 参数值得一提:如果您必须保留因子的原始顺序,请将其与 FALSE 值一起使用。
将 gdata 仅用于 drop.levels 会产生“gdata:read.xls 支持 'XLS' (Excel 97-2004) 文件已启用。” “gdata:无法加载 read.xls() 所需的 perl 库” “gdata:支持 'XLSX' (Excel 2007+) 文件。” "gdata: 运行函数 'installXLSXsupport()'" "gdata: 自动下载和安装 perl"。使用 baseR (stackoverflow.com/a/17218028/9295807) 中的 droplevels
事情会随着时间的推移而发生。您正在评论我九年前写的答案。因此,让我们以此为暗示,通常更喜欢基本的 R 解决方案,因为那些使用从现在起 N 年后仍将存在的功能的解决方案。
C
Community

另一种相同的方法,但使用 dplyr

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

编辑:

也有效!感谢agenis

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

A
Aurèle

为了完整起见,现在 forcatshttp://forcats.tidyverse.org/reference/fct_drop.html 中也有 fct_drop

它在处理 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

a
ars

这是另一种方式,我认为它等同于 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 年发布了这个......
M
Matt Parker

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

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

不会工作。

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


j
jangorecki

查看它封装到 factor 函数的 droplevels 方法 code in the R source you can see。这意味着您基本上可以使用 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"

我认为 data.table 方式类似于 for (j in names(DT)[sapply(DT, is.factor)]) set(DT, j = j, value = factor(DT[[j]]))
@DavidArenburg 它在这里没有太大变化,因为我们只调用一次 [.data.table
D
David Arenburg

这是一种方法

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

这是 5 年前发布的 this 答案的欺骗。
B
Brendan OConnor

我编写了实用程序函数来做到这一点。现在我知道了 gdata 的 drop.levels,它看起来非常相似。它们在这里(来自 here):

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
}

D
DfAC

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

   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(...)。似乎比其他答案更糟糕。
S
Sandy

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

对于所有包含零值级别的 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)

J
Jerome Smith

不幸的是,使用 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)

N
Naga Pakalapati

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

不确定性能问题。


S
Sebastian

一个真正的 droplevels 函数,它比 droplevels 快得多并且不执行任何类型的不必要的匹配或值制表是 collapse::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