0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ChatGPTと一緒に作る!時系列極値抽出スクリプト

Posted at

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, ...)について極大値・極小値の行を抽出できるように改良した関数です。


🔁 関数の概要

  1. df内の列名から X_1, X_2, ... のような ベース変数名を抽出
  2. 各ベース変数について、そのラグ列群の平均値を算出(極値検出のため)。
  3. 極大値・極小値を検出して、該当行を抜き出す。

✅ 改良版関数

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列の極大値または極小値がある行を抽出する。


🧠 方針

  1. 'lag_label'列から X_1, X_2, ... のベース変数名を抽出。
  2. ベース変数ごとにデータをグループ化。
  3. 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_lag1X_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:

もちろんです!今回の会話の内容(時系列データの極値抽出、ラベル処理、インデックス活用、端点対応など)に基づいて、ブログ記事のタイトル候補を以下にご提案します。


🔖 技術志向のタイトル

  1. 「Pandas × 時系列処理:ラグ変数から極大値・極小値を抽出する方法」
  2. 「時系列の端点も見逃さない!Pythonで極値を正確に抽出する」
  3. 「インデックスに埋もれたラグ変数を活かす:極値検出の実装テクニック」
  4. 「ラグ付き時系列データから極値を抽出:Pandas+SciPyでスマートに」
  5. 「Pythonで極大値・極小値検出:ラグ変数とインデックス操作の実践」

💡 学習・応用向けタイトル

  1. 「データ前処理の一歩先へ:時系列ラグ変数の極値抽出を自動化しよう」
  2. 「極値はどこに?ラグ付き時系列データを解析してみた」
  3. 「時系列データ分析Tips:極値検出で注意すべき3つのポイント」
  4. 「Pythonで始める時系列の極値抽出:インデックスとラベルの活用法」
  5. 「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:

どういたしまして!お役に立ててうれしいです 😊
またいつでもお気軽にご相談ください。次回も一緒に楽しくコーディングや記事作成を進めましょう!

こちらこそ、今後ともよろしくお願いします!🙌

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?