pythonで複数のlistやnumpy配列を同時にソートする

pythonで1つの配列を基準として、複数の配列を同時にソートする方法を紹介します。pythonのリスト(list)とnumpy配列(array)のそれぞれについて方法を紹介します。

この記事でできること

pythonのリスト(list)やnumpy配列(array)が複数ある場合に、そのうちの1つの配列を基準に、同じ順番で他の配列もソートする方法を紹介します。

例えば、次のような3つの配列(list)をソートするとします。この記事では、物体検出AIの出力である矩形座標”rects”、識別クラス”classes”、信頼度(スコア)”scores”の配列を例として、信頼度”scores”の値を基準として、”scores”の大きさの順番で”rects”と”classes”も並び替えます。

    rects = [[10,20,100,200], 
             [11,21,110,210], 
             [12,22,120,220], 
             [13,23,130,230]]
    classes = ["woman", "man", "woman", "man"]
    scores = [0.5, 0.2, 0.9, 0.3]

複数配列の同時ソートは、物体検出AIの評価指標であるmAPの算出やNMS(Non-Maximum Supression)の算出等に必要となる場面があります。NMSのサンプルコードについては次の記事で以前紹介しました。この記事では使いませんでしたが、複数配列の同時ソートを使うと、より綺麗に実装できたりします。

また、本記事では昇順と降順の両方について実装例を紹介します。

pythonリスト(list)の複数同時ソート

まずは、リストの同時ソートについて説明します。

昇順(小さい順)

複数リストを降順で同時ソートするサンプルコードは次の通りです。昇順とは小さい順のことです。

    # zipで一つの変数"zip_lists"にまとめる
    # ソートの基準としたいリスト(ここではscores)を一番左においてzip
    zip_lists = zip(scores, classes, rects)
    # 昇順でソート
    zip_sort = sorted(zip_lists)
    # zipを解除
    scores, classes, rects = zip(*zip_sort)

    print("scores", scores)
    print("classes", classes)
    print("rects", rects)

リストのソートはpythonの関数sorted()で行うことができます。複数の同時ソートをするには、一旦関数zip()で1つの変数にまとめ、関数sorted()でソートしたあと、zip(*)でzip状態を解除するという手順で行います。

関数zip()で1つの変数にまとめる際、ソートの基準としたいリスト”scores”を一番最初に指定するのがミソです。このサンプルコードの出力結果は次のようになります。

scores (0.2, 0.3, 0.5, 0.9)
classes ('man', 'man', 'woman', 'woman')
rects ([11, 21, 110, 210], [13, 23, 130, 230], [10, 20, 100, 200], [12, 22, 120, 220])

リスト”classes”や”rects”も、”scores”の値の昇順(小さい順)で並び替えることができました。

降順(大きい順)

降順で複数リストをソートするサンプルコードは次の通りです。降順とは大きい順のことです。

    # zipで一つの変数"zip_lists"にまとめる
    # ソートの基準としたいリスト(ここではscores)を一番左においてzip
    zip_lists = zip(scores, classes, rects)
    # "reverse=True"を指定すると降順でソート
    zip_sort = sorted(zip_lists, reverse=True)
    # zipを解除
    scores, classes, rects = zip(*zip_sort)

    print("scores", scores)
    print("classes", classes)
    print("rects", rects)

降順にするには、関数sorted()の引数に”reverse=True”を指定し、sorted(reverse=True)とするだけです。このコードの出力結果は次のようになります。

scores (0.9, 0.5, 0.3, 0.2)
classes ('woman', 'woman', 'man', 'man')
rects ([12, 22, 120, 220], [10, 20, 100, 200], [13, 23, 130, 230], [11, 21, 110, 210])

numpy array(配列)の複数同時ソート

上では複数リストの同時ソートを扱いましたが、次に、numpy arrayを同時にソートする方法を紹介します。

