移动numpy数组中的元素

chr*_*ock 57 python numpy

从几年前的这个问题开始跟进,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)]
Run Code Online (Sandbox Code Playgroud)

使用它就像:

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])
Run Code Online (Sandbox Code Playgroud)

这个问题来自于我昨天尝试写一个快速的rolling_product.我需要一种"转移"累积产品的方法,而我所能想到的只是复制逻辑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)))
Run Code Online (Sandbox Code Playgroud)

更快的版本只需预先分配数组:

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
Run Code Online (Sandbox Code Playgroud)

Ed *_*ith 72

不是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)
Run Code Online (Sandbox Code Playgroud)

其中default是从数组外部引入一个带值的常量值,在cval此处设置为nan.这给出了所需的输出,

array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])
Run Code Online (Sandbox Code Playgroud)

负面转变同样有效,

shift(xs, -3, cval=np.NaN)
Run Code Online (Sandbox Code Playgroud)

提供输出

array([  3.,   4.,   5.,   6.,   7.,   8.,   9.,  nan,  nan,  nan])
Run Code Online (Sandbox Code Playgroud)

  • scipy shift函数非常慢.我使用np.concatenate完成了自己的操作,速度更快. (15认同)
  • numpy.roll更快.熊猫也用它.https://github.com/pandas-dev/pandas/blob/v0.19.2/pandas/core/internals.py#L1044 (7认同)
  • 刚刚针对此页面上列出的所有其他替代方案测试了 scipy.ndimage.interpolation.shift (scipy 1.4.1)(请参阅下面的我的答案),这是***最慢***可能的解决方案。仅当速度在您的应用中不重要时才使用。 (2认同)

gzc*_*gzc 44

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

基准

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)
Run Code Online (Sandbox Code Playgroud)

基准结果:

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
Run Code Online (Sandbox Code Playgroud)

结论

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

  • 在shift5的最后一个子句中,最好写成`result [:] = arr`而不是`result = arr`,以使函数行为保持一致。 (2认同)
  • 应该选择它作为答案 (2认同)
  • @Josmoor98那是因为`type(np.NAN)是float`。如果使用这些函数移位整数数组,则需要指定整数 fill_value。 (2认同)

np8*_*np8 12

基准测试和介绍 Numba

一、总结

  • 接受的答案 ( scipy.ndimage.interpolation.shift) 是本页中列出的最慢的解决方案。
  • 当数组大小小于 ~25.000 时,Numba (@numba.njit) 会提供一些性能提升
  • 当数组大小很大(> 250.000)时,“任何方法”同样好。
  • 最快的选项实际上取决于
        (1) 数组的长度
        (2) 您需要做的移位量。
  • 下面是本页 (2020-07-11) 上列出的所有不同方法的时序图,使用常量 shift = 10。正如我们所看到的,对于小数组大小,某些方法使用的时间比最好的方法。

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

2. 具有最佳选项的详细基准

  • shift4_numba如果您想要优秀的全能选手,请选择(定义如下)

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

3. 代码

3.1 shift4_numba

  • 好全能;最多 20% 重量。任何数组大小的最佳方法
  • 中等数组大小的最佳方法:~ 500 < N < 20.000。
  • 警告:Numba jit(即时编译器)只有在多次调用装饰函数时才会提高性能。第一个调用通常比后续调用长 3-4 倍。您可以通过提前编译 numba获得更多的性能提升。
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)))
Run Code Online (Sandbox Code Playgroud)

3.2. shift5_numba

  • 小 (N <= 300.. 1500) 阵列大小的最佳选择。阈值取决于所需的移位量。
  • 在任何数组大小上都有良好的性能;与最快的解决方案相比,最大 + 50%。
  • 警告:Numba jit(即时编译器)只有在多次调用装饰函数时才会提高性能。第一个调用通常比后续调用长 3-4 倍。您可以通过提前编译 numba获得更多的性能提升。
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
Run Code Online (Sandbox Code Playgroud)

3.3. shift5

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

4 附录

4.1 所用方法的详细信息

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

shift2shift3包含的功能是不是由当前numba(0.50.1)的支持。

4.2 其他测试结果

4.2.1 相对时间,所有方法

4.2.2 原始时间,所有方法

4.2.3 原始时间,几个最好的方法


Iro*_*k20 7

没有一个功能可以满足您的需求.您对班次的定义与大多数人的做法略有不同.移动数组的方法通常是循环的:

>>>xs=np.array([1,2,3,4,5])
>>>shift(xs,3)
array([3,4,5,1,2])
Run Code Online (Sandbox Code Playgroud)

但是,您可以使用两个功能执行所需操作.
考虑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]
Run Code Online (Sandbox Code Playgroud)

在您给定的函数和上面提供的代码上运行cProfile之后,我发现您提供的代码进行了42次函数调用,而shift2当arr为正时进行了14次调用,当为负时进行了16次调用.我将尝试计时,看看每个如何使用真实数据.


小智 6

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

例子:

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.])
Run Code Online (Sandbox Code Playgroud)


Ran*_*ssi 6

您也可以使用 Pandas 执行此操作:

\n\n

使用 2356 长的数组:

\n\n
import numpy as np\n\nxs = np.array([...])\n
Run Code Online (Sandbox Code Playgroud)\n\n

使用 scipy:

\n\n
from scipy.ndimage.interpolation import shift\n\n%timeit shift(xs, 1, cval=np.nan)\n# 956 \xc2\xb5s \xc2\xb1 77.9 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 1000 loops each)\n
Run Code Online (Sandbox Code Playgroud)\n\n

使用熊猫:

\n\n
import pandas as pd\n\n%timeit pd.Series(xs).shift(1).values\n# 377 \xc2\xb5s \xc2\xb1 9.42 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 1000 loops each)\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n

  • 最快的方法是我在问题末尾发布的预分配。你的“Series”技术在我的计算机上花费了 146 us,而我的方法花费了不到 4 us。 (4认同)