Python, pandasで任意の順番にソート(ソート順を指定)

Modified: | Tags: 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_orderTrue(=要素が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.Seriesmap()メソッドに辞書を指定すると、値を置換した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])))
# ['北海道', '東京都', '東京', '京都府', '沖縄県']

単純に, , のありなし両方をリストまたは辞書にする方法もある。ここでは元のリストと最後の文字を除外したリストを連結してソート、不要な北海を削除して新たなリストを生成している。

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))
# ['北海道', '東京都', '東京', '京都府', '沖縄県']

関連カテゴリー

関連記事