パンダ:極小-極大に基づくデータのジグザグ分割


10

時系列データがあります。データを生成する

date_rng = pd.date_range('2019-01-01', freq='s', periods=400)
df = pd.DataFrame(np.random.lognormal(.005, .5,size=(len(date_rng), 3)),
                  columns=['data1', 'data2', 'data3'],
                  index= date_rng)
s = df['data1']

極大値と極小値を結ぶジグザグ線を作成します。これは|highest - lowest value|、各ジグザグ線のy軸上で、前の距離のパーセンテージ(たとえば20%)を超える必要があるという条件を満たすことです。ジグザグ線、および事前に記述された値k(1.2など)

私はこのコードを使用してローカル極値を見つけることができます:

# Find peaks(max).
peak_indexes = signal.argrelextrema(s.values, np.greater)
peak_indexes = peak_indexes[0]

# Find valleys(min).
valley_indexes = signal.argrelextrema(s.values, np.less)
valley_indexes = valley_indexes[0]
# Merge peaks and valleys data points using pandas.
df_peaks = pd.DataFrame({'date': s.index[peak_indexes], 'zigzag_y': s[peak_indexes]})
df_valleys = pd.DataFrame({'date': s.index[valley_indexes], 'zigzag_y': s[valley_indexes]})
df_peaks_valleys = pd.concat([df_peaks, df_valleys], axis=0, ignore_index=True, sort=True)

# Sort peak and valley datapoints by date.
df_peaks_valleys = df_peaks_valleys.sort_values(by=['date'])

しかし、それにしきい値条件を適用する方法がわかりません。この条件の適用方法を教えてください。

データには100万のタイムスタンプが含まれる可能性があるため、効率的な計算が強く推奨されます

より明確な説明: ここに画像の説明を入力してください

私のデータからの出力例:

 # Instantiate axes.
(fig, ax) = plt.subplots()
# Plot zigzag trendline.
ax.plot(df_peaks_valleys['date'].values, df_peaks_valleys['zigzag_y'].values, 
                                                        color='red', label="Zigzag")

# Plot original line.
ax.plot(s.index, s, linestyle='dashed', color='black', label="Org. line", linewidth=1)

# Format time.
ax.xaxis_date()
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d"))

plt.gcf().autofmt_xdate()   # Beautify the x-labels
plt.autoscale(tight=True)

plt.legend(loc='best')
plt.grid(True, linestyle='dashed')

ここに画像の説明を入力してください

私の望ましい出力(これに似たもの、ジグザグは重要なセグメントのみを接続します) ここに画像の説明を入力してください

回答:


3

私は質問をよく理解しているところに答えました。しかし、変数Kがフィルターにどのように影響するかは明らかではありません。

実行中の条件に基づいて極値をフィルタリングしたいとします。最後にマークされた極値までの相対距離がp%より大きいすべての極値をマークしたいとします。さらに、時系列の最初の要素を常に有効/関連性のあるポイントと見なしていると仮定します。

これを次のフィルター関数で実装しました。

def filter(values, percentage):
    previous = values[0] 
    mask = [True]
    for value in values[1:]: 
        relative_difference = np.abs(value - previous)/previous
        if relative_difference > percentage:
            previous = value
            mask.append(True)
        else:
            mask.append(False)
    return mask

コードを実行するには、まず依存関係をインポートします。

from scipy import signal
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

コードを再現可能にするために、ランダムシードを修正します。

np.random.seed(0)

ここからの残りはcopypastaです。結果を明確にするためにサンプルの量を減らしたことに注意してください。

date_rng = pd.date_range('2019-01-01', freq='s', periods=30)
df = pd.DataFrame(np.random.lognormal(.005, .5,size=(len(date_rng), 3)),
                  columns=['data1', 'data2', 'data3'],
                  index= date_rng)
s = df['data1']
# Find peaks(max).
peak_indexes = signal.argrelextrema(s.values, np.greater)
peak_indexes = peak_indexes[0]
# Find valleys(min).
valley_indexes = signal.argrelextrema(s.values, np.less)
valley_indexes = valley_indexes[0]
# Merge peaks and valleys data points using pandas.
df_peaks = pd.DataFrame({'date': s.index[peak_indexes], 'zigzag_y': s[peak_indexes]})
df_valleys = pd.DataFrame({'date': s.index[valley_indexes], 'zigzag_y': s[valley_indexes]})
df_peaks_valleys = pd.concat([df_peaks, df_valleys], axis=0, ignore_index=True, sort=True)
# Sort peak and valley datapoints by date.
df_peaks_valleys = df_peaks_valleys.sort_values(by=['date'])

次に、filter関数を使用します。

p = 0.2 # 20% 
filter_mask = filter(df_peaks_valleys.zigzag_y, p)
filtered = df_peaks_valleys[filter_mask]

そして、以前のプロットと新しくフィルタリングされた極値の両方を行ったようにプロットします。

 # Instantiate axes.
(fig, ax) = plt.subplots(figsize=(10,10))
# Plot zigzag trendline.
ax.plot(df_peaks_valleys['date'].values, df_peaks_valleys['zigzag_y'].values, 
                                                        color='red', label="Extrema")
