[Python]pandasのデータフレームを標準化(偏差値化)する

[Python]pandasのデータフレームを標準化(偏差値化)する
pandas上でデータを標準化する方法について書いていきます。最近だと正規化呼んだりもします。変数も特徴量って呼んだり、そういう呼び方でその人のデータ分析の背景が見えてきたりして興味深いなぁ〜とたまに思ったりします。

先ずは、モジュールの呼び出しとダミーデータを用意します。

In [1]:
import pandas as pd
import numpy as np

df = pd.DataFrame({
    'segment':['A','A','A','B','B','B','C','C','C','D','D','D'],
    'score_A':np.random.normal(size=12),
    'score_B':np.random.normal(size=12)
},columns=['segment','score_A','score_B'])
df
Out[1]:
segment score_A score_B
0 A 1.331488 -0.072466
1 A -2.241023 0.200983
2 A 1.203518 1.775138
3 B 0.204725 1.000204
4 B -0.703218 0.862893
5 B -0.636959 0.121187
6 C 1.060702 -0.395215
7 C -0.061274 -2.650494
8 C -1.238797 -0.587040
9 D -0.224044 1.984073
10 D -1.263220 0.565483
11 D -0.396752 -0.706947

では、このscoreを使って標準化をしていきます。

標準化

applyを使って、各scoreごとに標準化処理を行っていきます。socoreの列をcontains関数を使って一括で抽出してます。

In [2]:
df_norm = df.loc[:,df.columns.str.contains('score')].apply(lambda x : ((x - x.mean())*1/x.std()+0),axis=0)
df_norm
Out[2]:
score_A score_B
0 1.456557 -0.199669
1 -1.839845 0.021128
2 1.338477 1.292182
3 0.416878 0.666460
4 -0.420893 0.555588
5 -0.359755 -0.043303
6 1.206699 -0.460273
7 0.171438 -2.281302
8 -0.915078 -0.615163
9 0.021247 1.460887
10 -0.937614 0.315444
11 -0.138113 -0.711981

ちゃんと標準化されているか、平均と分散を計算してみましょう。

In [3]:
round(df_norm.mean())
Out[3]:
score_A    0.0
score_B    0.0
dtype: float64
In [4]:
df_norm.std()
Out[4]:
score_A    1.0
score_B    1.0
dtype: float64

無事に平均0、分散1に標準化されてます。

偏差値化

ついでに偏差値化もしてみます。偏差値とは、平均50、標準偏差10に基準化したスコアを指します。それに合わせてapply関数内の計算式を変更します。

In [5]:
df_stand = df.loc[:,df.columns.str.contains('score')].apply(lambda x : ((x - x.mean())*10/x.std()+50),axis=0)
df_stand
Out[5]:
score_A score_B
0 64.565570 48.003315
1 31.601550 50.211280
2 63.384773 62.921821
3 54.168783 56.664605
4 45.791070 55.555884
5 46.402453 49.566966
6 62.066994 45.397272
7 51.714379 27.186983
8 40.849219 43.848372
9 50.212475 64.608875
10 40.623863 53.154442
11 48.618872 42.880187

確認してみます。

In [6]:
round(df_stand.mean())
Out[6]:
score_A    50.0
score_B    50.0
dtype: float64
In [7]:
df_stand.std()
Out[7]:
score_A    10.0
score_B    10.0
dtype: float64

セグメントごとに標準化

カテゴリーごとに標準化をしてみます。結構、社内ではこの依頼が多い。。。

In [8]:
seg_df_norm = pd.DataFrame() #結果を格納するデータフレーム

seg_list = set(df['segment']) #標準化するセグメントをリスト化

for seg in seg_list:
    seg_norm = df.query('segment == "{seg}"'.format(seg = seg)).copy()
    seg_norm.loc[:,seg_norm.columns.str.contains('score')] = seg_norm.loc[:,seg_norm.columns.str.contains('score')].\
                                                       apply(lambda x : ((x - x.mean())*1/x.std()+0),axis=0)
    seg_df_norm = pd.concat([seg_df_norm,seg_norm])

seg_df_norm
Out[8]:
segment score_A score_B
9 D 0.725442 1.017605
10 D -1.140731 -0.036192
11 D 0.415289 -0.981413
6 C 0.991851 0.652355
7 C 0.016103 -1.151299
8 C -1.007954 0.498943
3 B 1.152224 0.716421
4 B -0.641565 0.426045
5 B -0.510659 -1.142465
0 A 0.608634 -0.709007
1 A -1.154125 -0.434789
2 A 0.545491 1.143796

ちゃんとセグメントごとに標準化されているか確認してみます。

In [9]:
round(seg_df_norm.groupby('segment').mean())
Out[9]:
score_A score_B
segment
A -0.0 -0.0
B -0.0 0.0
C 0.0 -0.0
D -0.0 -0.0
In [10]:
round(seg_df_norm.groupby('segment').std())
Out[10]:
score_A score_B
segment
A 1.0 1.0
B 1.0 1.0
C 1.0 1.0
D 1.0 1.0

ちゃんとセグメントごとに平均0、標準偏差1に標準化されてますね。

まとめ

sklernモジュールのStandardScalerクラスtransformで行うことも可能です。ただ、そのためだけにsklernを呼び出すのはメモリの無駄ですし、今回紹介した方法の方がデータフレームのレイアウトに合わせた形で処理ができるので、単純にスコアを標準化するだけならば、こちらでよく使います。あと、axisで処理の方向を決められるので、横方向に標準化という荒業もできたりします。

Pythonカテゴリの最新記事