在numpy数组中移动元素


83

几年前从这个问题开始,在numpy中是否有规范的“移位”功能?我从文档中看不到任何东西。

这是我正在寻找的简单版本:

def shift(xs, n):
    if n >= 0:
        return np.r_[np.full(n, np.nan), xs[:-n]]
    else:
        return np.r_[xs[-n:], np.full(-n, np.nan)]

使用它就像:

In [76]: xs
Out[76]: array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.])

In [77]: shift(xs, 3)
Out[77]: array([ nan,  nan,  nan,   0.,   1.,   2.,   3.,   4.,   5.,   6.])

In [78]: shift(xs, -3)
Out[78]: array([  3.,   4.,   5.,   6.,   7.,   8.,   9.,  nan,  nan,  nan])

这个问题来自于我昨天尝试编写快速滚动产品的尝试。我需要一种“转移”累积乘积的方法,我所能想到的就是在中复制逻辑np.roll()


因此np.concatenate()比快得多np.r_[]。此版本的功能执行得更好:

def shift(xs, n):
    if n >= 0:
        return np.concatenate((np.full(n, np.nan), xs[:-n]))
    else:
        return np.concatenate((xs[-n:], np.full(-n, np.nan)))

更快的版本只是简单地预分配了数组:

def shift(xs, n):
    e = np.empty_like(xs)
    if n >= 0:
        e[:n] = np.nan
        e[n:] = xs[:-n]
    else:
        e[n:] = np.nan
        e[:n] = xs[-n:]
    return e

想知道是否np.r_[np.full(n, np.nan), xs[:-n]]可以用np.r_[[np.nan]*n, xs[:-n]]同样的方式代替其他条件,而无需np.full
零时

2
@JohnGalt[np.nan]*n是纯Python,因此将比慢np.full(n, np.nan)。不适合small n,但是它将被np.r_转换为numpy数组,从而失去了优势。
swenzel

@swenzel刚刚定时,并且[np.nan]*nnp.full(n, np.nan)的速度更快n=[10,1000,10000]。需要检查是否np.r_受到打击。
2015年

如果需要考虑速度,则阵列大小对于最佳算法起着巨大作用(在下面添加了基准比较)。同样,如今,如果反复调用numba.njit,它可以使转换更快。
np8

Answers:


99

不是numpy,而是scipy提供了您想要的转换功能,

import numpy as np
from scipy.ndimage.interpolation import shift

xs = np.array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.])

shift(xs, 3, cval=np.NaN)

默认情况是从数组外部引入一个带有值的常量值cval,此处设置为nan。这样可以提供所需的输出,

array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])

负移的作用相似

shift(xs, -3, cval=np.NaN)

提供输出

array([  3.,   4.,   5.,   6.,   7.,   8.,   9.,  nan,  nan,  nan])

23
Scipy移位功能真的很慢。我使用np.concatenate滚动了自己的文件,速度更快。
凡2015年

12
numpy.roll更快。熊猫也使用它。github.com/pandas-dev/pandas/blob/v0.19.2/pandas/core/...
FX-麒麟

刚刚针对此页面上列出的所有其他替代方案测试了scipy.ndimage.interpolation.shift(scipy 1.4.1)(请参阅下面的答案),这是最慢的解决方案。仅当速度对您的应用程序不重要时才使用。
np8

70

对于那些只想复制和粘贴shift最快实现的人,有一个基准和结论(请参阅最后)。另外,我介绍了fill_value参数并修复了一些错误。

基准测试

import numpy as np
import timeit

# enhanced from IronManMark20 version
def shift1(arr, num, fill_value=np.nan):
    arr = np.roll(arr,num)
    if num < 0:
        arr[num:] = fill_value
    elif num > 0:
        arr[:num] = fill_value
    return arr

# use np.roll and np.put by IronManMark20
def shift2(arr,num):
    arr=np.roll(arr,num)
    if num<0:
         np.put(arr,range(len(arr)+num,len(arr)),np.nan)
    elif num > 0:
         np.put(arr,range(num),np.nan)
    return arr

# use np.pad and slice by me.
def shift3(arr, num, fill_value=np.nan):
    l = len(arr)
    if num < 0:
        arr = np.pad(arr, (0, abs(num)), mode='constant', constant_values=(fill_value,))[:-num]
    elif num > 0:
        arr = np.pad(arr, (num, 0), mode='constant', constant_values=(fill_value,))[:-num]

    return arr

# use np.concatenate and np.full by chrisaycock
def shift4(arr, num, fill_value=np.nan):
    if num >= 0:
        return np.concatenate((np.full(num, fill_value), arr[:-num]))
    else:
        return np.concatenate((arr[-num:], np.full(-num, fill_value)))

