Подтвердить что ты не робот

Matplotlib xticks не выстраивается с гистограммой

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

Здесь образец кода, который я использую для генерации гистограммы:

from matplotlib import pyplot as py

py.hist(histogram_data, 49, alpha=0.75)
py.title(column_name)
py.xticks(range(49))
py.show()

Я знаю, что все значения в массиве histogram_data находятся в [0,1,...,48]. Который, предполагая, что я сделал правильную математику, означает, что существует 49 уникальных значений. Я бы хотел показать гистограмму каждого из этих значений. Вот картина того, что сгенерировано.

testing

Как настроить график таким образом, чтобы все xticks были выровнены влево, в середине или справа от каждой из баров?

4b9b3361

Ответ 1

Короткий ответ: Используйте plt.hist(data, bins=range(50)) вместо этого, чтобы получить выровненные по левому краю ячейки, plt.hist(data, bins=np.arange(50)-0.5), чтобы получить выровненные по центру ячейки и т.д.

Кроме того, если производительность имеет значение, потому что вы хотите подсчет уникальных целых чисел, есть несколько несколько более эффективных методов (np.bincount), которые я покажу в конце.

Заявление о проблемах


В качестве отдельного примера того, что вы видите, рассмотрите следующее:

import matplotlib.pyplot as plt
import numpy as np

# Generate a random array of integers between 0-9
# data.min() will be 0 and data.max() will be 9 (not 10)
data = np.random.randint(0, 10, 1000)

plt.hist(data, bins=10)
plt.xticks(range(10))
plt.show()

enter image description here

Как вы заметили, ящики не выровнены с целыми интервалами. Это в основном потому, что вы задали 10 бункеров между 0 и 9, что не совсем то же самое, что запрашивать бункеры для 10 уникальных значений.

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

Чтобы объяснить, что происходит, пропустите matplotlib.pyplot.hist и просто используйте базовую функцию numpy.histogram.

Например, допустим, что у вас есть значения [0, 1, 2, 3]. Ваш первый инстинкт должен был сделать:

In [1]: import numpy as np

In [2]: np.histogram([0, 1, 2, 3], bins=4)
Out[2]: (array([1, 1, 1, 1]), array([ 0.  ,  0.75,  1.5 ,  2.25,  3.  ]))

Первый возвращаемый массив - это подсчеты, а второй - края бункера (другими словами, где границы бруса будут в вашем графике).

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

Затем вы можете попробовать:

In [3]: np.histogram([0, 1, 2, 3], bins=3)
Out[3]: (array([1, 1, 2]), array([ 0.,  1.,  2.,  3.]))

Обратите внимание, что края бина (второй массив) - это то, что вы ожидали, но подсчеты не совпадают. Это потому, что последний бит ведет себя иначе, чем другие, как указано в документации для numpy.histogram:

Notes
-----
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.

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

In [4]: np.histogram([0, 1, 2, 3], bins=range(5))
Out[4]: (array([1, 1, 1, 1]), array([0, 1, 2, 3, 4]))

Выравнивание бинов


Теперь применим это к первому примеру и посмотрим, как он выглядит:

import matplotlib.pyplot as plt
import numpy as np

# Generate a random array of integers between 0-9
# data.min() will be 0 and data.max() will be 9 (not 10)
data = np.random.randint(0, 10, 1000)

plt.hist(data, bins=range(11)) # <- The only difference
plt.xticks(range(10))
plt.show()

enter image description here

Хорошо, отлично! Однако теперь у нас теперь есть выровненные слева вырезы. Что, если бы мы хотели, чтобы выровненные по центру ячейки лучше отражали тот факт, что это уникальные значения?

Быстрый способ - просто сместить края бункера:

import matplotlib.pyplot as plt
import numpy as np

# Generate a random array of integers between 0-9
# data.min() will be 0 and data.max() will be 9 (not 10)
data = np.random.randint(0, 10, 1000)

bins = np.arange(11) - 0.5
plt.hist(data, bins)
plt.xticks(range(10))
plt.xlim([-1, 10])

plt.show()

enter image description here

Аналогично для выровненных по правому краю бинов просто сдвиньте на -1.

Другой подход


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

Если вы имеете дело с уникальными целыми числами, начиная с 0, вам лучше использовать numpy.bincount, чем использовать numpy.hist.

Например:

import matplotlib.pyplot as plt
import numpy as np

data = np.random.randint(0, 10, 1000)
counts = np.bincount(data)

# Switching to the OO-interface. You can do all of this with "plt" as well.
fig, ax = plt.subplots()
ax.bar(range(10), counts, width=1, align='center')
ax.set(xticks=range(10), xlim=[-1, 10])

plt.show()

enter image description here

Есть два больших преимущества для этого подхода. Один - это скорость. numpy.histogram (и поэтому plt.hist) в основном запускает данные через numpy.digitize, а затем numpy.bincount. Поскольку вы имеете дело с уникальными целыми значениями, нет необходимости выполнять шаг numpy.digitize.

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

import matplotlib.pyplot as plt
import numpy as np

data = np.random.randint(0, 10, 1000)
counts = np.bincount(data)

# Switching to the OO-interface. You can do all of this with "plt" as well.
fig, ax = plt.subplots()
ax.bar(range(10), counts, width=0.8, align='center')
ax.set(xticks=range(10), xlim=[-1, 10])

plt.show()

enter image description here

Ответ 2

Использование интерфейса OO для настройки тиков имеет то преимущество, что центрирует метки при сохранении меток. Кроме того, он работает с любой функцией построения графиков и не зависит от np.bincount() или ax.bar()

import matplotlib.ticker as tkr
data = np.random.randint(0, 10, 1000)
mybins = range(11)
fig, ax = subplots()
ax.hist(data, bins=mybins, rwidth=0.8)
ax.set_xticks(mybins)
ax.xaxis.set_minor_locator(tkr.AutoMinorLocator(n=2))
ax.xaxis.set_minor_formatter(tkr.FixedFormatter(mybins))
ax.xaxis.set_major_formatter(tkr.NullFormatter())

for tick in ax.xaxis.get_minor_ticks():
    tick.tick1line.set_markersize(0)

result

Ответ 3

Если комментарий bins.append(sorted(set(labels))[-1]):

enter image description here

bins = [i_bin - 0.5 for i_bin in set(labels)]
# bins.append(sorted(set(labels))[-1])
plt.hist(labels, bins)
plt.show()

Если не:

the last bar is visible in plot

bins = [i_bin - 0.5 for i_bin in set(labels)]
bins.append(sorted(set(labels))[-1])
plt.hist(labels, bins)
plt.show()