35
56

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python Pandasを使った時系列データの移動平均計算: 実装と分析技法

Posted at

はじめに

image.png

時系列データの分析は、ビジネス、金融、科学研究など、様々な分野で重要な役割を果たしています。その中でも、移動平均は最も基本的かつ強力なツールの一つです。この記事では、Pandasを使用した移動平均の計算と可視化について、基礎から応用まで幅広く解説します。

この記事を読むメリット

  1. 実践的なデータ分析スキルの向上: 単純な移動平均から適応型移動平均まで、様々な手法の実装方法を学べます。これらのスキルは、株価予測、需要予測、センサーデータの分析など、実務で即座に活用できます。

  2. 効率的なコード設計とパフォーマンス最適化: 大規模データセットの処理技術や、再利用性の高いコード設計について学べます。これにより、より効率的で保守性の高い分析プログラムを作成できるようになります。

  3. 分析手法と可視化技術の習得: 移動平均の交差シグナルやボリンジャーバンドなど、分析手法と、それらを効果的に可視化する技術を学べます。これにより、データからより深い洞察を得て、説得力のある分析結果を提示できるようになります。

この記事を通じて、時系列データ分析の実践的スキルを身につけ、より効率的で洞察力のある分析を行う能力を獲得できます。さらに、ここで学ぶテクニックは、機械学習モデルの特徴量エンジニアリングにも応用可能で、予測モデルの精度向上にも貢献します。

基本的な実装

まずは基本的な実装から始めましょう。

image.png

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from typing import Union

# サンプルデータの作成
def create_sample_data(start: str = '2023-01-01', end: str = '2023-12-31', freq: str = 'D') -> pd.DataFrame:
    dates = pd.date_range(start=start, end=end, freq=freq)
    np.random.seed(42)
    values = np.random.randn(len(dates)) * 10 + 100
    df = pd.DataFrame({'date': dates, 'value': values})
    return df.set_index('date')

# 移動平均の計算
def calculate_moving_average(df: pd.DataFrame, column: str, window: int) -> pd.Series:
    return df[column].rolling(window=window, min_periods=1).mean()

# データの可視化
def plot_time_series(df: pd.DataFrame, title: str):
    plt.figure(figsize=(12, 6))
    plt.plot(df.index, df['value'], label='Original Data')
    plt.plot(df.index, df['MA7'], label='7-day MA')
    plt.plot(df.index, df['MA30'], label='30-day MA')
    plt.title(title)
    plt.xlabel('Date')
    plt.ylabel('Value')
    plt.legend()
    plt.grid(True)
    plt.show()

# メイン処理
df = create_sample_data()
df['MA7'] = calculate_moving_average(df, 'value', 7)
df['MA30'] = calculate_moving_average(df, 'value', 30)
plot_time_series(df, 'Time Series Data with Moving Averages')

image.png

応用例

ここでは、さまざまな応用的な移動平均テクニックの実装と結果の可視化を行います。

image.png

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# サンプルデータの作成(ランダムウォーク)
def create_sample_data(start: str = '2023-01-01', end: str = '2023-12-31', freq: str = 'D') -> pd.DataFrame:
    dates = pd.date_range(start=start, end=end, freq=freq)
    np.random.seed(42)
    values = np.cumsum(np.random.randn(len(dates))) + 100  # ランダムウォーク
    df = pd.DataFrame({'date': dates, 'value': values})
    return df.set_index('date')

# 1. 適応型移動平均(Adaptive Moving Average)
def adaptive_moving_average(df: pd.DataFrame, column: str, min_window: int, max_window: int) -> pd.Series:
    volatility = df[column].rolling(window=min_window).std()
    volatility = volatility.fillna(method='ffill').fillna(method='bfill')
    
    if volatility.max() == volatility.min():
        normalized_volatility = pd.Series(0.5, index=volatility.index)
    else:
        normalized_volatility = (volatility - volatility.min()) / (volatility.max() - volatility.min())
    
    window_size = min_window + (max_window - min_window) * (1 - normalized_volatility)
    window_size = window_size.clip(lower=min_window, upper=max_window).round().astype(int)
    
    result = pd.Series(index=df.index, dtype=float)
    for size in range(min_window, max_window + 1):
        mask = window_size == size
        if mask.any():
            result[mask] = df.loc[mask, column].rolling(window=size, min_periods=1).mean()
    
    return result

