Pandalar: Yerel minima-maxima'ya dayalı verilerin zikzak segmentasyonu


10

Bir zaman çizelgesi verilerim var. Veri oluşturma

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']

Her bir zig-zag çizgisinin y ekseninde, |highest - lowest value|bir önceki mesafenin bir yüzdesini (% 20) aşması koşulunu yerine getiren yerel maxima ve yerel minima arasında bağlanan bir zig-zag hattı oluşturmak istiyorum zig-zag çizgisi VE önceden belirlenmiş bir değer k (diyelim 1.2)

Bu kodu kullanarak yerel ekstrema bulabilirsiniz:

# 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'])

ama eşik koşulunun nasıl uygulanacağını bilmiyorum. Lütfen bu durumun nasıl uygulanacağı konusunda bana bilgi verin

Veriler milyon zaman damgası içerebileceğinden, etkili bir hesaplama şiddetle tavsiye edilir

Daha net açıklama için: resim açıklamasını buraya girin

Verilerimden örnek çıktı:

 # 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')

resim açıklamasını buraya girin

İstediğim çıktı (buna benzer bir şey, zikzak sadece önemli segmentleri bağlar) resim açıklamasını buraya girin

Yanıtlar:


3

Soruyu en iyi şekilde anladım. Yine de K değişkeninin filtreyi nasıl etkilediği açık değildir.

Ekstremi çalışma koşullarına göre filtrelemek istiyorsunuz. Son işaretli ekstremum ile bağıl mesafesi % p'den büyük olan tüm ekstremi işaretlemek istediğinizi varsayalım . Ayrıca zaman çizelgelerinin ilk öğesini daima geçerli / ilgili bir nokta olarak gördüğünüzü varsayıyorum.

Bunu aşağıdaki filtre işleviyle uyguladım:

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

Kodunuzu çalıştırmak için önce bağımlılıkları içe aktarıyorum:

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

Kodu tekrarlanabilir hale getirmek için rastgele tohumu düzeltirim:

np.random.seed(0)

Geri kalanlar kopya makarna. Sonucu netleştirmek için örnek miktarını azalttığımı unutmayın.

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'])

Sonra filtre işlevini kullanıyoruz:

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

Ve hem önceki grafiğinizi hem de yeni filtrelenmiş ekstremi yaptığınız gibi çizin:

 # 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')

resim açıklamasını buraya girin

DÜZENLE :

Hem ilk hem de son noktayı geçerli olarak değerlendirmek istiyorsanız, filtre işlevini aşağıdaki gibi uyarlayabilirsiniz:

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

merhaba, harika cevap için teşekkürler. Evet, varsayımınız doğru "son işaretli uç noktaya olan bağıl mesafesi% p'den büyük olan tüm ekstremaları işaretleyin." Ve hem ilk hem de son nokta her zaman dikkate alınmalıdır. Cevabınızı kontrol ettim, bazen son noktayı kaçırdınız, bana bu konuda yardımcı olabilir misiniz?
Thanh Nguyen

3

Yerel ekstremi oluşturmak için Pandaların yuvarlanma işlevini kullanabilirsiniz. Bu, Scipy yaklaşımınıza kıyasla kodu biraz basitleştirir.

Ekstremi bulma işlevleri:

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)

Zikzak oluşturma işlevi, Dataframe'e bir kerede uygulanabilir (her sütun üzerinde), ancak bu, NaN'leri tanıtacaktır, çünkü döndürülen zaman damgaları her sütun için farklı olacaktır. Bunları daha sonra aşağıdaki örnekte gösterildiği gibi kolayca bırakabilir veya işlevi Veri Çerçevenizdeki tek bir sütuna uygulayabilirsiniz.

Testi bir eşiğe karşı uncommented unutmayın, kbu kısmı tam olarak doğru anlamak emin değilim. Önceki ve mevcut uç arasındaki mutlak farkın daha büyük olması gerekiyorsa, bunu dahil edebilirsiniz k:& (ext_val.diff().abs() > k)

Son zikzakın her zaman orijinal bir yüksekten alçağa mı yoksa tam tersine mi hareket edeceğinden emin değilim. Bunu yapması gerektiğini düşündüm, aksi takdirde fonksiyonun sonunda aşırı aramayı kaldırabilirsiniz.

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

Bazı örnek veriler oluşturun:

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()

İşlevi uygulayın ve 'data1' sütununun sonucunu çıkarın:

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

Sonucu görselleştirin:

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()

resim açıklamasını buraya girin


Cevabınız için teşekkürler. Bu çizgiyi sormak istiyorum (ext_val.diff().abs() > (ext_val.shift(-1).abs() * p)), anladığım kadarıyla, iki nokta arasındaki mesafeyi p%son nokta ile karşılaştırıyorsunuz, değil mi? Çünkü her bir zikzak parçasını bir önceki parçayla karşılaştırmak ve koşul sağlanana kadar tekrarlamak istiyorum.
Thanh Nguyen
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.