pandasの使い方|DataFrame・抽出・欠損・集計を練習する
※本記事にはPRを含む場合があります。
こんにちは。『脱会社員』脱サラを応援し続けるブログ、運営者の『なかびー』です。
pandasで表を読めたものの、空欄をどう扱うか分からない、groupbyの合計が元の明細と合わない、mergeしたら行が増えた。こうしたつまずきは、関数名を覚えるだけでは解決しにくいものです。表の形、値の型、集計対象、結合の条件を一つずつ決めると、どこで結果が変わったかを追いやすくなります。
この記事では、8行の架空売上と3行の商品台帳を使って、DataFrameの確認、数量の整形、欠損の保留、商品情報の結合、カテゴリー別集計までを一つの練習として進めます。完成するのは合計表だけではありません。正常明細4行、保留明細3行、取消1行に分け、入力8行が説明できること、数量7・金額2,140円を手で検算できることも目標です。
仕様確認日は2026年10月4日です。掲載コードはLinux・Python 3.12.14で、pandas 2.2.3と3.0.6の両方を使って実行しました。pandas 3.0では文字列型の推論やCopy-on-Writeなどに変更があるため、今回は文字列型を明示し、抽出後の表をcopyで分け、結果を二つの版で照合しています。Windows・macOSの操作画面、Notebookの表示、大容量データの速度は未検証です。
DataFrameの行・列・型を、小さな売上表で理解する

DataFrameとSeriesを見分ける
pandasはPythonで表形式のデータを扱うためのライブラリです。DataFrameは行と列を持つ二次元の表で、一つの列を取り出したものは通常Seriesになります。salesというDataFrameからsales[‘qty_raw’]と書くと数量の列を、sales[[‘sale_id’, ‘qty_raw’]]と書くと指定した二列を持つDataFrameを取得できます。かっこが一段か二段かで結果の形が変わる点を、最初に押さえましょう。
行のラベルであるindexと、売上IDなどの業務上の識別子も別物です。初期状態では0、1、2と順番が付くことが多いですが、抽出や並べ替えをするとindexが連続しない場合があります。売上IDのS01を探すつもりでindexの1を指定すると、違う意味の指定になります。位置で見るiloc、ラベルで見るloc、列の値を比較する条件を使い分けます。
Excelのセルのように見える表でも、pandasでは列ごとの型とindexによる対応付けが計算に関わります。見た目が「2」でも、文字列と数値ではできる演算が異なります。headだけでなくshape、columns、dtypes、isnaの結果を確認すると、表を取り込めた後の判断材料が増えます。基本構造はpandas公式の表形式データ入門でも説明されています。
最初はファイル読み込みを分けて考える
今回はデータをコード内に直接書きます。文字コード、保存場所、区切り文字の問題をいったん外し、DataFrameで値を整えて集計する流れに集中するためです。実際のCSVを使うときはread_csvなどで取り込んだ後、今回と同様に列名・型・行数を確認します。読み込みが成功しただけで、商品IDの先頭ゼロや空欄の意味まで正しく保たれたとは限りません。
実行環境には、作業用の仮想環境を使ってください。pandasが導入済みなら、まずpython -c “import pandas as pd; print(pd.__version__)”で版を確認します。新規環境で本記事の検証版に合わせる場合は、公式配布元で確認したうえでpython -m pip install “pandas==3.0.6″を使います。これはパッケージを取得して導入する操作なので、組織管理PCでは許可された方法に従ってください。既存案件をこの記事のために更新する必要はありません。
コードはpractice.pyなど、ライブラリ名と重ならない名前で保存します。pandas.pyという名前にすると、import pandasが自分のファイルを指してしまう原因になります。ターミナルとエディターで違うPythonを選んでいないかも確認し、使ったPythonとpandasの版を実行結果に残しましょう。
売上8行と商品3行の意味を決める
次の売上はすべて架空です。qty_rawとprice_rawは、まだ検証していない数量・単価の元文字列です。S03の数量は欠損、S08は漢字の「二」、S05の商品は台帳にありません。S04の数量ゼロは、確認済みのゼロとして正常行に残す、という今回のルールにします。S06の取消は、売上集計から分けて保管します。
※表は横にスクロールできます。
| 売上ID | 商品ID | 数量の元値 | 単価の元値 | 状態 |
|---|---|---|---|---|
| S01 | P01 | 2 | 120 | complete |
| S02 | P02 | 3 | 300 | complete |
| S03 | P03 | 欠損 | 500 | complete |
| S04 | P01 | 0 | 120 | complete |
| S05 | P99 | 1 | 200 | complete |
| S06 | P02 | 2 | 300 | cancelled |
| S07 | P03 | 2 | 500 | complete |
| S08 | P01 | 二 | 120 | complete |
商品台帳はP01が鉛筆・文具、P02がノート・文具、P03が小物入れ・雑貨です。商品IDは台帳内で一意にします。売上側では同じ商品が何度出てもよいので、関係は「売上の複数行に対して商品台帳の一行」です。商品名は説明用で、結合には商品IDを使います。表記が似た商品名同士を推測で結び付ける処理は行いません。
単価は売上の記録にある値を使います。現在の商品台帳の価格へ置き換えると、過去の取引価格が変わってしまう可能性があるためです。今回は整数円、税・割引・返品・送料は扱わない練習とします。数量や単価に小数が必要な業務では、丸め規則や精度を別途設計します。例のルールをそのまま実務の会計処理に当てはめないでください。
先に「正常・保留・取消」の出口を用意する
入力の問題を見つけたとき、行を黙って消すと後から元データと合計を照合できません。今回は正常な集計対象accepted、確認が必要なheld、集計外のcancelledを別々に返します。入力8行が4+3+1に分かれれば、どの行がどこへ行ったかを説明できます。保留をゼロ円として正常に混ぜる処理にはしません。
一方、売上IDの重複、想定外の状態、商品台帳の重複など、処理全体の前提が崩れる問題では停止します。一行の数量が不明なのか、台帳の一意性が壊れたのかで扱いを変える設計です。どんな問題でも同じ例外処理で握りつぶすのではなく、行単位で保留にできるものと、入力全体を見直すものを分けます。
型変換・欠損・抽出の条件を決め、元データを残す

完結コードを動かして全体の結果を見る
次のコードを一つのファイルへ保存して実行します。前半で二つの表を作り、summarizeが検証・結合・集計を行います。後半は確認用の表示です。元ファイルの読込みや外部への送信は行いません。まず変更せずに実行し、入力8・正常4・保留3・取消1、数量合計7、金額合計2140が出ることを確認してください。
import pandas as pd
sales = pd.DataFrame([
['S01', 'P01', '2', '120', 'complete'],
['S02', 'P02', '3', '300', 'complete'],
['S03', 'P03', None, '500', 'complete'],
['S04', 'P01', '0', '120', 'complete'],
['S05', 'P99', '1', '200', 'complete'],
['S06', 'P02', '2', '300', 'cancelled'],
['S07', 'P03', '2', '500', 'complete'],
['S08', 'P01', '二', '120', 'complete'],
], columns=['sale_id', 'product_id', 'qty_raw', 'price_raw', 'status'], dtype='string')
products = pd.DataFrame([
['P01', '鉛筆', '文具'],
['P02', 'ノート', '文具'],
['P03', '小物入れ', '雑貨'],
], columns=['product_id', 'product_name', 'category'], dtype='string')
def summarize(sales, products):
sale_columns = ['sale_id', 'product_id', 'qty_raw', 'price_raw', 'status']
product_columns = ['product_id', 'product_name', 'category']
if not set(sale_columns) <= set(sales.columns):
raise ValueError('売上の必要列が不足しています')
if not set(product_columns) <= set(products.columns):
raise ValueError('商品台帳の必要列が不足しています')
if sales.columns.duplicated().any() or products.columns.duplicated().any():
raise ValueError('列名が重複しています')
if len(sales) > 1_000_000:
raise ValueError('この練習は100万行以下です')
work = sales[sale_columns].astype('string').copy().reset_index(drop=True)
master = products[product_columns].astype('string').copy()
for frame, columns in [(work, sale_columns), (master, product_columns)]:
for column in columns:
frame[column] = frame[column].str.strip()
if work['sale_id'].isna().any() or work['sale_id'].eq('').any():
raise ValueError('売上IDが空です')
if work['sale_id'].duplicated().any():
raise ValueError('売上IDが重複しています')
if not work['status'].isin(['complete', 'cancelled']).all():
raise ValueError('状態に想定外の値があります')
if master.isna().any().any() or master.eq('').any().any():
raise ValueError('商品台帳に空欄があります')
if master['product_id'].duplicated().any():
raise ValueError('商品台帳のIDが重複しています')
cancelled = work.loc[work['status'].eq('cancelled')].copy()
target = work.loc[work['status'].eq('complete')].copy()
merged = target.merge(master, on='product_id', how='left',
validate='many_to_one', indicator=True)
merged['reason'] = pd.Series('', index=merged.index, dtype='string')
for raw_column, new_column, label in [
('qty_raw', 'qty', '数量'), ('price_raw', 'price', '単価')
]:
ok = merged[raw_column].str.fullmatch(r'[0-9]{1,6}', na=False)
merged[new_column] = pd.to_numeric(
merged[raw_column].where(ok), errors='coerce'
).astype('Int64')
merged.loc[~ok, 'reason'] += label + '不備;'
merged.loc[merged['_merge'].ne('both'), 'reason'] += '商品未登録;'
held = merged.loc[merged['reason'].ne('')].copy()
accepted = merged.loc[merged['reason'].eq('')].copy()
accepted['amount'] = accepted['qty'] * accepted['price']
summary = accepted.groupby('category', as_index=False, dropna=False).agg(
rows=('sale_id', 'size'), quantity=('qty', 'sum'), amount=('amount', 'sum')
).sort_values('category').reset_index(drop=True)
if len(accepted) + len(held) + len(cancelled) != len(work):
raise RuntimeError('行数の照合に失敗しました')
if int(summary['amount'].sum()) != int(accepted['amount'].sum()):
raise RuntimeError('金額の照合に失敗しました')
return accepted, held, cancelled, summary
if __name__ == '__main__':
accepted, held, cancelled, summary = summarize(sales, products)
print('pandas:', pd.__version__)
print('入力 / 正常 / 保留 / 取消:', len(sales), len(accepted), len(held), len(cancelled))
print(summary.to_string(index=False))
print(held[['sale_id', 'reason']].to_string(index=False))
print('数量合計:', int(accepted['qty'].sum()))
print('金額合計:', int(accepted['amount'].sum()))
コードは少し長めですが、目的は関数を増やすことではなく、前提と出口を同じ場所に置くことです。必要列の検証、文字の前後の空白除去、IDの確認、状態による分割、結合、数値化、保留理由、集計という順番になっています。合計が違ったときには、この順に途中結果を見れば、どの工程で対象が変わったかを追えます。
この記事の関数は、売上と商品台帳がDataFrameであることを前提にしています。列が不足した場合や重複した列名がある場合は停止します。表のセル内に複雑なオブジェクトが入る入力や、別単位の通貨が混在する入力までは受け付ける設計ではありません。対応するデータの範囲を決めた小さな集計処理として使ってください。
文字列から整数へ変える前に、許す形を限定する
今回の数量と単価は、前後の空白を除いた半角数字1〜6桁を許可します。0から999999までの非負整数が対象です。「二」「-1」「1.5」「1e2」、全角数字は保留にします。どれもPythonで変換できるかだけではなく、この練習の入力規則に合うかで判断します。先頭ゼロのある000002は、数量として2へ変換する扱いです。
str.fullmatchの結果をokという条件にし、条件を満たす値だけをto_numericへ渡します。変換できない値を欠損にするerrors=’coerce’は便利ですが、変換後の欠損をそのまま合計から消すと元の誤入力が見えなくなります。このコードでは、変換前のqty_raw・price_rawを残し、条件を満たさない行へ「数量不備;」「単価不備;」という理由を付けます。
変換後は欠損を表現できる整数型Int64を使います。先頭が大文字のInt64は、欠損を扱えるpandasの型です。欠損を含む列を通常の整数型へ無条件に変えようとすると失敗する場合があるため、型名と欠損の関係も確認します。数量が未入力の行を0に置き換えれば変換しやすくなりますが、売上ゼロという意味を勝手に作ることになるので行いません。
数量・単価の上限と入力100万行以下という制限は、この小例で整数計算の範囲を限定するために置いています。最大999999×999999を100万行足しても、符号付き64ビット整数の範囲内です。上限を外して巨大な値を受け付けたり、小数や無限大を通したりする場合には、精度・桁あふれ・メモリーの設計をやり直す必要があります。
欠損・空文字・ゼロを同じものにしない
欠損は、値が分からない状態です。ゼロは数値が0だと分かっている状態です。空文字は文字列の内容が空で、扱いは入力規則によって決めます。pandasのisnaはNoneや各種の欠損を見つけますが、通常の空文字まで自動で欠損扱いにするわけではありません。今回は数値の書式確認でも空文字を落とすので、欠損と空文字はどちらも数量不備として保留になります。
欠損の判定に「値がpd.NAと等しいか」という比較を使わないでください。欠損は通常の値の比較と同じ動作ではなく、判定結果そのものが不明になる場合があります。isna・notnaで存在を調べ、必要なときだけfillnaの方針を決めます。平均で補う、前の行の値を使う、といった処理も、データの意味に根拠がある場合に選びます。詳しくは欠損データの公式ガイドを確認してください。
※表は横にスクロールできます。
| 元の数量 | 今回の判定 | 集計への扱い | 確認する意味 |
|---|---|---|---|
| 2 | 正常 | 整数2として採用 | 数量が明確 |
| 0 | 正常 | 数量0・金額0で行を残す | ゼロを確認済み |
| None・空文字 | 不備 | 理由を付けて保留 | 不明をゼロにしない |
| 二・全角数字 | 不備 | 元文字列を残して保留 | 指定した半角数字規則と違う |
| -1・1.5・1e2 | 不備 | 保留 | 返品・小数・指数表記は今回対象外 |
| 1000000以上 | 不備 | 保留 | 今回の桁数上限を超える |
条件の組み合わせと代入先を明示する
DataFrameから条件で行を選ぶときは、work.loc[条件]の形を使います。複数条件を組み合わせる場合、Seriesの条件には通常&や|を使い、それぞれをかっこで囲みます。Pythonのandやorで表の条件を結ぼうとすると、一つの真偽値にできずエラーになる場合があります。条件式を一つずつ変数に分けて確認すると、どの条件がFalseになったか分かりやすくなります。
コードでは抽出した表にcopyを付け、acceptedへamount列を追加しています。変更したい表を明確にするためです。df[条件][‘列’]=値のような連鎖した代入に頼ると、意図した元の表へ反映されるかが分かりにくくなります。pandas 3.0のCopy-on-Writeの条件も踏まえ、元表を更新するならdf.loc[条件, ‘列’]=値のように一度の指定で対象を示します。
ここでは入力表をコピーして処理するので、summarizeを呼んだ後も元のsalesとproductsは変わりません。再実行しても数量が二倍になったり、前回の計算列を使い回したりしないように、入力と加工後を分けています。今回の実行試験でも呼出し前後のDataFrameを比較し、元データが保持されることを確認しました。
mergeで商品情報を結び、行が増える原因を防ぐ

結合のキーと一対多の向きを決める
商品情報を付けるには、売上のproduct_idと商品台帳のproduct_idを合わせます。結合前に台帳側の商品IDが重複していないか確認し、mergeにもvalidate=’many_to_one’を指定しています。左側の売上は同じ商品が複数行あってよく、右側の商品台帳は一商品一行、という前提をコードで表しています。
例えば台帳にP01の行が二つある状態で、検証なしに売上と結合すると、P01の売上が二つの台帳行それぞれに対応し、行数が増える可能性があります。合計だけ見ると「売上が増えた」ように見えてしまいます。重複後にdrop_duplicatesすればよいと考えるのではなく、どの台帳行が正しいのか、キーを増やす必要があるのかを先に判断します。
価格が日付ごとに変わる台帳なら、商品IDだけでは一意にならないこともあります。その場合は有効日や版などの設計が必要です。この記事の商品台帳は商品名とカテゴリーを付けるだけの単純なものなので、P01〜P03を一意にできる前提にしています。実務の台帳に合わせてキーの意味を見直すところまでが、結合処理の設計です。
left結合とindicatorで未登録を残す
how=’left’は左側の対象売上を残すための指定です。商品台帳にないP99のS05も、消さずに結合結果へ残します。indicator=Trueで追加される_mergeを調べ、bothでなければ「商品未登録;」を理由に付けます。こうすると、未登録商品を除いた集計と、対応待ちの明細を一緒に確認できます。
inner結合にすると、両側に一致する商品だけが残ります。それ自体が誤りではありませんが、今回のように入力行をすべて説明したい処理では、S05を見失う原因になります。結合方式を選ぶときは、「一致しない行を落としてよいか」「後から確認する必要があるか」を決めてください。関数の既定値を知らずに使うより、howとonを明記する方が意図を残せます。
今回、取消1行を分けた後の対象は7行です。台帳が一意なのでleft結合後も7行になります。そのうち数量不備2行、商品未登録1行を保留にし、正常4行を集計します。結合の前後と、保留へ分けた後でそれぞれ行数を見ると、どの工程で件数が変わるべきかを確認できます。mergeの公式仕様ではhow、validate、indicatorの役割を確認できます。
空のキーが結び付く場合に注意する
pandasのmergeでは、両側の結合キーが欠損の場合に互いに一致することがあり、一般的なSQLのNULL結合と同じだと考えると間違えやすい点があります。今回の商品台帳は空欄を許さず停止するため、空の商品ID同士が偶然一致する状況を避けています。売上側の商品IDが空なら、登録商品に一致しない行として保留にします。
空白の扱いも決めます。コードでは全対象列の前後の空白を除くので、「 P01 」はP01として扱われます。一方、大文字小文字の変換、記号の除去、ゼロ埋めは行いません。IDを数値化すると先頭ゼロが消えることがあるため、商品IDは文字列として保持します。実データの表記揺れを直す場合は、何を同一視するかを関係者と決め、修正履歴を残します。
保留理由は重なっても消さない
一行に問題が複数あることもあります。数量が「二」で単価が「-1」、商品IDがP99なら、数量不備、単価不備、商品未登録の三つが付く設計です。最初の理由だけで処理を終えると、一つ直した後にまた別の不備で戻ってくる場合があります。元の値と複数の理由が見えると、確認先へ必要な情報をまとめて返せます。
ただし、取消行は今回の数量・単価検査の対象外です。売上集計から外すことが確定している行として別表に残しているためです。取消も含めた入力品質を点検したい業務では、別の検査を追加します。コードがどこまで調べたかを曖昧にせず、「集計対象の品質確認」と「全入力の監査」の範囲を区別しましょう。
groupbyの合計を、件数・数量・金額で検算する

