※本記事のコードや情報は執筆時点の仕様に基づいています。投資は自己責任であり、必ずデモ環境や少額資金でテストした上で運用してください。
yfinanceで株価データを取得した直後、DataFrameの行番号が日付になっていて扱いにくいと感じたことかもしれません。
reset_index()はPandasのDataFrameにおけるインデックス(行ラベル)を整理するためのメソッドです。株価データの加工・結合・CSV出力など、あらゆる場面でこの操作が必要になります。
しかし「reset_index()したら日付列が消えた」「フィルタリング後に行番号が歯抜けになった」「mergeやconcatでインデックスがずれてデータが崩れた」といったトラブルが頻発します。
原因は、drop引数やinplace引数の挙動を正確に理解しないまま操作していること、そしてインデックスと通常の列の違いを意識していないことにあります。
ということで、この記事では、yfinanceで取得した株価データを題材に、reset_index()の基本動作から応用パターンまでをコピペで動くコード付きで解説します。set_index()との使い分け、マルチインデックスの解除、データ結合時のインデックス整理まで網羅します。
すべてのコードはそのまま実行できます。自分のデータ加工パイプラインに組み込んで、インデックス起因のエラーから解放されてください。
Pandasのインデックスと株価データの関係
インデックスとは何か
インデックス(Index)とは、DataFrameの各行に付与されるラベルです。Excelでいう行番号に近い役割ですが、数値以外に日付や文字列もインデックスに設定できる点が異なります。
yfinanceで取得したDataFrameでは、日付が自動的にインデックスに設定されます。この状態ではdf["Date"]のように列としてアクセスできません。
インデックスに設定された列は、.loc[]によるラベルベースのアクセスや、時系列のresample()に使えるという利点があります。一方で、CSV出力時にインデックスが余計な列として出力されたり、merge()でキーとして認識されなかったりする不便さも生じます。
reset_index()の基本動作
reset_index()は、現在のインデックスを通常の列に戻し、代わりに0始まりの連番を新しいインデックスとして割り当てるメソッドです。
主要な引数は2つあります。
| 引数 | デフォルト値 | 動作 |
|---|---|---|
drop |
False |
Trueにすると旧インデックスを列に戻さず破棄する |
inplace |
False |
Trueにすると元のDataFrameを直接変更する |
drop=False(デフォルト)では日付列が保持されます。drop=Trueを指定すると日付列が完全に失われるため、意図的に破棄したい場合以外は使わないでください。
【コピペOK】reset_indexの基本パターン集
pip install yfinance pandas
# ==============================
# reset_index 基本パターン集
# ==============================
import pandas as pd
import yfinance as yf
# ==============================
# 設定エリア
# ==============================
TICKER: str = "7203.T" # 銘柄コード(トヨタ自動車)
PERIOD: str = "3mo" # データ取得期間(3ヶ月)
# ==============================
# 関数定義: データ取得
# ==============================
def fetch_stock_data(ticker: str, period: str) -> pd.DataFrame:
'株価データを取得しDataFrameで返す'
df: pd.DataFrame = yf.download(ticker, period=period, interval="1d", progress=False)
if df.empty:
raise ValueError(f"データを取得できませんでした: {ticker}")
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
return df
# ==============================
# 関数定義: パターン1 - 基本のreset_index
# ==============================
def pattern_basic_reset(df: pd.DataFrame) -> pd.DataFrame:
'日付インデックスを列に戻し、連番を振り直す'
df_reset: pd.DataFrame = df.reset_index()
return df_reset
# ==============================
# 関数定義: パターン2 - drop=Trueで日付を破棄
# ==============================
def pattern_drop_index(df: pd.DataFrame) -> pd.DataFrame:
'日付を破棄して連番のみにする(日付が不要な場合のみ使用)'
df_dropped: pd.DataFrame = df.reset_index(drop=True)
return df_dropped
# ==============================
# 関数定義: パターン3 - フィルタリング後の振り直し
# ==============================
def pattern_after_filter(df: pd.DataFrame, threshold_volume: int) -> pd.DataFrame:
'出来高フィルタ後に歯抜けの行番号を振り直す'
df_filtered: pd.DataFrame = df[df["Volume"] >= threshold_volume].copy()
df_filtered = df_filtered.reset_index(drop=False)
return df_filtered
# ==============================
# 関数定義: パターン4 - set_indexとの往復
# ==============================
def pattern_roundtrip(df: pd.DataFrame) -> pd.DataFrame:
'reset_index → 加工 → set_indexで日付インデックスに戻す'
df_work: pd.DataFrame = df.reset_index()
df_work["Date"] = pd.to_datetime(df_work["Date"])
df_work["曜日"] = df_work["Date"].dt.day_name()
df_result: pd.DataFrame = df_work.set_index("Date")
return df_result
# ==============================
# 関数定義: パターン5 - ソート後の振り直し
# ==============================
def pattern_after_sort(df: pd.DataFrame) -> pd.DataFrame:
'終値降順ソート後にインデックスを振り直す'
df_sorted: pd.DataFrame = df.sort_values("Close", ascending=False)
df_sorted = df_sorted.reset_index(drop=False)
return df_sorted
# ==============================
# 関数定義: 結果表示
# ==============================
def show_pattern_result(label: str, df: pd.DataFrame, rows: int = 5) -> None:
'パターン名とDataFrameの先頭行を表示する'
print(f"n{'=' * 55}")
print(f" {label}")
print(f"{'=' * 55}")
print(f"インデックス名: {df.index.name}")
print(f"インデックス型: {type(df.index).__name__}")
print(f"列一覧: {list(df.columns)}")
print(df.head(rows).to_string())
# ==============================
# メイン処理
# ==============================
if __name__ == "__main__":
df_raw: pd.DataFrame = fetch_stock_data(TICKER, PERIOD)
print("【元データの状態】")
show_pattern_result("元データ(日付がインデックス)", df_raw)
df_p1: pd.DataFrame = pattern_basic_reset(df_raw)
show_pattern_result("パターン1: 基本のreset_index(日付を列に戻す)", df_p1)
df_p2: pd.DataFrame = pattern_drop_index(df_raw)
show_pattern_result("パターン2: drop=True(日付を破棄)", df_p2)
median_vol: int = int(df_raw["Volume"].median())
df_p3: pd.DataFrame = pattern_after_filter(df_raw, median_vol)
show_pattern_result(f"パターン3: 出来高{median_vol:,}以上でフィルタ後", df_p3)
df_p4: pd.DataFrame = pattern_roundtrip(df_raw)
show_pattern_result("パターン4: reset → 曜日列追加 → set_indexで復元", df_p4)
df_p5: pd.DataFrame = pattern_after_sort(df_raw)
show_pattern_result("パターン5: 終値降順ソート後の振り直し", df_p5)
コードの処理フロー解説
上記のコードは、以下の5ステップで構成されています。
* fetch_stock_data関数でyfinanceから3ヶ月分の日足データを取得し、マルチインデックスが発生した場合はフラット化する
* パターン1〜2でreset_index()のdrop引数による動作の違いを比較する。drop=Falseで日付列が保持され、drop=Trueで日付が消えることを確認できる
* パターン3でフィルタリング後の歯抜けインデックス(例:0, 2, 5, 8…)を連番に振り直す実用例を示す
* パターン4でreset_index()→列加工→set_index()という往復操作を行い、日付インデックスを維持したまま曜日列を追加する
* パターン5でソート後のインデックス振り直しにより、ランキング形式のDataFrameを作成する
パターン4のset_index()による復元は、resample()やloc["2025-01"]のような時系列操作を行いたい場合に必須のテクニックです。
各パターンの使い分けと実践的な判断基準
場面別の選択ガイド
どのパターンを使うべきかは、加工後のデータの用途で決まります。
| 用途 | 推奨パターン | 理由 |
|---|---|---|
| CSV出力 | パターン1(drop=False) |
日付を通常列として保持し、index=FalseでCSV出力すると列の重複を防げる |
| 機械学習の特徴量作成 | パターン2(drop=True) |
日付自体は特徴量にならないケースが多く、連番インデックスで十分 |
| 出来高や条件でのフィルタ後 | パターン3 | 歯抜け番号はスライス操作でバグの原因になるため、必ず振り直す |
| 時系列分析の途中工程 | パターン4(往復) | 列追加後に日付インデックスを復元し、resample()を引き続き使える状態にする |
| ランキング・ソート結果の出力 | パターン5 | ソート後の並び順を行番号で明示する |
迷った場合は、パターン1(drop=False)を選んでください。日付列は後からset_index()で戻せますが、drop=Trueで消えた日付は復元できません。
inplace引数を使わない理由
reset_index(inplace=True)は元のDataFrameを直接書き換えます。一見便利ですが、デバッグ時に元データを参照できなくなるため、実務では推奨しません。
df = df.reset_index()のように、戻り値を新しい変数(または同名変数)に代入する書き方を標準にしてください。この書き方なら、処理前後の状態を別々の変数に保持できます。
【コピペOK】マルチインデックスの解除と複数銘柄データの結合
# ==============================
# マルチインデックス解除 & 複数銘柄結合
# ==============================
import pandas as pd
import yfinance as yf
# ==============================
# 設定エリア
# ==============================
MULTI_TICKERS: list[str] = ["7203.T", "9984.T", "6758.T"]
MULTI_PERIOD: str = "1mo"
# ==============================
# 関数定義: マルチインデックス解除
# ==============================
def flatten_multi_index(df: pd.DataFrame) -> pd.DataFrame:
'マルチインデックスの列をフラット化する'
if isinstance(df.columns, pd.MultiIndex):
df.columns = ["_".join(col).strip("_") for col in df.columns]
df = df.reset_index()
return df
# ==============================
# 関数定義: 複数銘柄を縦結合
# ==============================
def fetch_and_concat(
tickers: list[str],
period: str,
) -> pd.DataFrame:
'複数銘柄を取得し、銘柄列を追加して縦結合する'
frames: list[pd.DataFrame] = []
for ticker in tickers:
df: pd.DataFrame = yf.download(
ticker, period=period, interval="1d", progress=False,
)
if df.empty:
print(f"スキップ: {ticker}")
continue
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
df = df.reset_index()
df["Ticker"] = ticker
frames.append(df)
if not frames:
raise ValueError("1銘柄もデータを取得できませんでした。")
combined: pd.DataFrame = pd.concat(frames, ignore_index=True)
return combined
# ==============================
# 関数定義: 銘柄別ピボット変換
# ==============================
def pivot_close_by_ticker(df: pd.DataFrame) -> pd.DataFrame:
'縦持ちデータを銘柄別の終値ピボットテーブルに変換する'
pivot: pd.DataFrame = df.pivot_table(
index="Date", columns="Ticker", values="Close",
)
pivot = pivot.reset_index()
pivot.columns.name = None
return pivot
# ==============================
# 関数定義: 結果表示
# ==============================
def show_result(label: str, df: pd.DataFrame) -> None:
'ラベルとDataFrameの先頭を表示する'
print(f"n{'=' * 55}")
print(f" {label}")
print(f"{'=' * 55}")
print(f"shape: {df.shape}")
print(f"columns: {list(df.columns)}")
print(df.head(8).to_string(index=False))
# ==============================
# メイン処理
# ==============================
if __name__ == "__main__":
print("--- マルチインデックス解除のデモ ---")
df_multi: pd.DataFrame = yf.download(
MULTI_TICKERS, period=MULTI_PERIOD, interval="1d", progress=False,
)
print(f"n取得直後の列構造: {type(df_multi.columns).__name__}")
print(f"列サンプル: {list(df_multi.columns[:6])}")
df_flat: pd.DataFrame = flatten_multi_index(df_multi)
show_result("マルチインデックス解除後", df_flat)
print("nn--- 複数銘柄の縦結合デモ ---")
df_concat: pd.DataFrame = fetch_and_concat(MULTI_TICKERS, MULTI_PERIOD)
show_result("縦結合(ignore_index=True)", df_concat)
print("nn--- ピボット変換デモ ---")
df_pivot: pd.DataFrame = pivot_close_by_ticker(df_concat)
show_result("銘柄別 終値ピボット", df_pivot)
コードの処理フロー解説
上記のコードは、以下の4ステップで構成されています。
* yf.download()に複数銘柄をリストで渡すとマルチインデックス(MultiIndex)の列構造が返されるため、flatten_multi_index関数で列名をClose_7203.T形式にフラット化し、日付インデックスもreset_index()で列に戻す
* fetch_and_concat関数で銘柄ごとに個別取得し、Ticker列を追加した上でpd.concat()にignore_index=Trueを指定して縦結合する。これにより全銘柄が1つのDataFrameに統合される
* pivot_close_by_ticker関数で縦持ちデータを横持ちに変換し、日付行×銘柄列の終値比較テーブルを作成する
* 各段階でDataFrameのshapeとcolumnsを出力し、構造の変化を目視確認できるようにしている
pd.concat()にignore_index=Trueを渡すことが重要です。この引数を省略すると、各銘柄のDataFrameが持っていた元のインデックスがそのまま結合され、行番号が重複します。
よくあるエラーと対処法
ValueError: cannot insert Date, already exists
reset_index()を2回連続で実行した場合に発生します。1回目のreset_index()でDate列がすでに通常の列に移動しているのに、2回目で再度同名の列を挿入しようとして衝突します。
以下を試してください。
* reset_index()の前にdf.index.nameをprint()して、現在のインデックスが日付かどうか確認する
* すでにDate列が存在する場合はreset_index()を実行しない条件分岐を入れる
* どうしても連番に振り直したい場合はdf.reset_index(drop=True)を使う
KeyError: ‘Date’(set_indexで復元しようとした時)
reset_index(drop=True)で日付を破棄した後に、set_index("Date")で日付インデックスを復元しようとすると発生します。drop=Trueを指定した時点で日付データは完全に失われています。
drop=Trueを指定した操作は取り消せません。日付を復元したい場合は、加工前のDataFrameを別の変数に保持しておくか、最初からdrop=False(デフォルト)を使用してください。
pd.concat()後にインデックスが重複する
ignore_index=Trueを指定せずにpd.concat()を実行すると、結合元のDataFrameそれぞれが持つインデックス(0, 1, 2…)がそのまま引き継がれます。結果として同じ行番号が複数回出現し、.loc[0]で複数行が返される原因になります。
以下を試してください。
* pd.concat(frames, ignore_index=True)のように必ずignore_index=Trueを指定する
* 結合後にdf.reset_index(drop=True)を実行しても同じ効果が得られる
* df.index.is_uniqueで重複がないことを確認する習慣をつける
まとめ
この記事では、reset_index()を使って株価データのインデックスを整理する基本操作から、マルチインデックスの解除・複数銘柄の結合パターンまでを解説しました。
実際に使ってみた要点をまとめます。
* reset_index(drop=False)は日付を列に戻しつつ連番を振る最も安全な基本操作である
* drop=Trueは日付を完全に破棄するため、意図的に不要な場合のみ使用する
* フィルタリングやソート後の歯抜けインデックスは、必ずreset_index()で振り直す
* pd.concat()にはignore_index=Trueを指定し、行番号の重複を防ぐ
* inplace=Trueは避け、戻り値を変数に代入する書き方を標準にする
また、reset_index()とset_index()の往復操作を、自分のデータ加工パイプラインの中に明示的に組み込んでみてください。「インデックスが日付の状態」と「連番の状態」を意識的に切り替えることで、resample()やCSV出力のたびに発生していたエラーが解消されます。
さらに、pd.MultiIndexの操作に慣れれば、複数銘柄×複数指標のデータを1つのDataFrameで効率的に管理できるようになります。このフラット化コードをベースに、自分の分析規模に合ったデータ構造を整理してみるといいかと思います。

