팬더 : 로컬 최소값을 기준으로 데이터의 지그재그 분할


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|각 지그재그 선의 거리의 백분율 (예 : 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'])

그러나 임계 조건을 적용하는 방법을 모르겠습니다. 그러한 조건을 적용하는 방법에 대해 알려주십시오.

데이터에는 백만 개의 타임 스탬프가 포함될 수 있으므로 효율적인 계산이 권장됩니다.

보다 명확한 설명을 위해 : 여기에 이미지 설명을 입력하십시오

내 데이터의 출력 예 :

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

그런 다음 필터 기능을 사용합니다.

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이 나타납니다. 아래 예제와 같이 나중에 쉽게 드롭하거나 데이터 프레임의 단일 열에 함수를 적용 할 수 있습니다.

threshold에 대해 테스트의 주석을 제거했습니다 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%마지막 점 과 비교하고 있습니다. 맞습니까? 각 지그재그 세그먼트를 이전 세그먼트와 비교하고 조건이 충족 될 때까지 반복하고 싶습니다.
Thanh Nguyen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.