この記事でわかること

  • 店舗のようなグループの中で、1から始まる連番を振る方法(groupbyとcumcount)
  • 番号の順番を決める並べ替えの書き方と、同じ日の行の扱い
  • 0から始まる番号を1からにする足し算と、整数の列にそろえる方法
  • 店舗が空の行や、番号をもう1回振るときにつまずく点

予約や受注の明細で、店舗や担当者ごとに「1件目、2件目…」と番号を手で打っていると、行を足したり並べ替えたりするたびに打ち直すことになります。この記事では、pandasで明細を店舗ごとに並べ、店舗の中で1から数え直す番号の列を作って、別のファイルに書き出すまでを、実際に動かした結果とあわせて説明します。

外部の事実は、pandas 3.1.0の公式の資料(DataFrameGroupBy.cumcount、DataFrameGroupBy.ngroup、DataFrame.groupby、DataFrame.sort_values、DataFrame.insert)を2026年11月3日に読んで確かめたものです。店舗の名前やメニューは、試すために作った架空の予約の明細のものです。

先に結論: 店舗ごとに並べてから、cumcountに1を足す

sort_valuesで店舗と受付日の順に並べ、groupby("店舗").cumcount()に1を足すと、店舗ごとに1から始まる番号になります。 cumcountは店舗の中で何行目かを0から数えるので、1を足すのを忘れないようにします。

手順 コード 10行の予約の明細での結果
店舗と受付日の順に並べる sort_values(["店舗", "受付日"]) 南口店・本店・駅前店の順にまとまる
店舗の中で何行目かを数える groupby("店舗").cumcount() 本店は0・1・2・3
1からの整数の番号にする + 1 と astype("Int64") 本店は1〜4、店舗が空の行は空

cumcountが数えるもの

pandasの資料では、cumcountは各グループの中の行に、0から「そのグループの行の数より1少ない数」までの番号を付けるとしています。返ってくるのは元の表と同じ行の並びの番号なので、そのまま新しい列として足せます。

番号の順番は、グループの中で行が並んでいる順です。groupbyの資料には、sortの指定はグループの名前を並べ替えるもので、グループの中の行の順には影響しないと書かれています。受付日の早い順に番号を付けたいなら、cumcountの前に表そのものを並べ替えておく必要があります。

cumcountにascending=Falseを渡すと、逆に「グループの行の数より1少ない数」から0へ向かって数えます。最後の行を0にしたいときに使います。

手順: 予約の明細に店舗の中の番号の列を足して書き出す

次のコードは、架空の予約の明細 yoyaku.xlsx を店舗と受付日の順に並べ、2列目に「店舗内の番号」を足した表を yoyaku_numbered.xlsx に書き出します。yoyaku.xlsx は読み込むだけで、上書きはしません。ファイルや列の名前は、自分の明細に合わせて直します。

import pandas as pd

# yoyaku.xlsx は読み込むだけで、番号を振った表は別のファイルに保存する
yoyaku = pd.read_excel("yoyaku.xlsx")

# 店舗ごとにまとめ、店舗の中は受付日の早い順に並べる
yoyaku = yoyaku.sort_values(["店舗", "受付日"], ignore_index=True)

# cumcount は店舗の中の何行目かを0から数えるので、1を足して1からの番号にする
numbers = yoyaku.groupby("店舗").cumcount() + 1
yoyaku.insert(1, "店舗内の番号", numbers.astype("Int64"))

print("店舗ごとの最後の番号:", yoyaku.groupby("店舗")["店舗内の番号"].max().to_dict())
print("店舗が空で番号のない行:", int(yoyaku["店舗内の番号"].isna().sum()))
yoyaku.to_excel("yoyaku_numbered.xlsx", index=False)

insertは、決めた位置に列を差し込みます。1は左から2番目(店舗の列のすぐ右)という意味です。astype("Int64")は、空の値を含んでも番号を整数のまま持てる型にそろえるために付けています。xlsxを読み書きするので、openpyxlも入れておきます。

試した結果