groupbyは何を一行にまとめるかを決める操作
正常明細には商品カテゴリーが付いているので、categoryでgroupbyして集計します。as_index=Falseを指定すると、カテゴリーを通常の列として持つ結果になり、後で表示や保存がしやすくなります。aggではrows、quantity、amountという出力名を指定し、何の列からどんな集計を作ったかを明示しています。
rowsにはsale_idのsizeを使います。これはグループの行数です。countは通常、対象列の欠損を除いて数えるので、列によってはsizeと結果が変わります。今回のsale_idは空を許さないため同じ数になりますが、「何件」を数えているのか説明できる名前と方法を選びます。顧客数や注文件数を知りたい場合、明細の行数と同じとは限りません。
dropna=Falseは、グループのキーに欠損がある場合も落とさず扱う指定です。今回の商品台帳ではカテゴリー欠損を前段で拒否するので、通常の結果には欠損グループは出ません。それでも集計時の方針を明記しておくと、後から入力規則を変えた際に無意識に行を落とすリスクを減らせます。集計列の欠損と、グループ名の欠損は別の問題として確認してください。
正常4行の計算を手で追う
正常明細はS01、S02、S04、S07です。S01は2×120=240円、S02は3×300=900円、S04は0×120=0円、S07は2×500=1,000円です。文具は前三行なので数量5、金額1,140円、雑貨は一行で数量2、金額1,000円になります。合計数量7、金額2,140円を、pandasの出力と比較します。
※表は横にスクロールできます。
| 区分 | 正常明細の行数 | 数量 | 金額 | 検算に使う明細 |
|---|---|---|---|---|
| 文具 | 3 | 5 | 1,140円 | S01・S02・S04 |
| 雑貨 | 1 | 2 | 1,000円 | S07 |
| 正常合計 | 4 | 7 | 2,140円 | 数量ゼロのS04も件数に含む |
| 保留 | 3 | 集計しない | 集計しない | S03・S05・S08 |
| 取消 | 1 | 集計しない | 集計しない | S06 |
数量ゼロのS04があるため、文具の行数3と数量5は違う値になります。ここを区別すると、件数だけ合っているのに数量が違う、数量は合っているのに金額が違う、といった問題を切り分けられます。また、S03は数量不明なので、500円の売上だと推定しません。S05の200円も、商品未登録の保留として正常合計には入れません。
入力8行=正常4+保留3+取消1、正常4行=文具3+雑貨1、正常明細の金額合計=カテゴリー別金額合計。この三つの関係を確認します。合計金額だけ一致しても、増えた行と減った行が偶然相殺されることがあります。行数、IDの集合、数量、金額を複数の観点から比べる方が、処理の誤りを見つけやすくなります。
並べ替えとindexの違いで結果を読み違えない
集計の最後にsort_values(‘category’)とreset_index(drop=True)を使い、表示順とindexをそろえています。これは売上データを増減させるための処理ではありません。表示順を変えた後も、商品やカテゴリーの値で対応を見ます。画面の上から何行目かだけで、前回と同じ明細だと判断しないでください。
二つのSeriesを計算するときも、pandasがindexを基に対応付ける場面があります。見た目の順番だけ合わせて別表の列を掛けると、意図しない欠損や対応になる可能性があります。今回のamountは同じaccepted内のqtyとpriceから作るので対応を保っています。別表から値を付けるときは、順番へ依存せず、意味のあるキーで結合する設計を優先します。
空の結果も異常と決めつけない
対象期間に売上がない、すべて取消だった、すべて保留になった場合、正常明細や集計表は空になります。空という結果が業務上正しい場合もあるので、無理にダミーの一行を作って埋めないでください。件数と保留理由を確認し、入力が本当にゼロなのか、入力はあったが採用できなかったのかを説明します。
今回の関数は必要列を持つ空の売上表も処理でき、四つの出力が空になることを実行確認しています。正常合計が0であることだけを見て「売上なし」と報告するのではなく、入力行数と保留行数を付けて解釈します。欠損を無視する集計の既定動作に頼る前に、どの値を集計対象とするかを前段で決めることが重要です。
結果を安全に保存し、失敗例と一緒に引き継ぐ

