Gấu trúc: Phân đoạn dữ liệu ngoằn ngoèo dựa trên cực tiểu địa phương


10

Tôi có một dữ liệu thời gian. Tạo dữ liệu

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

Tôi muốn tạo một đường zig-zag kết nối giữa cực đại cục bộ và cực tiểu cục bộ, thỏa mãn điều kiện là trên trục y, |highest - lowest value|của mỗi đường zig-zag phải vượt quá một tỷ lệ phần trăm (khoảng 20%) khoảng cách của trước đó đường zig-zag, VÀ một giá trị được nêu trước k (giả sử 1.2)

Tôi có thể tìm thấy extrema cục bộ bằng cách sử dụng mã này:

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

nhưng tôi không biết làm thế nào để áp dụng điều kiện ngưỡng cho nó. Xin tư vấn cho tôi về cách áp dụng điều kiện như vậy.

Vì dữ liệu có thể chứa hàng triệu dấu thời gian, nên tính toán hiệu quả rất được khuyến khích

Để mô tả rõ ràng hơn: nhập mô tả hình ảnh ở đây

Ví dụ đầu ra, từ dữ liệu của tôi:

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

nhập mô tả hình ảnh ở đây

Đầu ra mong muốn của tôi (một cái gì đó tương tự như thế này, ngoằn ngoèo chỉ kết nối các phân đoạn quan trọng) nhập mô tả hình ảnh ở đây

Câu trả lời:


3

Tôi đã trả lời cho sự hiểu biết tốt nhất của tôi về câu hỏi. Tuy nhiên, không rõ ràng về cách biến K ảnh hưởng đến bộ lọc.

Bạn muốn lọc extrema dựa trên một điều kiện chạy. Tôi giả sử rằng bạn muốn đánh dấu tất cả các điểm cực trị có khoảng cách tương đối đến điểm cực trị được đánh dấu cuối cùng lớn hơn p%. Tôi cũng giả định rằng bạn luôn coi yếu tố đầu tiên của thời gian là điểm hợp lệ / có liên quan.

Tôi đã thực hiện điều này với chức năng lọc sau:

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

Để chạy mã của bạn, trước tiên tôi nhập phụ thuộc:

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

Để làm cho mã có thể lặp lại, tôi sửa hạt giống ngẫu nhiên:

np.random.seed(0)

Phần còn lại từ đây là copypasta. Lưu ý rằng tôi đã giảm số lượng mẫu để làm cho kết quả rõ ràng.

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

Sau đó, chúng tôi sử dụng chức năng lọc:

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

Và cốt truyện như bạn đã làm cả cốt truyện trước đó cũng như cực đoan mới được lọc:

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

nhập mô tả hình ảnh ở đây

CHỈNH SỬA :

Nếu muốn cả hai coi điểm đầu tiên cũng như điểm cuối là hợp lệ, thì bạn có thể điều chỉnh chức năng lọc như sau:

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

chào, cảm ơn vì câu trả lời tuyệt vời Có, giả định của bạn là đúng "đánh dấu tất cả các điểm cực trị có khoảng cách tương đối đến điểm cực trị được đánh dấu cuối cùng lớn hơn p%." Và luôn luôn phải xem xét cả điểm đầu tiên và điểm cuối cùng. Tôi đã kiểm tra câu trả lời của bạn, đôi khi nó bị mất điểm cuối cùng, bạn có thể giúp tôi về điều đó không?
Thanh Nguyễn

3

Bạn có thể sử dụng chức năng lăn Pandas để tạo ra extrema cục bộ. Điều đó đơn giản hóa mã một chút so với cách tiếp cận Scipy của bạn.

Chức năng tìm cực trị:

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)

Hàm tạo zigzag, nó có thể được áp dụng trên Dataframe cùng một lúc (trên mỗi cột), nhưng điều này sẽ giới thiệu NaN vì các dấu thời gian được trả về sẽ khác nhau cho mỗi cột. Bạn có thể dễ dàng loại bỏ chúng sau này như trong ví dụ bên dưới hoặc chỉ cần áp dụng hàm trên một cột trong Dataframe của bạn.

Lưu ý rằng tôi đã bỏ qua bài kiểm tra theo ngưỡng k, tôi không chắc liệu có hiểu đúng về phần đó không. Bạn có thể bao gồm nó nếu sự khác biệt tuyệt đối giữa nhu cầu cực đoan trước đây và hiện tại cần lớn hơn k:& (ext_val.diff().abs() > k)

Tôi cũng không chắc là zigzag cuối cùng sẽ luôn luôn chuyển từ mức cao ban đầu xuống mức thấp hay ngược lại. Tôi giả sử nó nên, nếu không bạn có thể loại bỏ tìm kiếm thứ hai cho cực trị ở cuối hàm.

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

Tạo một số dữ liệu mẫu:

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

Áp dụng hàm và trích xuất kết quả cho cột 'data1':

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

Hình dung kết quả:

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

nhập mô tả hình ảnh ở đây


cảm ơn câu trả lời của bạn. Tôi muốn hỏi về dòng này (ext_val.diff().abs() > (ext_val.shift(-1).abs() * p)), vì tôi hiểu, bạn đang so sánh khoảng cách giữa hai điểm với p%điểm cuối cùng, phải không? Bởi vì tôi muốn so sánh từng phân đoạn ngoằn ngoèo với phân khúc trước đó và lặp lại cho đến khi điều kiện được thỏa mãn.
Thanh Nguyễn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.