R言語にファクターというデータ型(みたいなもの)があるんですが、これが分かりにくい。一応納得した後でも「なんで、こんなことになったんだろう?」「たちの悪い冗談だったのか?」という疑念が拭えません。
ご説明いたしましょう。
内容:
どのくらい奇妙か
ファクター(factor, 因子)とは、Rで扱うデータの一種です。factor()関数がそのコンストラクタです。factor()関数で、ファクター・データを作ってみます。
- 元にするベクトル・データは c(5, 0, 5, 0, 10) です。
- levels(レベル達)という名前の引数に c(10, 5, 0) を指定します。
- labels(ラベル達)という名前の引数に c("a", "b", "c") を指定します。
これを実行して、できたデータxを調べてみます。
> x <- factor(c(5, 0, 5, 0, 10), levels=c(10, 5, 0), labels=c("a", "b", "c"))
> levels(x) [1] "a" "b" "c" > labels(x) [1] "1" "2" "3" "4" "5" > as.integer(x) [1] 2 3 2 3 1 >
- levelsを c(10, 5, 0) と指定したデータのlevelsは c("a", "b", "c") でした。
- labelsを c("a", "b", "c") と指定したデータのlabelsは c("1", "2", "3", "4", "5") でした。
- c(5, 0, 5, 0, 10) から作ったファクター・データを整数値ベクトルに変換すると、c(2, 3, 2, 3, 1) でした。
常人が理解できる状況ではありません。これはいったい、何が起こっているのでしょうか?
そもそもファクターって何?
R言語のファクターは、他の言語の列挙型(enum型)と似たものです。いやっ、あんまり似てないか? まー、用途は列挙型と共通しているとは言えます。
例えば、ある程度の人数の社員データがあり、性別がgenderという名前のベクトルで記述されるとしましょう。男性を"MALE"、女性を"FEMALE"という文字列で表す約束をすると、genderベクトルは、"MALE", "MALE", "FEMALE", ... というような長い列になります。
文字列"MALE"、"FEMALE"は人間が読むには適してますが、メモリを消費するし*1扱いにくいこともあります。こんな状況で列挙型(enum型)の値が使われます。次は、TypeScriptによる列挙型の定義です。
// 男女の別 enum Gender { MALE, // 男性 = 0 FEMALE, // 女性 = 1 UNKNOWN // 不明 = 2 }
プログラム中ではMALE、FEMALE、そしてUNKNOWNという名前を使えますが、実際には整数値なのです。コメントに書いてある 0, 1, 2 が名前に対応する整数値です。ここでは、値に付けられた名前を表示名、実際の整数値を内部値と呼ぶことにします。
表示名に対する内部値を、明示的に指定することもできます。
// 男女の別 enum Gender { MALE = -1, FEMALE = 1, UNKNOWN = 0 }
この例では、表示名と内部値の対応は次のようになります。
表示名 | 内部値 |
---|---|
MALE | -1 |
FEMALE | 1 |
UNKNOWN | 0 |
このような対応を、表示名-内部値・マッピングと呼ぶことにします。列挙型を定義するとは、表示名-内部値・マッピングを指定することに他なりません。
R言語のファクターは、型ではないのですが、*2個々のベクトルに表示名-内部値・マッピングを持たせることによって、列挙型データと同様な扱いを可能としたものです。
錯綜してしまったファクターの用語法
R言語のファクターの場合、水準という言葉が使われます。困ったことに、「水準」が2つの意味で使われて解釈が難しいので、ここでは水準ラベルと水準値と使い分けます。その意味は、列挙型と対応させて理解してください。(列挙値とは、列挙型のインスタンスのことです。)
多くのプログラミング言語 | R言語 |
---|---|
列挙値を要素とする配列 | ファクター・データ |
列挙値の表示名 | 水準ラベル |
列挙値の内部値 | 水準値 |
「水準」が、水準ラベルと水準値のどちらを意味するか曖昧だったり、さらには、「水準値」と言っても実は水準ラベルの意味だったりして、頭が痛くなります。
ここで、先に挙げたファクター・データ生成のコマンド(関数呼び出しと代入)をもう一度見てみましょう。
> x <- factor(c(5, 0, 5, 0, 10), levels=c(10, 5, 0), labels=c("a", "b", "c"))
factor()関数の最初の引数は必須で、ファクター・データを作るための元データを指定します。与えられた元データに対してファクター・データを作るには、次の2段階の処理が必要です。
- 元データの値を水準値(内部値)に変換する。
- 結果のベクトルに、水準ラベル(表示名)の情報をくっ付ける。
詳しくは次節で述べますが、水準値への変換は、c(5, 0, 5, 0, 10) → c(2, 3, 2, 3, 1) となります。水準ラベルの情報は、(labelsではなく)levelsという属性としてくっ付けます。
> as.integer(x) [1] 2 3 2 3 1 > attr(x, "levels") [1] "a" "b" "c" >
levels、labelsという名前がどのように使われているかをまとめます。驚くべきヒドサです。
元データ値→水準値変換を指定する引数名 | levels | 値は元データと同じ型 |
水準ラベルを指定する引数名 | labels | 値はラベル |
水準ラベル情報を持つ属性名 | levels | 値はラベル |
水準ラベル情報を取得する関数名 | levels | |
ファクターと何の関係もない関数名 | labels | 騙されるな! |
ファクター・データ生成の詳細
ファクターに関する用語・名前は錯綜していて紛らわしいのですが、それだけでなく、ファクター・データ生成の手順はけっこう複雑なのです。こりゃ分かりにくいわ。
まず注意すべきは、R言語のファクターの場合、水準値=内部値は自動的に決まり、ユーザーは変更できないことです。常に、1から始まる連番整数値が水準値=内部値として使われます。内部値なので、Rは実際の値を隠そうとします。代わりに、水準ラベル=表示名を見せるようにします。
> x <- factor(c(5, 0, 5, 0, 10), levels=c(10, 5, 0), labels=c("a", "b", "c"))
> x [1] b c b c a Levels: a b c >
ファクター・データ生成の過程では、2つのマッピング(対応表)が作られます。“元データ値-水準値・マッピング”と“水準ラベル-水準値・マッピング”です。対応は1:1なので、どちらのマッピングも可逆、つまり対応方向を逆にして使うことができます。
factor()関数のlevels引数で“元データ値-水準値・マッピング”を指定し、labels引数では“水準ラベル-水準値・マッピング”を指定します。
levels=c(10, 5, 0) の解釈は:
元データ値 | 水準値 |
---|---|
10 | 1 |
5 | 2 |
0 | 3 |
このマッピングに従い、元データをファクター・データ(実体は整数ベクトル)に変換します。実例では、c(5, 0, 5, 0, 10) → c(2, 3, 2, 3, 1) でしたね。
labels=c("a", "b", "c") の解釈は:
水準ラベル | 水準値 |
---|---|
"a" | 1 |
"b" | 2 |
"c" | 3 |
このマッピングにより、表示名と内部値の相互変換をします。labels引数に指定された文字列ベクトルは、ファクター・データのlevels属性としてそのままくっ付けます(labels引数がlevels属性になるぞ、注意、注意、注意!!)。
でき上がったファクター・データxの実体である整数ベクトルを見るには、as.integer(x) とします。xの水準ラベル=表示名を見るには、levels(x) です、labels(x) ではないのですよ。labels()関数は、ファクターの水準ラベル=表示名とは関係ありません。
ややこしいデフォルト処理
factor()関数のlevels引数が指定されなかった場合、“元データ値-水準値・マッピング”は次のようにして作られます。
- 元データに出現した値のソート順に、水準値 1, 2, ... を割り振る。
元データが c(5, 0, 5, 0, 10) の場合は次のようなマッピングになります。
元データ値 | 水準値 |
---|---|
0 | 1 |
5 | 2 |
10 | 3 |
> x <- factor(c(5, 0, 5, 0, 10), labels=c("a", "b", "c"))
> as.integer(x) [1] 2 1 2 1 3 > levels(x) [1] "a" "b" "c" > x [1] b a b a c Levels: a b c >
labels引数が指定されなかった場合、“水準ラベル-水準値・マッピング”は次のようにして作られます。
- “元データ値-水準値・マッピング”の元データを文字列化する。
> x <- factor(c(5, 0, 5, 0, 10), levels=c(10, 5, 0))
> as.integer(x) [1] 2 3 2 3 1 > levels(x) [1] "10" "5" "0" > x [1] 5 0 5 0 10 Levels: 10 5 0 >
このケースでは:
元データ値 | 水準値 |
---|---|
10 | 1 |
5 | 2 |
0 | 3 |
水準ラベル | 水準値 |
---|---|
"10" | 1 |
"5" | 2 |
"0" | 3 |
levels引数もlabels引数も省略されると、デフォルトのlevels(元データ値-水準値・マッピング)を作り、それを元にしてlabels(水準ラベル-水準値・マッピング)を作ります。くどい注意ですが、labels情報はlevels属性になります。
> x <- factor(c(5, 0, 5, 0, 10))
> as.integer(x) [1] 2 1 2 1 3 > levels(x) [1] "0" "5" "10" > x [1] 5 0 5 0 10 Levels: 0 5 10 >
この場合の結果であるxは、一見すると元データと変わってません。しかし、実体は水準値=内部値のベクトルに変換され、水準ラベル=表示名の文字列ベクトルが値であるlevels属性が付与されています。
レベルとラベルって、そもそも似ていて紛らわしい言葉なのに、この混乱した使用法、いいかげんにしろよと言いたい。