note.nkmk.me

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

Date: 2019-07-01 / tags: Python, pandas, リスト

Pythonのsort(), sorted()、pandasのsort_values()などでは、数値の大小や文字コードの順番に従って要素がソートされる。

ここでは、任意のソート順を指定して自由に並べ替える方法を説明する。

  • sort(), sorted()の場合
    • 引数keyを指定
      • リストを使用
      • 辞書を使用
    • 注意点
  • 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にはソートされる(各要素が比較される)前にリストの各要素に適用される呼び出し可能オブジェクト(関数やメソッド)を指定する。

リストを使用する方法と辞書を使用する方法がある。

処理速度は辞書を使うほうが高速。要素数が少ない場合は特に気にしなくてもよいが、要素数が増えるとリストを使用する方法だとかなり時間がかかるので注意。

ここでもサンプルコードはsorted()を使っているがsort()でも同様に引数keyを指定すればよい。

リストを使用

要素が所望の順番に並んだリストを用意し、そのリストのindex()メソッドをkeyに指定する。各要素がindex()の引数に渡され、その結果を元にソートされる。

l_order = ['Alice', 'Bob', 'Apple', 'Banana']

print(sorted(l, key=l_order.index))
# ['Alice', 'Bob', 'Apple', 'Banana']

各要素のindex()の値を確認すると以下の通り。ここではリスト内包表記を使っている。

index()の結果に従ってソートされていることが分かる。

print(l)
# ['Banana', 'Alice', 'Apple', 'Bob']

print([l_order.index(s) for s in l])
# [3, 0, 2, 1]

print(sorted(l, key=l_order.index))
# ['Alice', 'Bob', 'Apple', 'Banana']

辞書を使用

辞書を使用する方法もある。

{元の要素: 順番, ...}という辞書を用意し、ラムダ式で値を取り出す。

d_order = {'Alice': 0, 'Bob': 1, 'Apple': 2, 'Banana': 3}

print(sorted(l, key=lambda x: d_order[x]))
# ['Alice', 'Bob', 'Apple', 'Banana']

注意点

リストの場合

リストを使用する場合、所望の順番のリストに元のリストの要素がすべて含まれていないとエラーになるので注意。

所望の順番のリストに含まれていない要素を先頭または末尾に並べたい場合は、ラムダ式と三項演算子、および、条件式としてin演算子を使う。

l = ['Banana', 'Alice', 'Apple', 'Bob', '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', 'Apple', 'Banana']

print(sorted(l, key=lambda x: l_order.index(x) if x in l_order else float('inf')))
# ['Alice', 'Bob', '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')を使っている。

ラムダ式(無名関数)ではなくdefで定義する普通の関数を使ってもよい。

def my_index(x):
    l_order = ['Alice', 'Bob', 'Apple', 'Banana']
    return l_order.index(x) if x in l_order else -1

print(sorted(l, key=my_index))
# ['xxx', 'Alice', 'Bob', 'Apple', 'Banana']

なお、反対に、所望の順番のリストに元のリストに含まれていない要素があっても問題はない。

l = ['Banana', 'Alice', 'Apple', 'Bob']
l_order = ['Alice', 'Bob', 'Apple', 'Banana', 'xxx']

print(sorted(l, key=l_order.index))
# ['Alice', 'Bob', 'Apple', 'Banana']

辞書の場合

辞書の場合も同様。元のリストの要素が順序を規定する辞書のキーに含まれていないとエラーとなる。

辞書の場合はget()メソッドを使う。第二引数にキーが存在しないときに返す値を指定できる。

l = ['Banana', 'Alice', 'Apple', 'Bob', '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', 'Apple', 'Banana']

print(sorted(l, key=lambda x: d_order.get(x, float('inf'))))
# ['Alice', 'Bob', 'Apple', 'Banana', 'xxx']

順序を規定する辞書のキーに元のリストの要素にないものが含まれていても問題ない。

l = ['Banana', 'Alice', 'Apple', 'Bob']
d_order = {'Alice': 0, 'Bob': 1, 'Apple': 2, 'Banana': 3, 'xxx': 4}

print(sorted(l, key=lambda x: d_order[x]))
# ['Alice', 'Bob', 'Apple', 'Banana']

pandasのsort_values()の場合

pandasで列やインデックスの値を基準にソートするにはsort_values(), sort_index()を使う。降順にしたい場合はascending=False

サンプルデータとして以下のCSVファイルを使っている。

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('name'))
#       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('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

sort_values(), sort_index()には、上述のsort(), sorted()におけるkeyのような引数はない。

pandasで任意の順番にソートしたい場合は、新たな列を追加し、その列を基準にソートする。

新たな列の生成には、sort(), sorted()keyに指定した呼び出し可能オブジェクトをapply()メソッドで適用すればOK。

l_order = ['Charlie', 'Alice', 'Dave', 'Bob']

