よちよちpython

独習 python/Qpython/Pydroid3/termux/Linux

【Pandas】成績表を順位付けし、積み上げ棒グラフを作成する

Pandasで成績表を順位付けし、df.plot.bar()で棒グラフを作成する

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



【実行環境】

  • Windows10
  • WSL:Ubuntu:Anaconda
  • Python3.8
  • Jupyter Notebook
  • 使用ライブラリ
    • numpy、pandas、matplotlib、japanize-matplotlib(グラフの日本語表示用)



Anacondaをインストールすると今回使うライブラリは最初から全部入っているようなので(japanize-matplotlib以外は)インストールは不要です。
入ってないようならcondapip

$ pip install notebook numpy pandas matplotlib japanize-matplotlib

など環境に合わせてインストールしておきます。



成績表をランダム生成する

国・数・社・理・英、各100点満点のテストの成績表を100人分、一様分布のランダムな値で生成します。

python日本語の文字を変数として使えるので、横着して・・・

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib
%matplotlib inline
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({
    "国語" : 国,
    "数学" : 数,
    "社会" : 社,
    "理科" : 理,
    "英語" : 英
})

# 表示
df
国語 数学 社会 理科 英語
0 44 48 70 18 51
1 47 49 85 17 30
2 64 69 31 93 53
3 67 41 13 84 58
4 67 35 71 2 43
... ... ... ... ... ...
95 58 61 67 91 98
96 23 83 35 51 97
97 79 33 30 99 43
98 13 32 29 18 3
99 85 100 33 34 12

100 rows × 5 columns

100人分の成績表ができました。

行単位で合計を算出し、成績表に追加する

次は、5教科の合計点を個人ごとに算出し、合計列を追加します。
行方向で合計を出すにはdf.sum(axis=1)で行います。

# 5教科合計列を追加する 行単位での合計値
df['合計'] = df.sum(axis=1)
# 表示
df
国語 数学 社会 理科 英語 合計
0 44 48 70 18 51 231
1 47 49 85 17 30 228
2 64 69 31 93 53 310
3 67 41 13 84 58 263
4 67 35 71 2 43 218
... ... ... ... ... ... ...
95 58 61 67 91 98 375
96 23 83 35 51 97 289
97 79 33 30 99 43 284
98 13 32 29 18 3 95
99 85 100 33 34 12 264

100 rows × 6 columns

合計列を追加できました。

Pandasのrank()メソッドで順位付けし、成績表に追加する

次に、成績表の合計点により、100人に順位をつけたいと思います。
人間にこのような単一の物差しで序列を作るせいで勉強嫌いが増えるのであります。

「合計」列のSeriesデータをrank()メソッドに渡すと順位が出せます。並び替えたりせずとも出来、なかなか便利です。
rank()メソッドはデフォルト設定では昇順でランク付けされます。つまり低い順から1位、2位・・・となる。
なので、合計点の高い順から降順でランク付けする必要があります。それには引数のascending=Falseとすればよい。

同一順位はその平均順位になります。1位が2人いれば、2人の順位は共に1.5になり、2位は居なくて3位以下に続きます。
rank()の引数methodがデフォルトで平均値averageになっています。

「1.5位とかないやろ~。」

はい、おっしゃる通り。
rank()の引数method='min'としてやれば、最小値を順位にできます。1位タイ、1位タイ、3位・・・と。
値はfloat型が返ってくるので、これを整数型に直し、成績表に順位列として追加します。



まず少ないデータでrank()メソッドの使い方を見て、その後に成績表の順位付けを行っていきます。

rank()の使い方

  • pandasのメソッド
  • デフォルト設定では昇順でランク付けされる。
    • 降順にするには、引数でascending=Falseとする。
  • 同一順位はその平均順位が返る。
    • 引数methodがデフォルトで平均値averageになっている。
      • 例) 1位が2人いれば、2人の順位は共に1.5になり、2位は居なくて3位以下に続きます。
    • 引数method='min'としてやれば、最小値を順位にできます。
    • 値はfloat型が返ってくる。
