Python:对一个坐标进行分箱并根据这些分箱对另一个坐标进行平均

Python: Binning one coordinate and averaging another based on these bins

我有两个向量 rev_countstars。这些形式对的元素(假设 rev_count 是 x 坐标,stars 是 y 坐标)。

我想按 rev_count 对数据进行分箱,然后在单个 rev_count bin 中平均 stars (我想沿 x 轴分箱并计算平均 y 坐标在那个箱子里)。

这是我尝试使用的代码(受我的 matlab 背景启发):

import matplotlib.pyplot as plt
import numpy

binwidth = numpy.max(rev_count)/10
revbin = range(0, numpy.max(rev_count), binwidth)
revbinnedstars = [None]*len(revbin)

for i in range(0, len(revbin)-1):
    revbinnedstars[i] = numpy.mean(stars[numpy.argwhere((revbin[i]-binwidth/2) < rev_count < (revbin[i]+binwidth/2))])

print('Plotting binned stars with count')
plt.figure(3)
plt.plot(revbin, revbinnedstars, '.')
plt.show()

然而,这似乎令人难以置信slow/inefficient。在 python 中有更自然的方法吗?

我想你正在使用 Python 2 但如果不是,你应该在计算步骤时将除法更改为 // (地板除法)否则 numpy 会很生气,因为它不能将浮点数解释为步骤.

binwidth = numpy.max(rev_count)//10 # Changed this to floor division
revbin = range(0, numpy.max(rev_count), binwidth)
revbinnedstars = [None]*len(revbin)

for i in range(0, len(revbin)-1):
    # I actually don't know what you wanted to do but I guess you wanted the
    # "logical and" combination in that bin (you don't need to use np.where here)
    # You can put that all in one statement but it gets crowded so I'll split it:
    index1 = revbin[i]-binwidth/2 < rev_count
    index2 = rev_count < revbin[i]+binwidth/2)
    revbinnedstars[i] = numpy.mean(stars[np.logical_and(index1, index2)])

这至少应该有效并给出正确的结果。如果你有庞大的数据集并且想要超过 10 个 bin,那将是非常低效的。

一个非常重要的收获:

  • 如果要索引数组,请不要使用 np.argwhere。该结果应该是 人类可读的 。如果你真的想要坐标使用 np.where。这可以用作索引,但如果您有多维输入,那么阅读起来就不那么漂亮了。

numpy documentation 在这一点上支持我:

The output of argwhere is not suitable for indexing arrays. For this purpose use where(a) instead.

这也是您的代码运行缓慢的原因。它试图做一些您不希望它做的事情,这可能 非常 占用大量内存和 cpu 使用。没有给你正确的结果。

我在这里所做的叫做boolean masks。比np.where(condition)写起来更短,少了一次计算。


可以通过定义一个知道哪些星星在哪个 bin 中的网格来使用完全矢量化的方法:

bins = 10
binwidth = numpy.max(rev_count)//bins
revbin = np.arange(0, np.max(rev_count)+binwidth+1, binwidth)

定义 bin 的更好方法是。请注意,您必须将 1 添加到最大值,因为您想要包含它,并且将 1 添加到 bin 的数量,因为您对 bin 起点和终点而不是 bin 的中心感兴趣:

number_of_bins = 10
revbin = np.linspace(np.min(rev_count), np.max(rev_count)+1, number_of_bins+1)

然后你可以设置网格:

grid = np.logical_and(rev_count[None, :] >= revbin[:-1, None], rev_count[None, :] < revbin[1:, None])

网格是 bins x rev_count 大(由于广播,我将每个数组的维度增加了一个 但是 而不是相同的)。这实质上是检查一个点是否大于较低的 bin 范围并且小于较高的 bin 范围(因此 [:-1][1:] 索引)。这是多维的,其中计数在第二维(numpy 轴=1)和第一维(numpy 轴=0)

因此,我们只需将这些与此网格相乘即可获得相应容器中星星的 Y 坐标:

stars * grid

要计算均值,我们需要此 bin 中坐标的总和,并将其除以该 bin 中的星星数(bin 沿 axis=1,不在此 bin 中的星星只有沿此轴的值为零):

revbinnedstars = np.sum(stars * grid, axis=1) / np.sum(grid, axis=1)

我其实不知道这样是否更有效率。它在内存中会贵很多,但在 CPU.

中可能会便宜一些

Scipy 有一个 function for this:

from scipy.stats import binned_statistic

revbinnedstars, edges, _ = binned_statistic(rev_count, stars, 'mean', bins=10)
revbin = edges[:-1]

如果您不想使用 scipy,numpy 中还有一个 histogram 函数:

sums, edges = numpy.histogram(rev_count, bins=10, weights=stars)
counts, _ = numpy.histogram(rev_count, bins=10)
revbinnedstars = sums / counts

我用于分箱 (x,y) 数据和确定汇总统计数据(例如这些分箱中的平均值)的函数基于 scipy.stats.statistic() 函数。我已经为它写了一个包装器,因为我经常使用它。您可能会发现这很有用...

def binXY(x,y,statistic='mean',xbins=10,xrange=None):
    """
    Finds statistical value of x and y values in each x bin. 
    Returns the same type of statistic for both x and y.
    See scipy.stats.binned_statistic() for options.
    
    Parameters
    ----------
    x : array
        x values.
    y : array
        y values.
    statistic : string or callable, optional
        See documentation for scipy.stats.binned_statistic(). Default is mean.
    xbins : int or sequence of scalars, optional
        If xbins is an integer, it is the number of equal bins within xrange.
        If xbins is an array, then it is the location of xbin edges, similar
        to definitions used by np.histogram. Default is 10 bins.
        All but the last (righthand-most) bin is half-open. In other words, if 
        bins is [1, 2, 3, 4], then the first bin is [1, 2) (including 1, but 
        excluding 2) and the second [2, 3). The last bin, however, is [3, 4], 
        which includes 4.    
        
    xrange : (float, float) or [(float, float)], optional
        The lower and upper range of the bins. If not provided, range is 
        simply (x.min(), x.max()). Values outside the range are ignored.
    
    Returns
    -------
    x_stat : array
        The x statistic (e.g. mean) in each bin. 
    y_stat : array
        The y statistic (e.g. mean) in each bin.       
    n : array of dtype int
        The count of y values in each bin.
        """
    x_stat, xbin_edges, binnumber = stats.binned_statistic(x, x, 
                                 statistic=statistic, bins=xbins, range=xrange)
    
    y_stat, xbin_edges, binnumber = stats.binned_statistic(x, y, 
                                 statistic=statistic, bins=xbins, range=xrange)
    
    n, xbin_edges, binnumber = stats.binned_statistic(x, y, 
                                 statistic='count', bins=xbins, range=xrange)
            
    return x_stat, y_stat, n