備忘ログ

チラシの裏的備忘録&メモ

Rの{psych}のrevese.code()のメモと逆転項目の処理のメモ、蛇足で{car}のrecode()で逆転項目を処理する方法も添えて

Rの{psych}パッケージに逆転項目を逆転させる処理するrevese.code()という関数があるのでそのメモ。

Rで逆転処理をする話がたまに見かけるので。

ここでの逆転項目というのは1~7の結果を1は7、2は6、……と結果を逆にしなければならない項目のこと。心理尺度なんかだとたまにある。(今どきは殆どないが、たまに日本語が不自然な逆転項目の質問項目(ないがあるみたいな)がある質問紙があって混乱することがある。)

ということで心理学系御用達(だと思う)の{psych}パッケージに逆転項目を逆転させる処理(1~7の場合に1を7、2を6……)するrevese.code()という関数があるがちょっと使いにくいのでまとめておく。

逆転項目を逆転させる処理をするのならこの関数よりも、項目の最大値と最小値を足した値から逆転項目の値を引く処理をしたほうが簡単だと思う(最後に書いた)。

あと蛇足で{car}パッケージのrecode()関数を使って逆転処理する方法も最後に書いた。{psych}revese.code()よりも用途によっては簡便だと思う。

revese.code()の基本的挙動

サンプルデータを作る。5項目(Item_1Item_5)分、すべてが1~7での回答結果が10人分あるようなデータをイメージしている。

set.seed(123)
df <- data.frame(Item_1 = sample(1:7, 10, replace = TRUE),
                 Item_2 = sample(1:7, 10, replace = TRUE),
                 Item_3 = sample(1:7, 10, replace = TRUE),
                 Item_4 = sample(1:7, 10, replace = TRUE),
                 Item_5 = sample(1:7, 10, replace = TRUE))

このデータは次のようになっている。head()をつかって最初6行のみを表示する。

head(df)
##   Item_1 Item_2 Item_3 Item_4 Item_5
## 1      7      4      4      3      5
## 2      7      6      1      4      7
## 3      3      6      1      6      1
## 4      6      1      5      1      1
## 5      3      2      3      3      2
## 6      2      3      2      7      7

ここで、Item_3Item_4が逆転項目のときrevese.code()をつかって処理するときには、keysの引数にその列名を指定するか、そのままの列を1とし逆転項目の列を-1としたベクトルを与えると変換できる。後者がちょっとわかりにくいが次に具体例を上げる。

その他の引数は、itemsで変換対象のデータセットを指定し、miniで項目の最小値(質問紙などでもともと想定している最小の値)を指定(指定しないとデータセット中の観測値の最小値で処理)、maxiで項目の最大値(質問紙などでもともと想定している最大値)を指定(指定しないとデータセット中の観測値の最大値で処理)する。

まずは列名を指定する方法はkeyに列名をベクトルで与えてやる次のようになる。

library(psych)
df.revese <- reverse.code(keys = c("Item_3", "Item_4"),
                          items = df,
                          mini = 1, maxi = 7)
head(df.revese)
##      Item_1 Item_2 Item_3- Item_4- Item_5
## [1,]      7      4       4       5      5
## [2,]      7      6       7       4      7
## [3,]      3      6       7       2      1
## [4,]      6      1       3       7      1
## [5,]      3      2       5       5      2
## [6,]      2      3       6       1      7

これで、Item_3Item_4が逆転される。このとき、逆転された列は列名の末尾に-がつく。

次に、keysにそのままの列を1とし逆転する列を-1としたベクトルを与えるパターンは、列分だけ1と-1で構成されたベクトルを作る。今回の例だとc(1, 1, -1, -1, 1)というベクトルを与える。

library(psych)
df.revese <- reverse.code(keys = c(1, 1, -1, -1, 1),
                          items = df,
                          mini = 1, maxi = 7)
head(df.revese)
##      Item_1 Item_2 Item_3- Item_4- Item_5
## [1,]      7      4       4       5      5
## [2,]      7      6       7       4      7
## [3,]      3      6       7       2      1
## [4,]      6      1       3       7      1
## [5,]      3      2       5       5      2
## [6,]      2      3       6       1      7

これで同様にItem_3Item_4が逆転される。このときも、逆転された列はもともとの列名の末尾に-がつく。

これが基本的挙動。

revese.code()の注意を要する点

入力される元データは数値のみのデータでなければならない

次のようなサンプルデータを作る。Item_1Item_5は先のサンプルデータを同じで1~7で回答された結果。それに加えてSexという列にMやFで回答者の性別が含まれているデータを想定している。

アンケート回答の集計結果ではありがちな感じだと思う。

set.seed(123)
df <- data.frame(Sex = sample(c("M", "F"), 10, replace = TRUE),
                 Item_1 = sample(1:7, 10, replace = TRUE),
                 Item_2 = sample(1:7, 10, replace = TRUE),
                 Item_3 = sample(1:7, 10, replace = TRUE),
                 Item_4 = sample(1:7, 10, replace = TRUE),
                 Item_5 = sample(1:7, 10, replace = TRUE))
