高效的numpy零阶保持
有没有一种高效的方法可以使用零阶保持(zero-order hold)来重新采样一个numpy数组?理想情况下,这个方法的使用方式应该和numpy.interp类似。
我知道有scipy.interpolate.interp1d这个工具,但我相信一定还有其他更简便的方法可以处理这种情况。
4 个回答
0
虽然来得有点晚,但这是我想到的:
from numpy import zeros, array, sign
def signal_zoh(x,y,epsilon = 0.001):
"""
Fills in the data from a Zero-Order Hold (stair-step) signal
"""
deltaX = array(x[1:],dtype='float') - x[:-1]
fudge = min(deltaX) *epsilon
retX = zeros((len(x)*2-1,))
retY = zeros((len(y)*2-1,))
retX[0::2] = x
retX[1::2] = x[1:]+fudge*sign(deltaX)
retY[0::2] = y
retY[1::2] = y[:-1]
return retX,retY
2
Numpy数组现在不再支持非整数索引,所以之前的解决方案已经不再有效。虽然scipy.interpolate.interp1d很可靠,但在很多情况下速度并不是最快的。这里有一个可靠的代码解决方案,它在可以的情况下使用np.searchsorted,当不行时再退回到scipy的版本。
import numpy as np
from scipy.interpolate import interp1d
def zero_order_hold(x, xp, yp, left=np.nan, assume_sorted=False):
r"""
Interpolates a function by holding at the most recent value.
Parameters
----------
x : array_like
The x-coordinates at which to evaluate the interpolated values.
xp: 1-D sequence of floats
The x-coordinates of the data points, must be increasing if argument period is not specified. Otherwise, xp is internally sorted after normalizing the periodic boundaries with xp = xp % period.
yp: 1-D sequence of float or complex
The y-coordinates of the data points, same length as xp.
left: int or float, optional, default is np.nan
Value to use for any value less that all points in xp
assume_sorted : bool, optional, default is False
Whether you can assume the data is sorted and do simpler (i.e. faster) calculations
Returns
-------
y : float or complex (corresponding to fp) or ndarray
The interpolated values, same shape as x.
Notes
-----
#. Written by DStauffman in July 2020.
Examples
--------
>>> import numpy as np
>>> xp = np.array([0., 111., 2000., 5000.])
>>> yp = np.array([0, 1, -2, 3])
>>> x = np.arange(0, 6001, dtype=float)
>>> y = zero_order_hold(x, xp, yp)
"""
# force arrays
x = np.asanyarray(x)
xp = np.asanyarray(xp)
yp = np.asanyarray(yp)
# find the minimum value, as anything left of this is considered extrapolated
xmin = xp[0] if assume_sorted else np.min(xp)
# check that xp data is sorted, if not, use slower scipy version
if assume_sorted or np.all(xp[:-1] <= xp[1:]):
ix = np.searchsorted(xp, x, side='right') - 1
return np.where(np.asanyarray(x) < xmin, left, yp[ix])
func = interp1d(xp, yp, kind='zero', fill_value='extrapolate', assume_sorted=False)
return np.where(np.asanyarray(x) < xmin, left, func(x))
这个代码通过了一些测试案例:
import unittest
import numpy as np
from scipy.interpolate import interp1d
class Test_zero_order_hold(unittest.TestCase):
r"""
Tests the zero_order_hold function with the following cases:
Subsample high rate
Supersample low rate
xp Not sorted
x not sorted
Left extrapolation
Lists instead of arrays
Notes
-----
#. Uses scipy.interpolate.interp1d as the gold standard (but it's slower)
"""
def test_subsample(self):
xp = np.linspace(0., 100*np.pi, 500000)
yp = np.sin(2 * np.pi * xp)
x = np.arange(0., 350., 0.1)
func = interp1d(xp, yp, kind='zero', fill_value='extrapolate', assume_sorted=True)
y_exp = func(x)
y = zero_order_hold(x, xp, yp)
np.testing.assert_array_equal(y, y_exp)
y = zero_order_hold(x, xp, yp, assume_sorted=True)
np.testing.assert_array_equal(y, y_exp)
def test_supersample(self):
xp = np.array([0., 5000., 10000., 86400.])
yp = np.array([0, 1, -2, 0])
x = np.arange(0., 86400.,)
func = interp1d(xp, yp, kind='zero', fill_value='extrapolate', assume_sorted=True)
y_exp = func(x)
y = zero_order_hold(x, xp, yp)
np.testing.assert_array_equal(y, y_exp)
y = zero_order_hold(x, xp, yp, assume_sorted=True)
np.testing.assert_array_equal(y, y_exp)
def test_xp_not_sorted(self):
xp = np.array([0, 10, 5, 15])
yp = np.array([0, 1, -2, 3])
x = np.array([10, 2, 14, 6, 8, 10, 4, 14, 0, 16])
y_exp = np.array([ 1, 0, 1, -2, -2, 1, 0, 1, 0, 3])
y = zero_order_hold(x, xp, yp)
np.testing.assert_array_equal(y, y_exp)
def test_x_not_sorted(self):
xp = np.array([0, 5, 10, 15])
yp = np.array([0, -2, 1, 3])
x = np.array([10, 2, 14, 6, 8, 10, 4, 14, 0, 16])
y_exp = np.array([ 1, 0, 1, -2, -2, 1, 0, 1, 0, 3])
y = zero_order_hold(x, xp, yp)
np.testing.assert_array_equal(y, y_exp)
def test_left_end(self):
xp = np.array([0, 5, 10, 15, 4])
yp = np.array([0, 1, -2, 3, 0])
x = np.array([-4, -2, 0, 2, 4, 6])
y_exp = np.array([-5, -5, 0, 0, 0, 1])
y = zero_order_hold(x, xp, yp, left=-5)
np.testing.assert_array_equal(y, y_exp)
def test_lists(self):
xp = [0, 5, 10, 15]
yp = [0, 1, 2, 3]
x = [-4, -2, 0, 2, 4, 6, 20]
y_exp = [-1, -1, 0, 0, 0, 1, 3]
y = zero_order_hold(x, xp, yp, left=-1)
np.testing.assert_array_equal(y, y_exp)
在前两个测试中的速度测试显示,使用numpy的解决方案在对高频数据进行子采样时大约快100倍,而在对低频数据进行超采样时大约快3倍。
4
Numpy < 1.12
因为你不会插入任何新的值,所以最简单有效的方法就是保持原来的数组不变,直接用浮点数来索引它。这实际上就是一种零阶保持。
>>> import numpy as np
>>> A = np.array(range(10))
>>> [A[i] for i in np.linspace(0, 9, num=25)]
[0, 0, 0, 1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 9]
Numpy >= 1.12
在numpy的1.11版本中,使用浮点数索引的功能被弃用了,并在1.12版本中被完全移除了(2017年1月)。所以在现在的numpy版本中,上面的代码会引发一个IndexError
错误。
如果你想在新的numpy版本中实现旧版本的浮点数索引行为,可以使用一个包装器来访问数组,这样可以在运行时将浮点索引转换为整数。当内存使用效率很重要时,这样做可以避免提前插入中间值,使用numpy.interp
或scipy.interpolate.interp1d
。