# preallocate empty array and assign slice by chrisaycock
def shift5(arr, num, fill_value=np.nan):
    result = np.empty_like(arr)
    if num > 0:
        result[:num] = fill_value
        result[num:] = arr[:-num]
    elif num < 0:
        result[num:] = fill_value
        result[:num] = arr[-num:]
    else:
        result[:] = arr
    return result

arr = np.arange(2000).astype(float)

def benchmark_shift1():
    shift1(arr, 3)

def benchmark_shift2():
    shift2(arr, 3)

def benchmark_shift3():
    shift3(arr, 3)

def benchmark_shift4():
    shift4(arr, 3)

def benchmark_shift5():
    shift5(arr, 3)

benchmark_set = ['benchmark_shift1', 'benchmark_shift2', 'benchmark_shift3', 'benchmark_shift4', 'benchmark_shift5']

for x in benchmark_set:
    number = 10000
    t = timeit.timeit('%s()' % x, 'from __main__ import %s' % x, number=number)
    print '%s time: %f' % (x, t)

基准测试结果:

benchmark_shift1 time: 0.265238
benchmark_shift2 time: 0.285175
benchmark_shift3 time: 0.473890
benchmark_shift4 time: 0.099049
benchmark_shift5 time: 0.052836

结论

shift5是赢家!这是OP的第三个解决方案。


感谢您的比较。知道不使用新阵列的最快方法是什么吗?
FiReTiTi

2
在最后一个子句中shift5,最好编写result[:] = arr而不是result = arr,以使函数行为保持一致。
avysk

2
应该选择它作为答案
wyx

@avysk注释非常重要-请更新shift5方法。有时返回副本且有时返回引用的函数是通向地狱的路径。
戴维

2
@ Josmoor98那是因为type(np.NAN) is float。如果使用这些函数移动整数数组,则需要指定整数fill_value。
gzc

8

没有单一功能可以满足您的需求。您对班次的定义与大多数人所做的略有不同。移位数组的方法更常见:

>>>xs=np.array([1,2,3,4,5])
>>>shift(xs,3)
array([3,4,5,1,2])

但是,您可以使用两个功能来完成所需的操作。
考虑a=np.array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])

def shift2(arr,num):
    arr=np.roll(arr,num)
    if num<0:
         np.put(arr,range(len(arr)+num,len(arr)),np.nan)
    elif num > 0:
         np.put(arr,range(num),np.nan)
    return arr
>>>shift2(a,3)
[ nan  nan  nan   0.   1.   2.   3.   4.   5.   6.]
>>>shift2(a,-3)
[  3.   4.   5.   6.   7.   8.   9.  nan  nan  nan]

在给定的函数和您提供的上述代码上运行cProfile之后,我发现您提供的代码进行42次函数调用,而shift2当arr为正时进行14次调用,当其为负时进行16次调用。我将尝试计时,以查看它们如何处理真实数据。


1
嘿,谢谢你看这个。我知道np.roll(); 我在问题链接中使用了该技术。至于您的实现,您是否有机会让函数针对负移位值工作?
chrisaycock

有趣的是,np.concatenate()它比快很多np.r_[]np.roll()毕竟,前者是什么。
chrisaycock 2015年

5

您可以先转换ndarraySeriesDataFrame与转换pandas,然后可以shift根据需要使用method。

例:

In [1]: from pandas import Series

In [2]: data = np.arange(10)

In [3]: data
Out[3]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [4]: data = Series(data)

In [5]: data
Out[5]: 
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int64

In [6]: data = data.shift(3)

In [7]: data
Out[7]: 
0    NaN
1    NaN
2    NaN
3    0.0
4    1.0
5    2.0
6    3.0
7    4.0
8    5.0
9    6.0
dtype: float64

In [8]: data = data.values

In [9]: data
Out[9]: array([ nan,  nan,  nan,   0.,   1.,   2.,   3.,   4.,   5.,   6.])

太好了,许多人将numpy与pandas一起使用,这非常有帮助!
VanDavv

5

基准和Numba简介

1.总结

  • 可接受的答案(scipy.ndimage.interpolation.shift)是此页面中列出的最慢的解决方案。
  • 当数组大小小于〜25.000时,Numba(@ numba.njit)会提高性能
  • 当数组大小较大(> 250.000)时,“任何方法”同样适用。
  • 最快的选择实际上取决于
        (1)数组的长度
        (2)您需要执行的移位量。
  • 下图是此页面(2020-07-11)列出的所有不同方法的使用固定位移= 10的时间的图片。可以看到,在较小的数组大小下,某些方法所用的时间比方法所占用的时间多+ 2000%。最好的方法。

