पंडों: स्थानीय मिनीमा-मैक्सिमा पर आधारित डेटा का ज़िगज़ैग विभाजन


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

मैं स्थानीय मैक्सिमा और स्थानीय मिनीमा के बीच जुड़ने वाली एक ज़िग-ज़ैग लाइन बनाना चाहता हूँ, जो इस शर्त को संतुष्ट करती है कि y- अक्ष पर, |highest - lowest value|प्रत्येक zig-zag लाइन की दूरी पिछले की दूरी के प्रतिशत (मान 20%) से अधिक होनी चाहिए zig-zag लाइन, और एक पूर्व-घोषित मूल्य 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'])

लेकिन मुझे नहीं पता कि इसे कैसे लागू किया जाए। कृपया मुझे सलाह दें कि ऐसी स्थिति को कैसे लागू किया जाए।

चूंकि डेटा में मिलियन टाइमस्टैम्प हो सकते हैं, एक कुशल गणना अत्यधिक अनुशंसित है

स्पष्ट विवरण के लिए: यहां छवि विवरण दर्ज करें

उदाहरण आउटपुट, मेरे डेटा से:

 # 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 फ़िल्टर को कैसे प्रभावित करता है।

आप एक चल रही स्थिति के आधार पर एक्स्ट्रेमा को फ़िल्टर करना चाहते हैं। मुझे लगता है कि आप सभी एक्स्ट्रेमा को चिह्नित करना चाहते हैं जिनकी अंतिम चिह्नित चरम सीमा के सापेक्ष दूरी पी% से बड़ी है। मैं आगे यह मानता हूं कि आप हमेशा समयों के पहले तत्व को एक मान्य / प्रासंगिक बिंदु मानते हैं।

मैंने इसे निम्नलिखित फ़िल्टर फ़ंक्शन के साथ कार्यान्वित किया:

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)

यहाँ से बाकी कोपिस्टा है। ध्यान दें कि मैंने परिणाम को स्पष्ट करने के लिए नमूने की मात्रा कम कर दी।

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

फिर हम फ़िल्टर फ़ंक्शन का उपयोग करते हैं:

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% से बड़ी है।", और पहले और अंतिम दोनों बिंदुओं पर हमेशा विचार किया जाना चाहिए। मैंने आपका उत्तर जांच लिया है, कभी-कभी यह अंतिम बिंदु से चूक जाता है, क्या आप उस पर मेरी मदद कर सकते हैं?
थान गुयेन

3

आप स्थानीय एक्स्ट्रेमा बनाने के लिए पंडों की रोलिंग कार्यक्षमता का उपयोग कर सकते हैं। यह आपके 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)

मुझे यह भी यकीन नहीं है कि अगर अंतिम ज़िगज़ैग हमेशा एक मूल उच्च से निम्न या इसके विपरीत चलना चाहिए। मैंने यह मान लिया, अन्यथा आपको फ़ंक्शन के अंत में चरम की दूसरी खोज निकालनी चाहिए।

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)), जैसा कि मैं समझता हूं, आप p%अंतिम बिंदु के साथ दो बिंदुओं के बीच की दूरी की तुलना कर रहे हैं, क्या मैं सही हूं? क्योंकि मैं पिछले खंड के साथ प्रत्येक ज़िगज़ैग खंड की तुलना करना चाहता हूं, और जब तक स्थिति संतुष्ट नहीं हो जाती तब तक दोहराएं।
थान गुयेन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.