# 2. 重み付き移動平均(Weighted Moving Average)
def weighted_moving_average(df: pd.DataFrame, column: str, window: int) -> pd.Series:
    weights = np.arange(1, window + 1)
    return df[column].rolling(window=window).apply(lambda x: np.dot(x, weights) / weights.sum(), raw=True)

# 3. 中央値ベースの移動平均(Median-based Moving Average)
def median_moving_average(df: pd.DataFrame, column: str, window: int) -> pd.Series:
    return df[column].rolling(window=window, min_periods=1).median()

# サンプルデータの作成
df = create_sample_data()

# 各種移動平均の計算
df['SMA7'] = df['value'].rolling(window=7).mean()
df['SMA30'] = df['value'].rolling(window=30).mean()
df['AMA'] = adaptive_moving_average(df, 'value', 7, 30)
df['WMA7'] = weighted_moving_average(df, 'value', 7)
df['MedMA7'] = median_moving_average(df, 'value', 7)

# 結果の可視化
plt.figure(figsize=(15, 10))

plt.subplot(2, 1, 1)
plt.plot(df.index, df['value'], label='Original Data', alpha=0.5)
plt.plot(df.index, df['SMA7'], label='Simple MA (7)', linestyle='--')
plt.plot(df.index, df['SMA30'], label='Simple MA (30)', linestyle='--')
plt.plot(df.index, df['AMA'], label='Adaptive MA (7-30)', linewidth=2)
plt.title('Comparison of Simple and Adaptive Moving Averages')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.grid(True)

plt.subplot(2, 1, 2)
plt.plot(df.index, df['value'], label='Original Data', alpha=0.5)
plt.plot(df.index, df['SMA7'], label='Simple MA (7)', linestyle='--')
plt.plot(df.index, df['WMA7'], label='Weighted MA (7)', linewidth=2)
plt.plot(df.index, df['MedMA7'], label='Median-based MA (7)', linewidth=2)
plt.title('Comparison of Simple, Weighted, and Median-based Moving Averages')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.show()

# 適応型移動平均の窓サイズの変化を確認
window_sizes = (df['AMA'] - df['value'].shift(1)) / (df['value'] - df['value'].shift(1))
window_sizes = window_sizes.clip(lower=7, upper=30)

plt.figure(figsize=(12, 6))
plt.plot(df.index, window_sizes, label='Adaptive Window Size')
plt.title('Adaptive Moving Average Window Size')
plt.xlabel('Date')
plt.ylabel('Window Size')
plt.ylim(5, 32)
plt.legend()
plt.grid(True)
plt.show()

# 結果の一部を表示
print(df[['value', 'SMA7', 'SMA30', 'AMA', 'WMA7', 'MedMA7']].head(10))
print("\n")
print(df[['value', 'SMA7', 'SMA30', 'AMA', 'WMA7', 'MedMA7']].tail(10))

image.png

image.png

                value        SMA7  SMA30         AMA        WMA7      MedMA7
date                                                                         
2023-01-01  100.496714         NaN    NaN  100.496714         NaN  100.496714
2023-01-02  100.358450         NaN    NaN  100.427582         NaN  100.427582
2023-01-03  101.006138         NaN    NaN  100.620434         NaN  100.496714
2023-01-04  102.529168         NaN    NaN  101.097618         NaN  100.751426
2023-01-05  102.295015         NaN    NaN  101.337097         NaN  101.006138
2023-01-06  102.060878         NaN    NaN  101.457727         NaN  101.533508
2023-01-07  103.640091  101.769493    NaN  101.769493  102.273917  102.060878
2023-01-08  104.407525  102.328181    NaN  104.407525  102.933425  102.295015
2023-01-09  103.938051  102.839552    NaN  102.040563  103.335893  102.529168
2023-01-10  104.480611  103.335906    NaN  104.480611  103.746157  103.640091


                 value        SMA7       SMA30         AMA        WMA7  \
