web-dev-qa-db-ja.com

N個のカテゴリを持つカテゴリカル因子をN個のバイナリ列に再コード化

元のデータフレーム:

v1 = sample(letters[1:3], 10, replace=TRUE)
v2 = sample(letters[1:3], 10, replace=TRUE)
df = data.frame(v1,v2)
df
 v1 v2 
 1 bc 
 2 aa 
 3 cc 
 4 ba 
 5 cc 
 6 cb 
 7 aa 
 8 ab 
 9 ac 
 10 ab 

新しいデータフレーム:

new_df = data.frame(row.names=rownames(df))
for (i in colnames(df)) {
    for (x in letters[1:3]) {
        #new_df[x] = as.numeric(df[i] == x)
        new_df[paste0(i, "_", x)] = as.numeric(df[i] == x)
    }
}
 v1_a v1_b v1_c v2_a v2_b v2_c 
 1 0 1 0 0 0 1 
 2 1 0 0 1 0 0 
 3 0 0 1 0 0 1 
 4 0 1 0 1 0 0 
 5 0 0 1 0 0 1 
 6 0 0 1 0 1 0 
 7 1 0 0 1 0 0 
 8 1 0 0 0 1 0 
 9 1 0 0 0 0 1 
 10 1 0 0 0 1 0 

小さなデータセットの場合はこれで問題ありませんが、非常に大きなデータセットの場合は遅くなります。

ループを使用せずにこれを行う方法を知っている人はいますか?

18
Keith Hughitt

@AnandaMahtoの検索機能の助けを借りて、

_model.matrix(~ . + 0, data=df, contrasts.arg = lapply(df, contrasts, contrasts=FALSE))
#    v1a v1b v1c v2a v2b v2c
# 1    0   1   0   0   0   1
# 2    1   0   0   1   0   0
# 3    0   0   1   0   0   1
# 4    0   1   0   1   0   0
# 5    0   0   1   0   0   1
# 6    0   0   1   0   1   0
# 7    1   0   0   1   0   0
# 8    1   0   0   0   1   0
# 9    1   0   0   0   0   1
# 10   1   0   0   0   1   0
_

これはあなたが探しているものだと思います。そうでない場合は削除させていただきます。 @ 優れた使用法 の_model.matrix_について@ G.Grothendieckに感謝します(もう一度)

_cbind(with(df, model.matrix(~ v1 + 0)), with(df, model.matrix(~ v2 + 0)))
#    v1a v1b v1c v2a v2b v2c
# 1    0   1   0   0   0   1
# 2    1   0   0   1   0   0
# 3    0   0   1   0   0   1
# 4    0   1   0   1   0   0
# 5    0   0   1   0   0   1
# 6    0   0   1   0   1   0
# 7    1   0   0   1   0   0
# 8    1   0   0   0   1   0
# 9    1   0   0   0   0   1
# 10   1   0   0   0   1   0
_

注:出力は次のとおりです。

_with(df, model.matrix(~ v2 + 0))
_

注2:これはmatrixを与えます。かなり明白ですが、_data.frame_が必要な場合は、as.data.frame(.)でラップしてください。

24
Arun

キャレットのパッケージには、必要な機能を実行する関数dummyVarsがあります。これは、著者のドキュメントからの使用例です: http://topepo.github.io/caret/preprocess.html

library(earth)
data(etitanic)

dummies <- caret::dummyVars(survived ~ ., data = etitanic)
head(predict(dummies, newdata = etitanic))

  pclass.1st pclass.2nd pclass.3rd sex.female sex.male     age sibsp parch
1          1          0          0          1        0 29.0000     0     0
2          1          0          0          0        1  0.9167     1     2
3          1          0          0          1        0  2.0000     1     2
4          1          0          0          0        1 30.0000     1     2
5          1          0          0          1        0 25.0000     1     2
6          1          0          0          0        1 48.0000     0     0

Model.matrixオプションは、スパースデータがあり、Matrix::sparse.model.matrixを使用したい場合に役立ちます。

8
marbel

ここに向けられた閉じられた質問を見たところ、dummiesパッケージの使用についてまだ誰も言及していません:

dummy.data.frame()の上に構築されたmodel.matrix()関数を使用して変数を再コード化できますが、構文が簡単で、いくつかの優れたオプションがあり、データフレームを返します。

_> dummy.data.frame(df, sep="_")
   v1_a v1_b v1_c v2_a v2_b v2_c
1     0    1    0    0    0    1
2     1    0    0    1    0    0
3     0    0    1    0    0    1
4     0    1    0    1    0    0
5     0    0    1    0    0    1
6     0    0    1    0    1    0
7     1    0    0    1    0    0
8     1    0    0    0    1    0
9     1    0    0    0    0    1
10    1    0    0    0    1    0
_

この関数のいくつかの素晴らしい側面は、新しい名前(_sep=_)の区切り文字を簡単に指定でき、エンコードされていない変数(_all=F_)を省略でき、独自のオプション_dummy.classes_を使用して、エンコードする列のクラスを指定します。

dummy()関数を使用して、これを1つの列だけに適用することもできます。

3
Andrew Haynes

私は最近別の方法に出くわしました。 contrastsFALSEに設定してコントラスト関数を実行すると、1つのホットエンコーディングが提供されることに気付きました。たとえば、contr.sum(5, contrasts = FALSE)

  1 2 3 4 5
1 1 0 0 0 0
2 0 1 0 0 0
3 0 0 1 0 0
4 0 0 0 1 0
5 0 0 0 0 1

すべての要因に対してこの動作を実現するには、新しいコントラスト関数を作成し、それをデフォルトとして設定します。例えば、

contr.onehot = function (n, contrasts, sparse = FALSE) {
  contr.sum(n = n, contrasts = FALSE, sparse = sparse)
}

options(contrasts = c("contr.onehot", "contr.onehot"))
model.matrix(~ . - 1, data = df)

これは

   v1a v1b v1c v2a v2b v2c
1    0   0   1   0   0   1
2    0   1   0   1   0   0
3    0   0   1   0   1   0
4    1   0   0   0   1   0
5    0   1   0   0   1   0
6    0   1   0   0   0   1
7    1   0   0   0   1   0
8    0   1   0   0   1   0
9    0   1   0   1   0   0
10   0   0   1   0   0   1
3
Andrew

かなり直接的なアプローチは、各列でtableを使用し、data.frameの行数で列の値を集計することです。

allLevels <- levels(factor(unlist(df)))
do.call(cbind, 
        lapply(df, function(x) table(sequence(nrow(df)), 
                                     factor(x, levels = allLevels))))
#    a b c a b c
# 1  0 1 0 0 0 1
# 2  1 0 0 1 0 0
# 3  0 0 1 0 0 1
# 4  0 1 0 1 0 0
# 5  0 0 1 0 0 1
# 6  0 0 1 0 1 0
# 7  1 0 0 1 0 0
# 8  1 0 0 0 1 0
# 9  1 0 0 0 0 1
# 10 1 0 0 0 1 0

"x"でfactorを使用して、たとえば列に "c"値がない場合でも、出力に "c"列があることを確認します。ゼロ付き。

これは、文字数が事前に指定されていない、より一般的なケースの解決策です。

convertABC <- function(x) {

    hold <- rep(0,max(match(as.matrix(df),letters))) # pre-format output

    codify <- function(x) {                          # define function for single char

        output <- hold                               # take empty vector
        output[match(x,letters)] <- 1                # place 1 according to letter pos
        return(output)
    }

    to.return <- t(sapply(as.character(x),codify))   # apply it to whole vector
    rownames(to.return) <- 1:nrow(to.return)         # Nice rownames
    colnames(to.return) <- do.call(c,list(letters[1:max(match(as.matrix(df),letters))])) # Nice columnnames
    return(to.return)
}

この関数は文字のベクトルを取り、それをバイナリ値に再コード化します。 dfのすべての変数を処理するには:

do.call(cbind,lapply(df,convertABC))
0
Maxim.K