Python 使用索引从数组中填充1D numpy数组
背景 我有一个1D NumPy数组,用零初始化Python 使用索引从数组中填充1D numpy数组,python,arrays,pandas,numpy,vectorization,Python,Arrays,Pandas,Numpy,Vectorization,背景 我有一个1D NumPy数组,用零初始化 import numpy as np section = np.zeros(1000) 然后我有一个熊猫数据框,其中索引分为两列: d= {'start': {0: 7200, 1: 7500, 2: 7560, 3: 8100, 4: 11400}, 'end': {0: 10800, 1: 8100, 2: 8100, 3: 8150, 4: 12000}} df = pd.DataFrame(data=d, columns=['s
import numpy as np
section = np.zeros(1000)
然后我有一个熊猫数据框,其中索引分为两列:
d= {'start': {0: 7200, 1: 7500, 2: 7560, 3: 8100, 4: 11400},
'end': {0: 10800, 1: 8100, 2: 8100, 3: 8150, 4: 12000}}
df = pd.DataFrame(data=d, columns=['start', 'end'])
对于每一对索引,我想将numpy数组中相应索引的值设置为True
我当前的解决方案
我可以通过对数据帧应用函数来实现这一点:
def fill_array(row):
section[row.start:row.end] = True
df.apply(fill_array, axis=1)
我想将此操作矢量化
这正如我所期望的,但是为了好玩,我想将操作矢量化。我对这方面不是很精通,而且我的在线搜索也没有让我走上正轨
如果可能的话,我将非常感谢您对如何将其转化为向量操作的任何建议。向量化
您已经通过使用slice赋值完成了最重要的向量化,但是您无法使用slice对其进行完全向量化,因为python不支持“多个切片”
如果您真的非常想使用矢量化,您可以使用
“真实”索引,如下所示
indices = np.r_[tuple(slice(row.start, row.end) for row in df.itertuples())]
section[indices] = True
for start, end in slices_union:
section[start:end] = True
但这很可能会更慢,因为它会创建一个带有索引的新临时数组
删除重复工作
也就是说,通过减少重复工作,您可以获得一些速度提升。具体来说,您可以使用,给您一组不相交的集合
在您的例子中,第一个间隔与除最后一个间隔之外的所有间隔重叠,因此您的数据帧相当于
d= {'start': {0: 7200, 1: 11400},
'end': {0: 10800, 1: 12000}}
这将减少多达60%的工作量!但首先我们需要找到这些时间间隔。根据上述答案,我们可以通过以下方式实现:
slices = [(row.start, row.end) for row in df.itertuples()]
slices_union = []
for start, end in sorted(slices):
if slices_union and slices_union[-1][1] >= start - 1:
slices_union[-1][1] = max(slices_union[-1][1], end)
else:
slices_union.append([start, end])
然后你可以像这样使用这些(希望更小的切片)
indices = np.r_[tuple(slice(row.start, row.end) for row in df.itertuples())]
section[indices] = True
for start, end in slices_union:
section[start:end] = True
接下来实现的技巧是,我们将在零初始化int数组的每个起点放置
1s
,在每个端点放置-1s
。接下来是实际的技巧,因为我们将对其进行累加,为bin(开始-停止对)边界覆盖的位置提供非零的数字。因此,最后一步是寻找作为布尔数组的最终输出的非零。因此,我们将有两个矢量化解决方案,它们的实现如下所示-
def filled_array(start, end, length):
out = np.zeros((length), dtype=int)
np.add.at(out,start,1)
np.add.at(out,end,-1)
return out.cumsum()>0
def filled_array_v2(start, end, length): #Using @Daniel's suggestion
out =np.bincount(start, minlength=length) - np.bincount(end, minlength=length)
return out.cumsum().astype(bool)
样本运行-
In [2]: start
Out[2]: array([ 4, 7, 5, 15])
In [3]: end
Out[3]: array([12, 12, 7, 17])
In [4]: out = filled_array(start, end, length=20)
In [7]: pd.DataFrame(out) # print as dataframe for easy verification
Out[7]:
0
0 False
1 False
2 False
3 False
4 True
5 True
6 True
7 True
8 True
9 True
10 True
11 True
12 False
13 False
14 False
15 True
16 True
17 False
18 False
19 False
在您的实际用例中,会有多少个开始对、结束对?@Divakar最坏情况下10000对,以及一个1-300万个索引的NumPy数组。事实证明,我们可以矢量化,只需要改变我们的方式:)他可以,但我怀疑这是否值得。最后,我用
np.r\uu
给出了一个解决方案,我想这是最简单的解决方案。对于试图纠正术语,我深表歉意,但列表/数据框理解并不是一个矢量化的解决方案,特别是当放在大标题中时:)谢谢你的建议,@JonasAdler我必须对你的代码做一点小改动才能让它正常工作。原始对象抛出了一个AttributeError:“str”对象没有属性“start”
错误。通过执行df.itertuples()
而不是df
可以轻松解决此问题。正如您所怀疑的,矢量化版本比迭代版本稍微慢一点,迭代版本为747µs,而迭代版本为649µs。作为比较,我的原始函数时钟为413µs。根据您的评论进行了更新。你试过另一个把戏吗?小诡辩:maxlen
应该是minlen
,因为如果你的zero
数组太短add.at
将失败。此外,为了提高性能,您可以执行out=np.bincount(start,minlength=minlen)-np.bincount(end,minlength=minlen)
和return out.cumsum().astype(bool)
自整数=0将在不进行比较的情况下解析为TRUE
step@DanielF好的观点,非常感谢!编辑。那个名字不是正确的。如果输入参数覆盖了所有的索引,请将其留给OP来确定要提及的长度。谢谢@Divakar!这是一个很好的答案。我喜欢你建议的算法。聪明!我在245µs下测量,根据@DanielF的建议,每个循环的测量值仅为150µs。