Pythonで2次元配列(リストのリスト)をソート
Pythonで2次元配列(リストのリスト)をソートする方法について、以下の内容を説明する。
- リストの順序(大小)比較
- 2次元配列を行ごと・列ごとにソート
sorted()
とリスト内包表記を利用- NumPyを利用:
np.sort()
- 2次元配列を特定の行・列を基準にソート
sorted()
やsort()
の引数key
を利用- 基本的な考え方
- 複数の行・列を基準にソート
- pandasを利用:
sort_values()
- NumPyを利用:
np.argsort()
基本的にはNumPyやpandasを使うほうが楽なので、外部ライブラリを使用できる環境であればそちらを使うのがオススメ。
以降のサンプルコードにおいて、出力を見やすくするためにpprintを使っている。
import pprint
リストの順序(大小)比較
Pythonにおけるリストオブジェクトの順序(大小)比較は、最初の等しくない要素に対して行われる。
順序比較をサポートしているコレクションの順序は、最初の等価でない要素の順序と同じになります (例えば、
[1,2,x] <= [1,2,y]
はx <= y
と同じ値になります)。 対応する要素が存在しない場合、短い方のコレクションの方が先の順序となります (例えば、[1,2] < [1,2,3]
は真です)。 6. 式 (expression) - 値の比較 — Python 3.8.6rc1 ドキュメント
print([100] > [-100])
# True
print([1, 2, 100] > [1, 2, -100])
# True
print([1, 2, 100] > [1, 100])
# False
以下の2次元配列(リストのリスト)を例とする。
l_2d = [[20, 3, 100], [1, 200, 30], [300, 10, 2]]
pprint.pprint(l_2d, width=20)
# [[20, 3, 100],
# [1, 200, 30],
# [300, 10, 2]]
sorted()
関数やリストのsort()
メソッドでソートする場合、デフォルトでは各リストが比較されソートされる。
上述のようにリスト同士の比較は最初の等しくない要素に対して行われるので、この例では各リストの最初(先頭)の要素同士が比較されソートされる。
pprint.pprint(sorted(l_2d), width=20)
# [[1, 200, 30],
# [20, 3, 100],
# [300, 10, 2]]
sort()
とsorted()
についての詳細は以下の記事を参照。
2次元配列を行ごと・列ごとにソート
行ごと・列ごとに独立してソートする方法について述べる。
sorted()とリスト内包表記を利用
行ごとにソートすることは、各リストをそれぞれソートすることに等しい。リスト内包表記で各リストにsorted()
を適用すればよい。
- 関連記事: Pythonリスト内包表記の使い方
pprint.pprint(l_2d, width=20)
# [[20, 3, 100],
# [1, 200, 30],
# [300, 10, 2]]
pprint.pprint([sorted(l) for l in l_2d], width=20)
# [[3, 20, 100],
# [1, 30, 200],
# [2, 10, 300]]
列ごとにソートしたい場合、元のオブジェクト(リストのリスト)を転置してから上の例のように各リストをソートし、再度転置すればよい。
リストのリストの転置はzip()
と*
を利用すると実現できる。
pprint.pprint(
[list(x) for x in zip(*[sorted(l) for l in zip(*l_2d)])],
width=20
)
# [[1, 3, 2],
# [20, 10, 30],
# [300, 200, 100]]
NumPyを利用: np.sort()
NumPyを利用するともっと楽。
np.sort()
を使う。デフォルトでは行ごと、axis=0
とすると列ごとにソートされる。
import numpy as np
print(np.sort(l_2d))
# [[ 3 20 100]
# [ 1 30 200]
# [ 2 10 300]]
print(np.sort(l_2d, axis=0))
# [[ 1 3 2]
# [ 20 10 30]
# [300 200 100]]
返り値はnumpy.ndarray
。リストに変換したい場合はtolist()
を使う。
print(type(np.sort(l_2d)))
# <class 'numpy.ndarray'>
print(np.sort(l_2d).tolist())
# [[3, 20, 100], [1, 30, 200], [2, 10, 300]]
print(type(np.sort(l_2d).tolist()))
# <class 'list'>
なお、NumPyにおいて多次元配列として扱われるのは、各次元の要素数が等しい場合のみ。以下のような場合はリストを要素とするnumpy.ndarray
とみなされるので所望の結果にはならない。要注意。
l_2d_error = [[1, 2], [3, 4, 5]]
print(np.sort(l_2d_error))
# [list([1, 2]) list([3, 4, 5])]
NumPyにおいては、numpy.ndarray
のメソッドとしてもsort()
が提供されている。詳細は以下の記事を参照。
2次元配列を特定の行・列を基準にソート
行ごと・列ごとに独立してソートするのではなく、特定の行・列を基準にソートする方法について述べる。
sorted()やsort()の引数keyを利用
基本的な考え方
上述のように、デフォルトでは各リストの最初の等しくない要素が比較されソートされる。
pprint.pprint(l_2d, width=20)
# [[20, 3, 100],
# [1, 200, 30],
# [300, 10, 2]]
pprint.pprint(sorted(l_2d), width=20)
# [[1, 200, 30],
# [20, 3, 100],
# [300, 10, 2]]
2列目や3列目を基準に並べ替えたい場合、sorted()
関数やsort()
メソッドの引数key
を利用する。
key
には、ソートされる(各要素が比較される)前に各要素に適用される関数を指定する。key
に指定した関数の結果に従ってソートされる。
今回の例ではリストから任意の位置(インデックス)の要素を取得する関数を指定すればよい。ラムダ式(無名関数)またはoperatorモジュールのitemgetter()
を使う。
ラムダ式を使う例。
pprint.pprint(sorted(l_2d, key=lambda x: x[1]), width=20)
# [[20, 3, 100],
# [300, 10, 2],
# [1, 200, 30]]
pprint.pprint(sorted(l_2d, key=lambda x: x[2]), width=20)
# [[300, 10, 2],
# [1, 200, 30],
# [20, 3, 100]]
operator.itemgetter()
を使う例。
import operator
pprint.pprint(sorted(l_2d, key=operator.itemgetter(1)), width=20)
# [[20, 3, 100],
# [300, 10, 2],
# [1, 200, 30]]
引数key
やoperatorモジュールについての詳細は以下の記事を参照。
特定の行を基準にソートしたい場合は、転置してから上の処理を行い、再度転置すればよい。
複数の行・列を基準にソート
重複した値を持つ場合を例とする。
l_2d_dup = [[20, 3, 100], [1, 200, 30], [1, 10, 2]]
pprint.pprint(l_2d_dup, width=20)
# [[20, 3, 100],
# [1, 200, 30],
# [1, 10, 2]]
デフォルトでは各リストの最初の等しくない要素が比較されソートされるので、1列目が等しい場合は2列目の値でソートされる。
pprint.pprint(sorted(l_2d_dup), width=20)
# [[1, 10, 2],
# [1, 200, 30],
# [20, 3, 100]]
引数key
にラムダ式やoperator.itemgetter()
で列を指定する場合、値が等しいと元の順序が保持される。
pprint.pprint(sorted(l_2d_dup, key=operator.itemgetter(0)), width=20)
# [[1, 200, 30],
# [1, 10, 2],
# [20, 3, 100]]
operator.itemgetter()
の引数に複数の値(インデックス)を指定すると、最初の値が等しい場合は次の値で比較されソートされる。
pprint.pprint(sorted(l_2d_dup, key=operator.itemgetter(0, 1)), width=20)
# [[1, 10, 2],
# [1, 200, 30],
# [20, 3, 100]]
ラムダ式でも同様の処理が可能。
pprint.pprint(sorted(l_2d_dup, key=lambda x: (x[0], x[1])), width=20)
# [[1, 10, 2],
# [1, 200, 30],
# [20, 3, 100]]
pandasを利用: sort_values()
pandasを利用するともっと楽。
リストのリストからpandas.DataFrame
を生成する。行名・列名は便宜上つけているだけなので、何でもよい。
import pandas as pd
df = pd.DataFrame(l_2d_dup, columns=['A', 'B', 'C'], index=['X', 'Y', 'Z'])
print(df)
# A B C
# X 20 3 100
# Y 1 200 30
# Z 1 10 2
sort_values()
で特定の列や行を基準にソートできる。デフォルトでは列、axis=1
とすると行を指定できる。
print(df.sort_values('A'))
# A B C
# Y 1 200 30
# Z 1 10 2
# X 20 3 100
print(df.sort_values('X', axis=1))
# B A C
# X 3 20 100
# Y 200 1 30
# Z 10 1 2
複数指定も可能。
print(df.sort_values(['A', 'B']))
# A B C
# Z 1 10 2
# Y 1 200 30
# X 20 3 100
昇順・降順を指定する引数ascending
など、sort_values()
の詳細は以下の記事を参照。
pandas.DataFrame
をリストやnumpy.ndarray
と相互に変換することも可能。
- 関連記事: pandas.DataFrame, SeriesとPython標準のリストを相互に変換
- 関連記事: pandas.DataFrame, SeriesとNumPy配列ndarrayを相互に変換
NumPyを利用: np.argsort()
NumPyで配列を特定の行・列を基準にソートするにはnp.argsort()
を使う方法がある。
詳細は以下の記事を参照されたいが、結構ややこしいので特にNumPyにこだわる必要がなければ上述のpandasを使うほうが簡単。