比较两个时间序列(模拟结果)

时间:2017-09-20 07:58:59

标签: python numpy matplotlib time-series timeserieschart

我想对模拟模型进行单元测试,为此,我运行一次模拟并将结果(时间序列)存储在csv文件中作为参考(参见示例here)。现在,当我更改模型时,我再次运行模拟,将新结果存储为csv文件,然后我比较结果。

结果通常不是100%相同,示例图如下所示:
参考结果以黑色绘制,新结果以绿色绘制。
两者的差异绘制在第二个图中,蓝色 可以看出,在一个步骤中差异可以变得任意高,而在其他任何地方,差异几乎为零。

因此,我宁愿使用不同的算法进行比较而不仅仅是减去两者,但我只能用图形方式描述我的想法: 当绘制参考线两次时,首先是浅色和高线宽,然后再次是深色和小线宽,那么看起来它在中心线周围有一个粉红色的管

请注意,在一个步骤中,管不仅在纵轴的方向上,而且在横坐标的方向上。 在进行比较时,我想知道绿线是否保持在粉红色管内。 enter image description here

现在我的问题是:我不想使用图表来比较两个时间序列,而是使用python脚本。必须有这样的东西,但我找不到它,因为我错过了正确的词汇,我相信。有任何想法吗?在numpy,scipy或类似的东西中是这样的吗?或者我自己要写比较吗?

附加问题:当脚本说两个系列不够相似时,我想按照上面描述的那样绘制它(使用matplotlib),但是线宽必须以某种方式在其他单位中定义,而不是我通常使用的单位。定义线宽。

4 个答案:

答案 0 :(得分:1)

我在这里假设您的问题可以通过假设您的函数必须接近另一个具有相同支撑点的函数(例如管的中心)来简化,然后允许一定数量的不连续性。 然后,与用于L^2范数的典型函数相比,我将实现不同的函数离散化(参见例如一些参考here)。

基本上,在连续的情况下,L^2范数放宽了两个函数在任何地方都很接近的约束,并允许它在有限数量的点上有所不同,称为奇点 这是有效的,因为有无数个点来计算积分,而有限数量的点在那里不会产生差异。

然而,由于这里没有连续的函数,只有它们的离散化,天真的方法是行不通的,因为任何奇点都可能对最终积分值产生重大影响。

因此,您可以做的是逐点检查两个函数是否接近(在一定容差范围内)并且最多允许num_exceptions个点关闭。

import numpy as np

def is_close_except(arr1, arr2, num_exceptions=0.01, **kwargs):
    # if float, calculate as percentage of number of points
    if isinstance(num_exceptions, float):
        num_exceptions = int(len(arr1) * num_exceptions)
    num = len(arr1) - np.sum(np.isclose(arr1, arr2, **kwargs))
    return num <= num_exceptions

相比之下,标准L^2范数离散化将导致类似于此集成(和标准化)指标的内容:

import numpy as np

def is_close_l2(arr1, arr2, **kwargs):
    norm1 = np.sum(arr1 ** 2)
    norm2 = np.sum(arr2 ** 2)
    norm = np.sum((arr1 - arr2) ** 2)
    return np.isclose(2 * norm / (norm1 + norm2), 0.0, **kwargs)

然而,对于任意大的峰值,这将失败,除非你设置了这么大的容差,基本上任何结果都是“接近”。

请注意,如果您要为kwargs或其他选项指定其他容差限制,则会使用np.isclose()

作为测试,您可以运行:

import numpy as np
import numpy.random

np.random.seed(0)

num = 1000
snr = 100
n_peaks = 5
x = np.linspace(-10, 10, num)
# generate ground truth
y = np.sin(x)
# distributed noise
y2 = y + np.random.random(num) / snr
# distributed noise + peaks
y3 = y + np.random.random(num) / snr
peak_positions = [np.random.randint(num) for _ in range(n_peaks)]
for i in peak_positions:
    y3[i] += np.random.random() * snr

# for distributed noise, both work with a 1/snr tolerance
is_close_l2(y, y2, atol=1/snr)
# output: True
is_close_except(y, y2, atol=1/snr)
# output: True

# for peak noise, since n_peaks < num_exceptions, this works
is_close_except(y, y3, atol=1/snr)
# output: True
# and if you allow 0 exceptions, than it fails, as expected
is_close_except(y, y3, num_exceptions=0, atol=1/snr)
# output: False

# for peak noise, this fails because the contribution from the peaks
# in the integral is much larger than the contribution from the rest
is_close_l2(y, y3, atol=1/snr)
# output: False

还有其他方法可以解决这个涉及高等数学的问题(例如傅里叶变换或小波变换),但我会坚持最简单的方法。

