📚

Rのscale関数の結果から平均と分散を取り出したい

2022/06/08に公開

はじめに

rのオブジェクトのnanes属性の使い方を知ると、関数の返り値の中から、種々情報を得られるので便利、という内容です。

回帰モデル作成の際、手持ちのデータを、学習データとテストデータに二分割し、学習データで回帰式を作成し、その式をテストデータや追加のデータに適用して、作成したモデルの検証や利用を行います。データを標準化してモデルを作成した場合には、学習データの各説明変数の平均と標準偏差を用いて、テストデータや追加のデータも標準化する必要があります。即ち、検証や利用に際して、学習データの各説明変数の平均と標準偏差が必要になります。

Rでlm関数による重回帰を実施していて、このlm関数へは、scale=Trueといった引数を渡せないため、テストデータを標準化するための学習データの各説明変数の平均と標準偏差を、どうやって求めようか、mean関数やsd関数でわざわざ再度計算するのか、と悩みました。
Rのscale関数が返すarrayの中には、これらの数値も含まれているので、改めて平均や標準偏差を計算する必要はないと、わかりました。これらの値の取り出し方と、テストデータや追加データの標準化に使う方法について、記載していきます。
本稿では、標準化とは、同一系列のデータの平均が0、標準偏差が1となるように調整することを指し、具体的には、「ある系列の個々のデータに対して、系列の値全体の平均との差を求め、標準偏差で割る」操作を加えることになります。

概要

データを正規化して、回帰する際、Rではscale関数を利用します。正規化した際の平均、標準偏差は、それぞれ、scale関数の返り値であるオブジェクトから、属性を使い、

  • attr(「data.frameにscale関数を適用した結果のオブジェクト」, "scaled:center")
  • attr(「data.frameにscale関数を適用した結果のオブジェクト」, "scaled:scale")

で取り出すことができます。
また、scale関数の引数として、計算に用いる平均、標準偏差を、下記のような形で与えることができます。

  • scale(「適用したいdata.frame」, center=「平均として適用したい値」, scale=「平均として適用したい値)

これらを組み合わせると、容易に「テストデータの標準化に、学習データの平均、標準偏差の値を適用する」ことができます。

内容

1. 状況説明

以下の状況を仮定します。

  • 解析対象とするデータセットを学習データとテストデータに二分割し、学習データで重回帰式を作成した後、テストデータでその妥当性を検証する。
  • 学習データはデータフレーム名 df.trainに、テストデータはデータフレーム名 df.testに格納されている。
  • 各データフレームの1列目からn列目までが説明変数、n+1列目が目的変数である。

2. 学習データの標準化と平均、標準偏差の取り出し

scale関数を使って、学習データdf.trainを、各列ごとに標準化し、その結果をtrain_scに代入します。
結果は、多次元の配列(array)の構造で、各要素に属性(attribute)を持っています。

train_sc <- scale(df.train)
is.array(train_sc)

[出力] TRUE

arrayであるtrain_scの構造はstr関数で確認できます。

str(train_sc)

[出力]:
num [1:(df_trainの行数), 1:(df_trainの列数)] (値) ...
- attr(*, "dimnames")=List of 2
..$ : chr [1:(df_trainの行数)] (df_trainの行名) ...
..$ : chr [1:(df_trainの列数)] (df_trainの列名)
- attr(*, "scaled:center")= Named num [1:(df_trainの列数)] (各列の平均)
..- attr(*, "names")= chr [1:(df_trainの列数)] (各列の列名)
- attr(*, "scaled:scale")= Named num [1:(df_trainの列数)] (各列の標準偏差)
..- attr(*, "names")= chr [1:(df_trainの列数)] (df_trainの列名)

この出力から、train_scには、df_trainを標準化する際の平均、標準偏差が、それぞれ"scaled:center"、"scaled:scale"というnanes属性を持つベクトルとして格納されていることが判り、改めてcnt、sclと名前をつけたベクトルとして、取り出しました。

cnt <- attr(train_sc, "scaled:center")
scl <- attr(train_sc, "scaled:scale")

2. 標準化したデータで重回帰式作成

Rの関数lmへは、scale=Trueといった引数を渡すことができませんので、標準化した学習データを重回帰して、回帰式 eq1を作ります。scale関数が返すオブジェクトは、上記のようにarrayなので、df.train_scという名前のdata.frameにしてから、lm関数で重回帰式 eq1を作りました。

df.train_sc <- as.data.frame(train_sc)
eq1 <- lm(「目的変数列名」~., data=df.train_sc)
summary(eq1)

3. テストデータの標準化

項目2. で作成した重回帰式 eq1は、標準化した学習データから得た式ですので、テストデータにそのまま適用することはできず、テストデータを標準化しておく必要があります。この際、テストデータの各列の平均、標準偏差を使うのではなく、学習データのそれらを使う必要があります。
学習データの平均、標準偏差は、先にcnt、sclとして取り出していますので、これらをテストデータを標準化する際の引数として、scale関数へ渡します。今回は、標準化は、説明変数である1列目からn列に対してのみ行いました。

test_sc <- scale(df.test[,1:n], center=cnt[1:n], scale=scl[1:n])

次いで、テストデータを標準化した結果をdf.test_scという名前のdata.frame形式にしてから、predict関数で項目2. で作成した重回帰式を適用して、予測値を算出し、df.test_scのpredict列に格納しました。

df.test_sc <- as.data.frame(test_sc)
df.test_sc$predict <- predict(eq1, newdata=df.test_sc)

4. 標準化したテストデータの予測値を戻す

ただし、このままでは得られた予測値(df.test_sc$predict)は、標準化されたスケールの数値です。標準化前の数値に戻すためには、ここで得た数値に、学習データの目的変数の標準偏差を掛け、平均を足してやる必要があります。スケールを戻した予測結果は、大元のデータを分割したテストデータのdata.frameであるdf.testにpredictという名前の列を作って、追記しました。
また、学習データ自身を項目2. で作成した重回帰式に適用した結果は、学習データのdata.frameであるdf.trainに、同じくpredictという名前の列を作って、追記しました。学習データの予測値は、再度predict関数で予測しなおす必要はありません。関数lmが返すlist型のオブジェクトの各要素も属性を持っており、["fitted.values"]で取り出すことができます。

df.test$predict <- df.test_sc$predict*scl[n+1]+cnt[n+1]
df.train$predict <- eq1[["fitted.values"]]*scl[n+1]+cnt[n+1]

まとめ

Rの標準関数であるlm関数を用い、標準化したデータでの重回帰を行なって、回帰式を得ました。また、この回帰式を、別のデータに適用し予測値を得る際に、追加分データの標準化や、得た予測値を元のスケールに戻す方法について記載しました。
Rの関数の返り値であるオブジェクトの要素がnames属性を持っている場合、その属性を使うことにより、再計算することなく、求める値を取り出すことができるので、本稿のケースに限らず、各オブジェクトの中身を確認して活用することをお勧めします。

Discussion