df['order'] = df['name'].apply(lambda x: l_order.index(x) if x in l_order else -1)
print(df)
#       name  age state  point  order
# 0    Alice   24    NY     64      1
# 1      Bob   42    CA     92      3
# 2  Charlie   18    CA     70      0
# 3     Dave   68    TX     70      2
# 4    Ellen   24    CA     88     -1
# 5    Frank   30    NY     57     -1

print(df.sort_values('order'))
#       name  age state  point  order
# 4    Ellen   24    CA     88     -1
# 5    Frank   30    NY     57     -1
# 2  Charlie   18    CA     70      0
# 0    Alice   24    NY     64      1
# 3     Dave   68    TX     70      2
# 1      Bob   42    CA     92      3

インデックスを振り直すにはreset_index()、不要になった基準列を削除するにはdrop()を使う。

print(df.sort_values('order').reset_index(drop=True).drop(columns='order'))
#       name  age state  point
# 0    Ellen   24    CA     88
# 1    Frank   30    NY     57
# 2  Charlie   18    CA     70
# 3    Alice   24    NY     64
# 4     Dave   68    TX     70
# 5      Bob   42    CA     92

{元の要素: 順番, ...}の辞書がある場合はmap()メソッドで値を置換した列を生成できる。

元の辞書にキーが存在しない場合はNaNとなる。sort_values()では、NaNはデフォルトで末尾、引数na_position'first'とすると先頭に並べられる。

d_order = {'Charlie': 0, 'Alice': 1, 'Dave': 2, 'Bob': 3}

df['order'] = df['name'].map(d_order)
print(df)
#       name  age state  point  order
# 0    Alice   24    NY     64    1.0
# 1      Bob   42    CA     92    3.0
# 2  Charlie   18    CA     70    0.0
# 3     Dave   68    TX     70    2.0
# 4    Ellen   24    CA     88    NaN
# 5    Frank   30    NY     57    NaN

print(df.sort_values('order'))
#       name  age state  point  order
# 2  Charlie   18    CA     70    0.0
# 0    Alice   24    NY     64    1.0
# 3     Dave   68    TX     70    2.0
# 1      Bob   42    CA     92    3.0
# 4    Ellen   24    CA     88    NaN
# 5    Frank   30    NY     57    NaN

print(df.sort_values('order', na_position='first'))
#       name  age state  point  order
# 4    Ellen   24    CA     88    NaN
# 5    Frank   30    NY     57    NaN
# 2  Charlie   18    CA     70    0.0
# 0    Alice   24    NY     64    1.0
# 3     Dave   68    TX     70    2.0
# 1      Bob   42    CA     92    3.0

このように、pandasでは辞書を使うほうがシンプル。処理速度も速い。

インデックスを振り直したり、不要になった基準列を削除する方法はリストを使う例と同じ。

print(df.sort_values('order', na_position='first').reset_index(drop=True).drop(columns='order'))
#       name  age state  point
# 0    Ellen   24    CA     88
# 1    Frank   30    NY     57
# 2  Charlie   18    CA     70
# 3    Alice   24    NY     64
# 4     Dave   68    TX     70
# 5      Bob   42    CA     92

活用例: 都道府県名を都道府県コード順にソート

具体的な活用例として、都道府県名を都道府県コード順にソートする例を示す。

漢字はUnicodeコードポイント(文字コード)の順にソートされるので、人間にとって意味のある順序にはならない。

import pprint

l = ['沖縄県', '東京都', '北海道', '京都府']

print(sorted(l))
# ['京都府', '北海道', '東京都', '沖縄県']

ここでpprintは途中で出力を見やすくするためにインポートしている。処理自体には必要ない。

例えば、都道府県名をJIS X 0401やISO 3166-2:JPで定められた都道府県コードの順番にソートしたい場合は、その順番に並んだリストを用意すればよい。

tdfk = ['北海道', '青森県', '岩手県', '宮城県', '秋田県', '山形県', '福島県',
        '茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県',
        '新潟県', '富山県', '石川県', '福井県', '山梨県', '長野県', '岐阜県',
        '静岡県', '愛知県', '三重県', '滋賀県', '京都府', '大阪府', '兵庫県',
        '奈良県', '和歌山県', '鳥取県', '島根県', '岡山県', '広島県', '山口県', 
        '徳島県', '香川県', '愛媛県', '高知県', '福岡県', '佐賀県', '長崎県',
        '熊本県', '大分県', '宮崎県', '鹿児島県', '沖縄県']

print(sorted(l, key=tdfk.index))
# ['北海道', '東京都', '京都府', '沖縄県']

リストではなく辞書を使う場合は{都道府県名: コード番号, ...}とする。{都道府県名: コード番号, ...}の辞書はコード番号順のリストから簡単に作成可能。

