user:
pandas dataframe 形式のデータに、時系列データが入っているものとします。ユーザが列名を指定すると、その列の数字から、極大値または極小値のある行だけを抜き出すPython関数を書いてください。
assistant:
以下は、Pandas DataFrame 形式の時系列データに対して、指定された列から**極大値(ローカル最大)または極小値(ローカル最小)**が存在する行を抽出する関数です。
import pandas as pd
import numpy as np
from scipy.signal import argrelextrema
def extract_local_extrema(df, column, order=1, mode='both'):
"""
指定された列において極大値または極小値の行を抽出する。
Parameters:
- df (pd.DataFrame): 時系列データを含むDataFrame
- column (str): 対象の列名
- order (int): 極値判定の比較範囲(前後のデータ点数)
- mode (str): 'max', 'min', 'both' のいずれかで極大値・極小値の選択
Returns:
- pd.DataFrame: 極大値または極小値を持つ行だけを抜き出したDataFrame
"""
if column not in df.columns:
raise ValueError(f"指定された列 '{column}' はDataFrameに存在しません。")
data = df[column].values
extrema_indices = []
if mode in ['max', 'both']:
max_idx = argrelextrema(data, np.greater, order=order)[0]
extrema_indices.extend(max_idx)
if mode in ['min', 'both']:
min_idx = argrelextrema(data, np.less, order=order)[0]
extrema_indices.extend(min_idx)
extrema_indices = sorted(set(extrema_indices)) # 重複排除して昇順にソート
return df.iloc[extrema_indices]
使用例:
# サンプルデータの作成
data = {
'date': pd.date_range(start='2024-01-01', periods=10),
'value': [1, 3, 2, 5, 4, 6, 3, 2, 4, 1]
}
df = pd.DataFrame(data)
# 極大値・極小値の行を抽出
extrema_df = extract_local_extrema(df, column='value', order=1, mode='both')
print(extrema_df)
mode
の説明:
-
'max'
:極大値のみ抽出 -
'min'
:極小値のみ抽出 -
'both'
:極大値と極小値の両方を抽出(デフォルト)
必要に応じて、時系列としてインデックスを使いたい場合は、df.set_index('date', inplace=True)
のようにしておくとより扱いやすくなります。さらにカスタマイズが必要な場合は教えてください。
user:
ありがとうございます。同じ dataframe の別の列に、 X_1_lag1, X_1_lag2, ... X_2_lag1, X_2_lag2, ... のように、ラグ変数のラベル情報があります。変数 X_1, X_2 ごとに、上記の極大値・極小値の列を得ることができるように、変数を改変してください。
assistant:
こちらが、X_1_lag1
, X_1_lag2
, X_2_lag1
, X_2_lag2
... のような ラグ変数を持つ列群に対応し、それぞれの元変数(X_1
, X_2
, ...)について極大値・極小値の行を抽出できるように改良した関数です。
🔁 関数の概要
-
df
内の列名からX_1
,X_2
, ... のような ベース変数名を抽出。 - 各ベース変数について、そのラグ列群の平均値を算出(極値検出のため)。
- 極大値・極小値を検出して、該当行を抜き出す。
✅ 改良版関数
import pandas as pd
import numpy as np
from scipy.signal import argrelextrema
import re
def extract_extrema_per_variable(df, order=1, mode='both'):
"""
ラグ付き列 (e.g., X_1_lag1, X_1_lag2, ...) に基づいて、変数ごとに極値の行を抽出。
Parameters:
- df (pd.DataFrame): 入力データフレーム
- order (int): 極値の比較範囲
- mode (str): 'max', 'min', 'both' のいずれか
Returns:
- dict: 変数名をキーとし、極値行を含むDataFrameを値とした辞書
"""
# ラグ付き列の抽出とベース名の取得
lagged_columns = [col for col in df.columns if re.match(r'(.+)_lag\d+', col)]
base_vars = sorted(set(re.match(r'(.+)_lag\d+', col).group(1) for col in lagged_columns))
results = {}
for var in base_vars:
# 該当するラグ列を集めて平均を取る(ラグ群の代表値)
lag_cols = [col for col in lagged_columns if col.startswith(var + '_lag')]
avg_series = df[lag_cols].mean(axis=1)
# 極値インデックスの取得
extrema_indices = []
data = avg_series.values
if mode in ['max', 'both']:
max_idx = argrelextrema(data, np.greater, order=order)[0]
extrema_indices.extend(max_idx)
if mode in ['min', 'both']:
min_idx = argrelextrema(data, np.less, order=order)[0]
extrema_indices.extend(min_idx)
extrema_indices = sorted(set(extrema_indices))
results[var] = df.iloc[extrema_indices]
return results
📘 使用例
# サンプルデータ作成
data = {
'X_1_lag1': [1, 2, 3, 2, 1, 2, 3, 4],
'X_1_lag2': [0, 1, 2, 3, 2, 1, 2, 3],
'X_2_lag1': [5, 6, 7, 6, 5, 6, 7, 6],
'X_2_lag2': [4, 5, 6, 7, 6, 5, 6, 5],
'date': pd.date_range(start='2024-01-01', periods=8)
}
df = pd.DataFrame(data)
# 関数の実行
extrema_results = extract_extrema_per_variable(df, order=1, mode='both')
# 出力確認(X_1 の極値行)
print("X_1 の極値行:")
print(extrema_results['X_1'])
print("\nX_2 の極値行:")
print(extrema_results['X_2'])
📝 補足
-
order=1
は1つ前後の値と比較する設定です。ノイズがある場合は 2 以上を推奨。 - 平均ではなく 特定のラグ列(例:lag1)のみを使いたい場合 は
avg_series = df[f'{var}_lag1']
などに変更してください。 - 結果は
dict
形式で返します:{"X_1": DataFrame, "X_2": DataFrame, ...}
必要に応じて、結果を連結したり、CSV出力する処理も追加できますので、お気軽にご相談ください。
user:
ありがとうございます。ちょっと違うので、もう一度説明しますね。
pandas dataframe 形式のデータに、時系列データが入っているものとします。X_1_lag1, X_1_lag2, ... X_2_lag1, X_2_lag2, ... のように、ラグ変数のラベル情報があります。ユーザは、ラグ変数のラベル情報を示す列名と、目的の値が入っている列名を指定します。目的の値が入っている列の数字から、変数 X_1, X_2, ... ごとに、極大値または極小値のある行だけを抜き出すPython関数を書いてください。
assistant:
ありがとうございます。状況がよくわかりました。
✅ 目的の整理
-
入力データ:時系列を含む
pandas.DataFrame
。 -
df
には:-
目的の数値列(たとえば
'value'
)があり、 - 各行に対応する ラグ変数のラベル(たとえば
'X_1_lag1'
のような文字列)が入った列(たとえば'lag_label'
)がある。
-
目的の数値列(たとえば
-
ユーザーは:
- 目的の数値列の名前(例:
'value'
)と、 - ラグ変数ラベルの列名(例:
'lag_label'
)を指定する。
- 目的の数値列の名前(例:
-
目的:ラグ変数のベース(例:
X_1
,X_2
)ごとに、value
列の極大値または極小値がある行を抽出する。
🧠 方針
-
'lag_label'
列からX_1
,X_2
, ... のベース変数名を抽出。 - ベース変数ごとにデータをグループ化。
-
value
列に対して極大値または極小値の行を抽出。
✅ Python関数コード
import pandas as pd
import numpy as np
from scipy.signal import argrelextrema
import re
def extract_extrema_by_base_variable(df, value_col, label_col, order=1, mode='both'):
"""
ラグ変数のラベル列と目的値列から、各変数(X_1, X_2, ...)ごとの極値行を抽出する。
Parameters:
- df (pd.DataFrame): 時系列データフレーム
- value_col (str): 数値データが格納された列名
- label_col (str): ラグ変数のラベル(例: 'X_1_lag1')が入っている列名
- order (int): 極値検出の比較範囲(scipyの引数)
- mode (str): 'max', 'min', 'both' のいずれか
Returns:
- dict: ベース変数名(例: 'X_1')をキー、対応する極値行のDataFrameを値とする辞書
"""
if value_col not in df.columns or label_col not in df.columns:
raise ValueError("指定された列が DataFrame に存在しません。")
# ラベル列からベース変数名(X_1など)を抽出
df = df.copy()
df['base_var'] = df[label_col].apply(lambda x: re.match(r'(.+)_lag\d+', x).group(1) if pd.notnull(x) else np.nan)
df = df.dropna(subset=['base_var'])
result = {}
for var, group in df.groupby('base_var'):
values = group[value_col].values
extrema_idx = []
if mode in ['max', 'both']:
max_idx = argrelextrema(values, np.greater, order=order)[0]
extrema_idx.extend(max_idx)
if mode in ['min', 'both']:
min_idx = argrelextrema(values, np.less, order=order)[0]
extrema_idx.extend(min_idx)
extrema_idx = sorted(set(extrema_idx))
result[var] = group.iloc[extrema_idx]
return result
✅ 使用例
# サンプルデータ
data = {
'timestamp': pd.date_range(start='2024-01-01', periods=10),
'lag_label': ['X_1_lag1', 'X_1_lag2', 'X_1_lag3', 'X_2_lag1', 'X_2_lag2',
'X_2_lag3', 'X_1_lag1', 'X_1_lag2', 'X_2_lag1', 'X_2_lag2'],
'value': [1, 3, 2, 4, 6, 5, 7, 6, 3, 2]
}
df = pd.DataFrame(data)
# 実行
extrema = extract_extrema_by_base_variable(df, value_col='value', label_col='lag_label', order=1, mode='both')
# 結果表示
for var, sub_df in extrema.items():
print(f"\n--- {var} の極値行 ---")
print(sub_df)
🔧 オプションについて
-
order=1
:前後1つと比較。ノイズが多いなら2や3に。 -
mode='max'
:極大値のみ。 -
mode='min'
:極小値のみ。 -
mode='both'
:両方(デフォルト)。
必要であれば、出力を一つのDataFrameにまとめたり、特定の条件でフィルターをかけたりする機能も追加できます。お気軽にどうぞ!
user:
ありがとうございます。label_colの情報が、その df の df.index に記述されている場合は、上記の関数をどう書き換えればよいですか?
assistant:
こちらこそ、丁寧にありがとうございます!
🧭 質問の要点:
これまでは label_col
が通常の列(例:df['lag_label']
)として存在していましたが、
**今回は「ラベル情報(例:X_1_lag1
など)が df.index
にある」**という前提に変更されました。
🔁 変更ポイント:
-
label_col
引数は不要になります。 -
df.index
から直接、X_1_lag1
→X_1
のような ベース変数名を抽出するようにします。
✅ 修正後の関数
import pandas as pd
import numpy as np
from scipy.signal import argrelextrema
import re
def extract_extrema_by_index_label(df, value_col, order=1, mode='both'):
"""
DataFrameのインデックスにあるラグ変数ラベル(例: 'X_1_lag1')に基づき、
各ベース変数(例: 'X_1')ごとに極大値または極小値の行を抽出する。
Parameters:
- df (pd.DataFrame): インデックスにラグ変数ラベルを持つ時系列データフレーム
- value_col (str): 数値データが格納された列名
- order (int): 極値検出の比較範囲
- mode (str): 'max', 'min', 'both'
Returns:
- dict: ベース変数名をキー、極値行を含むDataFrameを値とする辞書
"""
if value_col not in df.columns:
raise ValueError("指定された value_col は DataFrame に存在しません。")
# インデックスからベース変数名(X_1など)を抽出
df = df.copy()
df['base_var'] = df.index.to_series().apply(lambda x: re.match(r'(.+)_lag\d+', str(x)).group(1) if pd.notnull(x) else np.nan)
df = df.dropna(subset=['base_var'])
result = {}
for var, group in df.groupby('base_var'):
values = group[value_col].values
extrema_idx = []
if mode in ['max', 'both']:
max_idx = argrelextrema(values, np.greater, order=order)[0]
extrema_idx.extend(max_idx)
if mode in ['min', 'both']:
min_idx = argrelextrema(values, np.less, order=order)[0]
extrema_idx.extend(min_idx)
extrema_idx = sorted(set(extrema_idx))
result[var] = group.iloc[extrema_idx]
return result
✅ 使用例
# サンプルデータ
data = {
'value': [1, 3, 2, 4, 6, 5, 7, 6, 3, 2],
'timestamp': pd.date_range(start='2024-01-01', periods=10)
}
index_labels = ['X_1_lag1', 'X_1_lag2', 'X_1_lag3', 'X_2_lag1', 'X_2_lag2',
'X_2_lag3', 'X_1_lag1', 'X_1_lag2', 'X_2_lag1', 'X_2_lag2']
df = pd.DataFrame(data, index=index_labels)
# 実行
extrema = extract_extrema_by_index_label(df, value_col='value', order=1, mode='both')
# 結果表示
for var, sub_df in extrema.items():
print(f"\n--- {var} の極値行 ---")
print(sub_df)
📝 補足
- ラベルが正規表現
(.+)_lag\d+
にマッチしない場合、NaN
になり無視されます。 - 目的変数が複数ある場合など、柔軟に拡張できますのでその際はご相談ください。
ご希望に沿っていれば嬉しいです。追加要望があればお気軽にどうぞ!
user:
ありがとうございます。私の要求をほぼ満たしています。ただ、極大値または極小値が、時系列データの最初又または最後にある場合は、うまく検出できないようですね。上記のコードをベースに、極大値または極小値が、時系列データの最初又または最後にある場合も、検出できるようにしてください。
assistant:
ご指摘ありがとうございます。おっしゃる通り、scipy.signal.argrelextrema
は「前後 order
個」と比較するため、最初と最後のデータ点は自動的に比較対象外となり、極値としては検出されません。
✅ 解決策
最初・最後の値については手動で比較して、極値かどうかを確認する処理を加えます。
✅ 修正版関数
import pandas as pd
import numpy as np
from scipy.signal import argrelextrema
import re
def extract_extrema_by_index_label(df, value_col, order=1, mode='both'):
"""
DataFrameのインデックスにあるラグ変数ラベル(例: 'X_1_lag1')に基づき、
各ベース変数(例: 'X_1')ごとに極大値または極小値の行を抽出する。
最初・最後のデータ点も極値として検出対象に含める。
Parameters:
- df (pd.DataFrame): インデックスにラグ変数ラベルを持つ時系列データフレーム
- value_col (str): 数値データが格納された列名
- order (int): 極値検出の比較範囲
- mode (str): 'max', 'min', 'both'
Returns:
- dict: ベース変数名をキー、極値行を含むDataFrameを値とする辞書
"""
if value_col not in df.columns:
raise ValueError("指定された value_col は DataFrame に存在しません。")
# インデックスから base_var を抽出
df = df.copy()
df['base_var'] = df.index.to_series().apply(
lambda x: re.match(r'(.+)_lag\d+', str(x)).group(1) if pd.notnull(x) else np.nan
)
df = df.dropna(subset=['base_var'])
result = {}
for var, group in df.groupby('base_var'):
values = group[value_col].values
extrema_idx = set()
if mode in ['max', 'both']:
max_idx = argrelextrema(values, np.greater, order=order)[0]
extrema_idx.update(max_idx)
if mode in ['min', 'both']:
min_idx = argrelextrema(values, np.less, order=order)[0]
extrema_idx.update(min_idx)
# 端点チェック
if len(values) >= 3:
# 先頭
head = values[0]
neighbors = values[1:1+order]
if mode in ['max', 'both'] and all(head > n for n in neighbors):
extrema_idx.add(0)
if mode in ['min', 'both'] and all(head < n for n in neighbors):
extrema_idx.add(0)
# 末尾
tail = values[-1]
neighbors = values[-(order+1):-1]
if mode in ['max', 'both'] and all(tail > n for n in neighbors):
extrema_idx.add(len(values) - 1)
if mode in ['min', 'both'] and all(tail < n for n in neighbors):
extrema_idx.add(len(values) - 1)
result[var] = group.iloc[sorted(extrema_idx)]
return result
✅ 改良ポイントまとめ
ポイント | 内容 |
---|---|
❗ 端点処理 | 先頭・末尾の値が周囲より大きい/小さいかを明示的にチェック |
✅ 汎用性 |
mode='max' , 'min' , 'both' 全てに対応 |
✅ 頑健性 | 最低3点以上あれば動作(それ未満なら端点比較も無意味) |
✅ 使用例(再掲)
data = {
'value': [9, 3, 5, 4, 2, 6, 1, 3, 2, 10],
'timestamp': pd.date_range(start='2024-01-01', periods=10)
}
index_labels = ['X_1_lag1', 'X_1_lag2', 'X_1_lag3', 'X_2_lag1', 'X_2_lag2',
'X_2_lag3', 'X_1_lag1', 'X_1_lag2', 'X_2_lag1', 'X_2_lag2']
df = pd.DataFrame(data, index=index_labels)
# 実行
extrema = extract_extrema_by_index_label(df, value_col='value', order=1, mode='both')
for var, sub_df in extrema.items():
print(f"\n--- {var} の極値行 ---")
print(sub_df)
ご希望どおり「端点にある極値も含めて抽出できる関数」になっています。
必要に応じて、極値の種類(第2極値など)に関する追加条件や平滑化処理なども加えられます。お気軽にご相談ください!
user:
ありがとうございます。これで大丈夫です。今回の会話をブログに記録したいので、タイトル候補をいくつか列挙していただけますか?
assistant:
もちろんです!今回の会話の内容(時系列データの極値抽出、ラベル処理、インデックス活用、端点対応など)に基づいて、ブログ記事のタイトル候補を以下にご提案します。
🔖 技術志向のタイトル
- 「Pandas × 時系列処理:ラグ変数から極大値・極小値を抽出する方法」
- 「時系列の端点も見逃さない!Pythonで極値を正確に抽出する」
- 「インデックスに埋もれたラグ変数を活かす:極値検出の実装テクニック」
- 「ラグ付き時系列データから極値を抽出:Pandas+SciPyでスマートに」
- 「Pythonで極大値・極小値検出:ラグ変数とインデックス操作の実践」
💡 学習・応用向けタイトル
- 「データ前処理の一歩先へ:時系列ラグ変数の極値抽出を自動化しよう」
- 「極値はどこに?ラグ付き時系列データを解析してみた」
- 「時系列データ分析Tips:極値検出で注意すべき3つのポイント」
- 「Pythonで始める時系列の極値抽出:インデックスとラベルの活用法」
- 「ChatGPTと一緒に作る!時系列極値抽出スクリプト」
ご希望に合わせて、カジュアルな文体や専門寄りのスタイルに調整も可能です。
気に入った候補や修正したい方向性があれば、お知らせください!
user:
「ChatGPTと一緒に作る!時系列極値抽出スクリプト」にしたいと思います。最後に、今回の会話のまとめを詳しく書いてください。
assistant:
ありがとうございます!
以下に、ブログ記事タイトル「ChatGPTと一緒に作る!時系列極値抽出スクリプト」にふさわしい、今回の会話の詳細なまとめを記載します。
📝 ChatGPTと一緒に作る!時系列極値抽出スクリプト
🔍 背景と目的
時系列データの中から「極大値(ピーク)」や「極小値(谷)」を抽出する処理は、経済分析やセンサーデータの異常検知、自然現象の分析などに広く用いられます。
今回の目的は、次のような条件に対応した 極値抽出の自動スクリプトを作ることです。
- データは
pandas.DataFrame
形式。 -
X_1_lag1
,X_1_lag2
,X_2_lag1
, ... のような ラグ変数名を持つ行がある。 - ユーザは、目的変数の列名(数値データ)を指定。
- ラグ変数のベース名(例:
X_1
,X_2
)ごとに、極大値・極小値の行だけを抽出。 - 極値は 中央だけでなく、時系列の最初と最後にも存在する可能性がある。
🧩 スクリプト構築のステップ
1. 基本形:argrelextremaを使った極値検出
まずは scipy.signal.argrelextrema()
を使って、極大値・極小値のインデックスを取得する関数を作成しました。
2. ラベル処理:ラグ変数をベース変数に分類
ラグ変数のラベル情報(例:X_1_lag3
)から、X_1
のようなベース変数名を正規表現で抽出し、グルーピングを可能にしました。
3. インデックス利用への対応
ラベル情報が 通常の列ではなく df.index
にある場合に対応するよう、インデックスから直接ベース変数名を抽出するように変更しました。
4. 端点補完:時系列の最初・最後にも極値があるか判定
argrelextrema
は端点を考慮しないため、先頭と末尾のデータ点も明示的に比較し、必要なら極値として追加するように改良しました。
🧪 実際の関数(最終版)
def extract_extrema_by_index_label(df, value_col, order=1, mode='both'):
...
この関数は、value_col
に指定された列の数値データをもとに、df.index
にあるラグ変数ラベルからベース変数名を抽出し、それぞれの変数に対して極大値・極小値を抽出する辞書を返します。
✅ 工夫・応用のポイント
-
mode='max'
,'min'
,'both'
の3パターンに対応。 - ラグ変数の正規表現抽出により、さまざまな命名規則に柔軟に対応可能。
- 将来的には、ローパスフィルタやスムージングを追加してノイズ耐性を向上させることも可能。
💬 感想
ChatGPTとのやり取りを通して、必要な仕様を段階的に詰めていくプロセスがとてもスムーズに進みました。自分一人では見落としがちな 端点処理 なども含めて、より実用的なスクリプトに仕上がったと思います。
🏷 記事タイトル
ChatGPTと一緒に作る!時系列極値抽出スクリプト
📌 こんな人におすすめ!
- 時系列データの極値検出を自動化したい方
- ラグ変数を含むデータを扱っている方
- ChatGPTを活用したコーディングプロセスを体験してみたい方
必要であれば、この記事向けにコードブロック付きで整形済みの全文テンプレートもご用意します。お気軽にお申し付けください!
user:
ありがとうございます。これで大丈夫です。今度もまたよろしくお願いします!
assistant:
どういたしまして!お役に立ててうれしいです 😊
またいつでもお気軽にご相談ください。次回も一緒に楽しくコーディングや記事作成を進めましょう!
こちらこそ、今後ともよろしくお願いします!🙌