为什么使用 as.factor() 而不是 factor()
Why use as.factor() instead of just factor()
我最近看到 Matt Dowle 用 as.factor()
写了一些代码,特别是
for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))
在 a comment to this answer.
我使用了这个片段,但我需要明确设置因子水平以确保水平以我想要的顺序出现,所以我不得不改变
as.factor(dt[[col]])
至
factor(dt[[col]], levels = my_levels)
这让我开始思考:使用 as.factor()
与仅使用 factor()
有什么好处(如果有的话)?
as.factor
是 factor
的包装器,但如果输入向量已经是一个因子,它允许快速 return:
function (x)
{
if (is.factor(x))
x
else if (!is.object(x) && is.integer(x)) {
levels <- sort(unique.default(x))
f <- match(x, levels)
levels(f) <- as.character(levels)
if (!is.null(nx <- names(x)))
names(f) <- nx
class(f) <- "factor"
f
}
else factor(x)
}
来自 Frank 的评论:它不仅仅是一个包装器,因为这个 "quick return" 将保留原样的因子水平,而 factor()
不会:
f = factor("a", levels = c("a", "b"))
#[1] a
#Levels: a b
factor(f)
#[1] a
#Levels: a
as.factor(f)
#[1] a
#Levels: a b
两年后的扩展答案,包括以下内容:
- 说明书怎么说的?
- 性能:
as.factor
> factor
当输入是一个因素时
- 性能:
as.factor
> factor
当输入为整数时
- 未使用的级别或 NA 级别
- 使用 R 的分组函数时的注意事项:注意未使用的或 NA 级别
说明书怎么说的?
?factor
的文档提到了以下内容:
‘factor(x, exclude = NULL)’ applied to a factor without ‘NA’s is a
no-operation unless there are unused levels: in that case, a
factor with the reduced level set is returned.
‘as.factor’ coerces its argument to a factor. It is an
abbreviated (sometimes faster) form of ‘factor’.
性能:as.factor
> factor
当输入是一个因素时
"no-operation"这个词有点歧义。不要把它当作"doing nothing";实际上,它的意思是 "doing a lot of things but essentially changing nothing"。这是一个例子:
set.seed(0)
## a randomized long factor with 1e+6 levels, each repeated 10 times
f <- sample(gl(1e+6, 10))
system.time(f1 <- factor(f)) ## default: exclude = NA
# user system elapsed
# 7.640 0.216 7.887
system.time(f2 <- factor(f, exclude = NULL))
# user system elapsed
# 7.764 0.028 7.791
system.time(f3 <- as.factor(f))
# user system elapsed
# 0 0 0
identical(f, f1)
#[1] TRUE
identical(f, f2)
#[1] TRUE
identical(f, f3)
#[1] TRUE
as.factor
确实给出了一个快速 return,但 factor
不是真正的 "no-op"。让我们分析一下 factor
看看它做了什么。
Rprof("factor.out")
f1 <- factor(f)
Rprof(NULL)
summaryRprof("factor.out")[c(1, 4)]
#$by.self
# self.time self.pct total.time total.pct
#"factor" 4.70 58.90 7.98 100.00
#"unique.default" 1.30 16.29 4.42 55.39
#"as.character" 1.18 14.79 1.84 23.06
#"as.character.factor" 0.66 8.27 0.66 8.27
#"order" 0.08 1.00 0.08 1.00
#"unique" 0.06 0.75 4.54 56.89
#
#$sampling.time
#[1] 7.98
它首先sort
输入向量f
的unique
值,然后将f
转换为字符向量,最后使用factor
进行强制转换字符向量回到一个因素。这里是factor
的源代码以供确认。
function (x = character(), levels, labels = levels, exclude = NA,
ordered = is.ordered(x), nmax = NA)
{
if (is.null(x))
x <- character()
nx <- names(x)
if (missing(levels)) {
y <- unique(x, nmax = nmax)
ind <- sort.list(y)
levels <- unique(as.character(y)[ind])
}
force(ordered)
if (!is.character(x))
x <- as.character(x)
levels <- levels[is.na(match(levels, exclude))]
f <- match(x, levels)
if (!is.null(nx))
names(f) <- nx
nl <- length(labels)
nL <- length(levels)
if (!any(nl == c(1L, nL)))
stop(gettextf("invalid 'labels'; length %d should be 1 or %d",
nl, nL), domain = NA)
levels(f) <- if (nl == nL)
as.character(labels)
else paste0(labels, seq_along(levels))
class(f) <- c(if (ordered) "ordered", "factor")
f
}
所以函数 factor
实际上是为处理字符向量而设计的,它会将 as.character
应用于其输入以确保这一点。我们至少可以从上面了解到两个与性能相关的问题:
- 对于一个数据框
DF
,如果有很多列是现成的因素,lapply(DF, as.factor)
在类型转换方面比lapply(DF, factor)
快得多。
- 那个函数
factor
很慢可以解释为什么一些重要的R函数很慢,比如table
:
性能:as.factor
> factor
当输入为整数时
因子变量是整数变量的近亲。
unclass(gl(2, 2, labels = letters[1:2]))
#[1] 1 1 2 2
#attr(,"levels")
#[1] "a" "b"
storage.mode(gl(2, 2, labels = letters[1:2]))
#[1] "integer"
这意味着将整数转换为因数比将数字/字符转换为因数更容易。 as.factor
只管这个。
x <- sample.int(1e+6, 1e+7, TRUE)
system.time(as.factor(x))
# user system elapsed
# 4.592 0.252 4.845
system.time(factor(x))
# user system elapsed
# 22.236 0.264 22.659
未使用的级别或 NA 级别
现在让我们看一些关于 factor
和 as.factor
对因子水平的影响的例子(如果输入已经是一个因子)。 Frank给了一个未使用的因子水平,我会提供一个NA
水平。
f <- factor(c(1, NA), exclude = NULL)
#[1] 1 <NA>
#Levels: 1 <NA>
as.factor(f)
#[1] 1 <NA>
#Levels: 1 <NA>
factor(f, exclude = NULL)
#[1] 1 <NA>
#Levels: 1 <NA>
factor(f)
#[1] 1 <NA>
#Levels: 1
有一个(通用)函数 droplevels
可用于删除未使用的因子水平。但是NA
级默认是不能掉的
## "factor" method of `droplevels`
droplevels.factor
#function (x, exclude = if (anyNA(levels(x))) NULL else NA, ...)
#factor(x, exclude = exclude)
droplevels(f)
#[1] 1 <NA>
#Levels: 1 <NA>
droplevels(f, exclude = NA)
#[1] 1 <NA>
#Levels: 1
使用 R 的分组依据函数时的注意事项:注意未使用的或 NA 级别
R 函数执行分组操作,如 split
、tapply
期望我们提供因子变量作为 "by" 变量。但通常我们只提供字符或数字变量。所以在内部,这些函数需要将它们转换为因子,并且可能大多数函数首先会使用 as.factor
(至少 split.default
和 tapply
是这样)。 table
函数看起来像一个异常,我在里面发现了 factor
而不是 as.factor
。不幸的是,当我检查它的源代码时,可能有一些特殊的考虑对我来说并不明显。
由于大多数 group-by R 函数使用 as.factor
,如果给它们一个未使用或 NA
级别的因子,这样的组将出现在结果中。
x <- c(1, 2)
f <- factor(letters[1:2], levels = letters[1:3])
split(x, f)
#$a
#[1] 1
#
#$b
#[1] 2
#
#$c
#numeric(0)
tapply(x, f, FUN = mean)
# a b c
# 1 2 NA
有趣的是,虽然 table
不依赖于 as.factor
,但它也保留了那些未使用的级别:
table(f)
#a b c
#1 1 0
有时这种行为是不受欢迎的。一个经典的例子是 barplot(table(f))
:
如果确实不需要,我们需要使用 droplevels
或 factor
.
从因子变量中手动删除未使用或 NA
水平
提示:
split
有一个参数 drop
,默认为 FALSE
,因此使用 as.factor
;通过 drop = TRUE
函数 factor
被代替。
aggregate
依赖于 split
,因此它也有一个 drop
参数并且默认为 TRUE
.
tapply
虽然也依赖split
,但没有drop
。特别是文档 ?tapply
说 as.factor
是(总是)使用的。
我最近看到 Matt Dowle 用 as.factor()
写了一些代码,特别是
for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))
在 a comment to this answer.
我使用了这个片段,但我需要明确设置因子水平以确保水平以我想要的顺序出现,所以我不得不改变
as.factor(dt[[col]])
至
factor(dt[[col]], levels = my_levels)
这让我开始思考:使用 as.factor()
与仅使用 factor()
有什么好处(如果有的话)?
as.factor
是 factor
的包装器,但如果输入向量已经是一个因子,它允许快速 return:
function (x)
{
if (is.factor(x))
x
else if (!is.object(x) && is.integer(x)) {
levels <- sort(unique.default(x))
f <- match(x, levels)
levels(f) <- as.character(levels)
if (!is.null(nx <- names(x)))
names(f) <- nx
class(f) <- "factor"
f
}
else factor(x)
}
来自 Frank 的评论:它不仅仅是一个包装器,因为这个 "quick return" 将保留原样的因子水平,而 factor()
不会:
f = factor("a", levels = c("a", "b"))
#[1] a
#Levels: a b
factor(f)
#[1] a
#Levels: a
as.factor(f)
#[1] a
#Levels: a b
两年后的扩展答案,包括以下内容:
- 说明书怎么说的?
- 性能:
as.factor
>factor
当输入是一个因素时 - 性能:
as.factor
>factor
当输入为整数时 - 未使用的级别或 NA 级别
- 使用 R 的分组函数时的注意事项:注意未使用的或 NA 级别
说明书怎么说的?
?factor
的文档提到了以下内容:
‘factor(x, exclude = NULL)’ applied to a factor without ‘NA’s is a
no-operation unless there are unused levels: in that case, a
factor with the reduced level set is returned.
‘as.factor’ coerces its argument to a factor. It is an
abbreviated (sometimes faster) form of ‘factor’.
性能:as.factor
> factor
当输入是一个因素时
"no-operation"这个词有点歧义。不要把它当作"doing nothing";实际上,它的意思是 "doing a lot of things but essentially changing nothing"。这是一个例子:
set.seed(0)
## a randomized long factor with 1e+6 levels, each repeated 10 times
f <- sample(gl(1e+6, 10))
system.time(f1 <- factor(f)) ## default: exclude = NA
# user system elapsed
# 7.640 0.216 7.887
system.time(f2 <- factor(f, exclude = NULL))
# user system elapsed
# 7.764 0.028 7.791
system.time(f3 <- as.factor(f))
# user system elapsed
# 0 0 0
identical(f, f1)
#[1] TRUE
identical(f, f2)
#[1] TRUE
identical(f, f3)
#[1] TRUE
as.factor
确实给出了一个快速 return,但 factor
不是真正的 "no-op"。让我们分析一下 factor
看看它做了什么。
Rprof("factor.out")
f1 <- factor(f)
Rprof(NULL)
summaryRprof("factor.out")[c(1, 4)]
#$by.self
# self.time self.pct total.time total.pct
#"factor" 4.70 58.90 7.98 100.00
#"unique.default" 1.30 16.29 4.42 55.39
#"as.character" 1.18 14.79 1.84 23.06
#"as.character.factor" 0.66 8.27 0.66 8.27
#"order" 0.08 1.00 0.08 1.00
#"unique" 0.06 0.75 4.54 56.89
#
#$sampling.time
#[1] 7.98
它首先sort
输入向量f
的unique
值,然后将f
转换为字符向量,最后使用factor
进行强制转换字符向量回到一个因素。这里是factor
的源代码以供确认。
function (x = character(), levels, labels = levels, exclude = NA,
ordered = is.ordered(x), nmax = NA)
{
if (is.null(x))
x <- character()
nx <- names(x)
if (missing(levels)) {
y <- unique(x, nmax = nmax)
ind <- sort.list(y)
levels <- unique(as.character(y)[ind])
}
force(ordered)
if (!is.character(x))
x <- as.character(x)
levels <- levels[is.na(match(levels, exclude))]
f <- match(x, levels)
if (!is.null(nx))
names(f) <- nx
nl <- length(labels)
nL <- length(levels)
if (!any(nl == c(1L, nL)))
stop(gettextf("invalid 'labels'; length %d should be 1 or %d",
nl, nL), domain = NA)
levels(f) <- if (nl == nL)
as.character(labels)
else paste0(labels, seq_along(levels))
class(f) <- c(if (ordered) "ordered", "factor")
f
}
所以函数 factor
实际上是为处理字符向量而设计的,它会将 as.character
应用于其输入以确保这一点。我们至少可以从上面了解到两个与性能相关的问题:
- 对于一个数据框
DF
,如果有很多列是现成的因素,lapply(DF, as.factor)
在类型转换方面比lapply(DF, factor)
快得多。 - 那个函数
factor
很慢可以解释为什么一些重要的R函数很慢,比如table
:
性能:as.factor
> factor
当输入为整数时
因子变量是整数变量的近亲。
unclass(gl(2, 2, labels = letters[1:2]))
#[1] 1 1 2 2
#attr(,"levels")
#[1] "a" "b"
storage.mode(gl(2, 2, labels = letters[1:2]))
#[1] "integer"
这意味着将整数转换为因数比将数字/字符转换为因数更容易。 as.factor
只管这个。
x <- sample.int(1e+6, 1e+7, TRUE)
system.time(as.factor(x))
# user system elapsed
# 4.592 0.252 4.845
system.time(factor(x))
# user system elapsed
# 22.236 0.264 22.659
未使用的级别或 NA 级别
现在让我们看一些关于 factor
和 as.factor
对因子水平的影响的例子(如果输入已经是一个因子)。 Frank给了一个未使用的因子水平,我会提供一个NA
水平。
f <- factor(c(1, NA), exclude = NULL)
#[1] 1 <NA>
#Levels: 1 <NA>
as.factor(f)
#[1] 1 <NA>
#Levels: 1 <NA>
factor(f, exclude = NULL)
#[1] 1 <NA>
#Levels: 1 <NA>
factor(f)
#[1] 1 <NA>
#Levels: 1
有一个(通用)函数 droplevels
可用于删除未使用的因子水平。但是NA
级默认是不能掉的
## "factor" method of `droplevels`
droplevels.factor
#function (x, exclude = if (anyNA(levels(x))) NULL else NA, ...)
#factor(x, exclude = exclude)
droplevels(f)
#[1] 1 <NA>
#Levels: 1 <NA>
droplevels(f, exclude = NA)
#[1] 1 <NA>
#Levels: 1
使用 R 的分组依据函数时的注意事项:注意未使用的或 NA 级别
R 函数执行分组操作,如 split
、tapply
期望我们提供因子变量作为 "by" 变量。但通常我们只提供字符或数字变量。所以在内部,这些函数需要将它们转换为因子,并且可能大多数函数首先会使用 as.factor
(至少 split.default
和 tapply
是这样)。 table
函数看起来像一个异常,我在里面发现了 factor
而不是 as.factor
。不幸的是,当我检查它的源代码时,可能有一些特殊的考虑对我来说并不明显。
由于大多数 group-by R 函数使用 as.factor
,如果给它们一个未使用或 NA
级别的因子,这样的组将出现在结果中。
x <- c(1, 2)
f <- factor(letters[1:2], levels = letters[1:3])
split(x, f)
#$a
#[1] 1
#
#$b
#[1] 2
#
#$c
#numeric(0)
tapply(x, f, FUN = mean)
# a b c
# 1 2 NA
有趣的是,虽然 table
不依赖于 as.factor
,但它也保留了那些未使用的级别:
table(f)
#a b c
#1 1 0
有时这种行为是不受欢迎的。一个经典的例子是 barplot(table(f))
:
如果确实不需要,我们需要使用 droplevels
或 factor
.
NA
水平
提示:
split
有一个参数drop
,默认为FALSE
,因此使用as.factor
;通过drop = TRUE
函数factor
被代替。aggregate
依赖于split
,因此它也有一个drop
参数并且默认为TRUE
.tapply
虽然也依赖split
,但没有drop
。特别是文档?tapply
说as.factor
是(总是)使用的。