head(df)
##   Sex Item_1 Item_2 Item_3 Item_4 Item_5
## 1   M      4      4      3      5      5
## 2   M      6      1      4      7      3
## 3   M      6      1      6      1      6
## 4   F      1      5      1      1      1
## 5   M      2      3      3      2      2
## 6   F      3      2      7      7      5

これを先程と同様にItem_3Item_4が逆転項目として処理しようとすると次のようにエラーとなり処理できない 。

df.revese <- reverse.code(keys = c("Item_3", "Item_4"),
                          items = df,
                          mini = 1, maxi = 7)
## Error in items %*% keys.d:  数値/複素数/行列またはベクトルが要求されます

これは1と-1でのベクトルで指定された場合でも同様にエラーがでる。

df.revese <- reverse.code(keys = c(1, 1, 1, -1, -1, 1),
                          items = df,
                          mini = 1, maxi = 7)
## Error in items %*% keys.d:  数値/複素数/行列またはベクトルが要求されます

文字列等の列が含まれないようにするとちゃんと処理できる。

df.revese <- reverse.code(keys = c("Item_3", "Item_4"),
                          items = df[2:6],
                          mini = 1, maxi = 7)
head(df.revese)
##      Item_1 Item_2 Item_3- Item_4- Item_5
## [1,]      4      4       5       3      5
## [2,]      6      1       4       1      3
## [3,]      6      1       2       7      6
## [4,]      1      5       7       7      1
## [5,]      2      3       5       6      2
## [6,]      3      2       1       1      5

これはrevese.code()が処理の過程で与えられたデータセットを逆転させる項目をマイナスに変換するために対角ベクトルとの内積をとっているためで文字列は内積をとれないためにエラーになっている。

ということで、revese.code()に入力するデータは数値のみのデータはとなるようにしなければならない。

revese.code()の出力結果は行列になる

これまでの出力結果をみてもらえると分かる通り、revese.code()の出力結果は行列になる。

is(df.revese)
## [1] "matrix"    "array"     "structure" "vector"

別にわかっていればよいのだけれど、データフレームやデータテーブルであることが前提になっている関数だとそのままだと処理できない。

999を超える値はNAになってしまう

心理尺度など処理しているとそういうことはほとんどないが、値が999を超えているとコード上NAにに置換されるのでなきものになってしまう。

df.over <- data.frame(V1 = 998:1002,
                      V2 = 1002:998)
reverse.code(keys = rep(-1, 2), items = df.over)
##      V1- V2-
## [1,]  NA 998
## [2,]  NA 999
## [3,]  NA  NA
## [4,] 999  NA
## [5,] 998  NA

マイナスの値が含まれていると逆転する処理ができないことがある

7件法のときになにか特殊な意図があって、-3~3(0を含む)として配点することがあるかもしれない。

これは逆転化できる。

df.minus <- data.frame(V1 = -3:3,
                       V2 = 3:-3)
reverse.code(keys = rep(-1, 2), items = df.minus, mini = -3, maxi = 3)
##      V1- V2-
## [1,]   3  -3
## [2,]   2  -2
## [3,]   1  -1
## [4,]   0   0
## [5,]  -1   1
## [6,]  -2   2
## [7,]  -3   3

ただし、全てマイナスか0の値だったり、0をまたいで左右対称ではない場合は逆転する処理がまくできない。

df.minus <- data.frame(V1 = -3:0,
                       V2 = 0:-3)
reverse.code(keys = rep(-1, 2), items = df.minus, mini = -3, maxi = 0)
##      V1- V2-
## [1,]   6   3
## [2,]   5   4
## [3,]   4   5
## [4,]   3   6

0を挟んで非対称の場合は次の通り。

df.minus <- data.frame(V1 = -3:2,
                       V2 = 2:-3)
reverse.code(keys = rep(-1, 2), items = df.minus, mini = -3, maxi = 2)
##      V1- V2-
## [1,]   4  -1
## [2,]   3   0
## [3,]   2   1
## [4,]   1   2
## [5,]   0   3
## [6,]  -1   4

意図した通りに逆転できていない。

revese.code()は逆転項目を処理する以外にもつかえ……る?

revese.code()のドキュメントを読むと

Reverse the coding of selected items prior to scale analysis

と大きく書かれていて、逆転項目を処理する専用関数に見えるが、実際の処理を追いかけてみると

  • 元のデータセットが行列データではない場合は行列化
  • 逆転項目のkeysに文字列で列名が指定された場合は、指定された列が-1になるように、逆転しない列が1、逆転する列が-1になるように列数分のベクトルを作る
  • keysのベクトルを用いて対角行列をつくる
  • 元のデータの行列と対角行列との内積をとる、つまり逆転項目の列の値は-1がかけられた値になる
  • 逆転項目は-1がかけられているのでここに指定された最大値と最小値を足したものを足す

という処理をしている。

この処理をコード上で追いかけると、別にkeysに入力される値が1か-1かに限定している部分はないのでどんな値でも受け入れる仕様になっている。

