Я создал график гистограммы, график выглядит хорошо, за исключением отметок по оси 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)






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