Python 2.7中用于在每个数据点之前复制上一个值的函数,分段常数插值。

3

我正在使用Python 2.7,有时间戳和相应的值。我想将这些值设置为每秒钟一个值的时间基准,即最后一次测量的值。所以:

[[1,  4,  6],
 [15, 17, 12]]

to:

[[1,  2,  3,  4,  4,  6],
 [15, 15, 15, 17, 17, 12]]

我想到了这个方法,它可以实现我想要的功能,但一定有更加优雅的方式。有人知道吗?
import numpy as np

#Example data:
origdata= {}
origdata['time'] = [4, 26, 37, 51, 59, 71, 93]
origdata['vals'] = [17, 5, 43, 21, 14, 8, np.NaN]

extratime = [t-1 for t in origdata['time']]
data={}
data['time'] = np.concatenate((origdata['time'][:-1], extratime[1:]), axis=0)
data['vals'] = np.concatenate((origdata['vals'][:-1], origdata['vals'][:-1]), axis=0)

sorter = data['time'].argsort()
data['time'] = data['time'][sorter]
data['vals'] = data['vals'][sorter]

filledOutData = {}
filledOutData['time'] = range(data['time'][0], data['time'][-1])
filledOutData['vals'] = np.interp(filledOutData['time'], data['time'], data['vals'])

使用以下代码将原始数据和期望结果绘制出来可得到下面的图像:

import matplotlib.pyplot as plt
plt.plot(origdata['time'], origdata['vals'], '-o', filledOutData['time'], filledOutData['vals'], '.-')
plt.legend(['original', 'desired result'])
plt.show

An illustration of what I want


我想知道是否有一个截断选项可以自动执行此操作,我会研究一下。 - Tadhg McDonald-Jensen
1
可能已经找到了,scipy.interpolate.interp1dkind='zero' 看起来很有前途。 - Swier
5个回答

2

试一试这个:

data = {}
times = [4, 26, 37, 51, 59, 71, 93]
vals = [17, 5, 43, 21, 14, 8, float('nan')]
# i don't have numpy so i had to change to nan

for i in range(times[0], times[-1]+1):
    if i in times:
        v = vals[times.index(i)]
    data.setdefault('time', []).append(i)
    data.setdefault('vals', []).append(v)

print data['time']

[4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93]

这是一个包含数字的数组。
print data['vals']

[17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 21, 21, 21, 21, 21, 21, 21, 21, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 未定义的数字]


这种方法非常低效,而且并没有以更优雅的方式完成原始问题所要求的任务。 - Tadhg McDonald-Jensen
在您的看法中,但它更短,不需要排序,所以我担心您夸大了它的低效性。 - gkusner
哦,对不起,我误解了原始代码的工作方式,但是你真的不需要在for循环中使用setdefault,因为你可以直接用实际的列表初始化数据。 - Tadhg McDonald-Jensen
true - 但据我理解,setdefault并不会增加太多开销,并且它允许您不添加初始键 - 但整个事情都是无意义的,因为scipy的答案可能更好:} - gkusner
实际运行setdefault并没有增加太多,但获取dict上的setdefault方法,创建大量新列表,因为它们从未被使用而删除这些列表以及获取append方法都增加了很多时间,比如我的测试中约为2.5倍,该测试保留了两个列表的.append引用。(使用timeit) - Tadhg McDonald-Jensen
虽然我建议的解决方案比那个快4倍,但我完全收回我所说这不够优雅的话,这是一个很好的方法。(除了 setdefault - Tadhg McDonald-Jensen

1
原来这被称为分段常数插值,可以使用scipy包完成:
import scipy as sp

interpolator = sp.interpolate.interp1d(origdata['time'], origdata['vals'], kind='zero')

filledOutData2 = {}
filledOutData2['time'] = range(origdata['time'][0],origdata['time'][-1])
filledOutData2['vals'] = interpolator(filledOutData2['time'])

编辑:

或者作为一个函数:

def interp_piecewise_constant(times,values):
  interpolator = sp.interpolate.interp1d(times, values, kind='zero')
  newTimes = range(times[0], times[-1])
  return(newTimes, interpolator(newTimes))

1
那是一个很好的答案 - 我不使用numpy或scipy,所以不知道它,但对于这个问题来说,这是一个非常明智的做法;} - gkusner
你不是只是在使用几分钟前提问者所做的评论吗? - Tadhg McDonald-Jensen
是的,一旦我终于找到了它,我就很容易实现了,这样我就可以回答自己的问题了。 - Swier
哈哈,我有点困惑是谁在提问/回答,以为这是别人在评论中窃取你建议的解决方案。 - Tadhg McDonald-Jensen

0
我会使用生成器来创建中间数据值:
def piecewise(x_vals,y_vals,offset=0.1):
    iter_x = iter(x_vals)
    iter_y = iter(y_vals) #use iterators
    y = next(iter_y)#get first y value
    yield next(iter_x),y #first pair
    while True:
        x = next(iter_x)
        yield x-offset, y #gives the x value - offset with previous y value
        y = next(iter_y)
        yield x,y        #actual data points

基本上对于[x1,x2,x3 ...]和[y1,y2,y3 ...]的列表,这将生成输出:
(x1,y1), (x2-1,y1), (x2,y2), (x3-1,y2), (x3,y3)...

因此,它只创建原始数据点的两倍数量,而不像其他解决方案一样创建所有可能的整数点之间的点。 (这也可以使用小数值很好地工作)

这样,您就可以像这样创建填充数据:

filledOutData= {'time':[],'vals':[]}
for x,y in piecewise(origdata['time'],origdata['vals']):
    filledOutData['time'].append(x)
    filledOutData['vals'].append(y)

或者使用zip和同时赋值来缩短代码:

filledOutData= {}
filledOutData['time'],filledOutData['vals'] = zip(*piecewise(origdata['time'],origdata['vals']))

diagram of output

请注意,这仅在原始时间列表正确排序的情况下才有效。

0

生成器怎么样?

>>> def fill(times, vals):
...     lt, lv = times[0], vals[0]
...     for nt, nv in zip(times[1:], vals[1:]):
...         while lt < nt:
...             yield lt, lv
...             lt += 1
...         lt, lv = nt, nv
...     yield nt, nv
...
>>> times = [4, 26, 37, 51, 59, 71, 93]
... vals = [17, 5, 43, 21, 14, 8, float('nan')]
...
>>> list(fill(times, vals))
[(4, 17),
(5, 17),
(6, 17),
...
(91, 8),
(92, 8),
(93, nan)]

如果您需要分开的列表,可以按照以下方式解压生成的元组:
>>> from itertools import tee
>>> filled_times, filled_vals = [list((t[idx] for t in it)) 
...                              for idx, it in enumerate(tee(fill(times, vals)))]

0

这应该很及时,而且有一个相当简单的逻辑结构。

def interpolate(data):
    times, values = data
    output = []

    time, end, index = times[0], times[-1], 0
    while time <= end:
        if time == times[index + 1]:
            index += 1
        output.append((time, values[index]))
        time += 1

    return zip(*output)

print interpolate([[1, 4, 6], [15, 17, 12]])
# [(1, 2, 3, 4, 5, 6), (15, 15, 15, 17, 17, 12)]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接