さらに、最小値や最大値は未入力だと観測値から持ってくることになっているが、これも任意で入力した場合は特に制限はないため、実際に観測されている最小値や最大値よりも狭い幅でもできる。しかも最大値や最小値は列ごとに任意で指定できる。

これが何を意味するかというとrevese.code()は逆転する関数というより、値を任意の倍数だったり、任意の範囲の値に変換したりすることができる関数ということ。その一端として逆転処理できているという感じ。

例えば、

set.seed(123)
df <- data.frame(Item_1 = sample(1:7, 10, replace = TRUE),
                 Item_2 = sample(1:7, 10, replace = TRUE),
                 Item_3 = sample(1:7, 10, replace = TRUE),
                 Item_4 = sample(1:7, 10, replace = TRUE),
                 Item_5 = sample(1:7, 10, replace = TRUE))
head(df)
##   Item_1 Item_2 Item_3 Item_4 Item_5
## 1      7      4      4      3      5
## 2      7      6      1      4      7
## 3      3      6      1      6      1
## 4      6      1      5      1      1
## 5      3      2      3      3      2
## 6      2      3      2      7      7

このデータに対して、Item_1を2倍、Item_2を0.5倍、Item_3を逆転処理、Item_4を逆転させて0をまたぐ-3~3の範囲の値に変換、Item_5をマイナスに変換するには次のようにするとできる。

reverse.code(keys = c(2, 0.5, -1, -1, -1),
             items = df,
             mini = c(1, 1, 1, -4, -3),
             maxi = c(7, 7, 7, -0, 3))
##       Item_1 Item_2 Item_3- Item_4- Item_5-
##  [1,]     14    2.0       4       1      -5
##  [2,]     14    3.0       7       0      -7
##  [3,]      6    3.0       7      -2      -1
##  [4,]     12    0.5       3       3      -1
##  [5,]      6    1.0       5       1      -2
##  [6,]      4    1.5       6      -3      -7
##  [7,]      4    2.5       1      -1      -3
##  [8,]     12    1.5       6       0      -4
##  [9,]      6    1.5       7      -3      -5
## [10,]     10    0.5       2       2      -7

とまぁいろいろ工夫すれば値の変換ができる。ただし、そもそも関数側の意図した挙動ではないと思う。

ということで逆転処理以外もできる……のか?

そもそも逆転処理するなら普通に足し引きをしたらよい気がする

冒頭にも書いたが、逆転処理するなら最大値と最小値を足した値から元の値をひいいたら良い。

set.seed(123)
df <- data.frame(Item_1 = sample(1:7, 10, replace = TRUE),
                 Item_2 = sample(1:7, 10, replace = TRUE),
                 Item_3 = sample(1:7, 10, replace = TRUE),
                 Item_4 = sample(1:7, 10, replace = TRUE),
                 Item_5 = sample(1:7, 10, replace = TRUE))
head(df)
##   Item_1 Item_2 Item_3 Item_4 Item_5
## 1      7      4      4      3      5
## 2      7      6      1      4      7
## 3      3      6      1      6      1
## 4      6      1      5      1      1
## 5      3      2      3      3      2
## 6      2      3      2      7      7

これのItem_3を逆転する(逆転項目をItem_3Rとする)なら、{dplyr}を使えば次のようにもかける。

library(dplyr)
## 
##  次のパッケージを付け加えます: 'dplyr'

##  以下のオブジェクトは 'package:stats' からマスクされています: 
## 
##      filter, lag

##  以下のオブジェクトは 'package:base' からマスクされています: 
## 
##      intersect, setdiff, setequal, union
df.revese <- df %>% 
  mutate(Item_3 = 7 + 1 - Item_3) %>% 
  rename(Item_3R = Item_3)
head(df.revese)
##   Item_1 Item_2 Item_3R Item_4 Item_5
## 1      7      4       4      3      5
## 2      7      6       7      4      7
## 3      3      6       7      6      1
## 4      6      1       3      1      1
## 5      3      2       5      3      2
## 6      2      3       6      7      7

で逆転できる。しかも名付けは自由。今回は列名を変えたが、別に名前を変えなくてもいいし、別の列として併存させることもできる。

その他の0をまたいで……や、マイナスにするなどの簡単にできる。

蛇足:car::recode()をつかって逆転処理をする

{car}パッケージのにも値変換系のrecode()関数もあり、これを使って逆転処理する方法もある。

df.revese <- df %>% 
  mutate(Item_3 = car::recode(Item_3, "7 = 1; 6 = 2; 5 = 3; 3 = 5; 2 = 6; 1 = 7")) %>% 
  rename(Item_3R = Item_3)
head(df.revese)
##   Item_1 Item_2 Item_3R Item_4 Item_5
## 1      7      4       4      3      5
## 2      7      6       7      4      7
## 3      3      6       7      6      1
## 4      6      1       3      1      1
## 5      3      2       5      3      2
## 6      2      3       6      7      7

一見簡単にみえるが、recodes引数の中の書き方がちょっと特殊。あと書くことが多い。

というかこんなに書くなら、{dplyr}case_when()とかでいい。