# 整数のランダムなSeriesデータを生成
np.random.randint(1,11,10)
array([5, 2, 2, 1, 8, 9, 5, 4, 6, 7])
# Seriesデータに対して順位付けをする
na = pd.Series(np.random.randint(1,11,10)) # ランダムデータ
default_rank = na.rank() # デフォルト設定 : 降順でランク付け、同一順位は平均
syoujun_average_rank = na.rank(ascending=True)  # 昇順でランク付け、同一順位は平均
koujun_min_rank = na.rank(ascending=False, method='min') # 降順でランク付け、同一順位は最小値
syoujun_min_rank = na.rank(ascending=True, method='min') # 昇順でランク付け、同一順位は最小値


data = {"元データ": na,
        "1":default_rank, 
       "2":syoujun_average_rank,
       "3":koujun_min_rank,
       "4":syoujun_min_rank}

print("0列目 :元データ")
print("1列目 :デフォルト設定:降順でランク付け、同一順位は平均")
print("2列目 :降順でランク付け、同一順位は平均")
print("3列目 :降順でランク付け、同一順位は最小値")
print("4列目 :昇順でランク付け、同一順位は最小値")

pd.DataFrame(data) 
0列目 :元データ
1列目 :デフォルト設定:降順でランク付け、同一順位は平均
2列目 :降順でランク付け、同一順位は平均
3列目 :降順でランク付け、同一順位は最小値
4列目 :昇順でランク付け、同一順位は最小値
元データ 1 2 3 4
0 4 4.5 4.5 6.0 4.0
1 3 3.0 3.0 8.0 3.0
2 10 9.5 9.5 1.0 9.0
3 9 7.5 7.5 3.0 7.0
4 2 2.0 2.0 9.0 2.0
5 5 6.0 6.0 5.0 6.0
6 1 1.0 1.0 10.0 1.0
7 9 7.5 7.5 3.0 7.0
8 4 4.5 4.5 6.0 4.0
9 10 9.5 9.5 1.0 9.0

元データ列の値に対して、rank()で順位評価した。
全てfloat型。.astype('int')などで整数型に出来る。
同一順位の設定がaverageだと、4.5や7.5のような半端な順位になる。
昇順と降順の設定で順位が真逆になる。



成績表の順位付け

上記を踏まえ、合計列に対して「降順」「同一順位は最小値」「整数値に変換」で順位付けをする。

# 合計列を順位付けし、順位列を追加する 「降順」「同一順位は最小値」「整数値に変換」
df['順位'] = df['合計'].rank(ascending=False, method='min').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

順位付きの成績表が完成しました。
インデックス番号はそのままで、順位の方がバラバラになっています。



成績表を棒グラフにする

合計点を棒グラフに

合計列でグラフ作成します。

# グラフのサイズと背景色の設定 (jupyterの背景が黒という個人的理由で、グラフの背景色を白にする
fig = plt.figure(figsize=(15,6), facecolor='w') 

# 合計列を棒グラフにする
plt.bar(df.index, df['合計'])

# グラフのタイトル設置
plt.title('成績表(5教科合計)')
# ラベルの設置
plt.xlabel('学籍番号')
plt.ylabel('合計点')
# グリッド線設置
plt.grid()

# 画像の保存
plt.savefig('成績表.jpg')

# グラフ表示
plt.show()

f:id:chayarokurokuro:20210626211503j:plain



pandas.DataFrame.plot.bar()で積み上げ棒グラフに

ついでに、積み上げグラフも作ります。積み上げ棒グラフはちょっと面倒です。
pandasにはdf.plot()でグラフが作成できる機能が付いています。これを使えばいくらか簡単にできる。
積み上げ棒グラフを描きたいならdf.plot.bar()などとします。

# グラフのサイズと背景色の設定 (jupyterの背景が黒という個人的理由で、グラフの背景色を白にする
fig, ax = plt.subplots(figsize=(20,8), facecolor='w') 

# 積み上げ棒グラフの描画
df.iloc[:, 0:5].plot.bar(y=df.columns[:5], ax=ax, alpha=0.8, stacked=True)

# グラフのタイトル設置
plt.title('積み上げ成績表(5教科合計)')
# ラベルの設置
plt.xlabel('学籍番号')
plt.ylabel('合計点')
# グリッド線設置
plt.grid()

# 画像の保存
plt.savefig('積み上げ成績表.jpg')

# グラフ表示
plt.show()

f:id:chayarokurokuro:20210626211625j:plain

pandasのグラフ機能を使えば、簡単っちゃ簡単。



df.sort_values()で成績順に並び替える

sort_values()はDataFrameでもSeriesでも使えるようです。引数によって昇順・降順を入れ替えたり、並び替えの基準を設定します。

df.sort_values('合計', ascending=False)で成績順に並び替える

合計列を基準に、成績順(降順)で並び替え、上の方法で積み上げ棒グラフを描いてみます。

# 成績表を成績順(降順)に並び替える
df_sort = df.sort_values('合計', ascending=False)
# 表示
df_sort
国語 数学 社会 理科 英語 合計 順位
29 79 62 87 89 80 397 1
95 58 61 67 91 98 375 2
31 64 94 32 91 79 360 3
77 99 67 36 72 80 354 4
20 81 73 24 92 73 343 5
... ... ... ... ... ... ... ...
63 42 24 10 36 7 119 96
52 28 32 9 19 25 113 97
98 13 32 29 18 3 95 98
56 36 19 3 13 23 94 99
87 3 56 1 4 9 73 100

100 rows × 7 columns

順位で並び変わりました。
これを基にdf.plot.bar()で積み上げ棒グラフを描きます。

# グラフのサイズと背景色の設定 (jupyterの背景が黒という個人的理由で、グラフの背景色を白にする
fig, ax = plt.subplots(figsize=(20,8), facecolor='w') 

# 棒グラフの描画
Y = df_sort.columns[:5]
df_sort.iloc[:, 0:5].plot.bar(y=Y, ax=ax, alpha=0.8, stacked=True)

# グラフのタイトル設置
plt.title('並び替え積み上げ成績表(5教科合計)')
# ラベルの設置
plt.xlabel('学籍番号')
plt.ylabel('合計点')
# グリッド線設置
plt.grid()

# 画像の保存
plt.savefig('並び替え積み上げ成績表.jpg')

# グラフ表示
plt.show()

f:id:chayarokurokuro:20210626211730j:plain

右上の凡例は自動で付いています。邪魔にならないような所に設置されている。
今度は、「順位」を基準に「昇順」で、成績に問題のある方から並び替えたDataFrameを用意して積み上げ棒グラフを描いてみます。
ついでに関数にします。

# 積み上げ棒グラフの作成関数
def stacked_bar(df, title, savefigname, cmap=None, figsize=(20,8)):
    # グラフのサイズと背景色の設定 (jupyterの背景が黒という個人的理由で、グラフの背景色を白にする
    fig, ax = plt.subplots(figsize=figsize, facecolor='w') 
    # 棒グラフの描画
    Y = df.columns[:5] # ラベル名
    df.plot.bar(y=Y, ax=ax, alpha=1.0, stacked=True, cmap=cmap)  # 積み上げ棒グラフ
    # グラフのタイトル設置
    plt.title(title)
    # ラベルの設置
    plt.xlabel('学籍番号')
    plt.ylabel('合計点')
    # グリッド線設置
    plt.grid()
    # 画像の保存
    plt.savefig(savefigname)
    # グラフ表示
    plt.show()
    
# 実行
DF = df.sort_values('順位', ascending=False) # 順位で昇順
TITLE = '順位昇順並び替え積み上げ成績表(5教科合計)'
FIGNAME = '順位昇順並び替え積み上げ成績表.jpg'
CMAP = 'cool'
# 関数に代入
stacked_bar(df=DF, title=TITLE, savefigname=FIGNAME, cmap=CMAP)

f:id:chayarokurokuro:20210626211904j:plain

cmapを設定してみた。きれい。教科の境目が見えにくいけど。

おわりに

今回は、ランダムな整数の配列生成、データフレームの作成、行単位での合計値の出し方、データフレームに列の追加、Seriesデータの順位付け、matplotlibでの通常の棒グラフ作成、Pandasのグラフ作成機能を使った積み上げ棒グラフを作成、sort_valuesでデータフレームの並び替え、と色いろ詰め込み過ぎた。

この成績表を基に、例えば100人を4クラスに分けたいとします。どのクラスも成績をなるべく平均化したい。合計点では平均的だが理数科目の好成績が片寄るという風なことも避けたい。性別データはありませんが男女比率も均等にしたいなど。機械学習でデータセットをtrain_test_splitなどでランダムに分割したり、片寄りが出ないようにクロスバリデーション(交差検証)を行ったりしますが、次回はデータの分割について少し考えてみたいと思います。

以上です。
次回、【Scikit-learn】k-平均法(k-means)を使って成績表からおまかせクラス編成する - よちよちpythonにつづく。