R中的因素:不仅仅是烦恼?

Posted

技术标签:

【中文标题】R中的因素:不仅仅是烦恼?【英文标题】:Factors in R: more than an annoyance? 【发布时间】:2011-03-27 14:18:39 【问题描述】:

R 中的基本数据类型之一是因子。根据我的经验,因素基本上是一种痛苦,我从不使用它们。我总是转换成字符。我奇怪地觉得我错过了什么。

在需要使用因子数据类型的情况下,是否有一些使用因子作为分组变量的重要函数示例?是否存在我应该使用因子的特定情况?

【问题讨论】:

我正在为可能会找到此问题的初学者 R 用户添加此评论。我最近写了一篇博文,将下面答案中的大部分信息汇编成一个关于何时、如何以及为什么在 R 中使用因子的指导教程。gormanalysis.com/?p=115 我一直认为因子的存储比字符更有效——好像每个条目都是指向级别的指针。但是在测试它以编写此内容时,我发现这不是真的! @isomorphismes 很好,在 R 的早期,曾经是真的,但现在已经改变了。请参阅此博客文章:simplystatistics.org/2015/07/24/… 5 年多后,这本“stringsAsFactors:未经授权的传记”写成:simplystatistics.org/2015/07/24/… 【参考方案1】:

您应该使用因子。是的,它们可能是一种痛苦,但我的理论是,它们之所以痛苦的 90% 是因为在 read.tableread.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.frames,[.data.frame() 的行为是不同的:参见this email 或?"[.data.frame"。在data.frames 上使用drop=TRUE 并不像你想象的那样工作:

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.frame 中每个因子的未使用因子水平(自 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 传奇。如果您使用字符向量伪造因子,则存在仅更改一个元素并意外创建单独的新级别的风险。

【讨论】:

stringsAsFactors 不是函数。【参考方案2】:

有序因素很棒,如果我碰巧喜欢橙子和讨厌苹果但不介意葡萄,我不需要管理一些奇怪的索引来这么说:

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", ]

【讨论】:

这是一个简洁的应用程序。从来没想过。 d$f &lt;- ordered(d$f, c("apples", "grapes", "oranges")) 做了什么?我会猜到它在数据框中订购了这些,但是在我运行该行并打印数据框之后,没有任何变化。即使打印的订单没有改变,它是否只是强加一个内部订单? ... 是的,我认为我写的是一个正确的句子。如果我理解您的观点,您就是在向我们展示您可以为因子分配排序,这是您无法对字符串执行的操作。 ordered() 从任何值创建任意排序 - 按照您所说的排序顺序。不幸的是,我使用了按字典顺序排序的值,这是一个巧合。例如,我将它用于“Z”不好,“3”很好但标签不是数字字母的数据 - 所以我做了ordered(data, c("Z", "B ", "A", "0", "1", "2", "3")) 等然后我就可以做数据 > "A" 和快乐的日子。【参考方案3】:

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

考虑一些例子。对于在美国各地收集的一些数据,州应该被记录为一个因素。在这种情况下,没有从特定州收集病例的事实是相关的。可能有来自该州的数据,但发生了(无论出于何种原因,这可能是一个感兴趣的原因)没有。如果收集了家乡,那将不是一个因素。没有预先说明的一组可能的家乡。如果数据是从三个城镇而不是全国收集的,那么城镇将是一个因素:一开始就给出了三种选择,如果在这三个城镇之一中没有发现相关的案例/数据,那就是相关的。

factors 的其他方面,例如提供一种为一组字符串提供任意排序顺序的方法,是factors 有用的次要特征,但不是它们存在的原因。

【讨论】:

+1。 Brian,我认为您在捕获数据中不存在的级别时一针见血。【参考方案4】:

在进行统计分析和实际探索数据时,因素非常有用。然而,在此之前,当一个人阅读、清理、故障排除、合并和一般操作数据时,因素是一件非常痛苦的事情。最近,与过去几年一样,许多功能得到了改进,可以更好地处理这些因素。例如,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 的字符串函数使用起来非常简单且合乎逻辑。因此,在操纵时,我通常更喜欢字符而不是因素。

【讨论】:

您有使用因子的统计分析示例吗? 现在有一个 base-R 函数 droplevels()。默认情况下它不会重新排序因子。【参考方案5】:

多么刻薄的标题!

我相信许多估计函数允许您使用因子来轻松定义虚拟变量……但我不使用它们。

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

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

【讨论】:

所以你真的只是用它们来节省存储空间。这是有道理的。 好吧,至少它曾经是;-)。但是在几个 R 版本之前,字符存储被重写为内部散列,所以这个历史论点的一部分现在是无效的。因子对于分组和建模仍然非常有用。 根据?factor,它是 R-2.6.0,它说:“整数值存储在 4 个字节中,而对字符串的每个引用都需要一个 4 或 8 个字节的指针。”如果字符串需要 8 个字节,你会节省转换为因子的空间吗? N @Eduardo 我得到了 4Kb 和 4.2Kb。对于N=100000,我得到了 391.5 Kb 和 391.8 Kb。所以 factor 需要更多的内存。【参考方案6】:

Factors 是一个出色的“独特案例”标记引擎。我已经多次糟糕地重现了这种情况,尽管偶尔会出现一些皱纹,但它们非常强大。

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 的这种功能被讨论过。

【讨论】:

【参考方案7】:

只有因子我们可以通过将它们设置为因子水平来处理NAs。这很方便,因为许多函数都忽略了NA 值。让我们生成一些玩具数据:

df <- data.frame(x= rnorm(10), g= c(sample(1:2, 9, replace= TRUE), NA))

如果我们想要按g 分组的x 的方法,我们可以使用

aggregate(x ~ g, df, mean)
  g          x
1 1  1.0415156
2 2 -0.3071171

如您所见,对于gNA 的情况,我们没有得到x 的平均值。如果我们改用by,同样的问题也会发生(参见by(df$x, list(df$g), mean))。还有许多其他类似的例子,其中函数(默认或一般情况下)不考虑NAs。

但我们可以添加NA 作为因子水平。见这里:

aggregate(x ~ addNA(g), df, mean)
  addNA(g)          x
1        1 -0.2907772
2        2 -0.2647040
3     <NA>  1.1647002

是的,我们看到x 的平均值,其中g 具有NAs。有人可能会争辩说,paste0 可以实现相同的输出,这是真的(试试aggregate(x ~ paste0(g), df, mean))。但只有使用addNA,我们才能将NAs 反向转换为实际缺失。所以我们先将g 转换成addNA,然后再进行反向转换:

df$g_addNA <- addNA(df$g)
df$g_back <- factor(as.character(df$g_addNA))
 [1] 2    2    1    1    1    2    2    1    1    <NA>
Levels: 1 2

现在g_back 中的NAs 是实际缺失的。请参阅any(is.na(df$g_back)),它返回一个TRUE

这甚至适用于 "NA" 是原始向量中的值的奇怪情况!例如,向量vec &lt;- c("a", "NA", NA) 可以使用vec_addNA &lt;- addNA(vec) 进行转换,我们实际上可以使用

as.character(vec_addNA)
[1] "a"  "NA" NA

另一方面,据我所知,我们不能对vec_paste0 &lt;- paste0(vec) 进行反向转换,因为在vec_paste0 中,"NA"NA 是相同的!见

vec_paste0
[1] "a"  "NA" "NA"

我以“只有通过将 NA 设置为因子水平才能处理 NA 的因子”开始回答。事实上,我会小心使用addNA,但不管与addNA 相关的风险如何,事实表明角色没有类似的选项。

【讨论】:

【参考方案8】:

应用(和聚合)取决于因素。这些功能的信息量比非常高。

例如,在一行代码中(调用下面的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

【讨论】:

这不是一个很好的例子,因为所有这些例子也适用于字符串。

以上是关于R中的因素:不仅仅是烦恼?的主要内容,如果未能解决你的问题,请参考以下文章

数据集必须包含 R 中 SVM 中的所有因素

基于R语言开展方差分析—双因素方差分析

R语言使用aov函数进行单因素方差分析(One-way ANOVA)R语言使用gplots包中的plotmeans函数可用于生成组均值及其置信区间f的图(95%置信区间的治疗方法的曲线图)

R语言使用aov函数进行双因素方差分析(Two-way factorial ANOVA)使用HH包中的interaction2wt函数为任何阶的双因素方差分析可视化主效应和交互作用图箱图显示主效应

R语言manova函数多元方差分析(MANOVA)单因素多元方差分析的两个假设是多元正态性和方差-协方差矩阵的齐性QQ图评估多元正态性mvoutlier包中的aq.plot函数检验多变量异常值

R语言使用aov函数进行单因素协方差分析(One-way ANCOVA)使用effects包中的effect函数来计算调整后的分组均值(calculate adjusted means)