date                                                                     
2023-12-22  101.406940  101.537713  103.930891   98.601433  101.873183   
2023-12-23  101.388427  101.743988  103.818912   99.442476  101.835862   
2023-12-24  101.099768  101.864611  103.670923   98.358846  101.674807   
2023-12-25  101.422487  101.820572  103.512887   98.703919  101.564275   
2023-12-26  100.595256  101.535833  103.306332   98.885773  101.257947   
2023-12-27  101.114602  101.348136  103.117496  100.397922  101.152639   
2023-12-28  102.647341  101.382117  103.009661   99.126103  101.477440   
2023-12-29  102.538581  101.543780  102.895673   99.765899  101.766556   
2023-12-30  102.940293  101.765475  102.817648   95.711616  102.115684   
2023-12-31  103.630437  102.127000  102.730123   94.290223  102.581925   

                MedMA7  
date                    
2023-12-22  101.730764  
2023-12-23  101.730764  
2023-12-24  101.730764  
2023-12-25  101.422487  
2023-12-26  101.406940  
2023-12-27  101.388427  
2023-12-28  101.388427  
2023-12-29  101.388427  
2023-12-30  101.422487  
2023-12-31  102.538581  

この実装では、以下の移動平均手法を比較しています:

  1. 単純移動平均(SMA): 7日と30日の窓サイズで計算しています。
  2. 適応型移動平均(AMA): 窓サイズを7日から30日の間で動的に調整します。
  3. 重み付き移動平均(WMA): 7日の窓サイズで、最新のデータにより大きな重みを付けています。
  4. 中央値ベースの移動平均(MedMA): 7日の窓サイズで、中央値を使用しています。

結果の可視化では、2つのグラフを作成しています:

  1. 単純移動平均と適応型移動平均の比較
  2. 単純移動平均、重み付き移動平均、中央値ベースの移動平均の比較

また、適応型移動平均の窓サイズの変化を別のグラフで示しています。

これらのグラフを比較することで、各手法の特徴や違いを視覚的に理解することができます:

  • 適応型移動平均(AMA): データの変動に応じて窓サイズを調整するため、急激な変化にも柔軟に対応できます。
  • 重み付き移動平均(WMA): 最新のデータにより大きな重みを付けるため、トレンドの変化をより早く捉えることができます。
  • 中央値ベースの移動平均(MedMA): 外れ値の影響を受けにくいため、ノイズの多いデータに対して効果的です。

最後に、計算結果の一部(最初の10行と最後の10行)を表示しています。これにより、各手法の数値的な違いを直接比較することができます。

この応用例を通じて、それぞれの移動平均手法の特徴や用途をより深く理解することができるでしょう。実際のデータ分析では、データの特性や分析の目的に応じて、最適な手法を選択することが重要です。

パフォーマンス最適化

大規模なデータセットを扱う場合、計算効率が重要になります。以下はパフォーマンスを向上させるためのテクニックです。

1. Numbaを使用した高速化

Numbaを使用して、移動平均の計算を高速化できます。

image.png

image.png

import pandas as pd
import numpy as np
from numba import jit
import matplotlib.pyplot as plt

@jit(nopython=True)
def numba_moving_average(arr, window):
    n = len(arr)
    result = np.empty(n)
    cumsum = np.zeros(n + 1)
    cumsum[1:] = np.cumsum(arr)
    
    for i in range(n):
        if i < window:
            result[i] = cumsum[i+1] / (i + 1)
        else:
            result[i] = (cumsum[i+1] - cumsum[i+1-window]) / window
    
    return result

def fast_moving_average(df: pd.DataFrame, column: str, window: int) -> pd.Series:
    values = df[column].values
    ma = numba_moving_average(values, window)
    return pd.Series(ma, index=df.index)

# サンプルデータの作成
def create_sample_data(start: str = '2023-01-01', end: str = '2023-12-31', freq: str = 'D') -> pd.DataFrame:
    dates = pd.date_range(start=start, end=end, freq=freq)
    np.random.seed(42)
    values = np.cumsum(np.random.randn(len(dates))) + 100
    df = pd.DataFrame({'date': dates, 'value': values})
    return df.set_index('date')

