├── .gitignore ├── LICENSE ├── README.md ├── histpoints.png ├── histpoints_binwidth.png ├── matplotlib_hep └── __init__.py ├── pull.png ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | target/ 61 | 62 | #Ipython Notebook 63 | .ipynb_checkpoints 64 | 65 | # pyenv 66 | .python-version 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Igor Babuschkin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # matplotlib-hep 2 | 3 | An add-on for matplotlib that simplifies the creation of plots for high energy physics. 4 | 5 | ## Getting started 6 | 7 | This package is not yet on PyPI. 8 | You can install it from this repository by running 9 | ```bash 10 | pip install --user git+https://github.com/ibab/matplotlib-hep 11 | ``` 12 | 13 | ## The histpoints plotting function 14 | 15 | In high energy physics (HEP), histograms are often displayed as a collection of 16 | data points, one for each bin. This allows one to easily compare the data to 17 | an underlying probability model. 18 | 19 | It can be debated if it is correct to attach error bars to the individual bin 20 | contents as opposed to the underlying model, as we want to know if our 21 | expectation for any given bin could fluctuate to match the data and not vice 22 | versa. But this is a convention widely used by the HEP community, and thus a 23 | way to use this kind of plot in Python is often necessary. 24 | 25 | A simple way to create these kinds of plots is missing from other Python 26 | packages like matplotlib. The `histpoints` function is designed to produce 27 | these plots conventiently, like in the following example: 28 | 29 | ```python 30 | from matplotlib_hep import histpoints 31 | import matplotlib.pyplot as plt 32 | import numpy as np 33 | import scipy.stats as stats 34 | 35 | data = np.random.normal(0, 1, 200) 36 | x, y, norm = histpoints(data) 37 | 38 | xs = np.linspace(-4, 4, 200) 39 | plt.plot(xs, norm * stats.norm.pdf(xs, 0, 1), 'b-', lw=2) 40 | 41 | plt.savefig('histpoints.png') 42 | ``` 43 | 44 |
45 | 46 | Or, displaying horizontal error bars to mark the bin width: 47 | ```python 48 | histpoints(data, xerr='binwidth') 49 | ``` 50 | 51 |
52 | 53 | Note that the `histpoints` function returns the `x` and `y` coordinates of the 54 | points, as well as `norm`, the integral of the histogram. This can be used to 55 | scale a probability distribution to the histogram, as opposed to the other way 56 | around, like in the example above. 57 | This is often preferred in HEP, as it allows you to gauge the amount of entries 58 | in your data sample more easily. 59 | 60 | By default, `histpoints` chooses the number of bins automatically via the 61 | [Freedman-Diaconis](https://en.wikipedia.org/wiki/Freedman%E2%80%93Diaconis_rule) 62 | rule. 63 | 64 | ## The plot\_pull function 65 | 66 | ```python 67 | from matplotlib_hep import plot_pull 68 | import matplotlib.pyplot as plt 69 | import numpy as np 70 | import scipy as sp 71 | 72 | data = np.random.normal(0, 1, 1000) 73 | 74 | func = lambda x: sp.stats.norm.pdf(x, 0, 1) 75 | 76 | plot_pull(data, func) 77 | plt.savefig('pull.png') 78 | ``` 79 | 80 |
81 | 82 | -------------------------------------------------------------------------------- /histpoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibab/matplotlib-hep/7ff83ffbc059a0ca9326f1ecb39979b13e33b22d/histpoints.png -------------------------------------------------------------------------------- /histpoints_binwidth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibab/matplotlib-hep/7ff83ffbc059a0ca9326f1ecb39979b13e33b22d/histpoints_binwidth.png -------------------------------------------------------------------------------- /matplotlib_hep/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | 3 | import numpy as np 4 | import scipy.stats as stats 5 | import scipy as sp 6 | import logging 7 | 8 | 9 | __all__ = ['histpoints', 'make_split', 'calc_nbins', 'plot_pull'] 10 | 11 | def calc_nbins(x, maximum=150): 12 | n = (max(x) - min(x)) / (2 * len(x)**(-1/3) * (np.percentile(x, 75) - np.percentile(x, 25))) 13 | return np.floor(min(n, maximum)) 14 | 15 | def poisson_limits(N, kind, confidence=0.6827): 16 | alpha = 1 - confidence 17 | upper = np.zeros(len(N)) 18 | lower = np.zeros(len(N)) 19 | if kind == 'gamma': 20 | lower = stats.gamma.ppf(alpha / 2, N) 21 | upper = stats.gamma.ppf(1 - alpha / 2, N + 1) 22 | elif kind == 'sqrt': 23 | err = np.sqrt(N) 24 | lower = N - err 25 | upper = N + err 26 | else: 27 | raise ValueError('Unknown errorbar kind: {}'.format(kind)) 28 | # clip lower bars 29 | lower[N==0] = 0 30 | return N - lower, upper - N 31 | 32 | def histpoints(x, bins=None, xerr=None, yerr='gamma', normed=False, **kwargs): 33 | """ 34 | Plot a histogram as a series of data points. 35 | 36 | Compute and draw the histogram of *x* using individual (x,y) points 37 | for the bin contents. 38 | 39 | By default, vertical poisson error bars are calculated using the 40 | gamma distribution. 41 | 42 | Horizontal error bars are omitted by default. 43 | These can be enabled using the *xerr* argument. 44 | Use ``xerr='binwidth'`` to draw horizontal error bars that indicate 45 | the width of each histogram bin. 46 | 47 | Parameters 48 | --------- 49 | 50 | x : (n,) array or sequence of (n,) arrays 51 | Input values. This takes either a single array or a sequence of 52 | arrays, which are not required to be of the same length. 53 | 54 | """ 55 | import matplotlib.pyplot as plt 56 | 57 | if bins is None: 58 | bins = calc_nbins(x) 59 | 60 | h, bins = np.histogram(x, bins=bins) 61 | width = bins[1] - bins[0] 62 | center = (bins[:-1] + bins[1:]) / 2 63 | area = sum(h * width) 64 | 65 | if isinstance(yerr, str): 66 | yerr = poisson_limits(h, yerr) 67 | 68 | if xerr == 'binwidth': 69 | xerr = width / 2 70 | 71 | if normed: 72 | h = h / area 73 | yerr = yerr / area 74 | area = 1. 75 | 76 | if not 'color' in kwargs: 77 | kwargs['color'] = 'black' 78 | 79 | if not 'fmt' in kwargs: 80 | kwargs['fmt'] = 'o' 81 | 82 | plt.errorbar(center, h, xerr=xerr, yerr=yerr, **kwargs) 83 | 84 | return center, (yerr[0], h, yerr[1]), area 85 | 86 | def make_split(ratio, gap=0.12): 87 | import matplotlib.pyplot as plt 88 | from matplotlib.gridspec import GridSpec 89 | from matplotlib.ticker import MaxNLocator 90 | cax = plt.gca() 91 | box = cax.get_position() 92 | xmin, ymin = box.xmin, box.ymin 93 | xmax, ymax = box.xmax, box.ymax 94 | gs = GridSpec(2, 1, height_ratios=[ratio, 1 - ratio], left=xmin, right=xmax, bottom=ymin, top=ymax) 95 | gs.update(hspace=gap) 96 | 97 | ax = plt.subplot(gs[0]) 98 | plt.setp(ax.get_xticklabels(), visible=False) 99 | bx = plt.subplot(gs[1], sharex=ax) 100 | 101 | return ax, bx 102 | 103 | def plot_pull(data, func): 104 | 105 | import numpy as np 106 | import matplotlib.pyplot as plt 107 | from matplotlib.ticker import MaxNLocator 108 | 109 | ax, bx = make_split(0.8) 110 | 111 | plt.sca(ax) 112 | 113 | x, y, norm = histpoints(data) 114 | 115 | lower, upper = ax.get_xlim() 116 | 117 | xs = np.linspace(lower, upper, 200) 118 | plt.plot(xs, norm * func(xs), 'b-') 119 | 120 | #plt.gca().yaxis.set_major_locator(MaxNLocator(prune='lower')) 121 | 122 | plt.sca(bx) 123 | 124 | resid = y[1] - norm * func(x) 125 | err = np.zeros_like(resid) 126 | err[resid >= 0] = y[0][resid >= 0] 127 | err[resid < 0] = y[2][resid < 0] 128 | 129 | pull = resid / err 130 | 131 | plt.errorbar(x, pull, yerr=1, color='k', fmt='o') 132 | plt.ylim(-5, 5) 133 | plt.axhline(0, color='b') 134 | 135 | plt.sca(ax) 136 | 137 | return ax, bx 138 | 139 | -------------------------------------------------------------------------------- /pull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibab/matplotlib-hep/7ff83ffbc059a0ca9326f1ecb39979b13e33b22d/pull.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='matplotlib-hep', 4 | version='0.1.0', 5 | description='A package for creating High Energy Physics plots more easily', 6 | url='http://github.com/ibab/matplotlib-hep', 7 | author='Igor Babuschkin', 8 | author_email='igor@babuschk.in', 9 | license='MIT', 10 | packages=['matplotlib_hep'], 11 | zip_safe=False) 12 | --------------------------------------------------------------------------------