# Plot zigzag trendline.
ax.plot(filtered['date'].values, filtered['zigzag_y'].values, 
                                                        color='blue', label="ZigZag")

# Plot original line.
ax.plot(s.index, s, linestyle='dashed', color='black', label="Org. line", linewidth=1)

# Format time.
ax.xaxis_date()
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d"))

plt.gcf().autofmt_xdate()   # Beautify the x-labels
plt.autoscale(tight=True)

plt.legend(loc='best')
plt.grid(True, linestyle='dashed')

ここに画像の説明を入力してください

編集

最初と最後の両方を有効と見なしたい場合は、フィルター関数を次のように調整できます。

def filter(values, percentage):
    # the first value is always valid
    previous = values[0] 
    mask = [True]
    # evaluate all points from the second to (n-1)th
    for value in values[1:-1]: 
        relative_difference = np.abs(value - previous)/previous
        if relative_difference > percentage:
            previous = value
            mask.append(True)
        else:
            mask.append(False)
    # the last value is always valid
    mask.append(True)
    return mask

こんにちは、素晴らしい答えをありがとう。はい、あなたの仮定は正しいです。「最後にマークされた極値までの相対距離がp%より大きいすべての極値をマークしてください。」そして最初と最後の点の両方を常に考慮する必要があります。私はあなたの答えをチェックしました、時々それは最後のポイントを逃しました、それで私を助けてもらえますか?
Thanh Nguyen

3

Pandasのローリング機能を使用して、ローカルの極値を作成できます。これにより、Scipyのアプローチに比べてコードが少し簡略化されます。

極値を見つける関数:

def islocalmax(x):
    """Both neighbors are lower,
    assumes a centered window of size 3"""
    return (x[0] < x[1]) & (x[2] < x[1])

def islocalmin(x):
    """Both neighbors are higher,
    assumes a centered window of size 3"""
    return (x[0] > x[1]) & (x[2] > x[1])

def isextrema(x):
    return islocalmax(x) or islocalmin(x)

ジグザグを作成する関数。データフレームに(列ごとに)一度に適用できますが、返されるタイムスタンプは列ごとに異なるため、これによりNaNが導入されます。以下の例に示すように、これらを後で簡単に削除したり、データフレームの単一の列に関数を適用したりできます。

しきい値に対するテストのコメントを外したことに注意してくださいk。その部分を正しく完全に理解しているかどうかはわかりません。以前と現在の極端な差の絶対差を次の値よりも大きくする必要がある場合は、これを含めることができますk& (ext_val.diff().abs() > k)

また、最後のジグザグが常に元の高から低に、またはその逆に移動する必要があるかどうかもわかりません。私はそうすべきだと思いましたが、そうでなければ、関数の最後にある極値の2番目の検索を削除できます。

def create_zigzag(col, p=0.2, k=1.2):

    # Find the local min/max
    # converting to bool converts NaN to True, which makes it include the endpoints    
    ext_loc = col.rolling(3, center=True).apply(isextrema, raw=False).astype(np.bool_)

    # extract values at local min/max
    ext_val = col[ext_loc]

    # filter locations based on threshold
    thres_ext_loc = (ext_val.diff().abs() > (ext_val.shift(-1).abs() * p)) #& (ext_val.diff().abs() > k)

    # Keep the endpoints
    thres_ext_loc.iloc[0] = True
    thres_ext_loc.iloc[-1] = True

    thres_ext_loc = thres_ext_loc[thres_ext_loc]

    # extract values at filtered locations 
    thres_ext_val = col.loc[thres_ext_loc.index]

    # again search the extrema to force the zigzag to always go from high > low or vice versa,
    # never low > low, or high > high
    ext_loc = thres_ext_val.rolling(3, center=True).apply(isextrema, raw=False).astype(np.bool_)
    thres_ext_val  =thres_ext_val[ext_loc]

    return thres_ext_val

いくつかのサンプルデータを生成します。

date_rng = pd.date_range('2019-01-01', freq='s', periods=35)

df = pd.DataFrame(np.random.randn(len(date_rng), 3),
                  columns=['data1', 'data2', 'data3'],
                  index= date_rng)

df = df.cumsum()

関数を適用し、「data1」列の結果を抽出します。

dfzigzag = df.apply(create_zigzag)
data1_zigzag = dfzigzag['data1'].dropna()

結果を視覚化します。

fig, axs = plt.subplots(figsize=(10, 3))

axs.plot(df.data1, 'ko-', ms=4, label='original')
axs.plot(data1_zigzag, 'ro-', ms=4, label='zigzag')
axs.legend()

ここに画像の説明を入力してください


ご回答有難うございます。私はこの線について質問したいの(ext_val.diff().abs() > (ext_val.shift(-1).abs() * p))ですが、私が理解しているように、あなたは2つの点の間の距離をp%最後の点と比較していますか?各ジグザグセグメントを前のセグメントと比較し、条件が満たされるまで繰り返したいので。
Thanh Nguyen
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.