# メイン処理
df = create_sample_data()

# Numbaを使用した高速移動平均の計算
df['FastMA7'] = fast_moving_average(df, 'value', 7)

# 標準的なPandasの移動平均計算(比較用)
df['MA7'] = df['value'].rolling(window=7).mean()

# 結果の可視化
plt.figure(figsize=(12, 6))
plt.plot(df.index, df['value'], label='Original Data', alpha=0.5)
plt.plot(df.index, df['FastMA7'], label='Fast MA (Numba)', linewidth=2)
plt.plot(df.index, df['MA7'], label='Standard MA', linestyle='--')
plt.title('Comparison of Numba-optimized and Standard Moving Averages')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.grid(True)
plt.show()

# 計算時間の比較
import time

start_time = time.time()
_ = fast_moving_average(df, 'value', 7)
numba_time = time.time() - start_time

start_time = time.time()
_ = df['value'].rolling(window=7).mean()
pandas_time = time.time() - start_time

print(f"Numba-optimized calculation time: {numba_time:.6f} seconds")
print(f"Standard Pandas calculation time: {pandas_time:.6f} seconds")
print(f"Speedup factor: {pandas_time / numba_time:.2f}x")

# 結果の一部を表示
print("\nFirst few rows of the DataFrame:")
print(df.head())

print("\nLast few rows of the DataFrame:")
print(df.tail())

image.png

応用的な分析と可視化

1. 移動平均の交差シグナル

短期と長期の移動平均の交差を用いて、トレンド変化のシグナルを検出します。

image.png

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

def create_sample_data(start: str = '2023-01-01', end: str = '2023-12-31', freq: str = 'D') -> pd.DataFrame:
    dates = pd.date_range(start=start, end=end, freq=freq)
    np.random.seed(42)
    values = np.cumsum(np.random.randn(len(dates))) + 100
    df = pd.DataFrame({'date': dates, 'value': values})
    return df.set_index('date')

def calculate_ma(df: pd.DataFrame, column: str, window: int) -> pd.Series:
    return df[column].rolling(window=window).mean()

def ma_crossover_signal(df: pd.DataFrame, short_window: int, long_window: int) -> pd.DataFrame:
    df['Short_MA'] = calculate_ma(df, 'value', short_window)
    df['Long_MA'] = calculate_ma(df, 'value', long_window)
    
    # シグナルの計算
    df['Signal'] = np.where(df['Short_MA'] > df['Long_MA'], 1, 0)
    df['Signal'] = df['Signal'].diff()
    
    return df

def plot_ma_crossover(df: pd.DataFrame):
    plt.figure(figsize=(12, 6))
    plt.plot(df.index, df['value'], label='Original Data', alpha=0.5)
    plt.plot(df.index, df['Short_MA'], label=f'{short_window}-day MA', linewidth=1)
    plt.plot(df.index, df['Long_MA'], label=f'{long_window}-day MA', linewidth=1)
    
    # 買いシグナル
    plt.scatter(df[df['Signal'] == 1].index, 
                df['value'][df['Signal'] == 1],
                marker='^', color='g', s=100, label='Buy Signal')
    
    # 売りシグナル
    plt.scatter(df[df['Signal'] == -1].index, 
                df['value'][df['Signal'] == -1],
                marker='v', color='r', s=100, label='Sell Signal')
    
    plt.title('Moving Average Crossover Signals')
    plt.xlabel('Date')
    plt.ylabel('Value')
    plt.legend()
    plt.grid(True)
    plt.show()

# メイン処理
if __name__ == "__main__":
    df = create_sample_data()
    short_window = 7
    long_window = 30
    
    df = ma_crossover_signal(df, short_window, long_window)
    plot_ma_crossover(df)
    
    # シグナルの統計
    buy_signals = df['Signal'][df['Signal'] == 1].count()
    sell_signals = df['Signal'][df['Signal'] == -1].count()
    
    print(f"買いシグナル数: {buy_signals}")
    print(f"売りシグナル数: {sell_signals}")
    
    # 最初と最後の数行を表示
    print("\nデータの最初の数行:")
    print(df.head())
    print("\nデータの最後の数行:")
    print(df.tail())

