如何使用具有大数量垃圾箱的pd.cut?

时间:2019-12-06 20:05:55

标签: python pandas numpy

TLDR: 当有超过3M个bin /类别时,如何将pandas.Series中的记录分配给pd.category?当前正在尝试pd.cut,但这很棘手。

我有一些数据经过直方图,然后使用peakutils查找直方图的峰。然后,我或多或少地在峰之间分配数据。我有一个快速的过程来执行此操作,结果看起来大致如下所示。

说我在bin位置[5, 9, 16]上有一个峰值。我想将位于容器[0, 1, 2, 4, 5, 6, 7]中的所有数据分配给类0[8, 9, 10, 11, 12]中的数据归为1类,而[13, 14, 15, 16, ...]中的数据归为2类。

回收箱的总数很大(> 3M)。可以,因为我的寻峰工作很快。 问题是,当我尝试使用pd.cut将垃圾箱映射回我的原始数据时,事情变得棘手。

通常情况下,我的代码如下所示

# data is a pd.Series with about 600k records in it
hist, edges = np.histogram(data, bins='fd')
peakIndex = peakutils.indexes(hist, thres=0.01, min_dist=10)
peaks_counts = np.zeros(len(enges)-1)

# takes forever when length of `edges` >= 3M
bdata = pd.cut(data, bins=edges, include_lowest=True) # <-- This is what needs to be sped up
bdata["codes"] = bdata[data.name].cat.codes
midpoints = peakIndex[:-1\ + np.ceil(np.diff(peakIndex)/2)
midpoints = np.insert(midpoints, 0, 0)
midpoints = np.append(midpoints, len(edges))

# merge the non point bins with the bins that are peaks as described above. 
for ix in range(len(midpoints)):
    _from = midpoints[ix].astype(int)
    _to = midpoints[ix].astype(int)
    current_peak = np.arange(_from, _to)
    bdata["codes"] = bdata["codes"].replace(current_peak, [edges[ix] for e in current_peak])

直方图hist的计数有0个计数。因此,我认为自己会很聪明,然后删除计数为0的垃圾箱。这样做减少了pd.cut循环进入37k范围内的bin总数。它能够在可接受的时间内完成此操作。我认为问题出在cat.codes的垃圾箱中。他们不再排队与我的current_peak

在撰写本文时,我只是想了些什么,但必须尝试一下,我仍然会问这个问题,以防比我聪明的人有更好的主意。也许我可以用bdata或类似的值来索引edges边。

无论如何,我希望这是清楚的。

TIA

1 个答案:

答案 0 :(得分:0)

大量研究Pandas源代码后,我发现pd.cut的较慢部分不是数据到bin的映射,而是分类数据类型的创建。我所做的或多或少如下:

bins = pd.core.algorithms.unique(bins) # probably not really needed but hey why take the chance

# this is what maps data to bins and its pretty quick
ids = pd.core.dtypes.common.ensure_int64(bins.searchsorted(data, side="left"))
ids[data == bins[0]] = 1 # this is the include lowest feature of `pd.cut`

labels = pd.core.reshape.tile._format_labels(bins, 3, right=True, include_lowest=True)
labels = pd.Categorical(labels, categories=labels, ordered=True)

bdata = pd.core.algorithms.take_nd(labels, ids - 1)
bdata = pd.DataFrame(bdata)
bdata["codes"] = ids

希望这可以帮助其他有类似问题的人。如果您有任何问题,我会尽力回答。