Гистограмма для дискретных значений с matplotlib

Мне иногда приходится записывать дискретные значения гистограммы с помощью matplotlib. В этом случае выбор биннинга может иметь решающее значение: если вы используете гистограмму [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], используя 10 бункеров, один из бункеров будет иметь два как многие считают, как другие. Другими словами, binsize обычно должен быть кратным размеру дискретизации.

В то время как этот простой случай относительно легко обрабатывать сам по себе, имеет ли кто-нибудь указатель на библиотеку/функцию, которая будет обрабатывать это автоматически, в том числе в случае данных с плавающей запятой, где размер дискретизации может быть немного изменен из-за округления FP?

Спасибо.

Ответ 1

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

Вы можете найти этот размер дискретизации (или, по крайней мере, строго, n раз этот размер, поскольку у вас могут не быть двух смежных сэмплов в ваших данных)

np.diff(np.unique(data)).min()

Это находит уникальные значения в ваших данных (np.unique), находит различия между ними (np.diff). Уникальный необходим, чтобы вы не получали нулевых значений. Затем вы найдете минимальную разницу. Там могут быть проблемы, когда постоянная дискретизации очень мала - я вернусь к этому.

Далее - вы хотите, чтобы ваши значения находились в середине бункера - ваша текущая проблема связана с тем, что и 9, и 10 находятся по краям последнего бина, который автоматически загружает matplotlib, поэтому вы получаете два образца в одном бункере.

Итак - попробуйте следующее:

import matplotlib.pyplot as plt
import numpy as np

data = range(11)
data = np.array(data)

d = np.diff(np.unique(data)).min()
left_of_first_bin = data.min() - float(d)/2
right_of_last_bin = data.max() + float(d)/2
plt.hist(data, np.arange(left_of_first_bin, right_of_last_bin + d, d))
plt.show()

Это дает:

Histogram of sample data


Малая нецелочисленная дискретизация

Мы можем сделать немного больше набора данных тестирования, например.

import random 

data = []
for _ in range(1000):
    data.append(random.randint(1,100))
data = np.array(data)
nasty_d = 1.0 / 597 #Arbitrary smallish discretization
data = data * nasty_d

Если вы затем запустите это через массив выше и посмотрите на d, что код выплюнул, вы увидите

>>> print(nasty_d)
0.0016750418760469012
>>> print(d)
0.00167504187605

Итак - обнаруженное значение d не является "реальным" значением nasty_d, с которым были созданы данные. Однако - с трюком смещения бункеров на половину d, чтобы получить значения в середине - это не должно иметь значения , если ваша дискретизация очень мала, поэтому ваш вниз в пределах точности от float или у вас есть 1000 ящиков, а разница между обнаруженной d и "реальной" дискретизацией может нарастать до такой точки, что один из бункеров "пропускает" точку данных. Это то, что нужно знать, но, вероятно, не ударит вас.

Примерный график для вышеописанного

Example histogram with small discretization


Неравномерная дискретизация/наиболее подходящие ячейки...

Для более сложных случаев вам может понравиться в этом сообщении в блоге, которое я нашел. Это рассматривает способы автоматического "обучения" лучшим ширинам бинов из (непрерывных/квазинепрерывных) данных, ссылаясь на несколько стандартных методов, таких как правило Стурджа и Фридмана и Diaconis ", прежде чем разработать собственный метод байесовского динамического программирования.

Если это ваш прецедент - вопрос гораздо шире и не подходит для окончательного ответа на переполнение стека, хотя, надеюсь, ссылки помогут.

Ответ 2

Возможно, менее полный ответ, чем J Richard Snape, но тот, который я недавно узнал и который я нашел интуитивно понятным и простым.

import numpy as np
import matplotlib.pyplot as plt

# great seed
np.random.seed(1337)

# how many times will a fair die land on the same number out of 100 trials.
data = np.random.binomial(n=100, p=1/6, size=1000)

# the trick is to set up the bins centered on the integers, i.e.
# -0.5, 0.5, 1,5, 2.5, ... up to max(data) + 1.5. Then you substract -0.5 to
# eliminate the extra bin at the end.
bins = np.arange(0, data.max() + 1.5) - 0.5

# then you plot away
fig, ax = plt.subplots()
_ = ax.hist(data, bins)
ax.set_xticks(bins + 0.5)

enter image description here

Оказывается, что около 16/100 бросков будет одинаковым числом!

Ответ 3

Еще одна версия для простой обработки небольшого количества кода! на этот раз используя numpy.unique и matplotlib.vlines:

import numpy as np
import matplotlib.pyplot as plt

# same seed/data as Manuel Martinez to make plot easy to compare
np.random.seed(1337)
data = np.random.binomial(100, 1/6, 1000)

values, counts = np.unique(data, return_counts=True)

plt.vlines(values, 0, counts, color='C0', lw=4)

# optionally set y-axis up nicely
plt.ylim(0, max(counts) * 1.06)

давая мне:

matplotlib output

который выглядит в высшей степени читабельным