同点を同じ順位にして、次の番号を飛ばすならmin、詰めるならdenseです。firstは同点を入力順で分けます。 小数を整数に変える前に、同点をどう扱う表なのかを決めましょう。
対象は、pandasで件数や点数の表を作り、順位の付け方で迷っている学習者です。列の選択と、メソッドを呼ぶ操作が分かれば読み進められます。本稿の点数とIDは編集部が作った架空データです。人の採用・入学・受講適性や、スクールの優劣を評価するものではありません。
掲載例は2026年11月3日にpandas 2.2.3で実行しました。同点と欠損値の扱いは、11月6日にpandas 3.1.1の公式資料でも確認しています。上から順に実行し、元の7行を変更せず、メモリー上の別の表で比較します。実ファイルの読み込みや上書きはありません。
同じ7行で、平均順位・min・dense・firstの差を見る
順位を付ける列と、大小の向きを先に決める
順位の番号が小さいことと、元の値が小さいことは別です。所要時間なら短い値を先にする場合がありますが、今回の練習用の点数は大きい値を1位にします。ascending=Falseを指定し、全ての比較で向きをそろえます。
公式のSeries.rankには、昇順・降順、同点、欠損の扱いが分けて定義されています。引数を省略した結果を、業務上の「順位」の意味へそのまま当てはめないようにします。
import pandas as pd
df = pd.DataFrame({
"id": ["R01", "R02", "R03", "R04", "R05", "R06", "R07"],
"score": [86.0, 92.0, 86.0, float("nan"), 73.0, 92.0, 65.0],
}).set_index("id")
original = df.copy(deep=True)
assert len(df) == 7
assert df["score"].notna().sum() == 6
print(df)
有効な値は6行、欠損はR04の1行です。92がR02とR06、86がR01とR03にあります。元の行順は点数順ではありません。この順序を残すことで、後でfirstの入力順による差を観察できます。
| ID | 練習用の値 | 確認する役割 |
|---|---|---|
| R01・R03 | 86 | 同じ値の二つ目の組 |
| R02・R06 | 92 | 降順で先頭になる同点の組 |
| R04 | 欠損 | 順位を付けない扱いを確認 |
| R05 | 73 | 同点の次の番号を比較 |
| R07 | 65 | 有効な値の末尾を比較 |
ここではDataFrame全体ではなく、数値のscore列を選んで順位を計算します。IDなどの文字列が混ざった表に一括で順位を付ける課題とは分けています。古い記事にある数値列の自動選択や既定値を、現行版の別の操作に流用しないためでもあります。
集計表を使う人には「練習用の値が大きい順」「欠損は対象外」という前提も伝えます。列名が単に「順位」だけだと、途中で小さい値が良い表へ転用された際に、向きの違いを発見しづらくなります。
元の値が数値かどうかも、最初の確認に含めます。CSVから入った数字が文字列のままなら、数値として比較する課題と条件が異なります。今回のscoreは小数点付きの数値で作っています。文字列の変換や、変換できない値の扱いは、この計算の前に終えておく工程です。
例えば「92点」という単位付き文字列や、末尾の空白がある入力を、そのまま今回の数値の表へ混ぜないようにします。変換して欠損になった行は、元から未入力だった行と理由が違います。原表の値と確認用の列を残せば、順位なしの行がどこから生じたかを説明できます。
ここで必要なのは、欠損を消して計算を進めることではありません。順位を付ける対象が確定したかを確認することです。後からR04に値が届けば、全員の順位が変わる場合があります。「6行の値が確認できた時点の順位」という対象範囲も、表の名前へ含められます。
小数になるのは、既定のaverageが同点の順位を平均するため
降順に有効な値を並べると、92、92、86、86、73、65です。92の2行が占める位置は1番目と2番目なので、平均は1.5です。86の2行は3番目と4番目を占め、平均は3.5になります。これは点数の平均ではなく、並びの位置の平均です。
ranks = pd.DataFrame(index=df.index)
for method in ["average", "min", "max", "dense", "first"]:
ranks[method] = df["score"].rank(
method=method, ascending=False, na_option="keep"
)
assert ranks.loc["R02", "average"] == 1.5
assert ranks.loc["R01", "average"] == 3.5
assert ranks.loc["R04"].isna().all()
print(df.join(ranks))
1.5という結果を見て、計算エラーと考えてastype(int)を加える前に、平均順位が求めたい意味かを確認してください。1.5を1へ切り捨てても、同点の後の番号をどう扱うかという規則を選んだことにはなりません。
平均順位が必要な分析もあります。一方、「1位タイ」のような表示を作りたい表では、別の方法が合う場合があります。小数の有無だけで方法の良し悪しを決めず、利用目的から選びます。
平均順位の1.5は、1位と2位を半分ずつ持つ二つの行に、それぞれ同じ番号を付けたものです。片方を1位、もう片方を2位にした結果ではありません。そのため、1.5を見て「どちらが先か」を読み取ることはできません。同点の中に順序が必要なら、別の条件を決める段階へ戻ります。
同点が三つになると、平均順位が整数になる場合もあります。例えば2・3・4番目を占める三つの同点の平均は3です。見た目が整数だからminやdenseで計算した、と推測しないようにします。計算方式の記録は、表示された数字だけでは復元できないことがあります。
この例で73の順位が5になるのは、その前に4行あるためです。92と86という二つの値しかないから3位、という数え方ではありません。行の位置を数える方法と、異なる値の組を数える方法を分けると、次のminとdenseを理解しやすくなります。
minは同点の先頭順位を使い、次の位置を飛ばす
minでは、92の組を1位、86の組を3位にします。最初の同点が2行あるので、次の組は3番目から始まります。73は5位、65は6位です。同点があるたびに、次の番号が欠ける場合があります。
assert ranks["min"].dropna().to_dict() == {
"R01": 3.0, "R02": 1.0, "R03": 3.0,
"R05": 5.0, "R06": 1.0, "R07": 6.0,
}
assert ranks["max"].loc[["R02", "R06"]].tolist() == [2.0, 2.0]
maxも同点を同じ番号にしますが、同点の組が占める最後の位置を使います。この例の92は2位、86は4位です。minとmaxは元の値の最小・最大を取り出す集計ではなく、同点の位置の選び方です。
番号が飛んでいることを「データが欠けた」と誤解しないよう、表の注記に「同点は同順位、後続は行数に応じて繰り下げ」と書けます。実際に行が消えていないかは、順位の連続性ではなくIDと行数で確認します。
今回の7行は、順位を計算した後も7行です。欠損のR04が見えなくなった場合は、rankではなく、別に実行した欠損除外や表示用の絞り込みを調べます。計算と抽出を一つの長い式にすると、この区別がしづらくなります。
確認をもう一段進めるなら、順位と元の値の対応を逆から見ます。今回のminで1という番号が付いたIDはR02とR06で、両方とも92です。3という番号はR01とR03で、両方86です。同じ順位に異なる値が混ざっていれば、順位を元表へ戻す際の行対応などを調べるきっかけになります。
一方、異なる順位に同じ値があることは、firstでは予定どおりです。このように「同じ値なら同じ順位であるべき」という確認条件も、選んだ方式によって使える範囲が変わります。検算のルールを、方式とは独立した万能な条件にしないようにします。
denseは異なる値の組ごとに番号を一つ進める
denseでは92が1位、86が2位、73が3位、65が4位です。各組に何行含まれていても、次の異なる値へ進むと番号が一つ増えます。順位の種類は四つですが、有効な値を持つ行は六つあります。
assert ranks["dense"].dropna().to_dict() == {
"R01": 2.0, "R02": 1.0, "R03": 2.0,
"R05": 3.0, "R06": 1.0, "R07": 4.0,
}
assert ranks["dense"].nunique() == df["score"].nunique()
| 値・ID | average | min | max | dense | first |
|---|---|---|---|---|---|
| 92・R02 / R06 | 1.5 / 1.5 | 1 / 1 | 2 / 2 | 1 / 1 | 1 / 2 |
| 86・R01 / R03 | 3.5 / 3.5 | 3 / 3 | 4 / 4 | 2 / 2 | 3 / 4 |
| 73・R05 | 5 | 5 | 5 | 3 | 5 |
| 65・R07 | 6 | 6 | 6 | 4 | 6 |
denseが向くのは、値の段階を番号にしたい場合などです。ただし、連続した番号が見やすいからという理由だけで、元の表で合意していた同点規則を変えないようにします。これまでの資料がmin方式なら、同じ値の順位でも番号が変わります。
例えば73のR05は、minでは5位、denseでは3位です。データそのものは同じなので、数値が改善したわけではありません。順位の方式を変えた前後を比較するときは、元の値も並べて説明してください。
表を期間ごとに作る場合も、順位だけを比較すると意味を取り違えることがあります。前月は異なる値が十種類、今月は四種類なら、denseの番号が動いた理由には、他の行の値の変化も含まれます。順位はその表の対象全体との関係で決まるので、元の値と対象行数を併記します。
この教材の範囲を別の集団へ絞る場合も同様です。先に全体の順位を計算してから一部を表示する表と、対象を絞ってから順位を付け直す表では、番号の意味が違います。部署別や月別へ広げる前に、「この7行全体の中での順位」という前提を固定して練習してください。
firstは同点を入力順で分けるので、元のIDと順序を残す
firstでは92のうちR02が先に現れるため1位、R06が2位です。86ではR01が3位、R03が4位になります。全てに別の番号を付けたいときに使えますが、その番号に追加の評価基準が含まれたとは限りません。
reversed_df = df.iloc[::-1]
reversed_first = reversed_df["score"].rank(
method="first", ascending=False, na_option="keep"
)
assert ranks.loc["R02", "first"] == 1
assert reversed_first.loc["R02"] == 2
assert reversed_first.loc["R06"] == 1
assert reversed_first.loc["R03"] == 3
reversed_min = reversed_df["score"].rank(method="min", ascending=False)
pd.testing.assert_series_equal(
ranks["min"], reversed_min.reindex(df.index), check_names=False
)
行を逆にしただけで、同点の中の番号が変わりました。minの結果はIDへ戻して比べると変わりません。この小さい検算で、値だけから決まる順位と、入力順も使う順位の違いを確認できます。
CSVの並び、結合の順番、画面のソート順などが変わると、firstの結果も変わる可能性があります。業務の順番として使うなら、入力順が何に由来するかを記録します。「一意の順位が付いた」ことと、「納得できる順序を定義した」ことは別です。
表示上の順番をそろえる目的なら、同点に同じ順位を付けたまま、補助的にIDで並べる選択肢もあります。行を見やすくするためだけに同点を分ける必要があるか、表の使い方へ戻って考えましょう。
欠損・表示・抽出の規則をそろえ、渡す前に検算する
欠損を順位なしにするか、末尾へ回すかを区別する
今回のR04には値がありません。値が0という意味でも、他の値より低いという意味でもありません。本文の基本例はna_option="keep"として、順位も欠損のまま残しています。
bottom = df["score"].rank(
method="min", ascending=False, na_option="bottom"
)
top = df["score"].rank(
method="min", ascending=False, na_option="top"
)
assert pd.isna(ranks.loc["R04", "min"])
assert bottom.loc["R04"] == 7
assert top.loc["R04"] == 1
assert top.loc["R02"] == 2
print(pd.DataFrame({"keep": ranks["min"], "bottom": bottom, "top": top}))
この比較でtopを使うと、欠損が先頭側の順位を取り、92の番号まで変わります。「空欄を目立たせたい」という表示目的なら、順位を変えず、別の欠損確認表へ分ける方法もあります。
また、欠損を末尾へ回す設定と、欠損を順位なしのまま表の最後へ並べる操作も別です。前者はR04へ7という順位を与えます。後者なら、欠損の順位を残したまま表示位置だけを変えられます。利用者が未確認の値を最下位と読み違えない方を選んでください。
欠損の理由が未提出・測定失敗・対象外など複数ある場合は、理由の列を別に持ちます。順位計算の引数一つで、それらの意味まで表すことはできません。今回の教材では理由を決めず、「値未確認の1行」として保留しています。
全ての値が欠損という入力では、今回の有効値6行の表とは条件が異なります。keepのままなら順位も全て未確定です。そこで番号を作る必要があるのか、それとも「比較する値がない」と返すのかを、表の仕様として決めます。空の順位表を、全員が同点の順位表と同じに扱わないようにします。
逆に全て同じ有効値なら、同点の組は一つです。以下は元の7行と別の小さな練習で、3行とも値10という入力です。minとdenseは両方1になりますが、firstは1・2・3です。同じ結果になる例だけで方式を選ばず、元のように異なる組がある例でも確認します。
same = pd.Series([10.0, 10.0, 10.0], index=["A", "B", "C"])
assert same.rank(method="min", ascending=False).tolist() == [1.0, 1.0, 1.0]
assert same.rank(method="dense", ascending=False).tolist() == [1.0, 1.0, 1.0]
assert same.rank(method="first", ascending=False).tolist() == [1.0, 2.0, 3.0]
整数に見せる前に、選んだ方式に小数がないかを確かめる
minやdenseを使っても、結果に1.0のような表示が出る場合があります。見た目の小数点と、1.5のような平均順位は分けて確認します。整数の順位と欠損を同時に残す例では、pandasの欠損対応整数型Int64を使えます。
min_integer = ranks["min"].astype("Int64")
assert min_integer.loc["R02"] == 1
assert min_integer.loc["R01"] == 3
assert pd.isna(min_integer.loc["R04"])
assert str(min_integer.dtype) == "Int64"
print(min_integer)
ここで大文字のInt64を使っている点に注意してください。欠損を0で埋めてから整数へ変えると、R04が0位であるように見える表を作ってしまいます。表示を整えるために、新しい順位を発生させないようにします。
この変換はminの結果を対象にしています。averageで出た1.5を、同じように整数として扱う手順ではありません。平均順位を残すなら、小数であることが分かる見出しや書式を選びます。
Excelへ渡すときは、ファイルの値とセルの表示書式も別に見ます。画面上で小数点を隠しただけなら、内部には1.5が残る場合があります。後でその表を読み戻したときに、番号が変わったと思わないよう、元の計算方式と値を記録しておきましょう。
同点の順番を固定するなら、補助キーを明示する
点数が同じときはIDの昇順に並べる、と練習上の規則を決めます。これはIDの小さい行が優秀だという意味ではなく、同じ入力から同じ表示順を作るための約束です。実務では、受付時刻など目的に合う基準が必要かを別に検討します。
ordered = df.reset_index().sort_values(
["score", "id"], ascending=[False, True], na_position="last"
).set_index("id")
ordered["sequence"] = ordered["score"].rank(
method="first", ascending=False, na_option="keep"
).astype("Int64")
assert ordered.index.tolist() == ["R02", "R06", "R01", "R03", "R05", "R07", "R04"]
assert ordered["sequence"].dropna().tolist() == [1, 2, 3, 4, 5, 6]
sort_valuesの公式資料を参照し、並べる列と、それぞれの向きを指定しています。IDが一意であるこの原表では、同点の順序をIDまで含めて説明できます。
補助キー自体も同じになるデータでは、その先の扱いが未定義のままです。一意にしたいからと、根拠なく行を削ったり、点数へ小さい乱数を足したりしないでください。必要な基準を先に決め、元の値を変更せずに並べる方が照合しやすくなります。
元の表へ順位を戻す場合も、見た目の行位置だけで貼り付けないようにします。ここでは一意のIDをインデックスにしているので、R01の結果をR01として確認できます。並べ替え後の配列を元の行順へそのまま代入する処理は、別の行の順位を付ける原因になります。
IDが重複していないかも確認事項です。順位の重複は同点によって予定されますが、照合用IDの重複は別の問題です。同じIDが二つの行を指していると、どちらの値を見ているかを説明しづらくなります。原表の行を追えるキーを用意してから、順位の方法を比較します。
二つの補助キーまで同じ場合に、乱数で順番を決める設計も考えられますが、それは別の選択規則です。今回の再現例には含めません。単に同じ表示を再現したいのか、公平な抽選を設計したいのかなど、解きたい課題を分け、必要な根拠を持つ方法を選んでください。
上位3行と、3位以内と、上位3種類は同じ集合ではない
「上位3つを見たい」という依頼には、少なくとも三つの意味があります。行数を3に固定したいのか、同点を含む3位以内なのか、異なる点数の上位3段階なのかです。表示件数を確認する前に、この言葉を具体化します。
within_min_3 = df.loc[ranks["min"] <= 3]
within_dense_3 = df.loc[ranks["dense"] <= 3]
first_3_rows = ordered.loc[ordered["sequence"] <= 3]
assert within_min_3.index.tolist() == ["R01", "R02", "R03", "R06"]
assert within_dense_3.index.tolist() == ["R01", "R02", "R03", "R05", "R06"]
assert first_3_rows.index.tolist() == ["R02", "R06", "R01"]
pd.testing.assert_frame_equal(df, original)
| 指定したい内容 | この例の件数 | 含まれるID |
|---|---|---|
| min方式で3位以内 | 4行 | R01・R02・R03・R06 |
| dense方式で上位3段階 | 5行 | 上記4行とR05 |
| 補助キーを決めて3行 | 3行 | R02・R06・R01 |
3位以内が4行になっても、抽出に失敗したわけではありません。86の同点2行が同じ3位なので、両方が対象です。行数を3に切ると、この同点を分ける判断が加わります。どちらを使うかは、rankが自動で決めるものではありません。
返す表には、「同点を含む」「最大3行」「異なる値で3段階」といった説明を添えます。元の行数7、有効値6、抽出後の行数も並べておくと、欠損の除外や境界の同点が結果にどう影響したかを追えます。
確認課題を小さくし、質問先へ規則と期待値を渡す
独学の最初の到達点は、7行のうちR05がminで5位、denseで3位になる理由を説明できることです。次にR02とR06の入力順を変え、firstだけが同点の中で変わることを確認します。最後にR04の欠損をどう表示するかを選びます。
練習を一つ追加するなら、R07の65を86へ変えた別コピーを作り、紙の上で順位を予想してから計算します。この場合の同点の組は92が2行、86が3行、73が1行です。minでは73が6位、denseでは3位になるはずです。元の表と別の教材として記録してください。
もう一つの確認は、値を計算する前に丸めた場合です。元の値が86.04と86.03なら別の値ですが、小数1桁へ丸めてから順位を付けると、両方86.0として扱う表になります。表示だけを丸めることと、順位に使う値そのものを丸めることを区別します。
この違いを確認するときは、元の値の列、丸めた値の列、それぞれの順位の列を残します。見た目では同じ86.0なのに順位が違う場合、画面に隠れた桁が原因の可能性があります。実際に同じ値なのかを確認する前に、rankの同点処理がおかしいと決めないようにしましょう。
質問するときは「順位が変」だけでなく、原表、比較したい列、大小の向き、同点規則、欠損の扱い、期待するIDの集合を渡します。今回なら「minで3位以内を取り、4行を返したい」と書けます。「3行にしたい」とは異なる依頼です。
講座や個別支援を比較する場合は、構文の説明だけでなく、表の仕様から検算まで相談できるかを確認します。pandasの名前がカリキュラムにあっても、業務データの持ち込みや、この7行の個別添削が含まれるとは限りません。学びたい範囲と支援期間・費用を事前に照合してください。
Pythonの基礎から学び、書いたコードを確認してもらう機会が欲しい人は、Python WinnerのPythonマスターも比較できます。公式ページではコードチェックを案内しています。集計表を題材に質問したい場合は、相談できる範囲を受講前に確認してください。
よくある質問
Q. rankの結果が1.5になるのはエラーですか?
既定のaverageでは、同点が占める順位の平均を返します。今回の92の2行は1番目と2番目なので1.5です。同点に1位を付けたいなら、表示を丸める前にminなどの規則を選びます。
Q. minとdenseは何が違いますか?
今回の92・92・86・86・73・65を降順で見ると、minは1・1・3・3・5・6、denseは1・1・2・2・3・4です。denseは異なる値の組ごとに番号を一つ進めます。
Q. firstを使えば、同点の中の順序も決まりますか?
その時点の入力順で番号が付きます。元の並びが変われば同点の番号も変わる場合があります。別の基準で固定したいなら、その補助キーを明示して並べた後に処理します。
Q. 欠損を0で埋めてから順位を整数にしてよいですか?
欠損が0という意味かを確認してください。今回のR04は値が未確認なので、keepで順位も欠損にし、Int64で整数と欠損を残します。見た目を整えるためだけに0位を作らないようにします。
Q. 3位以内を取ったら4行になりました。
同点を含むなら、その結果が意図に合う場合があります。今回のminでは86の2行が両方3位です。行数を3に固定する場合は、同点をどの基準で分けるかを別に決めます。
Q. rankを使うと元の表も点数順になりますか?
順位の計算と並べ替えは別の操作です。本稿は元の7行を保持し、表示用の別の表をsort_valuesで並べています。元のIDを残して、計算結果を照合してください。