ここでは、次のサンプルコードに示す3つのnumpy array、矩形座標”rects_np”、識別クラス”classes_np”、信頼度(スコア)”scores_np”を例に扱います。

    import numpy as np

    rects = [[10,20,100,200], 
             [11,21,110,210], 
             [12,22,120,220], 
             [13,23,130,230]]
    classes = ["woman", "man", "woman", "man"]
    scores = [0.5, 0.2, 0.9, 0.3]

    # リストをnumpy arrayに変換
    scores_np = np.array(scores)
    classes_np = np.array(classes)
    rects_np = np.array(rects)

昇順(小さい順)

複数のnumpy arrayの同時ソート(昇順)のサンプルコードは次の通りです。

    # scores_npを昇順ソートした場合の配列インデックスを取得
    sorted_idx = np.argsort(scores_np)
    print("sorted_idx", sorted_idx)
    # 各numpy arrayの要素の順番を"sorted_idx"の順番に変換
    scores_np = scores_np[sorted_idx]
    classes_np = classes_np[sorted_idx]
    rects_np = rects_np[sorted_idx]

    print("scores_np", scores_np)
    print("classes_np", classes_np)
    print("rects_np", rects_np)

numpy arrayのソートを行う関数として”np.sort()”がありますが、ここでは関数”np.argsort()”を使用します。関数”np.argsort()”は、ソートした結果の要素のインデックスを順番に出力します。つまり、引数として指定したnumpy arrayの最も小さい要素のインデックスから順番に(今回は昇順となります)、インデックス番号の配列を出力します。

複数numpy配列を同時ソートするには、まず”np.argsort()”で”scores_np”をソートしたときのインデックス番号の配列”sorted_idx”を取得して、”sorted_idx”に含まれるインデックス番号の順番で各numpy arrayの要素を並び替えます。numpy arrayの要素を並び替えるには、インデックスの配列”sorted_idx”を”classes_np[sorted_idx]”のようにして参照すればOKです。

このサンプルコードの出力結果は次のようになります。

sorted_idx [1 3 0 2]
scores_np [0.2 0.3 0.5 0.9]
classes_np ['man' 'man' 'woman' 'woman']
rects_np [[ 11  21 110 210]
 [ 13  23 130 230]
 [ 10  20 100 200]
 [ 12  22 120 220]]

降順(大きい順)

次に降順の場合の、複数numpy arrayの同時ソート方法を紹介します。

    # scores_npにマイナスをかけた値を昇順ソートした場合の配列インデックスを取得
    # -scores_npの昇順ソートなのでscores_npの降順ソートのインデックスとなる
    sorted_idx = np.argsort(-scores_np)
    print("sorted_idx", sorted_idx)
    # 各numpy arrayの要素の順番を"sorted_idx"の順番に変換
    scores_np = scores_np[sorted_idx]
    classes_np = classes_np[sorted_idx]
    rects_np = rects_np[sorted_idx]

    print("scores_np", scores_np)
    print("classes_np", classes_np)
    print("rects_np", rects_np)

“関数np.argsort()”は昇順でソートを行いますが、これを降順にするにはソートの対象とする配列”scores_np”にマイナスを掛け、”-scores_np”を引数としてソートすれば実現できます。マイナスをつけた値をソートすれば、大小の順番が逆になるというわけです。

“-scores_np”を引数とすることで、昇順の時とは反対の順番のインデックス”sorted_idx”を取得でき、このときに得られる”sorted_idx”を使って、numpy arrayの要素を並び替えます。

このサンプルコードの出力結果は次の通りです。

sorted_idx [2 0 3 1]
scores_np [0.9 0.5 0.3 0.2]
classes_np ['woman' 'woman' 'man' 'man']
rects_np [[ 12  22 120 220]
 [ 10  20 100 200]
 [ 13  23 130 230]
 [ 11  21 110 210]]

終わりに

何番煎じかになってしまいましたが、複数配列の同時ソートについて紹介しました。

コメント

タイトルとURLをコピーしました