Python, pandasで任意の順番にソート(ソート順を指定)
Pythonの組み込み関数sort()
やsorted()
メソッド、pandasのsort_values()
などでは、数値の大小や文字コードの順番に従って要素がソートされる。
ここでは、任意のソート順を指定して自由に並べ替える方法を説明する。
最後に例を挙げるように、都道府県名を北から順に並べたいというような場合に便利。
sort(), sorted()で任意の順に並び替え
Pythonでは、リストのsort()
メソッドや組み込み関数sorted()
でソートを行う。これらの違いについては以下の記事を参照。
デフォルトでは文字列のリストはアルファベット順にソートされる。降順にしたい場合はreverse=True
とする。サンプルコードはsorted()
を使っているがsort()
でも同じ。
l = ['Banana', 'Alice', 'Apple', 'Bob']
print(sorted(l))
# ['Alice', 'Apple', 'Banana', 'Bob']
print(sorted(l, reverse=True))
# ['Bob', 'Banana', 'Apple', 'Alice']
引数keyを指定
任意の順番にソートしたい場合、sort()
, sorted()
の引数key
を使う。
key
にはソートされる(各要素が比較される)前にリストの各要素に適用される呼び出し可能オブジェクト(関数など)を指定する。例えば、組み込み関数len()
を指定すると、文字数の順にソートできる。
l = ['Banana', 'Alice', 'Apple', 'Bob']
print(sorted(l, key=len))
# ['Bob', 'Alice', 'Apple', 'Banana']
任意のソート順を指定するには、リストまたは辞書を使用する。
なお、リストの方法で用いるindex()
は時間計算量がO(n)
の遅い処理なので、辞書を使うほうが高速。要素数が少ない場合は特に気にしなくてもよいが、要素数が増えるとリストを使用する方法だとかなり時間がかかるので注意。
以降のサンプルコードはsorted()
を使っているがsort()
でも同様に引数key
を指定すればよい。
リストを使用
要素が所望の順番に並んだリストを用意し、そのリストのindex()
メソッドをkey
に指定する。各要素がindex()
の引数に渡され、その結果を元にソートされる。
l = ['Bob', 'Banana', 'Alice', 'Apple', 'Bob', 'Apple']
l_order = ['Alice', 'Bob', 'Apple', 'Banana']
print(sorted(l, key=l_order.index))
# ['Alice', 'Bob', 'Bob', 'Apple', 'Apple', 'Banana']
辞書を使用
辞書を使用する方法もある。
{元の要素: 順番, ...}
という辞書を用意し、キーから値を取り出すラムダ式を指定する。
l = ['Bob', 'Banana', 'Alice', 'Apple', 'Bob', 'Apple']
d_order = {'Alice': 0, 'Bob': 1, 'Apple': 2, 'Banana': 3}
print(sorted(l, key=lambda x: d_order[x]))
# ['Alice', 'Bob', 'Bob', 'Apple', 'Apple', 'Banana']
この辞書は所望の順番に並んだリストから生成可能。辞書内包表記とenumerate()
を使う。
l_order = ['Alice', 'Bob', 'Apple', 'Banana']
d_order = {x: i for i, x in enumerate(l_order)}
print(d_order)
# {'Alice': 0, 'Bob': 1, 'Apple': 2, 'Banana': 3}
注意点
リストの場合
リストを使用する場合、所望の順番のリストにソートするリストの要素がすべて含まれていないとエラーになるので注意。
所望の順番のリストに含まれていない要素を先頭または末尾に並べたい場合は、ラムダ式と三項演算子、および、条件式としてin
演算子を使う。
l = ['Bob', 'Banana', 'Alice', 'Apple', 'Bob', 'Apple', 'xxx']
l_order = ['Alice', 'Bob', 'Apple', 'Banana']
# print(sorted(l, key=l_order.index))
# ValueError: 'xxx' is not in list
print(sorted(l, key=lambda x: l_order.index(x) if x in l_order else -1))
# ['xxx', 'Alice', 'Bob', 'Bob', 'Apple', 'Apple', 'Banana']
print(sorted(l, key=lambda x: l_order.index(x) if x in l_order else float('inf')))
# ['Alice', 'Bob', 'Bob', 'Apple', 'Apple', 'Banana', 'xxx']
若干ややこしいが、三項演算子の条件式部分を括弧で囲むと以下のようになる。
l_order.index(x) if (x in l_order) else -1
x in l_order
がTrue
(=要素がl_order
に含まれている)だとl_order.index(x)
、False
(=要素がl_order
に含まれていない)だと-1
が返される。
含まれていない要素を最後に並べたい場合、適当な大きい値を使ってもいいが、ここでは無限大を表すfloat('inf')
を使っている。
反対に、所望の順番のリストにソートするリストに含まれていない要素があっても問題ない。
l = ['Bob', 'Banana', 'Alice', 'Apple', 'Bob', 'Apple']
l_order = ['Alice', 'Bob', 'Apple', 'Banana', 'xxx']
print(sorted(l, key=l_order.index))
# ['Alice', 'Bob', 'Bob', 'Apple', 'Apple', 'Banana']
辞書の場合
辞書の場合も同様。順序を規定する辞書のキーにソートするリストの要素がすべて含まれていないとエラーとなる。
辞書の場合はget()
メソッドを使う。キーが存在しないときに返す値を第二引数に指定できる。
l = ['Bob', 'Banana', 'Alice', 'Apple', 'Bob', 'Apple', 'xxx']
d_order = {'Alice': 0, 'Bob': 1, 'Apple': 2, 'Banana': 3}
# print(sorted(l, key=lambda x: d_order[x]))
# KeyError: 'xxx'
print(sorted(l, key=lambda x: d_order.get(x, -1)))
# ['xxx', 'Alice', 'Bob', 'Bob', 'Apple', 'Apple', 'Banana']
print(sorted(l, key=lambda x: d_order.get(x, float('inf'))))
# ['Alice', 'Bob', 'Bob', 'Apple', 'Apple', 'Banana', 'xxx']
順序を規定する辞書のキーにソートするリストの要素にないものが含まれていても問題ない。
l = ['Bob', 'Banana', 'Alice', 'Apple', 'Bob', 'Apple']
d_order = {'Alice': 0, 'Bob': 1, 'Apple': 2, 'Banana': 3, 'xxx': 4}
print(sorted(l, key=lambda x: d_order[x]))
# ['Alice', 'Bob', 'Bob', 'Apple', 'Apple', 'Banana']
pandasのsort_values()で任意の順に並び替え
pandasで列やインデックスの値を基準にソートするにはsort_values()
, sort_index()
を使う。降順にしたい場合はascending=False
。
import pandas as pd
df = pd.read_csv('data/src/sample_pandas_normal.csv')
print(df)
# name age state point
# 0 Alice 24 NY 64
# 1 Bob 42 CA 92
# 2 Charlie 18 CA 70
# 3 Dave 68 TX 70
# 4 Ellen 24 CA 88
# 5 Frank 30 NY 57
print(df.sort_values('age'))
# name age state point
# 2 Charlie 18 CA 70
# 0 Alice 24 NY 64
# 4 Ellen 24 CA 88
# 5 Frank 30 NY 57
# 1 Bob 42 CA 92
# 3 Dave 68 TX 70
print(df.sort_values('name', ascending=False))
# name age state point
# 5 Frank 30 NY 57
# 4 Ellen 24 CA 88
# 3 Dave 68 TX 70
# 2 Charlie 18 CA 70
# 1 Bob 42 CA 92
# 0 Alice 24 NY 64
サンプルデータは以下のCSVファイル。
sort_values()
やsort_index()
の引数key
(バージョン1.1.0
で追加)には、指定した列(= pandas.Series
)やpandas.Index
自体を受け取る関数を指定する。Pythonの組み込み関数sorted()
やsort()
メソッドのように、各要素を受け取る関数ではないので注意。
pandas.Series
のmap()
メソッドに辞書を指定すると、値を置換したpandas.Series
を生成できる。キーが存在しない場合はNaN
。
{元の要素: 順番, ...}
という辞書を用意して使えばよい。
d_order = {'Charlie': 0, 'Alice': 1, 'Ellen': 2, 'Bob': 3}
print(df['name'].map(d_order))
# 0 1.0
# 1 3.0
# 2 0.0
# 3 NaN
# 4 2.0
# 5 NaN
# Name: name, dtype: float64
print(df.sort_values('name', key=lambda col: col.map(d_order)))
# name age state point
# 2 Charlie 18 CA 70
# 0 Alice 24 NY 64
# 4 Ellen 24 CA 88
# 1 Bob 42 CA 92
# 3 Dave 68 TX 70
# 5 Frank 30 NY 57
デフォルトではNaN
が末尾になるが、na_position='first'
とすると先頭に並べられる。
print(df.sort_values('name', key=lambda col: col.map(d_order), na_position='first'))
# name age state point
# 3 Dave 68 TX 70
# 5 Frank 30 NY 57
# 2 Charlie 18 CA 70
# 0 Alice 24 NY 64
# 4 Ellen 24 CA 88
# 1 Bob 42 CA 92
活用例: 都道府県名を都道府県コード順にソート
具体的な活用例として、都道府県名を都道府県コード順にソートする例を示す。
漢字はUnicodeコードポイント(文字コード)の順にソートされるので、人間にとって意味のある順序にはならない。
l = ['沖縄県', '東京都', '北海道', '京都府']
print(sorted(l))
# ['京都府', '北海道', '東京都', '沖縄県']
例えば、都道府県名をJIS X 0401やISO 3166-2:JPで定められた都道府県コードの順番にソートしたい場合は、その順番に並んだリストを用意すればよい。
tdfk = ['北海道', '青森県', '岩手県', '宮城県', '秋田県', '山形県', '福島県',
'茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県',
'新潟県', '富山県', '石川県', '福井県', '山梨県', '長野県', '岐阜県',
'静岡県', '愛知県', '三重県', '滋賀県', '京都府', '大阪府', '兵庫県',
'奈良県', '和歌山県', '鳥取県', '島根県', '岡山県', '広島県', '山口県',
'徳島県', '香川県', '愛媛県', '高知県', '福岡県', '佐賀県', '長崎県',
'熊本県', '大分県', '宮崎県', '鹿児島県', '沖縄県']
print(sorted(l, key=tdfk.index))
# ['北海道', '東京都', '京都府', '沖縄県']
リストではなく辞書を使う場合は{都道府県名: コード番号, ...}
とする。この辞書はコード番号順のリストから作成可能。
d_tdfk = {x: i for i, x in enumerate(tdfk)}
print(d_tdfk)
# {'北海道': 0, '青森県': 1, '岩手県': 2, '宮城県': 3, '秋田県': 4, '山形県': 5, '福島県': 6, '茨城県': 7, '栃木県': 8, '群馬県': 9, '埼玉県': 10, '千葉県': 11, '東京都': 12, '神奈川県': 13, '新潟県': 14, '富山県': 15, '石川県': 16, '福井県': 17, '山梨県': 18, '長野県': 19, '岐阜県': 20, '静岡県': 21, '愛知県': 22, '三重県': 23, '滋賀県': 24, '京都府': 25, '大阪府': 26, '兵庫県': 27, '奈良県': 28, '和歌山県': 29, '鳥取県': 30, '島根県': 31, '岡山県': 32, '広島県': 33, '山口県': 34, '徳島県': 35, '香川県': 36, '愛媛県': 37, '高知県': 38, '福岡県': 39, '佐賀県': 40, '長崎県': 41, '熊本県': 42, '大分県': 43, '宮崎県': 44, '鹿児島県': 45, '沖縄県': 46}
print(sorted(l, key=lambda x: d_tdfk[x]))
# ['北海道', '東京都', '京都府', '沖縄県']
例えば五十音順に並べたい場合は、その順番のリストを用意すればよい。ひらがなやカタカナは五十音順にソートされるので、{都道府県名: 読み仮名, ...}
という辞書を使うことも可能。
リストに都道府県名以外の要素がある場合の処理の仕方も上述の通り。
l = ['沖縄県', '東京都', '北海道', '京都府', 'xxx']
# print(sorted(l, key=tdfk.index))
# ValueError: 'xxx' is not in list
print(sorted(l, key=lambda x: tdfk.index(x) if x in tdfk else -1))
# ['xxx', '北海道', '東京都', '京都府', '沖縄県']
print(sorted(l, key=lambda x: tdfk.index(x) if x in tdfk else float('inf')))
# ['北海道', '東京都', '京都府', '沖縄県', 'xxx']
print(sorted(l, key=lambda x: d_tdfk.get(x, -1)))
# ['xxx', '北海道', '東京都', '京都府', '沖縄県']
print(sorted(l, key=lambda x: d_tdfk.get(x, float('inf'))))
# ['北海道', '東京都', '京都府', '沖縄県', 'xxx']
繰り返しになるが、リストの方法で用いるindex()
は時間計算量がO(n)
の遅い処理なので、辞書を使うほうが高速。要素数が少ない場合は特に気にしなくてもよいが、要素数が増えるとリストを使用する方法だとかなり時間がかかるので注意。
pandasの例は省略するが、上述のように、{都道府県名: コード番号, ...}
の辞書をmap()
メソッドに使い、sort_values()
の引数key
に指定すればよい。
末尾の都府県のありなし
都道府県の場合は、末尾に都
, 府
, 県
が付いているものと付いていないものが混在しているパターンがある。
l = ['沖縄県', '東京', '北海道', '京都府']
# print(sorted(l, key=tdfk.index))
# ValueError: '東京' is not in list
両方に対応させたい場合、先頭の2文字のみで判定する方法がある。出力を見やすくするためにpprintを使っている。
import pprint
tdfk2 = [s[:2] for s in tdfk]
pprint.pprint(tdfk2, compact=True)
# ['北海', '青森', '岩手', '宮城', '秋田', '山形', '福島', '茨城', '栃木', '群馬', '埼玉', '千葉', '東京',
# '神奈', '新潟', '富山', '石川', '福井', '山梨', '長野', '岐阜', '静岡', '愛知', '三重', '滋賀', '京都',
# '大阪', '兵庫', '奈良', '和歌', '鳥取', '島根', '岡山', '広島', '山口', '徳島', '香川', '愛媛', '高知',
# '福岡', '佐賀', '長崎', '熊本', '大分', '宮崎', '鹿児', '沖縄']
d_tdfk2 = {x[:2]: i for i, x in enumerate(tdfk)}
print(sorted(l, key=lambda x: tdfk2.index(x[:2])))
# ['北海道', '東京', '京都府', '沖縄県']
print(sorted(l, key=lambda x: d_tdfk2[x[:2]]))
# ['北海道', '東京', '京都府', '沖縄県']
l = ['沖縄県', '東京', '北海道', '京都府', 'xxx']
print(sorted(l, key=lambda x: tdfk2.index(x[:2]) if x[:2] in tdfk2 else -1))
# ['xxx', '北海道', '東京', '京都府', '沖縄県']
print(sorted(l, key=lambda x: d_tdfk2.get(x[:2], -1)))
# ['xxx', '北海道', '東京', '京都府', '沖縄県']
この場合、例えば東京
と東京都
が混在しているとどちらも同じ順番だとみなされるので、元のリストの順番が保持される。
l = ['沖縄県', '東京', '東京都', '北海道', '京都府']
print(sorted(l, key=lambda x: tdfk2.index(x[:2])))
# ['北海道', '東京', '東京都', '京都府', '沖縄県']
l = ['沖縄県', '東京都', '東京', '北海道', '京都府']
print(sorted(l, key=lambda x: tdfk2.index(x[:2])))
# ['北海道', '東京都', '東京', '京都府', '沖縄県']
単純に都
, 府
, 県
のありなし両方をリストまたは辞書にする方法もある。ここでは元のリストと最後の文字を除外したリストを連結してソート、不要な北海
を削除して新たなリストを生成している。
- 関連記事: Pythonでリスト(配列)に要素を追加するappend, extend, insert
- 関連記事: Pythonでリスト(配列)の要素を削除するclear, pop, remove, del
tdfk3 = sorted(tdfk + [s[:-1] for s in tdfk], key=lambda x: tdfk2.index(x[:2]))
tdfk3.remove('北海')
pprint.pprint(tdfk3, compact=True)
# ['北海道', '青森県', '青森', '岩手県', '岩手', '宮城県', '宮城', '秋田県', '秋田', '山形県', '山形', '福島県',
# '福島', '茨城県', '茨城', '栃木県', '栃木', '群馬県', '群馬', '埼玉県', '埼玉', '千葉県', '千葉', '東京都',
# '東京', '神奈川県', '神奈川', '新潟県', '新潟', '富山県', '富山', '石川県', '石川', '福井県', '福井', '山梨県',
# '山梨', '長野県', '長野', '岐阜県', '岐阜', '静岡県', '静岡', '愛知県', '愛知', '三重県', '三重', '滋賀県',
# '滋賀', '京都府', '京都', '大阪府', '大阪', '兵庫県', '兵庫', '奈良県', '奈良', '和歌山県', '和歌山', '鳥取県',
# '鳥取', '島根県', '島根', '岡山県', '岡山', '広島県', '広島', '山口県', '山口', '徳島県', '徳島', '香川県',
# '香川', '愛媛県', '愛媛', '高知県', '高知', '福岡県', '福岡', '佐賀県', '佐賀', '長崎県', '長崎', '熊本県',
# '熊本', '大分県', '大分', '宮崎県', '宮崎', '鹿児島県', '鹿児島', '沖縄県', '沖縄']
この場合、例えば東京
と東京都
が混在しているときの結果は順序を規定するリストに準ずる。
l = ['沖縄県', '東京', '東京都', '北海道', '京都府']
print(sorted(l, key=tdfk3.index))
# ['北海道', '東京都', '東京', '京都府', '沖縄県']
l = ['沖縄県', '東京都', '東京', '北海道', '京都府']
print(sorted(l, key=tdfk3.index))
# ['北海道', '東京都', '東京', '京都府', '沖縄県']