よちよちpython

独習 python/Qpython/Pydroid3/termux/Linux

【Numpy・Pandas・Scikit-learn】成績表のDataFrameを行でシャッフルし、クラス分けする

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回目。
クラス分けをする際、人数は同じに、成績表の合計点と教科ごとでも適度に分散し平均的になるように分けたい。

標準偏差」を用いればバラツキが抑えられるのではないか。
バラツキが大きいと標準偏差も大きくなりますので、標準偏差が小さくなるようにクラス分け出来れば目標に近づけるはず・・・



【クラス分けの方法について】

方法の手順として、

  1. データフレームをランダムにシャッフルして、クラス番号を順に割り振る
  2. クラスごとで教科や合計等の平均値を出す
  3. クラス間で平均値の標準偏差を出しバラツキを見る
  4. クラス間の教科や合計の標準偏差の更に標準偏差を出し、その最小値を探す



でやってみようと思います。上手く行くかは・・・
とりあえず、今回は上の①のみ。シャッフルしてクラス番号をつける。



【前回までの投稿内容】

前々回は、学校のテストの成績表のようなものを適当に作り、その合計点を算出してPandasで順位付けを行いました。
また、Pandasのグラフ作成機能を使って、積み上げ棒グラフを作成しました。

前回は趣旨を離れて、機械学習ライブラリScikit-learnk-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以外は)インストールは不要です。
入ってないようならcondapip

$ 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につづく。