При построении графика гистограммы возникает проблема с делениями по оси X

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

График гистограммы - Скриншот

На скриншоте тики под столбцами расположены случайным образом, но мне нужно, чтобы положение тиков было точно посередине под столбцом, например 191.

def generate_histogram(data_dict):
    result_folder = 'graphs'
    if not os.path.exists(result_folder):
        os.makedirs(result_folder)
    pdf_file = os.path.join(result_folder, '{filename?_{timestamp}.pdf")
   
    with PdfPages(pdf_file) as pdf:
        for key, values in data_dict.items):
            plt.figure(figsize=(25, 20))
            n, bins, patches = plt.hist(values, bins=38, edgecolor = "black" , alpha=0.7, rwidth=0.8, botton=0, label=key)
            if key.endswith("Width_in_ps'):
                key_value = "Width in ps"
            if key.endswith(Height_in_mV):
                key_value = "Height in mv"
            plt.xlabel(f' {key_valve}')
            plt.ylabel('Count')
            if key:
                t1 = key.replace("in_ps", ""), replace(" in_m", "")
            plt.title(t1)


            # Calculate tick positions for the y-axis
            max_count = int (max(n))
            # Set y-axis ticks to include 0 and the maximum count
            y_ticks = [0, max_count]
            plt.yticks(y_ticks)

            # Ensure unique x-axis tick values
            unique_values = np.unique(values)
            plt.xticks(unique_values, rotation=45)   #To rotate values on x-axis ticks use thisline

            # Annotate each bar with its count
            for i in range(len(patches)):
                if n[i] != 0:
                plt.text(patches[i].get_x) + patches[i].get_width() / 2, patches[i].get_height()/1,
                  str(int(n[i])), ha='center', color='black') # Centering count on top of the bar

            plt.tight_layout()
            pdt.savefig()
            plt.close()

    print(f"\033[92mPDF file '{pdf_file}' has been generated. \033[0m")`\

generate_histogram(data_dict)
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
60
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

bin_centers = 0.5 * (bins[:-1] + bins[1:])
plt.xticks(bin_centers, np.round(bin_centers, 0))
Ответ принят как подходящий

Явные края интервала

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

Вот как это может выглядеть:

import matplotlib.pyplot as plt
import numpy as np

# generate some random test data, similar to the original question
np.random.seed(20240517)
values = np.random.choice(np.random.randint(113, 218, 15), 500)

# explicitly calculate bin edges away from the input values
unique_values = np.unique(values)
bins = np.arange(min(unique_values) - 0.5, max(unique_values) + 1)

plt.figure(figsize=(12, 7))
counts, bin_edges, patches = plt.hist(values, bins=bins, edgecolor = "black", alpha=0.7, rwidth=0.8)

# Calculate tick positions for the y-axis
max_count = int(max(counts))
# Set y-axis ticks to include 0 and the maximum count
y_ticks = [0, max_count]
plt.yticks(y_ticks)

# Ensure unique x-axis tick values
plt.xticks(unique_values, rotation=45)

# Annotate each bar with its count
for count, patch in zip(counts, patches):
    if count != 0:
        plt.text(patch.get_x() + patch.get_width() / 2, patch.get_height(),
                 f'{count:.0f}\n', ha='center', va='center', color='black')

plt.margins(x=0.03)  # less whitespace left and right
plt.tight_layout()
plt.show()

Оригинальные контейнеры

При использовании bins=38 края интервала распределяются равномерно между первым и последним значением, предполагая, что входные данные являются действительными, игнорируя целочисленные края.

Таким образом, например. 185 и 186 (в новом примере) помещаются в одно и то же ведро.

На графике ниже края интервала показаны красными линиями, а положения значений — синим.

import matplotlib.pyplot as plt
import numpy as np

np.random.seed(20240517)
values = np.random.choice(np.random.randint(113, 218, 15), 500)
plt.figure(figsize=(12, 7))
counts, bin_edges, patches = plt.hist(values, bins=38, edgecolor = "black", alpha=0.7, rwidth=0.8)

# Calculate tick positions for the y-axis
max_count = int(max(counts))
# Set y-axis ticks to include 0 and the maximum count
y_ticks = [0, max_count]
plt.yticks(y_ticks)

# Ensure unique x-axis tick values
unique_values = np.unique(values)
plt.xticks(unique_values, rotation=45)

# Annotate each bar with its count
for count, patch in zip(counts, patches):
    if count != 0:
        plt.text(patch.get_x() + patch.get_width() / 2, patch.get_height(),
                 f'{count:.0f}\n', ha='center', va='center', color='black')

# show where the bin edges are located
for edge in bin_edges:
    plt.axvline(edge, color='r', ls='--')
# show where the values are located
for x in unique_values:
    plt.axvline(x, color='b', ls='--', lw=0.5)

plt.margins(x=0.02)  # less whitespace left and right
plt.tight_layout()
plt.show()

График Сиборна с дискретным = True

В качестве альтернативы вы можете использовать histplot Seaborn с discrete=True, который рассчитает края интервала с учетом целочисленных значений.

Новая функция bar_label() Matplotlib может помочь расположить метки (хотя в настоящее время в ней отсутствует возможность пропускать пустые столбцы).

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

np.random.seed(20240517)
values = np.random.choice(np.random.randint(113, 218, 15), 500)
unique_values = np.unique(values)

plt.figure(figsize=(12, 7))
ax = sns.histplot(values, discrete=True)

ax.bar_label(ax.containers[0],
             labels=[f'{bar.get_height()}' if bar.get_height() > 0 else "" for bar in ax.containers[0]])
ax.set_xticks(unique_values)
ax.tick_params(axis='x', rotation=45)

ax.margins(x=0.02)
plt.tight_layout()
plt.show()

Вам просто нужно изменить эту строку:

plt.xticks(bins, rotation=45)

Другие вопросы по теме