d_tdfk = dict(zip(tdfk, range(len(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]))
# ['北海道', '東京都', '京都府', '沖縄県']

また、例えば五十音順に並べたい場合は、その順番のリストを用意すればOK。ひらがなやカタカナは五十音順にソートされるので、{都道府県名: 読み仮名, ...}という辞書を使うことも可能。

リストに都道府県名以外の要素がある場合の処理の仕方も上述の例の通り。

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']

末尾の都府県のありなし

都道府県の場合は、末尾に, , が付いているものと付いていないものが混在しているパターンがある。

l = ['沖縄県', '東京', '北海道', '京都府']

# print(sorted(l, key=tdfk.index))
# ValueError: '東京' is not in list

両方に対応させたい場合、先頭の2文字のみで判定する方法がある。

tdfk_head2 = [s[:2] for s in tdfk]
pprint.pprint(tdfk_head2, compact=True)
# ['北海', '青森', '岩手', '宮城', '秋田', '山形', '福島', '茨城', '栃木', '群馬', '埼玉', '千葉', '東京',
#  '神奈', '新潟', '富山', '石川', '福井', '山梨', '長野', '岐阜', '静岡', '愛知', '三重', '滋賀', '京都',
#  '大阪', '兵庫', '奈良', '和歌', '鳥取', '島根', '岡山', '広島', '山口', '徳島', '香川', '愛媛', '高知',
#  '福岡', '佐賀', '長崎', '熊本', '大分', '宮崎', '鹿児', '沖縄']

print(sorted(l, key=lambda x: tdfk_head2.index(x[:2])))
# ['北海道', '東京', '京都府', '沖縄県']

l = ['沖縄県', '東京', '北海道', '京都府', 'xxx']
print(sorted(l, key=lambda x: tdfk_head2.index(x[:2]) if x[:2] in tdfk_head2 else -1))
# ['xxx', '北海道', '東京', '京都府', '沖縄県']

この場合、例えば東京東京都が混在しているとどちらも同じ順番だとみなされるので、元のリストの順番が保持される。

l = ['沖縄県', '東京', '東京都', '北海道', '京都府']
print(sorted(l, key=lambda x: tdfk_head2.index(x[:2])))
# ['北海道', '東京', '東京都', '京都府', '沖縄県']

l = ['沖縄県', '東京都', '東京', '北海道', '京都府']
print(sorted(l, key=lambda x: tdfk_head2.index(x[:2])))
# ['北海道', '東京都', '東京', '京都府', '沖縄県']

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

tdfk_ex = sorted(tdfk + [s[:-1] for s in tdfk], key=lambda x: tdfk_head2.index(x[:2]))
tdfk_ex.remove('北海')
pprint.pprint(tdfk_ex, compact=True)
# ['北海道', '青森県', '青森', '岩手県', '岩手', '宮城県', '宮城', '秋田県', '秋田', '山形県', '山形', '福島県',
#  '福島', '茨城県', '茨城', '栃木県', '栃木', '群馬県', '群馬', '埼玉県', '埼玉', '千葉県', '千葉', '東京都',
#  '東京', '神奈川県', '神奈川', '新潟県', '新潟', '富山県', '富山', '石川県', '石川', '福井県', '福井', '山梨県',
#  '山梨', '長野県', '長野', '岐阜県', '岐阜', '静岡県', '静岡', '愛知県', '愛知', '三重県', '三重', '滋賀県',
#  '滋賀', '京都府', '京都', '大阪府', '大阪', '兵庫県', '兵庫', '奈良県', '奈良', '和歌山県', '和歌山', '鳥取県',
#  '鳥取', '島根県', '島根', '岡山県', '岡山', '広島県', '広島', '山口県', '山口', '徳島県', '徳島', '香川県',
#  '香川', '愛媛県', '愛媛', '高知県', '高知', '福岡県', '福岡', '佐賀県', '佐賀', '長崎県', '長崎', '熊本県',
#  '熊本', '大分県', '大分', '宮崎県', '宮崎', '鹿児島県', '鹿児島', '沖縄県', '沖縄']

この場合、東京東京都が混在しているときの結果は順序を規定するリストに準ずる。

l = ['沖縄県', '東京', '東京都', '北海道', '京都府']
print(sorted(l, key=tdfk_ex.index))
# ['北海道', '東京都', '東京', '京都府', '沖縄県']

l = ['沖縄県', '東京都', '東京', '北海道', '京都府']
print(sorted(l, key=tdfk_ex.index))
# ['北海道', '東京都', '東京', '京都府', '沖縄県']

pandasの例は省略するが、上の例のように、sort(), sorted()keyに指定した呼び出し可能オブジェクトをapply()メソッドで都道府県名の列に適用して新たな列を生成し、その列を基準にsort_values()でソートすればよい。{都道府県名: コード番号, ...}または{都道府県名: 読み仮名, ...}の辞書があれば、map()メソッドが使える。

上述のように、処理速度は辞書を使うほうが高速。要素数や行数が多い場合にリストを使うのは注意が必要。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事