1. ホーム
  2. r

[解決済み] なぜfactor()ではなく、as.factor()なのか?

2022-03-13 15:05:38

質問

最近、Matt Dowleが、あるコードを as.factor() 具体的には

for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))

この回答に対するコメント .

このスニペットを使用しましたが、レベルが希望する順序で表示されるようにするために、要因レベルを明示的に設定する必要があったため、以下のように変更する必要がありました。

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)
}


コメント フランク この「クイックリターン」では、ファクターレベルをそのままにしておくことができますので、単なるラッパーではありません。 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


2年後に以下のように回答を拡大。

  • マニュアルにはどう書いてある?
  • パフォーマンス as.factor > factor 入力が要因の場合
  • パフォーマンス as.factor > factor 入力が整数の場合
  • 未使用レベルまたはNAレベル
  • Rのgroup-by関数使用時の注意点:未使用または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 入力が要因の場合

無操作という言葉は、少し曖昧です。実際には、「多くのことを行うが、本質的には何も変えない」という意味です。以下はその例です。

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つのパフォーマンスに関する問題を学ぶことができます。

  1. データフレームの場合 DF , lapply(DF, as.factor) よりもはるかに高速です。 lapply(DF, factor) は、多くのカラムが容易に因子となる場合、型変換に使用されます。
  2. その関数 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 

未使用のレベルまたはNAレベル

では、次の例を見てみましょう。 factoras.factor の要因レベルへの影響(入力がすでに要因である場合)。 フランク が未使用の因子レベルを持つものを提供しているので、私が提供するのは 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のgroup-by関数を使用する際の注意:未使用またはNAのレベルに注意

グループバイ操作を行うR関数、例えば split , tapply は、因子変数をquot;by"変数として提供することを想定しています。しかし、多くの場合、文字や数値の変数を提供するだけです。そのため、これらの関数は内部でこれらを係数に変換する必要がありますし、おそらくほとんどの関数では、係数に変換するために as.factor は、そもそも(少なくとも split.defaulttapply ). また table 関数は例外のように見え、私は factor ではなく as.factor の中に入っています。何か特別な配慮があるのかもしれませんが、残念ながらそのソースコードを調べても明らかではありません。

ほとんどのグループバイ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 .

ヒント

  1. split は、引数 drop であり、デフォルトは FALSE したがって as.factor が使用されます。 drop = TRUE 機能 factor が代わりに使われます。
  2. aggregate に依存しています。 split ということで、これもまた drop を引数にとり、デフォルトは TRUE .
  3. tapply を持ちません。 drop にも依存していますが split . 特に、ドキュメント ?tapply には次のように書かれています。 as.factor が(常に)使用されます。