店舗・受付日・メニュー・金額の4列で、データの行が10行の架空の明細を使いました。店舗は本店が4件、駅前店が3件、南口店が2件で、1行だけ店舗を空欄にしました。本店には10月1日の予約が2件あり、元の表ではカラーの行がカットの行より上にあります。

  • 画面には「店舗ごとの最後の番号: {'南口店': 2, '本店': 4, '駅前店': 3}」と表示され、どの店舗も件数と同じ数で終わりました
  • 「店舗が空で番号のない行: 1」と出ました。店舗が空の行は、並べ替えで表のいちばん下に来て、番号の欄は空でした
  • 書き出した表の2列目が「店舗内の番号」になり、南口店の1・2、本店の1〜4、駅前店の1〜3の順に並びました
  • 本店の10月1日の2件は、元の表と同じくカラーが1、カットが2になりました
  • 番号の列は「1.0」のような小数ではなく、1・2・3の整数で書き出されていました
  • 動かす前と後で、元の yoyaku.xlsx のハッシュ値は同じでした

動かしたのは手元のMacで、Python 3.12.15に、pandasの3.1.0とopenpyxlの3.1.5を入れました。書き出したファイルはExcelのアプリで開いていません。中身はopenpyxlで読み直して見ています。

自分で番号を振ってよいか、相談するかの判断

目安は、「その番号を、後から誰かが手がかりに使うか」です。印刷や確認のためにその場で振るだけの番号なら、上のコードで振り直してかまいません。

番号が受付票や問い合わせの番号として外に出ていて、後から行を足したときに同じ番号を保ってほしい、という使い方なら、毎回振り直すと番号がずれます。そのときは、すでに付いた番号を残すのか、振り直してよいのかを、番号を使う人に先に確かめます。番号を付けた表は別のファイルに書き出し、元の明細は残しておきます。

つまずきやすいところ

  • 番号が0から始まる: cumcountだけで列を作ると、本店の4件は0・1・2・3になりました。1からの番号にするには、上のコードのように1を足します
  • 並べ替えないと、元の表の上からの順で番号が付く: sort_valuesを飛ばすと、本店の番号は元の表で上にある行の順になり、10月3日の予約が2、10月1日のカットが3になりました。日付の順に番号を付けたいときは、先に並べ替えます
  • 店舗が空の行のせいで、番号が小数になる: astypeを付けずに列を足すと、店舗が空の行の番号が空になり、ほかの行の番号まで1.0・2.0のような小数になりました。astype("Int64")を付けると、空の欄は空のまま、ほかは整数になります
  • 同じ受付日の行の順が気になる: 資料では、並べ替えのやり方を決めるkindの指定は、1つの列で並べ替えるときだけ使われるとしています。試したときは、同じ日の2行は元の表の順のままでしたが、順を決めたいなら、受付の時刻のような列を並べ替えの列に足します
  • 番号を付けたファイルでもう1回動かすと止まる: 書き出したファイルを読み込んで同じコードを動かすと、「店舗内の番号」の列がすでにあるという内容のValueErrorで止まりました。insertは同じ名前の列があるとエラーにするので、元の明細から動かすか、yoyaku["店舗内の番号"] = のように上書きします
  • 同じ値の行に同じ番号を付けたい: 売上のような値の大きさで順位を決め、同じ値の行を同じ順位にする方法は、PythonでExcelの表に順位の列を付ける手順で紹介しています
  • 番号を振らずに、店舗ごとの上位の行だけがほしい: 店舗の中で大きい順に並べて先頭の数行を取り出す方法は、PythonでExcelの表からグループごとに上位の行を取り出す手順にまとめています
  • 並べ替えの書き方を、ほかの例でも確かめたい: 大きい順・小さい順の指定や、上位の行を別のファイルにする方法は、PythonでExcelの表を並べ替えて上位の行を取り出す手順が参考になります

まとめ

  • 店舗ごとの連番は、店舗と受付日の順に並べてから、groupby("店舗").cumcount()に1を足して作ります
  • cumcountはグループの中の行の順に数えるので、番号の順番は並べ替えで決めます
  • 店舗が空の行があるときは、astype("Int64")で番号を整数の列にそろえます
  • 番号を付けたファイルではなく元の明細から動かし、別のファイルに書き出します