0

I have two lists given. One, named "bin_edge", represents the lower and upper borders of 24 bins by 25 values. The second, named "counts", represents the according counts (=values) of each bin.

My aim is, if possible, to get a Matplotlib histogram that should look like somewhat that:

enter image description here

So, the first difficulty I face is that the first bin is a section located between the first two values of bin_edge, the second bin is a section between the second and the third element of bin_edge and so on... Means that one value of counts is connected to the two bounds (lower and upper bound) of the bin_edge list.

And the second sticking point: I would like to draw logarithmic x and y axes.

The code I was trying:

bin_edge = [0.36, 0.46, 0.66, 1.00, 1.30, 1.70, 2.20, 3.00, 4.00, 
            5.20, 6.50, 8.00, 10.00, 12.00, 14.00, 16.00, 18.00, 
            20.00, 22.00, 25.00, 28.00, 31.00, 34.00, 37.00, 40.00]

counts = [159746491, 9316595, 855578, 166092, 151198, 41293, 51051, 
          26098, 38536, 1172, 2.872e-12, 24598, 3.27097e-12, 3.86874e-12, 
          4.46613e-12, 5.06328e-12, 5.6602754e-12, 6.2571442e-12, 4.6652e-12, 
          5.26229e-12, 5.8592429e-12, 0, 7.052837e-12, 0] 

plt.hist([counts], bins=bin_edge)
plt.xscale('log')
plt.yscale('log')
plt.xlabel('Size / µm')
plt.ylabel('counts')
plt.title('Histogram')
plt.show()

This leads to an empty plot.

Any other solutions beside Matplotlib including bokeh are welcome, of course as well :-)

Swawa
  • 143
  • 1
  • 9
  • I think you can use plt.step method – Danil Kononyhin Apr 30 '22 at 17:24
  • @Danil Kononyhin Do you have a suggestion how this could look like? I get an ValueError: x and y must have same first dimension, but have shapes (25,) and (24,) – Swawa Apr 30 '22 at 18:03
  • I just took first 24 elements of both arrays :) here is a similar question, I think: https://stackoverflow.com/questions/64616617/matplotlib-plot-histogram-given-pre-computed-counts-and-bins – Danil Kononyhin Apr 30 '22 at 20:08
  • Also, here is GitHub issue related to this problem https://github.com/matplotlib/matplotlib/issues/6669 – Danil Kononyhin Apr 30 '22 at 20:14
  • @DanilKononyhin: well taking the first values only will draw a wrong bin center. The linked similar question is good -I didn`t find it. But as soon as I chose a logarithmic scale on both axes I also get an empty plot with that solution. – Swawa Apr 30 '22 at 20:23

1 Answers1

3

As you already have the heights for each bin, you should create a bar plot.

The x-values should be the bin edges, except for the last. By default, the bars are centered; you need align='edge' to align them with the bin edges. The widths of the bars are the differences of the bin edges.

from matplotlib import pyplot as plt
from matplotlib.ticker import FormatStrFormatter
import numpy as np

bin_edge = [0.36, 0.46, 0.66, 1.00, 1.30, 1.70, 2.20, 3.00, 4.00,
            5.20, 6.50, 8.00, 10.00, 12.00, 14.00, 16.00, 18.00,
            20.00, 22.00, 25.00, 28.00, 31.00, 34.00, 37.00, 40.00]
counts = [159746491, 9316595, 855578, 166092, 151198, 41293, 51051,
          26098, 38536, 1172, 2.872e-12, 24598, 3.27097e-12, 3.86874e-12,
          4.46613e-12, 5.06328e-12, 5.6602754e-12, 6.2571442e-12, 4.6652e-12,
          5.26229e-12, 5.8592429e-12, 0, 7.052837e-12, 0]
fig, ax = plt.subplots(figsize=(12, 5))
ax.bar(x=bin_edge[:-1], height=counts, width=np.diff(bin_edge), align='edge', fc='MediumOrchid', ec='black')
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_xlabel('Size / µm')
ax.set_ylabel('counts')
ax.set_title('Histogram')
ax.margins(x=0.01)  # less margin left and right

ax.xaxis.set_major_formatter(FormatStrFormatter('%g'))
ax.xaxis.set_minor_formatter(FormatStrFormatter('%g'))
# "length" is the length of the tick mark, it also changes the text offset
# "labelsize" is the fontsize of the tick label
ax.tick_params(axis='x', which='minor', length=5, labelsize=8)
ax.tick_params(axis='x', which='major', length=10, labelsize=12)
plt.tight_layout()
plt.show()

histogram with explicit bin edges

JohanC
  • 71,591
  • 8
  • 33
  • 66
  • This is exactly what I was looking for. Thanks a lot also for the helpful explanation of the alignment of the edges. This is a really big help for me in further python understanding :-) Nice Sunday. – Swawa May 01 '22 at 16:30
  • would there anyhow the possibility to print also the minor ticks? I tried `from matplotlib.ticker import FixedLocator, FixedFormatter x_formatter = FixedFormatter(["0.1", "1", "10", "20"]) x_locator = FixedLocator([0.1, 1, 10, 20]) ax.xaxis.set_major_formatter(x_formatter) ax.xaxis.set_major_locator(x_locator)` to test it but minor ticks are not shown – Swawa May 01 '22 at 19:33
  • Is the updated answer what you're after? See e.g. [show labels for minor ticks also](https://stackoverflow.com/questions/17165435/matplotlib-show-labels-for-minor-ticks-also) for more ideas. – JohanC May 01 '22 at 20:19
  • ok, that is working now. The minors shown minors seem not to be possible without the ax-object? – Swawa May 04 '22 at 04:56
  • The `ax` object is always created, whether you use it or not. See e.g. https://matplotlib.org/matplotblog/posts/pyplot-vs-object-oriented-interface/ – JohanC May 04 '22 at 06:14
  • ah, ok, that was a useful hint. Thanks a lot for the insight :-) – Swawa May 05 '22 at 18:24