相对定时,恒定移位(10),所有方法

2.最佳选择的详细基准测试

  • shift4_numba如果您想要出色的全方位服务,请选择(定义如下)

相对时机,最佳方法(基准)

3.代码

3.1 shift4_numba

  • 全方位的好;最高20%wrt。任何数组大小的最佳方法
  • 中等阵列大小的最佳方法:〜500 <N <20.000。
  • 注意:仅当您多次调用装饰函数时,Numba jit(及时编译器)才会提高性能。第一次通话通常比后续通话时间长3-4倍。
import numba

@numba.njit
def shift4_numba(arr, num, fill_value=np.nan):
    if num >= 0:
        return np.concatenate((np.full(num, fill_value), arr[:-num]))
    else:
        return np.concatenate((arr[-num:], np.full(-num, fill_value)))

3.2。 shift5_numba

  • 较小(N <= 300 .. 1500)阵列大小的最佳选择。阈值取决于所需的移位量。
  • 在任何阵列大小上均具有良好的性能;与最快的解决方案相比,最高+ 50%。
  • 注意:仅当您多次调用装饰函数时,Numba jit(及时编译器)才会提高性能。第一次通话通常比后续通话时间长3-4倍。
import numba

@numba.njit
def shift5_numba(arr, num, fill_value=np.nan):
    result = np.empty_like(arr)
    if num > 0:
        result[:num] = fill_value
        result[num:] = arr[:-num]
    elif num < 0:
        result[num:] = fill_value
        result[:num] = arr[-num:]
    else:
        result[:] = arr
    return result

3.3。 shift5

  • 数组大小〜20.000 <N <250.000的最佳方法
  • 与相同shift5_numba,只需删除@ numba.njit装饰器。

4附录

4.1有关使用方法的详细信息

  • shift_scipyscipy.ndimage.interpolation.shift(scipy 1.4.1)-可接受答案中的选项,这显然是最慢的选择
  • shift1np.rollout[:num] xnp.nanIronManMark20GZC
  • shift2np.rollnp.putIronManMark20
  • shift3np.padsliceGZC
  • shift4np.concatenatenp.fullchrisaycock
  • shift5result[slice] = xchrisaycock使用两次
  • shift#_numba:@ numba .njit装饰了以前的版本。

当前numba(0.50.1)不支持的shift2和所shift3包含的函数。

4.2其他测试结果

4.2.1相对时间,所有方法

4.2.2原始计时,所有方法

4.2.3原始时间,最佳方法很少


4

您也可以使用Pandas来做到这一点:

使用2356长的数组:

import numpy as np

xs = np.array([...])

使用scipy:

from scipy.ndimage.interpolation import shift

%timeit shift(xs, 1, cval=np.nan)
# 956 µs ± 77.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

使用熊猫:

import pandas as pd

%timeit pd.Series(xs).shift(1).values
# 377 µs ± 9.42 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

在此示例中,使用Pandas的速度是Scipy的约8倍


2
最快的方法是我在问题末尾发布的预分配。您的Series技术使我的计算机花费了146 us,而我的方法花费了4 us。
chrisaycock19年

0

如果您希望从numpy获得单线并且不太在乎性能,请尝试:

np.sum(np.diag(the_array,1),0)[:-1]

说明:np.diag(the_array,1)创建一个矩阵,矩阵的对角线是一个对角线,np.sum(...,0)将矩阵按列求和,并...[:-1]采用与原始数组的大小相对应的元素。1和和:-1as参数一起玩可以使您朝不同的方向移动。


-2

一种不将代码洒入案例的方法

与数组:

def shift(arr, dx, default_value):
    result = np.empty_like(arr)
    get_neg_or_none = lambda s: s if s < 0 else None
    get_pos_or_none = lambda s: s if s > 0 else None
    result[get_neg_or_none(dx): get_pos_or_none(dx)] = default_value
    result[get_pos_or_none(dx): get_neg_or_none(dx)] = arr[get_pos_or_none(-dx): get_neg_or_none(-dx)]     
    return result

使用矩阵可以做到这一点:

def shift(image, dx, dy, default_value):
    res = np.full_like(image, default_value)

    get_neg_or_none = lambda s: s if s < 0 else None
    get_pos_or_none = lambda s : s if s > 0 else None

    res[get_pos_or_none(-dy): get_neg_or_none(-dy), get_pos_or_none(-dx): get_neg_or_none(-dx)] = \
        image[get_pos_or_none(dy): get_neg_or_none(dy), get_pos_or_none(dx): get_neg_or_none(dx)]
    return res

这既不干净也不快捷。
chrisaycock
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.