Numpy гистограмма больших массивов - PullRequest
15 голосов
/ 17 марта 2010

У меня есть несколько наборов данных CSV, размером около 10 Гб каждый. Я хотел бы генерировать гистограммы из их столбцов. Но похоже, что единственный способ сделать это в numpy - это сначала загрузить весь столбец в массив numpy, а затем вызвать numpy.histogram для этого массива. Это потребляет ненужный объем памяти.

Numpy поддерживает онлайн-биннинг? Я надеюсь на что-то, что перебирает мой CSV построчно и значения бинов, когда он их читает. Таким образом, в памяти одновременно может храниться не более одной строки.

Нетрудно было бы кататься самостоятельно, но мне интересно, если бы кто-то уже изобрел это колесо.

Ответы [ 4 ]

12 голосов
/ 17 марта 2010

Как вы сказали, это не так сложно накатить свой собственный. Вам нужно будет самостоятельно настроить ячейки и повторно использовать их при переборе файла. Следующее должно быть хорошей отправной точкой:

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

Я предполагаю, что производительность будет иметь проблемы с такими большими файлами, и затраты на вызов гистограммы в каждой строке могут быть слишком медленными @ предложение Дуга генератора кажется хорошим способом решения этой проблемы.

6 голосов
/ 17 марта 2010

Вот способ напрямую связать ваши значения:

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)

Учитывая размер вашего набора данных, полезно использовать 'loadtxt' Numpy для создания генератора:

data_array = NP.loadtxt(data_file.txt, delimiter=",")
def fnx() :
  for i in range(0, data_array.shape[1]) :
    yield dx[:,i]
4 голосов
/ 29 декабря 2013

Объединение с деревом Фенвика (очень большой набор данных; необходимы процентильные границы)

Я публикую второй ответ на тот же вопрос, поскольку этот подход сильно отличается и затрагивает разные проблемы.

Что если у вас ОЧЕНЬ большой набор данных (миллиарды образцов), и вы заранее не знаете, ГДЕ должны быть границы вашего бина? Например, может быть, вы хотите объединить вещи в квартили или децили.

Для небольших наборов данных ответ прост: загрузите данные в массив, затем сортируйте, затем считывайте значения с любого заданного процентиля, переходя к индексу, который указан в процентах от массива.

Для больших наборов данных, в которых объем памяти для хранения массива нецелесообразен (не говоря уже о времени сортировки) ... затем рассмотрите возможность использования дерева Фенвика, также известного как «Двоичное индексированное дерево».

Я думаю, что они работают только для положительных целочисленных данных, поэтому вам, по крайней мере, нужно знать достаточно о вашем наборе данных, чтобы сместить (и, возможно, масштабировать) ваши данные, прежде чем заносить их в таблицу Fenwick Tree.

Я использовал это, чтобы найти медиану из 100 миллиардов выборочных наборов данных, в разумные сроки и в очень удобных пределах памяти. (Рассмотрите возможность использования генераторов для открытия и чтения файлов, как в моем другом ответе; это все еще полезно.)

Подробнее о деревьях Фенвика:

2 голосов
/ 29 декабря 2013

Объединение с генераторами ( большой набор данных; ячейки фиксированной ширины; данные с плавающей запятой )

Если вы заранее знаете ширину желаемых корзин - даже если есть сотни или тысячи корзин - тогда я думаю, что развертывание вашего собственного решения будет быстрым (как для записи, так и для запуска). Вот некоторый 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

Значения могут быть числами с плавающей точкой, но это предполагает, что вы используете целочисленную двоичную ширину; вам может понадобиться немного изменить это, если вы хотите использовать бинарную ширину некоторого значения с плавающей запятой.

Что касается 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
...