[英]Efficiently get indices of histogram bins in Python
我有一個大的10000x10000元素圖像,我把它分成幾百個不同的扇區/箱。 然后,我需要對每個bin中包含的值執行一些迭代計算。
如何使用bin值提取每個bin的索引以有效地執行計算?
我正在尋找的是一個解決方案,它避免了每次從我的大型數組中選擇ind == j
的瓶頸。 有沒有辦法一次性直接獲得屬於每個bin的元素的索引?
實現我需要的一種方法是使用如下代碼(參見例如THIS相關答案),其中我數字化我的值然后有一個j循環選擇數字化索引等於j如下所示
import numpy as np
# This function func() is just a place mark for a much more complicated function.
# I am aware that my problem could be easily speed up in the specific case of
# of the sum() function, but I am looking for a general solution to the problem.
def func(x):
y = np.sum(x)
return y
vals = np.random.random(1e8)
nbins = 100
bins = np.linspace(0, 1, nbins+1)
ind = np.digitize(vals, bins)
result = [func(vals[ind == j]) for j in range(1, nbins)]
我正在尋找的是一個解決方案,它避免了每次從我的大型數組中選擇ind == j
的瓶頸。 有沒有辦法一次性直接獲得屬於每個bin的元素的索引?
對於用戶定義函數的一般情況,上述方法與scipy.stats.binned_statistic中實現的方法相同。 直接使用Scipy,可以通過以下方式獲得相同的輸出
import numpy as np
from scipy.stats import binned_statistics
vals = np.random.random(1e8)
results = binned_statistic(vals, vals, statistic=func, bins=100, range=[0, 1])[0]
另一個Scipy替代方法是使用scipy.ndimage.measurements.labeled_comprehension 。 使用該函數,上面的例子就變成了
import numpy as np
from scipy.ndimage import labeled_comprehension
vals = np.random.random(1e8)
nbins = 100
bins = np.linspace(0, 1, nbins+1)
ind = np.digitize(vals, bins)
result = labeled_comprehension(vals, ind, np.arange(1, nbins), func, float, 0)
不幸的是,這種形式效率低下,特別是它沒有速度優勢超過我原來的例子。
為了進一步說明,我正在尋找的是與IDL語言HERE的HISTOGRAM
函數中的REVERSE_INDICES
關鍵字等效的功能。 這個非常有用的功能可以在Python中有效復制嗎?
具體來說,使用IDL語言可以將上面的示例寫成
vals = randomu(s, 1e8)
nbins = 100
bins = [0:1:1./nbins]
h = histogram(vals, MIN=bins[0], MAX=bins[-2], NBINS=nbins, REVERSE_INDICES=r)
result = dblarr(nbins)
for j=0, nbins-1 do begin
jbins = r[r[j]:r[j+1]-1] ; Selects indices of bin j
result[j] = func(vals[jbins])
endfor
上面的IDL實現比Numpy快了大約10倍,因為不必為每個bin選擇bin的索引。 並且有利於IDL實施的速度差異隨着箱的數量而增加。
我發現特定的稀疏矩陣構造函數可以非常有效地實現所需的結果。 它有點模糊,但我們可以為此目的濫用它。 下面的函數可以與scipy.stats.binned_statistic幾乎相同的方式使用,但速度可以快幾個數量級
import numpy as np
from scipy.sparse import csr_matrix
def binned_statistic(x, values, func, nbins, range):
'''The usage is nearly the same as scipy.stats.binned_statistic'''
N = len(values)
r0, r1 = range
digitized = (float(nbins)/(r1 - r0)*(x - r0)).astype(int)
S = csr_matrix((values, [digitized, np.arange(N)]), shape=(nbins, N))
return [func(group) for group in np.split(S.data, S.indptr[1:-1])]
我避免使用np.digitize
因為它沒有使用所有bin都相等寬度因此很慢的事實,但我使用的方法可能無法完美處理所有邊緣情況。
我假設無法更改在帶有digitize
的示例中完成的分箱。 這是一種方法,您可以一次性進行排序。
vals = np.random.random(1e4)
nbins = 100
bins = np.linspace(0, 1, nbins+1)
ind = np.digitize(vals, bins)
new_order = argsort(ind)
ind = ind[new_order]
ordered_vals = vals[new_order]
# slower way of calculating first_hit (first version of this post)
# _,first_hit = unique(ind,return_index=True)
# faster way:
first_hit = searchsorted(ind,arange(1,nbins-1))
first_hit.sort()
#example of using the data:
for j in range(nbins-1):
#I am using a plotting function for your f, to show that they cluster
plot(ordered_vals[first_hit[j]:first_hit[j+1]],'o')
該圖顯示了垃圾箱實際上是預期的集群:
您可以先通過排序數組將計算時間減半,然后使用np.searchsorted
。
vals = np.random.random(1e8)
vals.sort()
nbins = 100
bins = np.linspace(0, 1, nbins+1)
ind = np.digitize(vals, bins)
results = [func(vals[np.searchsorted(ind,j,side='left'):
np.searchsorted(ind,j,side='right')])
for j in range(1,nbins)]
使用1e8
作為我的測試用例,我將從34秒計算到大約17秒。
一個有效的解決方案是使用numpy_indexed包(免責聲明:我是它的作者):
import numpy_indexed as npi
npi.group_by(ind).split(vals)
Pandas有一個非常快速的分組代碼(我認為它是用C語言編寫的),所以如果你不介意加載庫,你可以這樣做:
import pandas as pd
pdata=pd.DataFrame({'vals':vals,'ind':ind})
resultsp = pdata.groupby('ind').sum().values
或更一般地說:
pdata=pd.DataFrame({'vals':vals,'ind':ind})
resultsp = pdata.groupby('ind').agg(func).values
雖然后者對標准聚合函數較慢(如sum,mean等)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.