横持ちの測定列を縦持ちへ変えるならmeltを使い、識別子の列をid_varsへ明示する。元へ戻すpivotでは、行キーと列キーの組が一意である必要がある。重複が出たときにpivot_tableで平均して通す前に、1行が何を表しているかを確認しよう。
最小例で確かめる
以下は説明用に作った小さなデータである。実測データや実行速度の測定結果ではない。コード全体をexample.pyとして保存すれば、入力ファイルを別途用意せずに実行できる。assertは、この例で成り立つべき形や値を確認するために入れてある。
import pandas as pd
wide = pd.DataFrame({"sample_id": ["001", "002"],
"t0": [1., 3.], "t10": [2., 4.]})
long = wide.melt(id_vars="sample_id", value_vars=["t0", "t10"],
var_name="time_label", value_name="value")
long["time_s"] = long["time_label"].str.removeprefix("t").astype(int)
back = long.pivot(index="sample_id", columns="time_s", values="value")
back = back.reindex(index=wide["sample_id"], columns=[0, 10])
print("long:")
print(long.to_string(index=False))
print("restored:")
print(back.to_string())
duplicated = pd.concat([long, long.iloc[[0]]], ignore_index=True)
try:
duplicated.pivot(index="sample_id", columns="time_s", values="value")
except ValueError:
print("expected: ValueError for duplicate key")
else:
raise AssertionError("duplicate pivot key was accepted")
assert len(long) == len(wide) * 2
assert not long.duplicated(["sample_id", "time_s"]).any()
assert back.to_numpy().tolist() == [[1., 2.], [3., 4.]]
assert back.index.tolist() == ["001", "002"]
実行結果
long:
sample_id time_label value time_s
001 t0 1.0 0
002 t0 3.0 0
001 t10 2.0 10
002 t10 4.0 10
restored:
time_s 0 10
sample_id
001 1.0 2.0
002 3.0 4.0
expected: ValueError for duplicate key
識別子と測定列を分ける
wideでは一つの試料が1行で、t0とt10が別々の列である。melt後は一つの試料と一つの時刻の組が1行になり、測定値はvalue列へまとまる。id_varsは各測定値について繰り返す識別情報、value_varsは縦へ積む測定列である。列を明示すれば、後でメモ列を追加したときに誤って測定値へ混ぜることも避けられる。
var_nameで元の列名を入れる列、value_nameで値を入れる列の名前を指定する。例ではtime_labelを残したうえで、tを除いた数値を秒単位のtime_sへ変換した。実データでt0、t10以外の名前も混ざるなら、既知の書式に一致するか検査してから変換する。
並び順を推測せずキーで読む
meltの出力が入力の行ごとに時刻順へ並ぶとは思い込まない方がよい。この例では測定列ごとのまとまりになっている。時系列順に並べたいならsample_idとtime_sで明示的にソートする。ただし計算値と試料の対応は行の位置ではなく、キーで追うことを基本にする。
time_labelを文字列のままソートすると、t100とt20などが数値順にならない場合がある。単位付きの列名を使っているなら、数値部分と単位を構造化した列へ分けると扱いやすい。秒と分が混在するデータを、接頭辞を削っただけで一つの時刻列にしてはいけない。
pivotは重複を解決しない
pivotは行キーsample_idと列キーtime_sで各セルの置き場所を決める。同じ組に値が2個あると、どちらを置くべきか決まらないため例外になる。例では最初の行を意図的に追加し、この失敗を確認している。値が同じでも重複した組であることに変わりはない。
pivot_tableは集約を伴うため、この問題を平均などでまとめられる。しかし、それは表の形を変えるだけでなく測定内容を要約する操作である。再測定を平均してよいなら集約規則を明示し、元の記録数も残す。繰り返しを区別したいなら測定回番号をキーへ加える方が適している。
往復できるか確認する
戻した表は列ラベルや行の順序が元と異なることがあるので、例では期待するsample_idと[0, 10]へreindexしてから値を比較した。見た目の行数だけでなく、キーと測定値の組が保存されたかを確かめる。欠測セルがある場合も、melt後に勝手に落ちていないか件数を確認したい。
複数の測定列でdtypeが異なると、縦持ちのvalue列はそれらを表現できる型へ変わることがある。温度と文字列の判定結果を同じ列へ入れるなど、意味の違う列をまとめると後の数値計算が難しくなる。単位や測定項目を列として持つ設計も含め、表の粒度を整えてから変換する。
動作確認環境と参考資料
Linux・CPython 3.12.14、NumPy 2.3.5、pandas 2.2.3、SciPy 1.17.0、Matplotlib 3.10.8の環境で掲載コードを実行した。使用するライブラリはコード冒頭のimportを参照してほしい。公式資料の最新版と、この実行確認版は区別している。数値の末尾や表の表示幅は環境によって変わることがある。
