R中的因素:除了烦恼之外?


95

R中的基本数据类型之一是因子。根据我的经验,因素基本上是痛苦的,我从不使用它们。我总是转换为字符。我感到奇怪的是,我想念一些东西。

在需要使用因子数据类型的情况下,有一些重要的函数示例将因子用作分组变量吗?是否有具体情况时,我应该使用的因素?


7
我为可能会发现此问题的初学者R用户添加此注释。我最近写了一篇博客,编译的多少从回答下面的信息到何时,如何以及为什么在R.使用因素的指导教程 gormanalysis.com/?p=115

我一直认为因素的存储比字符存储的效率更高,就像每个条目都是指向关卡的指针一样。但是在测试它来写这篇文章时,我发现那是不对的!
同构

2
@isomorphismes好,那使用的是真实的,在R的早期,但已经改变。请参阅此博客文章:simplystatistics.org/2015/07/24/…–
MichaelChirico

4
5+年后这个“stringsAsFactors:未经授权的传记”写:simplystatistics.org/2015/07/24/...
JD龙

Answers:


49

您应该使用因素。是的,它们可能会很痛苦,但我的理论是,为什么会造成疼痛的90%是因为in read.table和中的默认read.csv参数stringsAsFactors = TRUE(大多数用户都忽略了这种微妙之处)。我说它们很有用,因为模型拟合程序包(例如lme4)使用因素和有序因素来差异拟合模型并确定要使用的对比类型。图形包也使用它们进行分组。ggplot而且大多数模型拟合函数都会将特征向量强制转换为因子,因此结果是相同的。但是,最终在代码中出现警告:

lm(Petal.Length ~ -1 + Species, data=iris)

# Call:
# lm(formula = Petal.Length ~ -1 + Species, data = iris)

# Coefficients:
#     Speciessetosa  Speciesversicolor   Speciesvirginica  
#             1.462              4.260              5.552  

iris.alt <- iris
iris.alt$Species <- as.character(iris.alt$Species)
lm(Petal.Length ~ -1 + Species, data=iris.alt)

# Call:
# lm(formula = Petal.Length ~ -1 + Species, data = iris.alt)

# Coefficients:
#     Speciessetosa  Speciesversicolor   Speciesvirginica  
#             1.462              4.260              5.552  

警告消息:在model.matrix.default(mt, mf, contrasts)

变量Species转换为factor

一件棘手的事情是整体drop=TRUE。在向量中,这很好地删除了数据中没有的因子水平。例如:

s <- iris$Species
s[s == 'setosa', drop=TRUE]
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa
s[s == 'setosa', drop=FALSE]
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa versicolor virginica

但是,对于data.frame,的行为[.data.frame()有所不同:请参阅此电子邮件?"[.data.frame"。像您想象的那样,drop=TRUEdata.frames上使用不起作用:

x <- subset(iris, Species == 'setosa', drop=TRUE)  # susbetting with [ behaves the same way
x$Species
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa versicolor virginica

幸运的是,您可以轻松droplevels()地删除因子,并为单个因子或data.frameR 中的每个因子降低未使用的因子水平(自R 2.12起):

x <- subset(iris, Species == 'setosa')
levels(x$Species)
# [1] "setosa"     "versicolor" "virginica" 
x <- droplevels(x)
levels(x$Species)
# [1] "setosa"

这是防止您选择的级别进入ggplot图例的方法。

在内部,factors是带有属性级别字符向量的整数(请参阅attributes(iris$Species)class(attributes(iris$Species)$levels)),这很干净。如果必须更改级别名称(并且您使用的是字符串),这将是效率得多的操作。而且我更改了很多级别名称,尤其是对于ggplot图例。如果使用字符向量伪造因子,则可能会只改变一个元素,并意外地创建一个单独的新级别。


1
stringsAsFactors不是功能。
IRTFM

30

有序因素很棒,如果我碰巧喜欢橙子和恨苹果,但不介意葡萄,我不需要管理一些怪异的指标就可以这样说:

d <- data.frame(x = rnorm(20), f = sample(c("apples", "oranges", "grapes"), 20, replace = TRUE, prob = c(0.5, 0.25, 0.25)))
d$f <- ordered(d$f, c("apples", "grapes", "oranges"))
d[d$f >= "grapes", ]

那是一个整洁的应用程序。没想到。
JD 2010年

怎么了d$f <- ordered(d$f, c("apples", "grapes", "oranges"))?我猜想它在数据框中排序了,但是在运行该行并打印数据框之后,没有任何变化。即使印刷顺序没有变化,它是否仅施加内部顺序?
Addem 2014年

...是的,我认为我写的东西像是正确的句子。如果我理解您的观点,那么您就是在向我们表明可以对因子进行排序,而这对于字符串是无法做到的。
Addem 2014年

4
ordered()根据任何值创建任意排序-按您说的顺序排序。不幸的是,我使用了按字典顺序排序的值,这是一个巧合。例如,我将其用于数据中“ Z”不好,“ 3”很好但标签不是数字字母的情况-因此我将其排序(数据,c(“ Z”,“ B”,“ A”,“ 0“,” 1“,” 2“,” 3“))),然后我就可以执行数据>” A“了,这是快乐的日子。
mdsumner 2014年

19

A factor与其他语言中的枚举类型最相似。它的适当用途是仅可使用一组规定值之一的变量。在这些情况下,并非所有可能的允许值都可以出现在任何特定的数据集中,并且“空”级别可以准确地反映出这一点。

考虑一些例子。对于整个美国收集的某些数据,应将州记录为一个因素。在这种情况下,没有从特定州收集任何案件的事实是相关的。可能有来自该状态的数据,但是发生了(无论出于何种原因,可能是引起人们关注的一个原因)。如果收集故乡,那将不是一个因素。没有预先设定的可能的家乡。如果从三个镇而不是从全国收集数据,则该镇将是一个因素:一开始就给出了三种选择,并且如果在这三个镇之一中未找到相关的案例/数据,则是相关的。

factors的其他方面,例如提供一种对一组字符串赋予任意排序顺序的方式,是factors的有用的辅助特征,但不是其存在的原因。


3
+1。布莱恩,我认为您无法捕获数据中没有显示的电平,这真是令人头疼。
里卡多·萨波特塔2013年

13

当人们进行统计分析和实际探究数据时,这些因素是奇妙的。但是,在此之前,在读取,清洁,故障排除,合并和一般地操作数据时,因素是总的困扰。最近,像过去几年一样,许多功能已得到改进,可以更好地处理这些因素。例如,rbind与他们打得很好。我仍然发现在子集函数之后留下空白级别是很麻烦的。

#drop a whole bunch of unused levels from a whole bunch of columns that are factors using gdata
require(gdata)
drop.levels(dataframe)

我知道重新编码一个因子的水平并重新标记标签很简单,而且还有许多很好的方法可以对水平进行重新排序。我的大脑无法记住它们,每次使用它都必须重新学习它。重新编码应该比现在容易得多。

R的字符串函数非常容易使用且合乎逻辑。因此,在操作时,我通常更喜欢字符而不是因素。


1
您是否有使用因素的统计分析示例?
JD 2010年

3
现在有一个base-R函数droplevels()。并且默认情况下,它不会重新排序因素。
Ben Bolker

6

多么狡猾的头衔!

我相信许多估算函数都允许您使用因子轻松定义虚拟变量...但是我不打算将它们用于此。

当我有非常大的字符向量而很少有独特观察时,我会使用它们。这可以减少内存消耗,尤其是在字符向量中的字符串较长的情况下。

PS-我在开玩笑。我看到了你的推文。;-)


1
因此,您实际上只是使用它们来节省存储空间。这就说得通了。
JD 2010年

13
好吧,至少它曾经;;)。但是一些R版本以前,字符存储被重写为内部哈希,因此此历史性论证的一部分现在无效。静止因素对于分组和建模非常有用。
Dirk Eddelbuettel

1
根据?factorR-2.6.0,它说:“整数值存储在4个字节中,而对字符串的每个引用都需要4或8个字节的指针。” 如果字符串需要8个字节,是否可以节省转换为因数的空间?
Joshua Ulrich 2010年

2
N <-1000; a <-sample(c(“ a”,“ b”,“ c”),N,replace = TRUE); print(object.size(a),units =“ Kb”); print(object.size(factor(a)),units =“ Kb”); 8 Kb 4.5 Kb,因此似乎仍然可以节省一些空间。
爱德华多·莱昂尼

2
@Eduardo我得到了4Kb和4.2Kb。因为N=100000我得到了391.5 Kb和391.8 Kb。因此,factor只占用很少的内存。
马雷克(Marek)2010年

1

因素是出色的“独特情况”徽章引擎。我已经多次重创了这个效果,尽管偶尔会出现一些皱纹,但是它们非常强大。

library(dplyr)
d <- tibble(x = sample(letters[1:10], 20, replace = TRUE))

## normalize this table into an indexed value across two tables
id <- tibble(x_u = sort(unique(d$x))) %>% mutate(x_i = row_number())
di <- tibble(x_i = as.integer(factor(d$x)))


## reconstruct d$x when needed
d2 <- inner_join(di, id) %>% transmute(x = x_u)
identical(d, d2)
## [1] TRUE

如果有更好的方法来完成此任务,我很乐意看到它,但我看不到这种factor讨论的功能。


-2

轻拍(和合计)取决于因素。这些功能的信息/工作量比率非常高。

例如,在一行代码中(请点击下面的tapply),您可以通过切割和颜色获得钻石的平均价格:

> data(diamonds, package="ggplot2")

> head(dm)

   Carat     Cut    Clarity Price Color
1  0.23     Ideal     SI2   326     E
2  0.21   Premium     SI1   326     E
3  0.23      Good     VS1   327     E


> tx = with(diamonds, tapply(X=Price, INDEX=list(Cut=Cut, Color=Color), FUN=mean))

> a = sort(1:diamonds(tx)[2], decreasing=T)  # reverse columns for readability

> tx[,a]

         Color
Cut         J    I    H    G    F    E    D
Fair      4976 4685 5136 4239 3827 3682 4291
Good      4574 5079 4276 4123 3496 3424 3405
Very Good 5104 5256 4535 3873 3779 3215 3470
Premium   6295 5946 5217 4501 4325 3539 3631
Ideal     4918 4452 3889 3721 3375 2598 2629

7
这不是一个很好的示例,因为所有这些示例也都适用于字符串。
hadley
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.