image.png

2. ボリンジャーバンド

移動平均と標準偏差を使用してボリンジャーバンドを計算し、可視化します。

def bollinger_bands(df: pd.DataFrame, column: str, window: int, num_std: float) -> pd.DataFrame:
    rolling_mean = df[column].rolling(window=window).mean()
    rolling_std = df[column].rolling(window=window).std()
    upper_band = rolling_mean + (rolling_std * num_std)
    lower_band = rolling_mean - (rolling_std * num_std)
    return pd.DataFrame({'Middle': rolling_mean, 'Upper': upper_band, 'Lower': lower_band})

bb = bollinger_bands(df, 'value', 20, 2)
df = pd.concat([df, bb], axis=1)

plt.figure(figsize=(12, 8))
plt.plot(df.index, df['value'], label='Original Data', alpha=0.5)
plt.plot(df.index, df['Middle'], label='20-day MA')
plt.plot(df.index, df['Upper'], label='Upper Band')
plt.plot(df.index, df['Lower'], label='Lower Band')
plt.fill_between(df.index, df['Upper'], df['Lower'], alpha=0.1)
plt.title('Bollinger Bands')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.grid(True)
plt.show()

image.png

まとめ

image.png

この記事では、Pandasを使用した時系列データの移動平均計算について、基本的な実装から高度なテクニックまで幅広く解説しました。以下に主要なポイントをまとめます:

  1. 基本的な移動平均: 単純移動平均(SMA)の実装と可視化方法を学びました。

  2. 高度な移動平均テクニック:

    • 適応型移動平均(AMA): 市場の変動性に応じて窓サイズを動的に調整する手法を実装しました。
    • 重み付き移動平均(WMA): 最新のデータにより大きな重みを付ける手法を紹介しました。
    • 中央値ベースの移動平均: 外れ値に対してより頑健な手法を実装しました。
  3. パフォーマンス最適化:

    • Numbaを使用した高速化: JITコンパイルによる計算速度の向上方法を学びました。
  4. 高度な分析と可視化:

    • 移動平均の交差シグナル: トレンド変化を検出する手法を実装しました。
    • ボリンジャーバンド: 移動平均と標準偏差を用いた変動性の分析方法を学びました。

これらの技術を組み合わせることで、より洞察力のある時系列データ分析が可能になります。実際のデータセットに適用する際は、データの特性や分析の目的に応じて、適切な手法を選択することが重要です。

移動平均は、その簡潔さと強力な分析能力から、多くの分野で広く使用されています。この記事で学んだテクニックを応用することで、ノイズの多いデータからトレンドを抽出したり、異常値を検出したり、将来の値を予測したりすることができます。

さらに、これらの手法は機械学習モデルの前処理や特徴量エンジニアリングにも活用できます。例えば、適応型移動平均を使用して、時系列データの動的な特徴を捉えることができます。

今後の学習方向

image.png

この記事の内容を十分に理解したら、以下のトピックに挑戦してみることをお勧めします:

  1. 指数平滑法: 単純移動平均の発展形として、過去のデータに対して指数関数的に重みを減少させる手法です。

  2. ARIMA(自己回帰和分移動平均)モデル: 時系列データの予測に広く使用される統計モデルです。

  3. 機械学習との組み合わせ: 移動平均を特徴量として使用し、機械学習モデル(例:LSTM)と組み合わせる方法を探索してみましょう。

  4. リアルタイムデータ処理: ストリーミングデータに対する移動平均の計算方法を学びます。

  5. 可視化の高度化: より対話的で情報量の多い可視化技術(例:Plotly、Bokeh)を学習します。

時系列データ分析は奥が深く、常に新しい手法や技術が登場しています。継続的な学習と実践を通じて、あなたのデータサイエンススキルをさらに向上させていってください。

参考資料

この記事が、皆さんの時系列データ分析スキルの向上に役立つことを願っています。

35
56
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
35
56

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?