96

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

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

4

8 回答 8

49

你应该使用因子。是的,它们可能是一种痛苦,但我的理论是,它们之所以痛苦的 90% 是因为在read.tableandread.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()有所不同:请参阅此电子邮件?"[.data.frame". 使用drop=TRUEon data.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(),删除单个因子或 a 中每个因子的未使用因子水平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传奇人物。如果您使用字符向量伪造因子,则存在仅更改一个元素并意外创建单独的新级别的风险。

于 2010-08-10T01:52:26.127 回答
31

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

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", ]
于 2010-08-10T07:37:27.060 回答
20

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

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

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

于 2013-10-16T16:08:01.457 回答
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 的字符串函数使用起来非常简单且合乎逻辑。因此,在操纵时,我通常更喜欢字符而不是因素。

于 2010-08-10T02:38:26.453 回答
6

多么刻薄的标题!

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

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

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

于 2010-08-10T01:43:17.623 回答
1

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讨论。

于 2016-09-06T15:03:19.050 回答
0

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

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

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

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

正如你所看到的,我们没有得到xg平均值NA。如果我们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

是的,我们看到了xwhere ghas NAs 的平均值。有人可能会争辩说,相同的输出是可能的paste0(尝试aggregate(x ~ paste0(g), df, mean))。但只有addNA这样我们才能将NAs 反向转换为实际缺失。因此,让我们首先转换gaddNA然后对其进行反向转换:

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

现在NAs ing_back是实际的缺失。看看any(is.na(df$g_back))哪个返回一个TRUE.

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

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

另一方面,据我所知,我们不能进行反向转换vec_paste0 <- paste0(vec),因为 in和 thevec_paste0是相同的!看"NA"NA

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

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

于 2021-12-23T23:02:00.923 回答
-2

tapply(和aggregate)依赖于因素。这些功能的信息投入比非常高。

例如,在一行代码(下面的tapply调用)中,您可以通过 Cut 和 Color 获得钻石的平均价格:

> 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
于 2010-08-10T03:00:47.743 回答