試料名は昇順、測定値は降順、未測定のNoneは最後にしたい場合は、比較用のタプルを返すkey関数を作る。数字の降順は符号を反転し、Noneかどうかを先に比較すれば、数値とNoneを直接比較せずに並べられる。条件ごとに別々に並べ替える場合は、Pythonの安定ソートを利用する。
並べ替え条件を優先順に分ける
ここでは最初に試料名、その試料の中では測定値の大きい順、未測定はその試料の最後、という順序を指定する。同じ試料で測定値も同じなら、入力で先にあったレコードを先に残す。優先順位を文章で決めてからkeyを書くと、式の意味を確認しやすい。
単純にreverse=Trueを付けると、タプル全体の順序が逆になり、試料名まで降順になってしまう。すべて同じ向きにする場合と、項目ごとに昇順・降順が異なる場合を区別することが大切である。
Noneを数値と直接比較しない
example.py
records = [
{"id": "B3", "sample": "B", "score": 3},
{"id": "A-none", "sample": "A", "score": None},
{"id": "A2-first", "sample": "A", "score": 2},
{"id": "A5", "sample": "A", "score": 5},
{"id": "A2-second", "sample": "A", "score": 2},
{"id": "B-none", "sample": "B", "score": None},
]
def score_key(row):
value = row["score"]
return (value is None, 0 if value is None else -value)
def combined_key(row):
return (row["sample"], *score_key(row))
ordered = sorted(records, key=combined_key)
ids = [row["id"] for row in ordered]
print(ids)
assert ids == ["A5", "A2-first", "A2-second", "A-none", "B3", "B-none"]
by_score = sorted(records, key=score_key)
by_sample = sorted(by_score, key=lambda row: row["sample"])
assert by_sample == ordered
assert records[0]["id"] == "B3"
assert sorted([], key=combined_key) == []
実行結果
['A5', 'A2-first', 'A2-second', 'A-none', 'B3', 'B-none']
キーの二番目は未測定かどうかを示す真偽値で、FalseがTrueより前に並ぶ。三番目は数値の符号を反転したものなので、大きな測定値ほど小さいキーになる。Noneの場合の0は、未測定同士で比較するための値であり、実測値0を意味していない。
同順位では入力順が保たれる
Pythonのsortedとlist.sortは安定ソートであり、比較キーが同じレコード同士の相対順序を保つ。この例ではAの測定値2が二件あり、A2-firstがA2-secondより先に残る。入力順を同順位の意味として使いたい場合に役立つ。
同順位にも明確なID順が必要なら、キーの末尾へIDを追加する。安定性は、元データ自体の順序が毎回違っていても同じ結果へそろえる仕組みではない。入力順を使うか、別の決定規則を使うかを先に選ぼう。
安定ソートで条件を重ねる
もう一つの方法は、優先順位の低い条件から順にソートすることである。例では測定値の順を先に作り、その後で試料名だけを並べ替える。試料名が同じレコードの中では直前の順序が保たれるため、タプルキーと同じ結果になる。
文字列のように符号を反転できない項目を、一部だけ降順にしたい場合には、この段階的な方法が便利なことがある。ただし、Noneを最後にするための真偽値までreverse=Trueで逆転させないよう、欠損判定を含む各段階の意味を確認する。
欠損値の種類と入力型をそろえる
この例の未測定はNoneだけである。floatのNaNはNoneではなく、通常の数値比較でも注意が必要なので、そのまま同じkeyへ混ぜない。NaNも未測定にしたいなら、事前にNoneへそろえるか、keyの欠損判定に明示的に含める。
文字列の”10″と数値の10が混ざる場合も、見た目は似ていても同じ比較規則ではない。数値へ変換できない値をどう扱うか決め、変換と検証を終えてからソートする。キー関数が短いほどよいという理由で、入力の不備を隠してしまわないようにしたい。
元の一覧を残す方法も選ぶ
sorted(records, key=…)は新しいリストを返すので、元の並びを比較用に残せる。records.sort(…)は元のリストを直接並べ替え、戻り値はNoneである。結果を変数へ受け取る書き方では、この違いを確認する必要がある。
どちらの場合も、レコードの辞書そのものを深くコピーするわけではない。並べ替えた後に値を書き換えると、元リストからも同じ変更が見える。順序の変更とデータの複製は別の操作として扱おう。
動作確認では、異なる試料、同じ測定値、None、空の一覧を混ぜて期待順を決める。単に昇順らしく見えるかではなく、優先順位と同順位の規則がすべて満たされているかを確認すると、条件が増えても修正しやすい。
動作確認と参考資料
掲載例はLinux・CPython 3.12.14で動作確認した。OS固有のコマンドや環境ごとに変わるパスは、本文中の条件を確認して使ってほしい。
