子集数据帧中的丢包因子级别


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"

Answers:


420

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

22
一次过就很好,但是在具有大量列的data.frame中,您必须在每个列上都这样做,这是一个因素...导致需要诸如drop.levels()之类的功能来自gdata。
Dirk Eddelbuettel,2009年

6
我看到了...但是从用户的角度来看,可以很快写出类似subdf [] <-lapply(subdf,function(x)if(is.factor(x))factor(x)else x)的东西…… drop.levels()在大型数据集上的计算效率更高还是更好?(我想一个人必须在for循环中重写上面的行,以获取巨大的数据帧。)
hatmatrix

1
谢谢Stephen&Dirk-我赞成这一观点,但希望人们能读到这些评论,以就清理因子的整个数据框架提出建议。
medriscoll

9
作为副作用,该函数将数据帧转换为列表,因此mydf <- droplevels(mydf)下面由RomanLuštrik和Tommy O'Dell建议的解决方案是更可取的。
2014年

1
另外:此方法确实保留了变量的顺序。
webelo '16

492

从R版本2.12开始,提供了一个droplevels()功能。

levels(droplevels(subdf$letters))

7
与使用相比,此方法的一个优点factor()是不需要修改原始数据帧或创建新的持久数据帧。我可以包装droplevels一个子集的数据框,并将其用作晶格函数的数据参数,然后将正确处理组。
火星

我注意到,如果我的因子中有NA水平(真正的NA水平),即使存在NA,它也会被降低的水平所降低。
2013年

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"

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
该函数的reorder参数drop.levels值得一提:如果必须保留因子的原始顺序,请与FALSE值一起使用。
daroczig 2011年

仅将gdata用于drop.levels会产生“ gdata:read.xls支持'XLS'(Excel 97-2004)文件”。“” gdata:无法加载read.xls()所需的perl库。“” gdata:支持'XLSX'(Excel 2007+)文件。“” gdata:运行功能'installXLSXsupport()'“”“ gdata:自动下载并安装perl”。使用来自baseR的droplevel(stackoverflow.com/a/17218028/9295807
Vrokipal,2018年

随时间推移会发生填充。您评论我九年前写的答案。因此,让我们以此作为暗示,通常更喜欢基本R解决方案,因为那些使用功能的解决方案从现在起将持续N年。
德克·埃德比布特

25

这样做的另一种方法是 dplyr

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

编辑:

也可以!感谢agenis

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


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年发表了这一点……
David Arenburg

8

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

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

将无法正常工作。

当您有很多关卡时,这显然不是理想的选择,但是对于一些关卡来说,这是快速且容易的。


8

查看R源代码中droplevels方法代码,您可以看到它可以包装factor功能。这意味着您基本上可以使用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"

1
我觉得data.table这样会是这样的for (j in names(DT)[sapply(DT, is.factor)]) set(DT, j = j, value = factor(DT[[j]]))
大卫Arenburg

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

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

如果不是全部,都尝试过这里的大多数示例,但在我看来,这些示例都不起作用。在奋斗了一段时间之后,我尝试在factor列上使用as.character()将其更改为带有字符串的col,这似乎工作得很好。

不确定性能问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.