保存は入力と別名にして、読み直して確認する
結果をCSVへ出したい場合は、集計コードを読み込んだ後で次を実行します。summary-new.csvという新しい名前を使い、mode=’x’で既存ファイルの上書きを拒否します。入力データのファイル名を出力先に指定しないでください。この例は同じフォルダーにpractice.pyとして完結コードを保存した場合の補助コードです。別名で保存した場合はimport元を合わせます。
import pandas as pd
from practice import sales, products, summarize
accepted, held, cancelled, summary = summarize(sales, products)
summary.to_csv('summary-new.csv', index=False, encoding='utf-8-sig', mode='x')
readback = pd.read_csv('summary-new.csv')
print(readback.to_string(index=False))
print('読戻し金額:', int(readback['amount'].sum()))
index=Falseは、DataFrameのindexを追加列として書き出さない指定です。utf-8-sigはBOM付きUTF-8で保存する選択です。受け取り側のソフトに合わせて文字コードを決め、保存したCSVを読み直して列名・行数・金額を確認します。今回のテストでは2行のカテゴリー集計と合計2140が戻ること、同名に再保存しようとするとFileExistsErrorになることを確認しました。
CSVはDataFrameの型情報をそのまま保持する形式ではありません。読み直すときにIDが数値と推論される、空欄の扱いが変わる、といったことがあります。今回の保存は単純な集計表なので比較しやすいですが、IDや日付を含む明細の受渡しではdtypeや日付の規則を別途決めます。保存成功の表示だけでは受け渡し完了とせず、受け取り方まで確かめましょう。
よくある失敗を症状から切り分ける
※表は横にスクロールできます。
| 症状 | 主な確認点 | 見直す順番 |
|---|---|---|
| KeyErrorが出る | 列名のつづり・前後空白・必要列 | columnsを確認し入力の仕様を合わせる |
| 数量を掛けられない | 数値に見える文字列・欠損 | 元値を保持して書式確認と型変換を行う |
| mergeで行が増える | 右側キーの重複・多対多 | 結合前の一意性とvalidateを確認する |
| 保留のはずの行が消える | inner結合・dropna・条件式 | 工程ごとの件数とIDを比較する |
| 代入が反映されない | 連鎖代入・別オブジェクトへの変更 | 対象表を決めlocで明示する |
| 合計だけ合わない | 対象条件・単価・数量・結合重複 | 正常明細から一行ずつ計算し直す |
エラーが出たときは、巨大な元データを丸ごと相手へ渡す前に、数行の架空データで再現できるかを試してください。列名、型、問題の値、期待する出力があれば、質問の焦点が明確になります。顧客情報や社内の売上を公開の質問場所へ貼る必要はありません。データの意味を保ちながら匿名の小例へ置き換え、秘密情報が残っていないか確認します。
また、警告をまとめて非表示にすると、将来の版で動作が変わる案内を見失うことがあります。pandasの版を記録し、警告の対象となる行を調べて書き方を直します。今回の2.2.3と3.0.6での一致は、掲載した小例と検証条件についての結果であり、すべての古いコードがそのまま3.0へ移せるという意味ではありません。移行時はCopy-on-Writeの公式説明など、使っている機能の変更点を確認してください。
成功例だけでなく、不正入力と順番の変化を試す
今回の実行試験では、基本の件数・数量・金額に加えて、入力の順番を20通り変えて同じカテゴリー集計になることを確認しました。数量については欠損、空文字、漢字、負数、小数、上限超過、全角数字、NaN、inf、指数表記を保留にし、0・前後空白・先頭ゼロ・上限内の値は指定ルールに沿って採用されることを試しています。
売上IDの重複、必要列不足、想定外の状態、商品台帳の重複や空欄では停止することも確認しました。さらに商品ID欠損、一行に複数の不備がある場合、空の入力、保存結果の読戻しと上書き拒否を試しています。正常データで一度合計が出ることより、誤ったデータをどう扱うかまで決まっている方が、使える範囲を説明しやすくなります。
実データへ適用するときは、ここに扱っていない条件を足します。期間の境界、返品、複数通貨、税、値引き、重複受信、商品台帳の改訂などです。すべてを最初から一つの長い関数へ詰め込まず、採用ルールと検算例を一つずつ増やします。性能の判断も小例の速さから推測せず、必要な行数とメモリー環境で別途測定してください。
処理条件と保留明細を納品物に含める
引き継ぎでは、入力列の定義、文字列の整形規則、数量と単価の範囲、取消の扱い、結合キー、保留理由、期待出力を残します。正常合計だけ渡すと、後で数字が違うと言われた際に、除外や保留の根拠を説明できません。保留明細を誰が確認し、修正後にどの入力として再実行するかも、実務では決めておきます。
作品として見せるなら、架空の小データ、処理コード、検算表、実行版をそろえると、表の加工だけでなく入力品質を考えていることを伝えられます。説明の組み立てはプログラミングのポートフォリオの作り方も参考になります。独学で型や結合の誤りを見抜くのが難しい場合は、コードレビューや質問支援の内容を確認し、必要に応じて副業向けプログラミングスクールの比較で学習方法を検討してください。
pandasの学習では、短い一行で書けることだけを目標にしなくて大丈夫です。元の8行を説明し、正常4行の数量7・金額2140を確かめ、保留3行の理由を残す。まずこの小さな処理を再実行できる形で完成させると、次のデータでも確認すべき場所が見えるようになります。
参照した仕様は、to_numeric、groupby、to_csv、locなどの公式資料です。掲載コードの実行範囲と公式資料の表示版を分けて確認し、利用する環境の版に合わせて検証してください。