Python 快速numpy花式索引

Python 快速numpy花式索引,python,numpy,indexing,slice,Python,Numpy,Indexing,Slice,我的切片numpy数组(通过奇特的索引)的代码非常慢。这是目前该计划的一个瓶颈 a.shape (3218, 6) ts = time.time(); a[rows][:, cols]; te = time.time(); print('%.8f' % (te-ts)); 0.00200009 要获得由矩阵a的行“rows”和列“col”的子集组成的数组,正确的numpy调用是什么?(事实上,我需要这个结果的转置)如果您使用奇特的索引和广播进行切片,您可以获得一些速度: from __fut

我的切片numpy数组(通过奇特的索引)的代码非常慢。这是目前该计划的一个瓶颈

a.shape
(3218, 6)

ts = time.time(); a[rows][:, cols]; te = time.time(); print('%.8f' % (te-ts));
0.00200009

要获得由矩阵a的行“rows”和列“col”的子集组成的数组,正确的numpy调用是什么?(事实上,我需要这个结果的转置)

如果您使用奇特的索引和广播进行切片,您可以获得一些速度:

from __future__ import division
import numpy as np

def slice_1(a, rs, cs) :
    return a[rs][:, cs]

def slice_2(a, rs, cs) :
    return a[rs[:, None], cs]

