【Python】列を代入したのに値がずれる理由

PythonのTopに戻る

SeriesをDataFrameの列へ代入すると、通常は行の位置ではなくindexラベルで対応する。並べ替えたSeriesを戻しても値の順番がそのまま入るとは限らない。試料ごとの値ならラベルを保ち、位置で入れる必要がある場合だけ、対応を確認して配列へ変換する。

最小例で確かめる

以下は説明用に作った小さなデータである。実測データや実行速度の測定結果ではない。コード全体をexample.pyとして保存すれば、入力ファイルを別途用意せずに実行できる。assertは、この例で成り立つべき形や値を確認するために入れてある。

import pandas as pd
df = pd.DataFrame({"value": [10, 20, 30]}, index=["A", "B", "C"])
computed = pd.Series([300, 100, 200], index=["C", "A", "B"])
df["by_label"] = computed
df["by_position"] = computed.to_numpy()  # あえて位置で代入
print(df.to_string())
partial = pd.Series([11, 99], index=["A", "X"])
df["partial"] = partial
print("missing after alignment:", df.index[df["partial"].isna()].tolist())
missing_keys = df.index.difference(computed.index)
extra_keys = computed.index.difference(df.index)
assert df.index.is_unique and computed.index.is_unique
assert missing_keys.empty and extra_keys.empty
aligned = computed.reindex(df.index)
print("aligned order:", aligned.tolist())
assert df["by_label"].tolist() == [100, 200, 300]
assert df["by_position"].tolist() == [300, 100, 200]
assert df.loc["A", "partial"] == 11
assert df.loc[["B", "C"], "partial"].isna().all()
assert aligned.index.equals(df.index)

実行結果

   value  by_label  by_position
A     10       100          300
B     20       200          100
C     30       300          200
missing after alignment: ['B', 'C']
aligned order: [100, 200, 300]

自動整列はラベルで行われる

computedはC、A、Bの順に値を持っているが、by_labelへ代入するとAに100、Bに200、Cに300が入る。それぞれのindexが同じもの同士を合わせるためである。計算途中で並べ替えても試料の対応を守れるので、この仕組み自体は便利である。

問題は、indexを単なる飾りと考えて値の並びだけを予想したときに起きる。DataFrameの列から作ったSeriesには元のindexが残ることが多いので、ソートや条件抽出をしたあともそのラベルを持っている。代入先のindexと両方を表示してから、どの対応が欲しいのか確認しよう。

to_numpyは対応情報を外す

computed.to_numpy()はSeriesからラベルを外し、値の配列を取り出す。この配列を代入すると位置順に300、100、200が入り、AへCの値が付く。例では違いを示すため意図的に両方を掲載したが、試料ごとの計算値を戻す処理でこの方法を使えば取り違えになる。

位置による代入が適切なのは、外部ライブラリの結果が元の行順を厳密に保つなど、その契約が確認できる場合である。単に長さが同じだから正しいとは言えない。ソートや欠測除外で行順が変わっていないかを確認し、必要なら計算前からIDを一緒に持ち回る。

欠けたラベルと余分なラベルを調べる

partialにはAとXしかないので、代入先のBとCはNaNになる。代入先に存在しないXの値は新しい行を自動追加するわけではなく、この列代入では使われない。例のように代入後の欠測を調べると気付けるが、元から欠測を許す列では見分けにくい。

全行に結果がある前提なら、index.differenceで不足キーと余分キーを代入前に確認する。キーが重複している場合は一意な対応にならないので、is_uniqueも調べる。setの比較だけでは重複回数を見落とすため、件数や一意性の検査を別に持つ方がよい。

reindexで意図する並びを見える形にする

computed.reindex(df.index)は、代入先のラベル順へ値を並べ直す操作である。例では[100, 200, 300]になり、どの順に戻すつもりなのかをコードで示せる。既にSeries代入が同じ整列を行う場面でも、検査やデバッグのために明示すると分かりやすい。

reset_index(drop=True)でラベルを消すことは、取り違えの一般的な解決策ではない。両者の試料順が違えば、連番へ振り直したことで誤った位置対応が正しいラベル対応に見えてしまう。ラベルに意味があるなら保持し、意味のない行番号なら別の一意なキーへ置き換えてから対応付ける。

この違いは列代入だけでなくSeries同士の算術にも現れる。NumPyは基本的にshapeと位置で計算し、pandasはラベルによる整列が加わる。ライブラリをまたぐ境目で「今、対応を決めるのは位置かキーか」を確認する習慣を付けると、数値はもっともらしいが試料が違う、という誤りを防ぎやすい。

動作確認環境と参考資料

Linux・CPython 3.12.14、NumPy 2.3.5、pandas 2.2.3、SciPy 1.17.0、Matplotlib 3.10.8の環境で掲載コードを実行した。使用するライブラリはコード冒頭のimportを参照してほしい。公式資料の最新版と、この実行確認版は区別している。数値の末尾や表の表示幅は環境によって変わることがある。

関連するTips

PythonのTopに戻る