pandas.DataFrameの行をシャッフルし、クラス分けする
今回は、NumpyとPandasとScikit-learnを使って、2次元配列やDataFrameを行でシャッフルする方法と、Numpyで配列要素の繰り返し配列を生成する方法です。
- DataFrameを行でシャッフル
numpy.random.permutation()
: 2次元のnumpy配列を行でシャッフルpandas.DataFrame.sample(frac=1)
: DataFrameのまま行でシャッフルsklearn.utils.shuffle()
: DataFrameのまま行でシャッフル- 配列を繰り返した配列を生成
np.repeat()
の使い方を学びます。
5教科の成績表をもとに100人を4つにクラス分けする方法を考えています。シリーズ3回目。
クラス分けをする際、人数は同じに、成績表の合計点と教科ごとでも適度に分散し平均的になるように分けたい。
「標準偏差」を用いればバラツキが抑えられるのではないか。
バラツキが大きいと標準偏差も大きくなりますので、標準偏差が小さくなるようにクラス分け出来れば目標に近づけるはず・・・
【クラス分けの方法について】
方法の手順として、
- データフレームをランダムにシャッフルして、クラス番号を順に割り振る
- クラスごとで教科や合計等の平均値を出す
- クラス間で平均値の標準偏差を出しバラツキを見る
- クラス間の教科や合計の標準偏差の更に標準偏差を出し、その最小値を探す
でやってみようと思います。上手く行くかは・・・
とりあえず、今回は上の①のみ。シャッフルしてクラス番号をつける。
【前回までの投稿内容】
前々回は、学校のテストの成績表のようなものを適当に作り、その合計点を算出してPandasで順位付けを行いました。
また、Pandasのグラフ作成機能を使って、積み上げ棒グラフを作成しました。
前回は趣旨を離れて、機械学習ライブラリScikit-learn
のk-means法(k-平均法)
を使った教師なし学習によるクラスタリングで4クラスにおまかせクラス編成する方法をやりました。
k-平均法のお任せクラスタ分類では似たメンバーが偏り、人数がバラバラなクラス編成になりました。
【実行環境】
- Windows10
- WSL:Ubuntu:Anaconda4.10.1
- Python3.8.10
- Jupyter Notebook6.4.0
- 使用ライブラリ
- numpy1.20.2、pandas1.2.4、matplotlib3.3.4、scikit-learn0.24.2、japanize-matplotlib(グラフの日本語表示用)
Anacondaをインストールすると今回使うライブラリは最初から全部入っているようなので(japanize-matplotlib以外は)インストールは不要です。
入ってないようならconda
やpip
で
$ pip install notebook numpy pandas matplotlib sklearn japanize-matplotlib
など環境に合わせてインストールしておきます。
成績表をランダム生成する
前回、前々回と同様に、国・数・社・理・英、各100点満点のテストの成績表を100人分、一様分布のランダムな値で生成します。
5教科の合計点を個人ごとに算出し、その値の大きい順から順位付けを行います。
pythonは日本語の文字を変数として使えるので、横着して・・・
# 成績表の生成import numpy as np import pandas as pd np.random.seed(0) # ランダムの固定# 教科ごとのランダムな配列を生成国 = np.random.randint(0,101,100) 数 = np.random.randint(0,101,100) 社 = np.random.randint(0,101,100) 理 = np.random.randint(0,101,100) 英 = np.random.randint(0,101,100) # 成績表データフレームの作成 df = pd.DataFrame({ "国語" : 国, "数学" : 数, "社会" : 社, "理科" : 理, "英語" : 英 }) # 5教科合計列の追加 df['合計'] = df.sum(axis=1) # 合計点順位列の追加 (降順で順位付け、同一順位は最小値を取る、順位を整数値に変換) df['順位'] = df['合計'].rank(method='min', ascending=False).astype('int') # 表示 df
国語 | 数学 | 社会 | 理科 | 英語 | 合計 | 順位 | |
---|---|---|---|---|---|---|---|
0 | 44 | 48 | 70 | 18 | 51 | 231 | 64 |
1 | 47 | 49 | 85 | 17 | 30 | 228 | 66 |
2 | 64 | 69 | 31 | 93 | 53 | 310 | 17 |
3 | 67 | 41 | 13 | 84 | 58 | 263 | 43 |
4 | 67 | 35 | 71 | 2 | 43 | 218 | 72 |
... | ... | ... | ... | ... | ... | ... | ... |
95 | 58 | 61 | 67 | 91 | 98 | 375 | 2 |
96 | 23 | 83 | 35 | 51 | 97 | 289 | 24 |
97 | 79 | 33 | 30 | 99 | 43 | 284 | 29 |
98 | 13 | 32 | 29 | 18 | 3 | 95 | 98 |
99 | 85 | 100 | 33 | 34 | 12 | 264 | 39 |
100 rows × 7 columns
100人分の成績表ができました。
インデックス番号が0~99まで順に付いています。
データフレームを行でランダムにシャッフルする
データフレームをインデックス(行)でシャッフルします。
上の表は0番~99番までインデックスが順番に並んでいますが、シャッフルする事でバラバラに組み替わります。
データフレームを行でシャッフルする方法として、
- Numpy :
numpy.random.permutation()
- Pandas :
pandas.DataFrame.sample(frac=1)
- Scikit-learn :
sklearn.utils.shuffle()
などの便利なメソッドがあるようです。
NumPyを使う場合は、一旦valuesを取り出して行いますので、折角のデータフレームのインデックスとカラム名を再設定しないといけなくなります。
PandasかScikit-learnの方を使えばデータフレームそのままでシャッフル出来るので便利です。
numpy.random.permutation()で2次元配列を行でシャッフル
2次元の配列を行でランダムにシャッフルします。
# numpy配列を行シャッフル np.random.seed(0) # 乱数固定 na = np.arange(15).reshape(-1, 3) # 配列生成 shuffled_na = np.random.permutation(na) # 行でシャッフル# 表示print('元の配列') print(na) print('\n行でランダムにシャッフル') print(shuffled_na)
元の配列
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]
[12 13 14]]
行でランダムにシャッフル
[[ 6 7 8]
[ 0 1 2]
[ 3 4 5]
[ 9 10 11]
[12 13 14]]
pandas.DataFrame.sample(frac=1)で行シャッフル
データフレームを行でシャッフルします。
# データフレーム作成 values = np.arange(1,16).reshape(-1,3) name = ["項目1", "項目2", "項目3"] _df = pd.DataFrame(values, columns=name) # 表示 _df
項目1 | 項目2 | 項目3 | |
---|---|---|---|
0 | 1 | 2 | 3 |
1 | 4 | 5 | 6 |
2 | 7 | 8 | 9 |
3 | 10 | 11 | 12 |
4 | 13 | 14 | 15 |
# pandasで行シャッフル _df.sample(frac=1, random_state=0) # random_stateで乱数固定
項目1 | 項目2 | 項目3 | |
---|---|---|---|
2 | 7 | 8 | 9 |
0 | 1 | 2 | 3 |
1 | 4 | 5 | 6 |
3 | 10 | 11 | 12 |
4 | 13 | 14 | 15 |
行でシャッフルされました。
random_stateで乱数固定すると、何度実行しても同じ出力が返ります。外せば実行毎に違う出力になる。
次はScikit-learnでやってみます。
sklearn.utils.shuffle()で行シャッフル
これもデータフレームのまま行でシャッフルします。
# データフレーム生成 na = np.arange(25).reshape(5,5) _df = pd.DataFrame(na) _df
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 0 | 1 | 2 | 3 | 4 |
1 | 5 | 6 | 7 | 8 | 9 |
2 | 10 | 11 | 12 | 13 | 14 |
3 | 15 | 16 | 17 | 18 | 19 |
4 | 20 | 21 | 22 | 23 | 24 |
# データフレームの行をランダムにシャッフルするimport sklearn sklearn.utils.shuffle(_df, random_state=0) # (random_stateでランダムを固定)
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
2 | 10 | 11 | 12 | 13 | 14 |
0 | 0 | 1 | 2 | 3 | 4 |
1 | 5 | 6 | 7 | 8 | 9 |
3 | 15 | 16 | 17 | 18 | 19 |
4 | 20 | 21 | 22 | 23 | 24 |
行でシャッフルされました。
では次に、成績表でやってみます。Scikit-learnを使って行ってみます。
成績表のデータフレームをScikit-learnで行シャッフル
# 成績表dfの行をランダムに入れ替える df_shuffled = sklearn.utils.shuffle(df, random_state=0) df_shuffled
国語 | 数学 | 社会 | 理科 | 英語 | 合計 | 順位 | |
---|---|---|---|---|---|---|---|
26 | 20 | 59 | 86 | 46 | 8 | 219 | 71 |
86 | 47 | 75 | 61 | 11 | 70 | 264 | 39 |
2 | 64 | 69 | 31 | 93 | 53 | 310 | 17 |
55 | 0 | 38 | 23 | 87 | 57 | 205 | 79 |
75 | 0 | 66 | 33 | 83 | 36 | 218 | 72 |
... | ... | ... | ... | ... | ... | ... | ... |
96 | 23 | 83 | 35 | 51 | 97 | 289 | 24 |
67 | 65 | 98 | 39 | 14 | 83 | 299 | 21 |
64 | 58 | 2 | 27 | 25 | 9 | 121 | 95 |
47 | 74 | 43 | 53 | 56 | 100 | 326 | 8 |
44 | 57 | 36 | 51 | 67 | 23 | 234 | 61 |
100 rows × 7 columns
成績表が行でランダムにシャッフルされました。
インデックス番号がランダムに入れ替わっています。
以上で、行方向のシャッフルについては終わりです。
次に、↑でシャッフルしたデータフレームにクラス番号を割り振ります。
0組~3組の4つに分けます。上から25人ずつ割り振っていきます。
クラス番号列の生成
↑でシャッフルした成績表のデータフレームに、クラス番号の配列[0,0,・・・,1,1,・・・2,2,・・・,3,3・・・]
のように数字が25個ずつ繰り返す列を追加したい。
[1,2,3]
という配列を[1,1,2,2,3,3]
にしたいような場合、numpy.repeat(配列, 回数, axis=整数)
を使うと簡単にできます。
※ 参考:
[1,2,3]
を[1,2,3,1,2,3,1,2,3]
というタイル状に繰り返したい場合はnp.tile()
が使えます。今回は使いません。
numpy.repeat(配列, 回数)で配列要素の繰り返し
まず、1次元配列で確認します。
# 配列の要素を増殖! a1 = np.array([1,2,3]) # 1次元配列 リスト型やタプル型などでもOK a1_3 = np.repeat(a1, 3) # 3回繰り返すprint("元の配列") print(a1) print("="*20) print("3回繰り返し") print(a1_3)
元の配列
[1 2 3]
====================
3回繰り返し
[1 1 1 2 2 2 3 3 3]
要素が指定回数に増えています。np.repeat()
の引数に渡す配列はリスト型やタプル型でもOK
です。どれも同じ出力になります。
pd.Seriesデータを引数に渡すと、↓の例のようにSeriesデータで返ってきます。
# 配列の要素を増殖! (Seriesデータの場合) a1_s = pd.Series(np.array([1,2,3])) # 1次元配列 Seriesに変換 a1_3_s = np.repeat(a1_s, 3) # 3回繰り返すprint("元の配列") print(a1_s) print("="*20) print("3回繰り返し") print(a1_3_s)
元の配列
0 1
1 2
2 3
dtype: int64
====================
3回繰り返し
0 1
0 1
0 1
1 2
1 2
1 2
2 3
2 3
2 3
dtype: int64
次は2次元配列で、axis=0とaxis=1の2パターン
# 配列の要素を増殖! a2 = np.array([[1,2,3],[4,5,6],[7,8,9]]) # 2次元配列 a2_2_0 = np.repeat(a2, 2, axis=0) # 2回繰り返す axis=0方向 a2_3_1 = np.repeat(a2, 3, axis=1) # 3回繰り返す axis=1方向print("元の配列") print(a2) print("="*20) print("2回繰り返す axis=0方向") print(a2_2_0) print("="*20) print("3回繰り返す axis=1方向") print(a2_3_1)
元の配列
[[1 2 3]
[4 5 6]
[7 8 9]]
====================
2回繰り返す axis=0方向
[[1 2 3]
[1 2 3]
[4 5 6]
[4 5 6]
[7 8 9]
[7 8 9]]
====================
3回繰り返す axis=1方向
[[1 1 1 2 2 2 3 3 3]
[4 4 4 5 5 5 6 6 6]
[7 7 7 8 8 8 9 9 9]]
axis=0がデフォルトです。
np.repeat()
の使い方を押さえたところで、
次は、クラス番号の配列[0,0,・・・,1,1,・・・2,2,・・・,3,3・・・]
のように数字が25個ずつ繰り返すものを生成して、ランダムにシャッフルした成績表のデータフレームに列を追加します。
# クラス番号列を追加 df_shuffled_class = df_shuffled.copy() # コピー df_shuffled_class['クラス番号'] = np.repeat(np.array([0,1,2,3]), 25) df_shuffled_class
国語 | 数学 | 社会 | 理科 | 英語 | 合計 | 順位 | クラス番号 | |
---|---|---|---|---|---|---|---|---|
26 | 20 | 59 | 86 | 46 | 8 | 219 | 71 | 0 |
86 | 47 | 75 | 61 | 11 | 70 | 264 | 39 | 0 |
2 | 64 | 69 | 31 | 93 | 53 | 310 | 17 | 0 |
55 | 0 | 38 | 23 | 87 | 57 | 205 | 79 | 0 |
75 | 0 | 66 | 33 | 83 | 36 | 218 | 72 | 0 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
96 | 23 | 83 | 35 | 51 | 97 | 289 | 24 | 3 |
67 | 65 | 98 | 39 | 14 | 83 | 299 | 21 | 3 |
64 | 58 | 2 | 27 | 25 | 9 | 121 | 95 | 3 |
47 | 74 | 43 | 53 | 56 | 100 | 326 | 8 | 3 |
44 | 57 | 36 | 51 | 67 | 23 | 234 | 61 | 3 |
100 rows × 8 columns
ランダムにシャッフルした成績表にクラス番号を割り振りました。
今回はここまで。
次回はクラスごとに平均値を出し、標準偏差でクラス間のバラツキを見るところからです。
ありがとうございました。
【Numpy・Pandas・Scikit-learn】成績表からランダムにクラス分けしたバラツキ具合を標準偏差で確認する - よちよちpythonにつづく。