>>> rows, cols = 3218, 6
>>> rs = np.unique(np.random.randint(0, rows, size=(rows//2,)))
>>> cs = np.unique(np.random.randint(0, cols, size=(cols//2,)))
>>> a = np.random.rand(rows, cols)
>>> import timeit
>>> print timeit.timeit('slice_1(a, rs, cs)',
                        'from __main__ import slice_1, a, rs, cs',
                        number=1000)
0.24083110865
>>> print timeit.timeit('slice_2(a, rs, cs)',
                        'from __main__ import slice_2, a, rs, cs',
                        number=1000)
0.206566124519

如果你从百分比的角度考虑,做15%的事情总是好的,但是在我的系统中,就你的阵列的大小而言,这需要减少40个us来进行切片,很难相信需要240个us的操作会成为你的瓶颈。

让我惊讶的是,这种冗长的表达,它计算第一个线性1D索引,比问题中的连续数组索引快50%:

(a.ravel()[(
   cols + (rows * a.shape[1]).reshape((-1,1))
   ).ravel()]).reshape(rows.size, cols.size)
更新:OP更新了初始数组形状的描述。使用更新的大小,加速比现在高于99%:

In [93]: a = np.random.randn(3218, 1415)

In [94]: rows = np.random.randint(a.shape[0], size=2000)

In [95]: cols = np.random.randint(a.shape[1], size=6)

In [96]: timeit a[rows][:, cols]
10 loops, best of 3: 186 ms per loop

In [97]: timeit (a.ravel()[(cols + (rows * a.shape[1]).reshape((-1,1))).ravel()]).reshape(rows.size, cols.size)
1000 loops, best of 3: 1.56 ms per loop
初始答案: 以下是发言稿:

In [79]: a = np.random.randn(3218, 6)
In [80]: a.shape
Out[80]: (3218, 6)

In [81]: rows = np.random.randint(a.shape[0], size=2000)
In [82]: cols = np.array([1,3,4,5])
时间方法1:

In [83]: timeit a[rows][:, cols]
1000 loops, best of 3: 1.26 ms per loop
时间方法2:

In [84]: timeit (a.ravel()[(cols + (rows * a.shape[1]).reshape((-1,1))).ravel()]).reshape(rows.size, cols.size)
1000 loops, best of 3: 568 us per loop
检查结果是否实际相同:

In [85]: result1 = a[rows][:, cols]
In [86]: result2 = (a.ravel()[(cols + (rows * a.shape[1]).reshape((-1,1))).ravel()]).reshape(rows.size, cols.size)

In [87]: np.sum(result1 - result2)
Out[87]: 0.0

让我试着总结一下Jaime和TheodrosZelleke的优秀答案,并加入一些评论

  • 始终返回副本,而不是视图
  • a[rows][:,cols]
    意味着两种奇特的索引操作,因此创建并丢弃中间副本
    a[rows]
    。方便易读,但效率不高。此外,请注意,
    [:,cols]
    通常从C-cont.源代码生成Fortran连续副本
  • a[rows.reformate(-1,1),cols]
    是一个单独的高级索引表达式,它基于
    rows.reformate(-1,1)
    cols
    符合预期结果的形状这一事实
  • 一个常见的经验是,在扁平数组中建立索引可能比奇特的索引更有效,因此另一种方法是

    indx = rows.reshape(-1,1)*a.shape[1] + cols
    a.take(indx)
    

  • 效率将取决于内存访问模式以及起始数组是C-连续还是Fortran连续,因此需要进行实验

  • 只有在真正需要时才使用奇特的索引:
    a[rstart:rstop:rstep,cstart:cstop:cstep]
    返回一个视图(虽然不是连续的),并且应该更快


  • 使用
    np.ix
    可以获得与拉威尔/重塑相似的速度,但代码更清晰:

    a = np.random.randn(3218, 1415)
    rows = np.random.randint(a.shape[0], size=2000)
    cols = np.random.randint(a.shape[1], size=6)
    a = np.random.randn(3218, 1415)
    rows = np.random.randint(a.shape[0], size=2000)
    cols = np.random.randint(a.shape[1], size=6)
    
    %timeit (a.ravel()[(cols + (rows * a.shape[1]).reshape((-1,1))).ravel()]).reshape(rows.size, cols.size)
    #101 µs ± 2.36 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
    
    %timeit ix_ = np.ix_(rows, cols); a[ix_]
    #135 µs ± 7.47 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
    ix_ = np.ix_(rows, cols)
    result1 = a[ix_]
    result2 = (a.ravel()[(cols + (rows * a.shape[1]).reshape((-1,1))).ravel()]).reshape(rows.size, cols.size)
    ​
    np.sum(result1 - result2)
    0.0
    

    time.time
    不是衡量时间的好方法。一般来说,最好使用
    timeit
    。1。你的程序到底在做什么?2.使用合适的python分析器。我发现切片不太可能是你的瓶颈——如果你使用
    @mgilson
    风格,它会向用户发送通知(每条评论一条)。@mgilson:我记得在某些情况下(4年前)有问题,可能不再适用。手册中说,对于索引数组的所有情况,返回的都是原始数据的副本,而不是切片时得到的视图。@Wolph这一点仍然适用:高级索引总是返回数据的副本(与返回视图的基本切片相比),结果是我有一个3218x1415数组,而不是3218x6。我只提取了几列和很多行。上面的代码显示切片1调用时间为0.08秒,切片2调用时间为0.0004秒。也许这就是我需要的!对于OP.Nice的新要求,它的速度大约是我提供的标准答案的两倍!这并不是说这些技巧不能加快速度(至少在特定情况下是如此),但所有这些都在很大程度上依赖于输入数组是C-连续的这一事实。毫不奇怪:请看一个相关的问题。
    a = np.random.randn(3218, 1415)
    rows = np.random.randint(a.shape[0], size=2000)
    cols = np.random.randint(a.shape[1], size=6)
    a = np.random.randn(3218, 1415)
    rows = np.random.randint(a.shape[0], size=2000)
    cols = np.random.randint(a.shape[1], size=6)
    
    %timeit (a.ravel()[(cols + (rows * a.shape[1]).reshape((-1,1))).ravel()]).reshape(rows.size, cols.size)
    #101 µs ± 2.36 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
    
    %timeit ix_ = np.ix_(rows, cols); a[ix_]
    #135 µs ± 7.47 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
    ix_ = np.ix_(rows, cols)
    result1 = a[ix_]
    result2 = (a.ravel()[(cols + (rows * a.shape[1]).reshape((-1,1))).ravel()]).reshape(rows.size, cols.size)
    ​
    np.sum(result1 - result2)
    0.0