Pythonで2次元配列(リストのリスト)をソート
Pythonで2次元配列(リストのリスト)をソートする方法について説明する。
NumPyやpandasを使うほうが楽なので、外部ライブラリを使用できる環境であればそちらを使うのがオススメ。
本記事のサンプルコードでは、出力を見やすくするためにpprintを使っている。
import pprint
コード中でpprint.pprint()
の引数としてwidth=20
を指定しているが、出力を整えるためなので特に気にしなくてよい。
2次元配列に対するsorted()やsort()のデフォルトの振る舞い
以下の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()
メソッドでソートする場合、デフォルトでは各リストが比較されソートされる。
リスト同士の比較は最初の等しくない要素に対して行われるので、この例では各リストの最初(先頭)の要素が比較されソートされる。
- 関連記事: Pythonでリストを比較
pprint.pprint(sorted(l_2d), width=20)
# [[1, 200, 30],
# [20, 3, 100],
# [300, 10, 2]]
sort()
とsorted()
についての詳細は以下の記事を参照。
2次元配列を行ごと・列ごとにソート
2次元配列を行ごと・列ごとに独立してソートする方法について述べる。
sorted()とリスト内包表記を利用
行ごとにソートすることは、各リストをそれぞれソートすることに等しい。リスト内包表記で各リストにsorted()
を適用すればよい。
- 関連記事: Pythonリスト内包表記の使い方
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]]
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を利用するともっと楽。以下の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]]
np.sort()
を使う。デフォルトでは行ごと、axis=0
とすると列ごとにソートされる。返り値はnumpy.ndarray
。
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]]
print(type(np.sort(l_2d)))
# <class 'numpy.ndarray'>
numpy.ndarray
をリストに変換したい場合はtolist()
を使う。
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において多次元配列として扱われるのは、各次元の要素数が等しい場合のみ。要素数が異なるリストのリストはエラーになるので注意。
l_2d_error = [[1, 2], [3, 4, 5]]
# print(np.sort(l_2d_error))
# ValueError: setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (2,) + inhomogeneous part.
numpy.ndarray
のメソッドとしてもsort()
が提供されている。詳細は以下の記事を参照。
2次元配列を特定の行・列を基準にソート
行ごと・列ごとに独立してソートするのではなく、特定の行・列を基準にソートする方法について述べる。
sorted()やsort()の引数keyを利用
基本的な考え方
上述のように、デフォルトでは1列目(各リストの先頭の要素)を基準にソートされる。
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]]
pprint.pprint(sorted(l_2d), width=20)
# [[1, 200, 30],
# [20, 3, 100],
# [300, 10, 2]]
2列目や3列目を基準に並べ替えたい場合、sorted()
関数やsort()
メソッドの引数key
を利用する。
key
に関数などの呼び出し可能オブジェクトを指定すると、各要素にその関数を適用した結果に従ってソートされる。
今回の例では、リストの任意の位置(インデックス)の要素を取得する関数を指定すればよい。ラムダ式(無名関数)またはoperatorモジュールのitemgetter()
を使う。
ラムダ式を使う例。2列目と3列目を基準にしている。0
始まりなので指定するのは1
と2
。
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]]
pprint.pprint(sorted(l_2d, key=operator.itemgetter(2)), width=20)
# [[300, 10, 2],
# [1, 200, 30],
# [20, 3, 100]]
引数key
やoperatorモジュールについての詳細は以下の記事を参照。
特定の行を基準にソートしたい場合は、転置してから上の処理を行い、再度転置すればよい。
複数の行・列を基準にソート
重複した値を持つ場合を例とする。
l_2d_dup = [[1, 3, 100], [1, 200, 30], [1, 3, 2]]
pprint.pprint(l_2d_dup, width=20)
# [[1, 3, 100],
# [1, 200, 30],
# [1, 3, 2]]
デフォルトでは各リストの最初の等しくない要素が比較されソートされるので、1列目が等しい場合は2列目、2列目も等しい場合は3列目の値でソートされる。
pprint.pprint(sorted(l_2d_dup), width=20)
# [[1, 3, 2],
# [1, 3, 100],
# [1, 200, 30]]
先頭から順番にではなく、任意の複数の列を基準にソートしたい場合は、引数key
を使う。
operator.itemgetter()
の引数に複数の値(インデックス)を指定すると、最初の値が等しい場合は次の値で比較されソートされる。以下の例では1列目と3列目を基準にしている。
pprint.pprint(sorted(l_2d_dup, key=operator.itemgetter(0, 2)), width=20)
# [[1, 3, 2],
# [1, 200, 30],
# [1, 3, 100]]
ラムダ式でも同様の処理が可能。
pprint.pprint(sorted(l_2d_dup, key=lambda x: (x[0], x[2])), width=20)
# [[1, 3, 2],
# [1, 200, 30],
# [1, 3, 100]]
特定の行を基準にソートしたい場合は、転置してから上の処理を行い、再度転置すればよい。
pandasを利用: sort_values()
pandasを利用するともっと楽。以下の2次元配列(リストのリスト)を例とする。
l_2d_dup = [[1, 3, 100], [1, 200, 30], [1, 3, 2]]
pprint.pprint(l_2d_dup, width=20)
# [[1, 3, 100],
# [1, 200, 30],
# [1, 3, 2]]
リストのリストから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 1 3 100
# Y 1 200 30
# Z 1 3 2
sort_values()
で特定の列や行を基準にソートできる。デフォルトでは列、axis=1
とすると行を指定できる。
print(df.sort_values('C'))
# A B C
# Z 1 3 2
# Y 1 200 30
# X 1 3 100
print(df.sort_values('Z', axis=1))
# A C B
# X 1 100 3
# Y 1 30 200
# Z 1 2 3
複数指定も可能。
print(df.sort_values(['A', 'C']))
# A B C
# Z 1 3 2
# Y 1 200 30
# X 1 3 100
pandas.DataFrame
生成時にcolumns
, index
引数を省略すると行名・列名は連番になる。行名・列名がどちらも数値になって分かりにくいが、これでも問題なく処理できる。
df = pd.DataFrame(l_2d_dup)
print(df)
# 0 1 2
# 0 1 3 100
# 1 1 200 30
# 2 1 3 2
print(df.sort_values(2))
# 0 1 2
# 2 1 3 2
# 1 1 200 30
# 0 1 3 100
print(df.sort_values(2, axis=1))
# 0 2 1
# 0 1 100 3
# 1 1 30 200
# 2 1 2 3
print(df.sort_values([0, 2]))
# 0 1 2
# 2 1 3 2
# 1 1 200 30
# 0 1 3 100
昇順・降順を指定する引数ascending
など、sort_values()
の詳細は以下の記事を参照。
pandas.DataFrame
をリストやnumpy.ndarray
と相互に変換することも可能。
- 関連記事: pandas.DataFrame, SeriesとPython標準のリストを相互に変換
- 関連記事: pandas.DataFrame, SeriesとNumPy配列ndarrayを相互に変換
例えば、以下のようにpandas.DataFrame
をリストに変換できる。
print(df.sort_values([0, 2]).values.tolist())
# [[1, 3, 2], [1, 200, 30], [1, 3, 100]]
print(type(df.sort_values([0, 2]).values.tolist()))
# <class 'list'>
NumPyを利用: np.argsort()
NumPyで配列を特定の行・列を基準にソートするにはnp.argsort()
を使う方法がある。
詳細は以下の記事を参照されたいが、結構ややこしいので特にNumPyにこだわる必要がなければ上述のpandasを使うほうが簡単。