最近、Matt Dowleがas.factor()
でコードを書くのを見ました。具体的には
for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))
in この回答へのコメント 。
このスニペットを使用しましたが、因子レベルを明示的に設定して、レベルが希望の順序で表示されるようにする必要があったため、変更する必要がありました
as.factor(dt[[col]])
に
factor(dt[[col]], levels = my_levels)
これは私に考えさせられました:as.factor()
対factor()
を使用する利点は(もしあれば)何ですか?
as.factor
はfactor
のラッパーですが、入力ベクトルが既にファクターである場合、すばやく戻ることができます。
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 からのコメント:これは単なるラッパーではありません。これは、この「クイックリターン」が因子レベルをそのままにして、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
入力が整数の場合?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
入力が要因の場合「無操作」という言葉は少し曖昧です。 「何もしない」と考えないでください。実際には、「多くのことを行うが、本質的には何も変更しない」ことを意味します。以下に例を示します。
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
はすぐに戻りますが、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
入力ベクトルのunique
値をf
変換し、次に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
を入力に適用してそれを保証します。上記から少なくとも2つのパフォーマンス関連の問題を学ぶことができます。
DF
、lapply(DF, as.factor)
は、多くの列がすぐに要因となる場合、型変換のlapply(DF, factor)
よりもはるかに高速です。factor
は遅いので、いくつかの重要なR関数が遅い理由を説明できます。たとえばtable
: R:驚くほど遅い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
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
split
、tapply
などのグループ化操作を行うR関数は、因子変数を「by」変数として提供することを期待しています。しかし、多くの場合、文字変数または数値変数を提供するだけです。内部的には、これらの関数はそれらをファクターに変換する必要があり、おそらくほとんどの場合、最初はas.factor
を使用します(少なくともsplit.default
およびtapply
はそうです)。 table
関数は例外のように見え、as.factor
の代わりに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))
です:
これが本当に望ましくない場合は、NA
またはdroplevels
を使用して、因子変数から未使用またはfactor
レベルを手動で削除する必要があります。
ヒント:
split
には引数drop
があり、デフォルトではFALSE
に設定されているため、as.factor
が使用されます。 by drop = TRUE
function factor
が代わりに使用されます。aggregate
はsplit
に依存しているため、drop
引数もあり、デフォルトはTRUE
です。tapply
にはdrop
もありませんが、split
はありません。特に、ドキュメント?tapply
は、as.factor
が(常に)使用されていると述べています。