简体   繁体   中英

Finding similar sub-sequences in a time series?

I have thousands of time series (24 dimensional data -- 1 dimension for each hour of the day). Out of these time series, I'm interested in a particular sub-sequence or pattern that looks like this:

I'm interested in sub-sequences that resemble the overall shape of the highlighted section -- that is, a sub-sequence with a sharp negative slope, followed by a period of several hours where the slope is relatively flat before finally ending with a sharp positive slope. I know the sub-sequences I'm interested in won't match each other exactly and most likely will be shifted in time, scaled differently, have longer/shorter periods where the slope is relatively flat, etc. but I would like to find a way to detect them all.

To do this, I have developed a simple Heuristic (based on my definition of the highlighted section) to quickly find some of the sub-sequences of interest. However, I was wondering if there was a more elegant way (in Python) to search thousands of time series for the sub-sequence I'm interested in (while taking into account things mentioned above -- differences in time, scale, etc.)?

Edit : a year later I cannot believe how much I overcomplicated flatline and slope detection; stumbling on the same question, I realized it's as simple as

idxs = np.where(x[1:] - x[:-1] == 0)
idxs = [i for idx in idxs for i in (idx, idx + 1)]

First line is implemented efficiently via np.diff(x) ; further, to eg detect slope > 5, use np.diff(x) > 5 . The second line is since differencing tosses out right endpoints (eg diff([5,6,6,6,7]) = [1,0,0,1] -> idxs=[1,2] , excludes 3, .


Functions below should do; code written with intuitive variable & method names, and should be self-explanatory with some readovers. The code is efficient and scalable.


Functionalities :

  • Specify min & max flatline length
  • Specify min & max slopes for left & right tails
  • Specify min & max average slopes for left & right tails, over multiple intervals

Example :

import numpy as np
import matplotlib.pyplot as plt

# Toy data
t = np.array([[ 5,  3,  3,  5,  3,  3,  3,  3,  3,  5,  5,  3,  3,  0,  4,  
                1,  1, -1, -1,  1,  1,  1,  1, -1,  1,  1, -1,  0,  3,  3,  
                5,  5,  3,  3,  3,  3,  3,  5,  7,  3,  3,  5]]).T
plt.plot(t)
plt.show()

# Get flatline indices
indices = get_flatline_indices(t, min_len=4, max_len=5)
plt.plot(t)
for idx in indices:
    plt.plot(idx, t[idx], marker='o', color='r')
plt.show()

# Filter by edge slopes
lims_left  = (-10, -2)
lims_right = (2,  10)
averaging_intervals = [1, 2, 3]
indices_filtered = filter_by_tail_slopes(indices, t, lims_left, lims_right,
                                         averaging_intervals)
plt.plot(t)
for idx in indices_filtered:
    plt.plot(idx, t[idx], marker='o', color='r')
plt.show()

def get_flatline_indices(sequence, min_len=2, max_len=6):
    indices=[]
    elem_idx = 0
    max_elem_idx = len(sequence) - min_len
        
    while elem_idx < max_elem_idx:
        current_elem = sequence[elem_idx]
        next_elem    = sequence[elem_idx+1]
        flatline_len = 0

        if current_elem == next_elem:
            while current_elem == next_elem:
                flatline_len += 1
                next_elem = sequence[elem_idx + flatline_len]
                
            if flatline_len >= min_len:
                if flatline_len > max_len:
                    flatline_len = max_len
    
                trim_start = elem_idx
                trim_end   = trim_start + flatline_len
                indices_to_append = [index for index in range(trim_start, trim_end)]
                indices += indices_to_append

            elem_idx += flatline_len
            flatline_len = 0
        else:
            elem_idx += 1
    return indices if not all([(entry == []) for entry in indices]) else []
def filter_by_tail_slopes(indices, data, lims_left, lims_right, averaging_intervals=1):
    indices_filtered = []
    indices_temp, tails_temp = [], []
    got_left, got_right = False, False
    
    for idx in indices:
        slopes_left, slopes_right = _get_slopes(data, idx, averaging_intervals)
        
        for tail_left, slope_left in enumerate(slopes_left):
            if _valid_slope(slope_left, lims_left):
                if got_left:
                    indices_temp = []  # discard prev if twice in a row
                    tails_temp = []
                indices_temp.append(idx)
                tails_temp.append(tail_left + 1)
                got_left = True
        if got_left:
            for edge_right, slope_right in enumerate(slopes_right):
                if _valid_slope(slope_right, lims_right):
                    if got_right:
                        indices_temp.pop(-1)
                        tails_temp.pop(-1)
                    indices_temp.append(idx)
                    tails_temp.append(edge_right + 1)
                    got_right = True

        if got_left and got_right:
            left_append  = indices_temp[0] - tails_temp[0]
            right_append = indices_temp[1] + tails_temp[1]
            indices_filtered.append(_fill_range(left_append, right_append))
            indices_temp = []
            tails_temp = []
            got_left, got_right = False, False
    return indices_filtered
def _get_slopes(data, idx, averaging_intervals):
    if type(averaging_intervals) == int:
        averaging_intervals = [averaging_intervals]

    slopes_left, slopes_right = [], []
    for interval in averaging_intervals:
        slopes_left  += [(data[idx] - data[idx-interval]) / interval]
        slopes_right += [(data[idx+interval] - data[idx]) / interval]
    return slopes_left, slopes_right

def _valid_slope(slope, lims):
    min_slope, max_slope = lims
    return (slope  >= min_slope) and (slope <= max_slope)

def _fill_range(_min, _max):
    return [i for i in range(_min, _max + 1)]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM