大数组的Numpy直方图

19

我有一堆csv数据集,每个大小约为10GB。我想从它们的列生成直方图。但似乎在numpy中唯一的方法是先将整个列加载到numpy数组中,然后对该数组调用numpy.histogram。这会消耗大量的内存。

numpy是否支持在线分箱?我希望有一种迭代我的csv文件,并在读取值时进行分箱的方法。这样,最多只有一行在内存中。

自己编写不难,但想知道是否已经有人发明了这个轮子。

4个回答

13

如你所说,自己编写并不难。你需要自己设置垃圾箱,并在文件迭代过程中重复使用它们。以下应该是一个不错的起点:

import numpy as np
datamin = -5
datamax = 5
numbins = 20
mybins = np.linspace(datamin, datamax, numbins)
myhist = np.zeros(numbins-1, dtype='int32')
for i in range(100):
    d = np.random.randn(1000,1)
    htemp, jnk = np.histogram(d, mybins)
    myhist += htemp

我猜性能会成为处理如此大的文件的问题,并且在每行调用直方图的开销可能太慢了。 @doug's suggestion 使用生成器似乎是解决这个问题的好方法。

好的解决方案。如果你想让它更快一点,你可以使用 myhist += htemp(我猜这样会更快,因为它直接在原地更新直方图)。 - Eric O. Lebigot
感谢 @EOL。我忘记了一些很好的Python特性,因为我还没有完全从Octave转换过来。还有像生成器这样的高级特性,我还需要学习。 - mtrw

6
这里有一种直接将值分组的方法:
import numpy as NP

column_of_values = NP.random.randint(10, 99, 10)

# set the bin values:
bins = NP.array([0.0, 20.0, 50.0, 75.0])

binned_values = NP.digitize(column_of_values, bins)
'binned_values'是一个索引数组,包含了每个在column_of_values列中的值所属的箱子的索引。 'bincount'会给出(显然)箱子计数:
NP.bincount(binned_values)

考虑到你的数据集的大小,使用Numpy的“loadtxt”构建生成器可能是有用的:

data_array = NP.loadtxt(data_file.txt, delimiter=",")
def fnx() :
  for i in range(0, data_array.shape[1]) :
    yield dx[:,i]

4
但是loadtxt不是会先将整个文件加载到内存中吗?这正是我想要避免的问题。 - pseudosudo
@pseudosudo 请参考以下链接中的Dan H的回答,了解如何制作一个不需要先加载整个文件的生成器:https://dev59.com/AXE95IYBdhLWcg3wE56C#20826401 - Cai

6
使用 Fenwick 树进行分箱 (大型数据集;需要使用百分位数边界) 这里有一个非常不同的方法,可以解决以下问题:当您拥有亿级别的样本数据,并且事先不知道分箱边界在哪里时,该怎么办?例如,您想将数据分成四分位或十分位。
对于小数据集,答案很简单:将数据加载到数组中,排序,然后通过跳转到数组中相应百分比的索引来读取任何给定百分位处的值。
对于大型数据集,无法实际存储数组所需的内存大小(更不用说排序所需的时间),因此考虑使用 Fenwick 树,又称“二进制索引树”。
我认为这些仅适用于正整数数据,因此您至少需要了解有关数据集的足够信息,以便在将其制表到 Fenwick 树之前进行数据移位(和可能的缩放)。
我已经使用此方法在合理的时间和非常舒适的内存限制下找到了 1000 亿个样本数据集的中位数。(考虑使用生成器打开和读取文件,就像我的其他答案一样。)
更多关于 Fenwick 树的内容:

计数是独立于顺序的,不需要一次性将数据加载到数组中或进行排序。 - rafaelvalle

2

使用生成器进行分箱大型数据集;定宽箱子;浮点数数据

如果您事先知道所需箱子的宽度 - 即使有成百上千个桶 - 那么我认为自己编写解决方案会很快(无论是编写还是运行)。以下是一些使用Python的代码,假设您有一个迭代器,可以从文件中获取下一个值:

from math import floor
binwidth = 20
counts = dict()
filename = "mydata.csv"
for val in next_value_from_file(filename):
   binname = int(floor(val/binwidth)*binwidth)
   if binname not in counts:
      counts[binname] = 0
   counts[binname] += 1
print counts

这些值可以是浮点数,但前提是你使用整型的binwidth;如果要使用某个浮点数值作为binwidth,则可能需要进行一些微调。

至于next_value_from_file(),如前所述,你可能需要编写自定义生成器或对象,并使用iter()方法以实现高效读取。该生成器的伪代码如下:

def next_value_from_file(filename):
  f = open(filename)
  for line in f:
     # parse out from the line the value or values you need
     val = parse_the_value_from_the_line(line)
     yield val

如果给定的一行有多个值,则使parse_the_value_from_the_line()要么返回一个列表,要么本身就是一个生成器,并使用以下伪代码:
def next_value_from_file(filename):
  f = open(filename)
  for line in f:
     for val in parse_the_values_from_the_line(line):
       yield val

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接