替换numpy数组中的元素,避免循环

时间:2018-11-05 13:52:15

标签: python numpy for-loop numpy-slicing

我有一个非常大的一维numpy数组Xold,具有给定的值。这些值应为 根据2d numpy数组Y指定的规则替换: 一个例子是

Xold=np.array([0,1,2,3,4])
Y=np.array([[0,0],[1,100],[3,300],[4,400],[2,200]])

每当Xold中的值与Y [:,0]中的值相同时,Xnew中的新值应为Y [:,1]中的对应值。这是通过两个嵌套的for循环完成的:

Xnew=np.zeros(len(Xold))
for i in range(len(Xold)):
for j in range(len(Y)):
    if Xold[i]==Y[j,0]:
        Xnew[i]=Y[j,1]

在给定的示例中,这将产生Xnew=[0,100,200,300,400]。 但是,对于大数据集,此过程非常慢。什么是更快,更优雅的方式来完成此任务的?

8 个答案:

答案 0 :(得分:3)

Y第一列中的数据不一定要排序的情况下,我们可以使用np.searchsorted-

sidx = Y[:,0].argsort()
out = Y[sidx[np.searchsorted(Y[:,0], Xold, sorter=sidx)],1]

样品运行-

In [53]: Xold
Out[53]: array([14, 10, 12, 13, 11])

In [54]: Y
Out[54]: 
array([[ 10,   0],
       [ 11, 100],
       [ 13, 300],
       [ 14, 400],
       [ 12, 200]])

In [55]: sidx = Y[:,0].argsort()
    ...: out = Y[sidx[np.searchsorted(Y[:,0], Xold, sorter=sidx)],1]

In [56]: out
Out[56]: array([400,   0, 200, 300, 100])

如果不是所有元素都有对应的映射可用,那么我们需要做更多的工作,像这样-

sidx = Y[:,0].argsort()
sorted_indx = np.searchsorted(Y[:,0], Xold, sorter=sidx)
sorted_indx[sorted_indx==len(sidx)] = len(sidx)-1
idx_out = sidx[sorted_indx]
out = Y[idx_out,1]
out[Y[idx_out,0]!=Xold] = 0 # NA values as 0s

答案 1 :(得分:3)

选择最快的方法

这个问题的答案提供了各种各样的方法来替换numpy数组中的元素。让我们检查一下,哪一个最快。

TL; DR: Numpy索引是赢家

 def meth1(): # suggested by @Slam
    for old, new in Y:  
        Xold[Xold == old] = new

 def meth2(): # suggested by myself, convert y_dict = dict(Y) first
     [y_dict[i] if i in y_dict.keys() else i for i in Xold]

 def meth3(): # suggested by @Eelco Hoogendoom, import numpy_index as npi first
     npi.remap(Xold, keys=Y[:, 0], values=Y[:, 1])

 def meth4(): # suggested by @Brad Solomon, import pandas as pd first 
     pd.Series(Xold).map(pd.Series(Y[:, 1], index=Y[:, 0])).values

  # suggested by @jdehesa. create Xnew = Xold.copy() and index
  # idx = np.searchsorted(Xold, Y[:, 0]) first
  def meth5():             
     Xnew[idx] = Y[:, 1]

结果并不令人惊讶

 In [39]: timeit.timeit(meth1, number=1000000)                                                                      
 Out[39]: 12.08

 In [40]: timeit.timeit(meth2, number=1000000)                                                                      
 Out[40]: 2.87

 In [38]: timeit.timeit(meth3, number=1000000)                                                                      
 Out[38]: 55.39

 In [12]: timeit.timeit(meth4, number=1000000)                                                                                      
 Out[12]: 256.84

 In [50]: timeit.timeit(meth5, number=1000000)                                                                                      
 Out[50]: 1.12

因此,良好的旧列表理解速度是第二快的,而成功的方法是将numpy索引与searchsorted()结合使用。

答案 2 :(得分:2)

您可以做的第一个改进是使用numpy索引,但仍然会有1个循环:

for old, new in Y: 
    Xold[Xold == old] = new

答案 3 :(得分:2)

这里是一种可能性:

import numpy as np

Xold = np.array([0, 1, 2, 3, 4])
Y = np.array([[0, 0], [1, 100], [3, 300], [4, 400], [2, 200]])
# Check every X value against every Y first value
m = Xold == Y[:, 0, np.newaxis]
# Check which elements in X are among Y first values
# (so values that are not in Y are not replaced)
m_X = np.any(m, axis=0)
# Compute replacement
# Xold * (1 - m_X) are the non-replaced values
# np.sum(Y[:, 1, np.newaxis] * m, axis=0) * m_X are the replaced values
Xnew = Xold * (1 - m_X) + np.sum(Y[:, 1, np.newaxis] * m, axis=0) * m_X
print(Xnew)

输出:

[  0 100 200 300 400]

此方法或多或少地适用于每种情况(未排序的数组,X中的值重复多次,X中的值未替换,Y中的值不替换X中的任何内容),除非您对I中的相同值进行两次替换是的,反正还是错的。但是,它的时间和空间复杂度是X和Y大小的乘积。如果您的问题有其他限制(数据已排序,无重复等),则可能可以做得更好。例如,如果X排序时没有重复的元素,并且Y中的每个值都替换了X中的一个值(如您的示例),则可能会更快:

import numpy as np

Xold = np.array([0, 1, 2, 3, 4])
Y = np.array([[0, 0], [1, 100], [3, 300], [4, 400], [2, 200]])
idx = np.searchsorted(Xold, Y[:, 0])
Xnew = Xold.copy()
Xnew[idx] = Y[:, 1]
print(Xnew)
# [  0 100 200 300 400]

答案 4 :(得分:1)

您可以将slicing功能与argsort方法结合使用。

Xnew = Y[Y[:,1].argsort()][:, 1][Xold] 

输出

array([  0, 100, 200, 300, 400])

答案 5 :(得分:0)

使用pd.Series.map()

的解决方案

如果您愿意使用Pandas库,也可以使用.map()以向量化的方式进行操作:

>>> import pandas as pd
>>> pd.Series(Xold).map(pd.Series(Y[:, 1], index=Y[:, 0]))                                                                                                                                                                    
0      0
1    100
2    200
3    300
4    400
dtype: int64

>>> pd.Series(Xold).map(pd.Series(Y[:, 1], index=Y[:, 0])).values                                                                                                                                                            
array([  0, 100, 200, 300, 400])

对于签名a.map(b)a在索引b中查找其对应的条目,并映射到b中的相应值。

bpd.Series(Y[:, 1], index=Y[:, 0]),它使用第0列作为索引,使用第1列作为要映射到的值。


直接使用pandas.core.algorithms

Under the hood,它将使用.get_indexer()和Cython实现的take_1d()

indexer = mapper.index.get_indexer(values)
new_values = algorithms.take_1d(mapper._values, indexer)

知道,如果阵列真的很大,您可以减少一些开销,如下所示:

from pandas.core import algorithms

indexer = pd.Index(Y[:, 0]).get_indexer(Xold)  
mapped = algorithms.take_1d(Y[:, 1], indexer)

答案 6 :(得分:0)

numpy_indexed软件包(免责声明;我是它的作者)包含一个有效的矢量化函数,可以解决一般问题:

import numpy_indexed as npi
Xnew = npi.remap(Xold, keys=Y[:, 0], values=Y[:, 1])

也就是说,这将适用于任何dtype,或者当要替换的键和值本身就是ndarrays时,您会遇到一个麻烦,可以指定如何对丢失的元素做出反应。

不确定在性能方面与大熊猫相比如何;但是该库中的设计选择之一是执行这样的基本操作(或进行分组操作等)不应涉及创建整个新数据类型(如Series或Table),这总是困扰着我使用这种类型的熊猫事情。

答案 7 :(得分:0)

您可以使用y = dict(Y)将Y转换为字典,然后运行以下列表理解

[y[i] if i in y.keys() else i for i in Xold]