编辑(已更新):

但是,如果工作假设不成立或者您不喜欢,例如因为两个函数具有不同的采样或者它们是由非内射关系描述的。 在这种情况下,您可以使用(x,y)数据跟踪管的中心,并计算距离目标(管中心)的欧几里德距离,并检查该距离是否小于允许的最大距离(管尺寸):

import numpy as np

# assume it is something with shape (N, 2) meaning (x, y)
target = ...

# assume it is something with shape (M, 2) meaning again (x, y)
trajectory = ...

# calculate the distance minimum distance between each point
# of the trajectory and the target
def is_close_trajectory(trajectory, target, max_dist):
    dist = np.zeros(trajectory.shape[0])
    for i in range(len(dist)):
        dist[i] = np.min(np.sqrt(
            (target[:, 0] - trajectory[i, 0]) ** 2 +
            (target[:, 1] - trajectory[i, 1]) ** 2))
    return np.all(dist < max_dist)

# same as above but faster and more memory-hungry
def is_close_trajectory2(trajectory, target, max_dist):
    dist = np.min(np.sqrt(
        (target[:, np.newaxis, 0] - trajectory[np.newaxis, :, 0]) ** 2 +
        (target[:, np.newaxis, 1] - trajectory[np.newaxis, :, 1]) ** 2),
        axis=1)
    return np.all(dist < max_dist)

这种灵活性的代价是,这将是一个明显变慢或内存饥渴的功能。

答案 1 :(得分:1)

游戏有点晚了,但我最近遇到了同样的难题,这似乎是网站上讨论这个特定问题的唯一问题。

一个基本的解决方案是使用时间和幅度容差值在数据周围创建一个“边界框”样式的包络(类似于您的粉红色管)。

我确信有更优雅的方法可以做到这一点,但是一个非常粗略编码的蛮力示例将类似于以下使用 Pandas 的内容:

import pandas as pd

data = pd.DataFrame()
data['benchmark'] = [0.1, 0.2, 0.3] # or whatever you pull from your expected value data set
data['under_test'] = [0.2, 0.3, 0.1] # or whatever you pull from your simulation results data set

sample_rate = 20 # or whatever the data sample rate is
st = 0.05 * sample_rate # shift tolerance adjusted to time series sample rate
                        # best to make it an integer so we can use standard
                        # series shift functions and whatnot

at = 0.05 # amplitude tolerance

bounding = pd.DataFrame()
# if we didn't care about time shifts, the following two would be sufficient
# (i.e. if the data didn't have severe discontinuities between samples)
bounding['top'] = data[['benchmark']] + at
bounding['bottom'] = data[['benchmark']] - at

# if you want to be able to tolerate large discontinuities
# the bounds can be widened along the time axis to accommodate for large jumps
bounding['bottomleft'] = data[['benchmark']].shift(-st) - at
bounding['topleft'] = data[['benchmark']].shift(-st) + at
bounding['topright'] = data[['benchmark']].shift(st) + at
bounding['bottomright'] = data[['benchmark']].shift(st) - at

# minimums and maximums give us a rough (but hopefully good enough) envelope
# these can be plotted as a parametric replacement of the 'pink tube' of line width
data['min'] = bounding.min(1)
data['max'] = bounding.max(1)

# see if the test data falls inside the envelope
data['pass/fail'] = data['under_test'].between(data['min'], data['max'])

# You now have a machine-readable column of booleans 
# indicating which data points are outside the envelope

答案 2 :(得分:0)

假设您已在我们已加载的评论中讨论过的表单中列出了结果列表:

from random import randint
import numpy
l1 = [(i,randint(0,99)) for i in range(10)]
l2 = [(i,randint(0,99)) for i in range(10)]
# I generate some random lists e.g: 
# [(0, 46), (1, 33), (2, 85), (3, 63), (4, 63), (5, 76), (6, 85), (7, 83), (8, 25), (9, 72)]
# where the first element is the time and the second a value
print(l1)
# Then I just evaluate for each time step the difference between the values
differences = [abs(x[0][1]-x[1][1]) for x in zip(l1,l2)]
print(differences)
# And I can just print hte maximum difference and its index:
print(max(differences))
print(differences.index(max(differences)))

如果您定义了&#34; tube&#34;例如10大你可以检查你找到的maxximum值是否大于你的thrashold以确定这些函数是否足够相似

答案 3 :(得分:0)

如果您需要跳过随机峰值,则必须首先从数据集中删除异常值。

您还可以尝试以下方法?

    from tslearn.metrics import dtw
    print(dtw(arr1,arr2)*100/<lengthOfArray>)