NumPy 配列のソートと比較する意味で,ここで説明した Python リストのソートについて再確認してみよう.Python リストの sort()
メソッドは,リスト自体を書き換えます.
Python リストをソートする:リスト自体を書き換える
scores = [30, 60, 55, 40]
scores.sort()
print(scores)
[30, 40, 55, 60]
汎用関数の sorted()
はソートされたリストのコピーを返すので,リスト自体を書き換えることはありません.
socores リストを降順でソートした結果を sorted_scores リストに代入する
scores = [30, 60, 55, 40]
sorted_scores = sorted(scores, reverse=True)
print(scores) # リストは書き換えられていない
print(sorted_scores)
[30, 60, 55, 40] [60, 55, 40, 30]
NumPy 配列をソートするときにも,配列自体を書き換える sort()
メソッドと,ソートされたリストのコピーを返す np.sort()
関数があります.まず,sort()
メソッドでは,配列自体を書き換えます.
NumPy 配列の sort メソッド
scores = np.array([30, 60, 55, 40])
scores.sort()
print(scores)
[30 40 55 60]
関数 np.sort()
は配列を書き換えることはありません.
np.sort() 関数
scores = np.array([30, 60, 55, 40])
sorted_scores = np.sort(scores)
print(scores)
print(sorted_scores)
[30 60 55 40] [30 40 55 60]
NumPy の sort()
には降順でソートするオプションはありません(Python の sort() にはありました).ここで説明したように,::-1
によって逆順に並べ替えることができます.
逆順に並べる
scores = np.array([30, 60, 55, 40])
print(scores[::-1])
[40 55 60 30]
したがって,昇順ソートした結果を逆順に並べることで結果的に降順ソートが行えます.
NumPy の 降順 sort:ソートして逆順に並べる
scores = np.array([30, 60, 55, 40])
rev_sorted_scores = np.sort(scores)[::-1] # 逆順に並べる
print(scores)
print(rev_sorted_scores)
[30 60 55 40] [60 55 40 30]
Python のリストは柔軟性が高いが処理速度の面では不利になります.大きなデータを頻繁にソートする必要がある場合は NumPy 配列の np.sort
を利用することでパフォーマンスが向上することが見込まれます.実際に処理速度に差があるかどうかを検証します.
まず,1千万個の乱数からなる Python リストを準備します.
import random
scores = random.sample(range(1,100000000), 10000000)
次に,その内容を NumPy 配列にコピーします.この時点で, Python リストと NumPy 配列には実質的には同じ値のデータが格納されているはずです.
npscores = np.array(scores)
マジックコマンドの %%timeit
を使って,Python リストのソートを繰り返し行い,実行時間を計測します.この結果,1回あたり平均で 2.55 秒かかったことがわかりました(この数値はPC環境に依存します).
%%timeit
sorted_scores = sorted(scores)
2.55 s ± 10.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
一方で,NumPy 配列についても同様にソートの実行時間を計測すると,0.601 秒となり,4倍以上高速であることがわかりました.
%%timeit
sorted_scores = np.sort(npscores)
601 ms ± 667 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
次に,2次元配列(行列)のソートを行います.まず,4回のテスト結果を3名分格納した2次元配列を準備します.
scores = np.array([
[30, 60, 55, 40],
[25, 70, 20, 60],
[45, 55, 60, 50]
])
print(scores)
[[30 60 55 40] [25 70 20 60] [45 55 60 50]]
この行列を np.sort()
関数でソートすると,行ごとにソートされることがわかります.
行ごとにソート
sorted_scores = np.sort(scores)
print(scores)
print(sorted_scores)
[[30 60 55 40] [25 70 20 60] [45 55 60 50]] [[30 40 55 60] [20 25 60 70] [45 50 55 60]]
オプショナル引数 axis=1
を指定した場合も行ごとのソートになります.
sorted_X = np.sort(X, axis=1)
print(X)
print(sorted_X)
[[30 60 55 40] [25 70 20 60] [45 55 60 50]] [[30 40 55 60] [20 25 60 70] [45 50 55 60]]
オプショナル引数で axis=0
と指定した場合には列ごとのソートになります.
列ごとにソート
sorted_X = np.sort(X, axis=0)
print(X)
print(sorted_X)
[[30 60 55 40] [25 70 20 60] [45 55 60 50]] [[25 55 20 40] [30 60 55 50] [45 70 60 60]]
np.argsort()
関数はソートされた要素のインデックスを返す関数です.この関数をうまく利用するとある配列をソートするときに同時に別の配列もソートすることができるようになります. この利用例を理解するために,テストの回数(あるいは名称)を保存する配列(ベクトル)とある学生の4回のテストの得点を保存する配列(ベクトル)を準備します.
# テストの回数(あるいは名称)を保存する配列を準備する
times = np.array([1, 2, 3, 4])
# ある学生の4回のテストの得点を準備する
scores = np.array([30, 60, 55, 40])
print(times)
print(scores)
[1 2 3 4] [30 60 55 40]
つまり,1回目の得点が30点,2回目の得点が60点...という意味です.np.argsort()
関数はソートされた要素のインデックスを返すので,scores
を引数に与えてそのインデックスを i
に格納します.この結果,最も得点が低かったのはインデックス 0 で,その次がインデックス 3 ... となる事がわかりました.
# np.argsort 関数はソートされた要素のインデックスを返す
i = np.argsort(scores)
print(i)
[0 3 2 1]
np.argsort()
で得られたインデックス i
を使って,別のベクトルをソートすることが可能です.
print(times[i])
print(scores[i])
[1 4 3 2] [30 40 55 60]
つまり,得点の低かった順に,1回目が30点,4回目が40点...のようにソートすることができました.
上の例では np.argsort()
を使ってベクトルをソートするときに,別のベクトルも合わせてソートする例を示しました.ここでは,ベクトルをソートするときに合わせて別の行列もソートする例を示します.
問題の理解を容易にするために,3名の学生が4回のテストを受験した得点データがあるときに,4回のテストの合計得点順に学籍番号の列ベクトルとテスト結果の行列をソートする状況を考えます.まず,3名の学籍番号の列ベクトルと得点の2次元配列(行列)を準備します.
3名の学籍番号と4回のテスト結果を準備
# 3名の学籍番号を準備
ids = np.array([101, 102, 103]).reshape(3,1)
# 3名の4回のテスト結果を2次元配列にする
scores = np.array([
[30, 60, 55, 40],
[25, 70, 20, 60],
[45, 55, 60, 50]
])
print(ids)
print(scores)
[[101] [102] [103]] [[30 60 55 40] [25 70 20 60] [45 55 60 50]]
次にソートの評価対象になる合計得点を計算して列ベクトルに変換します.
学生ごとに4回のテストの合計得点を計算する
totals = np.sum(scores, axis=1).reshape(3, 1)
print(totals)
[[185] [175] [210]]
合計得点の(低い)順にソートし,要素のインデックスを取得します.
# 合計得点の順にソートし,要素のインデックスを取得
i = np.argsort(totals, axis=0).reshape(1,3)
print(i)
[[1 0 2]]
インデックスを用いると,得点順に学籍番号を得ることができます.
print(ids[i].reshape(3,1))
[[102] [101] [103]]
さらに,行列に対しても同様で,得点順に得点一覧を得ることができます.
print(scores[i, :].reshape(3,4))
[[25 70 20 60] [30 60 55 40] [45 55 60 50]]
最後に合計得点も確認します.
print(totals[i].reshape(3,1))
[[175] [185] [210]]
つまり,最も得点が低かったのは学籍番号 102
の学生で,その得点が 25, 70, 20, 60
,合計が 175
であったことがわかります.なお,上の例で .reshape(m, n)
がなくても結果を得ることができますが,配列の次元が異なる結果になることはぜひ確認してください.
なお,次のページで説明する構造化配列を利用すれば,このようなソートをより簡単に記述できるようになります.