Python 查找每个唯一箱子的最大位置(binargmax) 安装程序
如果我有Python 查找每个唯一箱子的最大位置(binargmax) 安装程序,python,numpy,Python,Numpy,如果我有 bins = np.array([0, 0, 1, 1, 2, 2, 2, 0, 1, 2]) vals = np.array([8, 7, 3, 4, 1, 2, 6, 5, 0, 9]) k = 3 我需要在bin中唯一bin的最大值位置 # Bin == 0 # ↓ ↓ ↓ # [0 0 1 1 2 2 2 0 1 2] # [8 7 3 4 1 2 6 5 0 9] # ↑ ↑ ↑ # ⇧ # [0 1 2 3 4 5 6 7 8
bins = np.array([0, 0, 1, 1, 2, 2, 2, 0, 1, 2])
vals = np.array([8, 7, 3, 4, 1, 2, 6, 5, 0, 9])
k = 3
我需要在bin
中唯一bin的最大值位置
# Bin == 0
# ↓ ↓ ↓
# [0 0 1 1 2 2 2 0 1 2]
# [8 7 3 4 1 2 6 5 0 9]
# ↑ ↑ ↑
# ⇧
# [0 1 2 3 4 5 6 7 8 9]
# Maximum is 8 and happens at position 0
(vals * (bins == 0)).argmax()
0
这些函数很粗糙,甚至不能概括为负值 问题: 如何使用Numpy以最有效的方式获取所有这些值 我试过的。
这是一个有趣的小问题要解决。我的方法是根据
bin
中的值,将索引放入vals
。使用where
获取索引为True
的点,并结合VAL中这些点上的argmax
给出结果值
def binargmaxA(bins, vals):
res = []
for v in unique(bins):
idx = (bins==v)
r = where(idx)[0][argmax(vals[idx])]
res.append(r)
return array(res)
通过使用range(k)
获取可能的bin值,可以删除对unique
的调用。这加快了速度,但随着k的增大,仍然会导致性能不佳
def binargmaxA2(bins, vals, k):
res = []
for v in range(k):
idx = (bins==v)
r = where(idx)[0][argmax(vals[idx])]
res.append(r)
return array(res)
最后一次尝试,比较每一个值会大大降低速度。此版本计算值的排序数组,而不是对每个唯一值进行比较。实际上,它会计算排序后的索引,并且只在需要时获取排序后的值,因为这样可以避免将VAL一次性加载到内存中。性能仍然随存储箱的数量而变化,但比以前慢得多
def binargmaxB(bins, vals):
idx = argsort(bins) # Find sorted indices
split = r_[0, where(diff(bins[idx]))[0]+1, len(bins)] # Compute where values start in sorted array
newmax = [argmax(vals[idx[i1:i2]]) for i1, i2 in zip(split, split[1:])] # Find max for each value in sorted array
return idx[newmax +split[:-1]] # Convert to indices in unsorted array
基准
以下是一些基准测试和其他答案
3000个元素
数据集稍大(bins=randint(0,30,3000);vals=randn(3000)
;k=30;)
- 171usbinargmax\u比例\u按Divakar排序2
- 209us这个答案,版本B
- 281usbinargmax\u比例\u按Divakar排序
- 329us用户广播版本545424
- 399us这个答案,版本A
- 416us由sacul使用lexsort回答
- 899us皮尔斯平方参考代码
bins=randint(0,30,30000);vals=randn(30000)
;k=30)。令人惊讶的是,这并没有改变解决方案之间的相对性能
- 1.27ms此答案,版本B
- 2.01msbinargmax\u刻度\u按刻度排序2
- 2.38ms用户广播版本545424
- 2.68ms此答案,版本A
- 5.71ms由sacul使用lexsort回答
- 9.12ms皮尔斯平方参考代码
k
,现在我已经修复了基准更加均匀的问题
1000箱值
增加唯一bin值的数量也可能会影响性能。Divakar和sacul的解决方案大多不受影响,而其他解决方案则有相当大的影响。
bins=randint(0,1000,30000);VAL=兰特(30000);k=1000
- 1.99msbinargmax\u刻度\u按刻度排序2
- 3.48ms此答案,版本B
- 6.15ms由sacul使用lexsort回答
- 10.6ms皮尔斯平方参考代码
- 27.2ms此答案,版本A
- 129ms用户广播版本545424
numpy\u
库:
我知道这在技术上不是numpy
,但是numpy\u索引的库有一个向量化的group\u by
函数,非常适合于此,只是想作为我经常使用的一个替代方案来分享:
>>> import numpy_indexed as npi
>>> npi.group_by(bins).argmax(vals)
(array([0, 1, 2]), array([0, 3, 9], dtype=int64))
使用简单的pandas
groupby
和idxmax
:
使用sparse.csr\u矩阵
此选项在非常大的输入上非常快速
sparse.csr_matrix(
(vals, bins, np.arange(vals.shape[0]+1)), (vals.shape[0], k)
).argmax(0)
# matrix([[0, 3, 9]])
演出
功能
设置
结果
结果的k
(这是广播受到严重冲击的地方):
从图中可以明显看出,当组数较少时,广播是一个很好的技巧,但是广播的时间复杂度/内存在较高的k
值下增长过快,从而使其具有很高的性能。这方面如何:
>>> import numpy as np
>>> bins = np.array([0, 0, 1, 1, 2, 2, 2, 0, 1, 2])
>>> vals = np.array([8, 7, 3, 4, 1, 2, 6, 5, 0, 9])
>>> k = 3
>>> np.argmax(vals*(bins == np.arange(k)[:,np.newaxis]),axis=-1)
array([0, 3, 9])
这里有一种方法是对每组数据进行偏移,这样我们就可以一次性对整个数据使用argsort
-
def binargmax_scale_sort(bins, vals):
w = np.bincount(bins)
valid_mask = w!=0
last_idx = w[valid_mask].cumsum()-1
scaled_vals = bins*(vals.max()+1) + vals
#unique_bins = np.flatnonzero(valid_mask) # if needed
return len(bins) -1 -np.argsort(scaled_vals[::-1], kind='mergesort')[last_idx]
如果你想要可读性,这可能不是最好的解决方案,但我认为它是可行的
def binargsort(bins,vals):
s = np.lexsort((vals,bins))
s2 = np.sort(bins)
msk = np.roll(s2,-1) != s2
# or use this for msk, but not noticeably better for performance:
# msk = np.append(np.diff(np.sort(bins)),1).astype(bool)
return s[msk]
array([0, 3, 9])
说明:
lexsort
根据bin
的排序顺序,然后按照vals
的顺序,对vals的索引进行排序:
>>> np.lexsort((vals,bins))
array([7, 1, 0, 8, 2, 3, 4, 5, 6, 9])
因此,您可以通过排序的箱
从一个索引到下一个索引的不同位置来屏蔽:
>>> np.sort(bins)
array([0, 0, 0, 1, 1, 1, 2, 2, 2, 2])
# Find where sorted bins end, use that as your mask on the `lexsort`
>>> np.append(np.diff(np.sort(bins)),1)
array([0, 0, 1, 0, 0, 1, 0, 0, 0, 1])
>>> np.lexsort((vals,bins))[np.append(np.diff(np.sort(bins)),1).astype(bool)]
array([0, 3, 9])
好的,这是我的线性时间条目,只使用索引和np.(max | min)inum.at
。它假设箱子从0上升到最大(箱子)
我知道你说过要用Numpy,但如果熊猫是可以接受的:
import numpy as np; import pandas as pd;
(pd.DataFrame(
{'bins':np.array([0, 0, 1, 1, 2, 2, 2, 0, 1, 2]),
'values':np.array([8, 7, 3, 4, 1, 2, 6, 5, 0, 9])})
.groupby('bins')
.idxmax())
values
bins
0 0
1 3
2 9
因此,k始终是唯一的存储箱数?是的,并且应该与存储箱相同。max()+1
是否保证每个存储箱的值是唯一的?你想要所有的maxima吗?不保证,我想要第一个位置。像np.array([1,2,2]).argmax()
返回1
@user3483203Sure。。。(:对不起,我错过了。完成了!这很聪明(:时间复杂度和内存需求将随着大k而增加(我想)@piRSquared,我已经为此设置了一些基准。30个左右的存储箱效果很好,性能下降了1000次。只有3个存储箱,这是迄今为止最快的答案。我也在这样做。这应该与vals
的长度成线性关系。当我应用Numba的njit
时,我的初始方法是最快的。我会展示它。我想要一个O(n)Numpy方法。这确实很接近。看valida
res = pd.DataFrame(
index=['chris', 'chris2', 'chris3', 'divakar', 'divakar2', 'user545424', 'user2699', 'sacul', 'piRSquared'],
columns=[10, 50, 100, 500, 1000, 5000, 10000, 50000, 100000, 500000],
dtype=float
)
k = 500
for f in res.index:
for c in res.columns:
bins = np.random.randint(0, k, c)
vals = np.random.rand(c)
df = pd.DataFrame({'bins': bins, 'vals': vals})
stmt = '{}(df)'.format(f) if f in {'chris2'} else '{}(bins, vals, k)'.format(f)
setp = 'from __main__ import bins, vals, df, k, {}'.format(f)
res.at[f, c] = timeit(stmt, setp, number=50)
ax = res.div(res.min()).T.plot(loglog=True)
ax.set_xlabel("N");
ax.set_ylabel("time (relative)");
plt.show()
>>> import numpy as np
>>> bins = np.array([0, 0, 1, 1, 2, 2, 2, 0, 1, 2])
>>> vals = np.array([8, 7, 3, 4, 1, 2, 6, 5, 0, 9])
>>> k = 3
>>> np.argmax(vals*(bins == np.arange(k)[:,np.newaxis]),axis=-1)
array([0, 3, 9])
def binargmax_scale_sort(bins, vals):
w = np.bincount(bins)
valid_mask = w!=0
last_idx = w[valid_mask].cumsum()-1
scaled_vals = bins*(vals.max()+1) + vals
#unique_bins = np.flatnonzero(valid_mask) # if needed
return len(bins) -1 -np.argsort(scaled_vals[::-1], kind='mergesort')[last_idx]
def binargsort(bins,vals):
s = np.lexsort((vals,bins))
s2 = np.sort(bins)
msk = np.roll(s2,-1) != s2
# or use this for msk, but not noticeably better for performance:
# msk = np.append(np.diff(np.sort(bins)),1).astype(bool)
return s[msk]
array([0, 3, 9])
>>> np.lexsort((vals,bins))
array([7, 1, 0, 8, 2, 3, 4, 5, 6, 9])
>>> np.sort(bins)
array([0, 0, 0, 1, 1, 1, 2, 2, 2, 2])
# Find where sorted bins end, use that as your mask on the `lexsort`
>>> np.append(np.diff(np.sort(bins)),1)
array([0, 0, 1, 0, 0, 1, 0, 0, 0, 1])
>>> np.lexsort((vals,bins))[np.append(np.diff(np.sort(bins)),1).astype(bool)]
array([0, 3, 9])
def via_at(bins, vals):
max_vals = np.full(bins.max()+1, -np.inf)
np.maximum.at(max_vals, bins, vals)
expanded = max_vals[bins]
max_idx = np.full_like(max_vals, np.inf)
np.minimum.at(max_idx, bins, np.where(vals == expanded, np.arange(len(bins)), np.inf))
return max_vals, max_idx
import numpy as np; import pandas as pd;
(pd.DataFrame(
{'bins':np.array([0, 0, 1, 1, 2, 2, 2, 0, 1, 2]),
'values':np.array([8, 7, 3, 4, 1, 2, 6, 5, 0, 9])})
.groupby('bins')
.idxmax())
values
bins
0 0
1 3
2 9