aptapy 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {aptapy-0.3.0 → aptapy-0.3.2}/PKG-INFO +13 -1
  2. {aptapy-0.3.0 → aptapy-0.3.2}/README.md +4 -0
  3. {aptapy-0.3.0 → aptapy-0.3.2}/docs/index.rst +1 -1
  4. {aptapy-0.3.0 → aptapy-0.3.2}/docs/release_notes.rst +19 -0
  5. {aptapy-0.3.0 → aptapy-0.3.2}/pyproject.toml +10 -0
  6. aptapy-0.3.2/src/aptapy/_version.py +1 -0
  7. {aptapy-0.3.0 → aptapy-0.3.2}/src/aptapy/hist.py +52 -7
  8. {aptapy-0.3.0 → aptapy-0.3.2}/src/aptapy/modeling.py +3 -3
  9. {aptapy-0.3.0 → aptapy-0.3.2}/tests/test_hist.py +32 -5
  10. {aptapy-0.3.0 → aptapy-0.3.2}/tests/test_modeling.py +1 -1
  11. aptapy-0.3.0/src/aptapy/_version.py +0 -1
  12. {aptapy-0.3.0 → aptapy-0.3.2}/.github/workflows/ci.yml +0 -0
  13. {aptapy-0.3.0 → aptapy-0.3.2}/.github/workflows/docs.yml +0 -0
  14. {aptapy-0.3.0 → aptapy-0.3.2}/.github/workflows/pypi.yml +0 -0
  15. {aptapy-0.3.0 → aptapy-0.3.2}/.gitignore +0 -0
  16. {aptapy-0.3.0 → aptapy-0.3.2}/CODE_OF_CONDUCT.md +0 -0
  17. {aptapy-0.3.0 → aptapy-0.3.2}/CONTRIBUTING.md +0 -0
  18. {aptapy-0.3.0 → aptapy-0.3.2}/LICENSE +0 -0
  19. {aptapy-0.3.0 → aptapy-0.3.2}/docs/Makefile +0 -0
  20. {aptapy-0.3.0 → aptapy-0.3.2}/docs/_static/favicon.ico +0 -0
  21. {aptapy-0.3.0 → aptapy-0.3.2}/docs/_static/logo.png +0 -0
  22. {aptapy-0.3.0 → aptapy-0.3.2}/docs/_static/logo_small.png +0 -0
  23. {aptapy-0.3.0 → aptapy-0.3.2}/docs/conf.py +0 -0
  24. {aptapy-0.3.0 → aptapy-0.3.2}/docs/examples/GALLERY_HEADER.rst +0 -0
  25. {aptapy-0.3.0 → aptapy-0.3.2}/docs/examples/composite_fit.py +0 -0
  26. {aptapy-0.3.0 → aptapy-0.3.2}/docs/examples/constrained_fit.py +0 -0
  27. {aptapy-0.3.0 → aptapy-0.3.2}/docs/examples/simple_fit.py +0 -0
  28. {aptapy-0.3.0 → aptapy-0.3.2}/docs/examples/simple_hist1d.py +0 -0
  29. {aptapy-0.3.0 → aptapy-0.3.2}/docs/examples/weighted_hist1d.py +0 -0
  30. {aptapy-0.3.0 → aptapy-0.3.2}/docs/hist.rst +0 -0
  31. {aptapy-0.3.0 → aptapy-0.3.2}/docs/make.bat +0 -0
  32. {aptapy-0.3.0 → aptapy-0.3.2}/docs/modeling.rst +0 -0
  33. {aptapy-0.3.0 → aptapy-0.3.2}/docs/strip.rst +0 -0
  34. {aptapy-0.3.0 → aptapy-0.3.2}/noxfile.py +0 -0
  35. {aptapy-0.3.0 → aptapy-0.3.2}/src/aptapy/__init__.py +0 -0
  36. {aptapy-0.3.0 → aptapy-0.3.2}/src/aptapy/plotting.py +0 -0
  37. {aptapy-0.3.0 → aptapy-0.3.2}/src/aptapy/py.typed +0 -0
  38. {aptapy-0.3.0 → aptapy-0.3.2}/src/aptapy/strip.py +0 -0
  39. {aptapy-0.3.0 → aptapy-0.3.2}/src/aptapy/typing_.py +0 -0
  40. {aptapy-0.3.0 → aptapy-0.3.2}/tests/test_strip.py +0 -0
  41. {aptapy-0.3.0 → aptapy-0.3.2}/tools/release.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aptapy
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Statistical tools for online monitoring and analysis
5
5
  Project-URL: Homepage, https://github.com/lucabaldini/aptapy
6
6
  Project-URL: Issues, https://github.com/lucabaldini/aptapy/issues
@@ -680,6 +680,14 @@ License: GNU GENERAL PUBLIC LICENSE
680
680
  Public License instead of this License. But first, please read
681
681
  <https://www.gnu.org/licenses/why-not-lgpl.html>.
682
682
  License-File: LICENSE
683
+ Classifier: Development Status :: 4 - Beta
684
+ Classifier: Intended Audience :: Science/Research
685
+ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
686
+ Classifier: Operating System :: OS Independent
687
+ Classifier: Programming Language :: Python
688
+ Classifier: Programming Language :: Python :: 3.7
689
+ Classifier: Programming Language :: Python :: 3.13
690
+ Classifier: Topic :: Scientific/Engineering
683
691
  Requires-Python: >=3.7
684
692
  Requires-Dist: cycler
685
693
  Requires-Dist: loguru
@@ -700,10 +708,14 @@ Description-Content-Type: text/markdown
700
708
 
701
709
  <img src="docs/_static/logo.png" alt="logo" width="175"/>
702
710
 
711
+ [![PyPI](https://img.shields.io/pypi/v/aptapy.svg)](https://pypi.org/project/aptapy/)
712
+ ![Python versions](https://img.shields.io/badge/python-3.7--3.13-blue)
703
713
  ![License](https://img.shields.io/github/license/lucabaldini/aptapy.svg)
704
714
  [![CI](https://github.com/lucabaldini/aptapy/actions/workflows/ci.yml/badge.svg)](https://github.com/lucabaldini/aptapy/actions/workflows/ci.yml)
705
715
  [![Docs](https://github.com/lucabaldini/aptapy/actions/workflows/docs.yml/badge.svg)](https://github.com/lucabaldini/aptapy/actions/workflows/docs.yml)
706
716
  [![Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://lucabaldini.github.io/aptapy/)
717
+ ![GitHub last commit](https://img.shields.io/github/last-commit/lucabaldini/aptapy)
718
+
707
719
  [![Ceasefire Now](https://badge.techforpalestine.org/default)](https://techforpalestine.org/learn-more)
708
720
 
709
721
  Statistical tools for online monitoring and analysis.
@@ -1,9 +1,13 @@
1
1
  <img src="docs/_static/logo.png" alt="logo" width="175"/>
2
2
 
3
+ [![PyPI](https://img.shields.io/pypi/v/aptapy.svg)](https://pypi.org/project/aptapy/)
4
+ ![Python versions](https://img.shields.io/badge/python-3.7--3.13-blue)
3
5
  ![License](https://img.shields.io/github/license/lucabaldini/aptapy.svg)
4
6
  [![CI](https://github.com/lucabaldini/aptapy/actions/workflows/ci.yml/badge.svg)](https://github.com/lucabaldini/aptapy/actions/workflows/ci.yml)
5
7
  [![Docs](https://github.com/lucabaldini/aptapy/actions/workflows/docs.yml/badge.svg)](https://github.com/lucabaldini/aptapy/actions/workflows/docs.yml)
6
8
  [![Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://lucabaldini.github.io/aptapy/)
9
+ ![GitHub last commit](https://img.shields.io/github/last-commit/lucabaldini/aptapy)
10
+
7
11
  [![Ceasefire Now](https://badge.techforpalestine.org/default)](https://techforpalestine.org/learn-more)
8
12
 
9
13
  Statistical tools for online monitoring and analysis.
@@ -16,7 +16,7 @@ and analysis of experimental data, with a focus on histogramming, time series, a
16
16
  fitting. It is designed to be lightweight and easy to use, making it suitable for
17
17
  integration into existing data processing pipelines.
18
18
 
19
- The :doc:`examples Gallery <auto_examples/index>` is probably the best place to start.
19
+ The :doc:`example gallery <auto_examples/index>` is probably the best place to start.
20
20
  Have fun!
21
21
 
22
22
 
@@ -4,6 +4,25 @@ Release notes
4
4
  =============
5
5
 
6
6
 
7
+ Version 0.3.2 (2025-10-09)
8
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
9
+
10
+ * Adding binned_statistics method in AbstractHistogram base class to calculate
11
+ statistics from histogram bins
12
+ * Adds extensive test coverage in both 1D and 2D histogram test functions with
13
+ statistical validation
14
+
15
+ * Pull requests merged:
16
+
17
+ - https://github.com/lucabaldini/aptapy/pull/6
18
+
19
+
20
+ Version 0.3.1 (2025-10-09)
21
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
22
+
23
+ * Minor changes.
24
+
25
+
7
26
  Version 0.3.0 (2025-10-08)
8
27
  ~~~~~~~~~~~~~~~~~~~~~~~~~~
9
28
 
@@ -13,6 +13,16 @@ description = "Statistical tools for online monitoring and analysis"
13
13
  readme = "README.md"
14
14
  license = { file = "LICENSE" }
15
15
  requires-python = ">=3.7"
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Programming Language :: Python",
19
+ "Programming Language :: Python :: 3.7",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Operating System :: OS Independent",
22
+ "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
23
+ "Topic :: Scientific/Engineering",
24
+ "Intended Audience :: Science/Research",
25
+ ]
16
26
  authors = [
17
27
  { name = "Luca Baldini", email = "luca.baldini@unipi.it" }
18
28
  ]
@@ -0,0 +1 @@
1
+ __version__ = "0.3.2"
@@ -17,7 +17,7 @@
17
17
  """
18
18
 
19
19
  from abc import ABC, abstractmethod
20
- from typing import List, Sequence
20
+ from typing import List, Sequence, Tuple
21
21
 
22
22
  import numpy as np
23
23
 
@@ -93,6 +93,43 @@ class AbstractHistogram(ABC):
93
93
  """
94
94
  return np.diff(self._edges[axis])
95
95
 
96
+ def binned_statistics(self, axis: int = 0) -> Tuple[float, float]:
97
+ """Return the mean and standard deviation along a specific axis, based
98
+ on the binned data.
99
+
100
+ .. note::
101
+
102
+ This is a crude estimate of the underlying statistics that might be
103
+ useful for monitoring purposes, but should not be relied upon for
104
+ quantitative analysis.
105
+
106
+ This is not the same as computing the mean and standard deviation of
107
+ the unbinned data that filled the histogram, as some information is
108
+ lost in the binning process.
109
+
110
+ In addition, note that we are not applying any bias correction to
111
+ the standard deviation, as we are assuming that the histogram is
112
+ filled with a sufficiently large number of entries. (In most circumstances
113
+ the effect should be smaller than that of the binning itself.)
114
+
115
+ Arguments
116
+ ---------
117
+ axis : int
118
+ the axis along which to compute the statistics.
119
+
120
+ Returns
121
+ -------
122
+ mean : float
123
+ the mean value along the specified axis.
124
+ stddev : float
125
+ the standard deviation along the specified axis.
126
+ """
127
+ values = self.bin_centers(axis)
128
+ weights = self.content.sum(axis=tuple(i for i in range(self.content.ndim) if i != axis))
129
+ mean = np.average(values, weights=weights)
130
+ variance = np.average((values - mean)**2, weights=weights)
131
+ return float(mean), float(np.sqrt(variance))
132
+
96
133
  def fill(self, *values: ArrayLike, weights: ArrayLike = None) -> "AbstractHistogram":
97
134
  """Fill the histogram from unbinned data.
98
135
 
@@ -229,10 +266,18 @@ class Histogram1d(AbstractHistogram):
229
266
  """
230
267
  # If we are not explicitly providing a label at plotting time, use
231
268
  # the one attached to the histogram, if any.
232
- kwargs.setdefault('label', self.label)
269
+ kwargs.setdefault("label", f"{self}")
233
270
  axes.hist(self.bin_centers(0), self._edges[0], weights=self.content, **kwargs)
234
271
  setup_axes(axes, xlabel=self.axis_labels[0], ylabel=self.axis_labels[1])
235
272
 
273
+ def __str__(self) -> str:
274
+ """String formatting.
275
+ """
276
+ mean, rms = self.binned_statistics()
277
+ text = self.label or self.__class__.__name__
278
+ text = f"{text}\nMean: {mean:g}\nRMS: {rms:g}"
279
+ return text
280
+
236
281
 
237
282
  class Histogram2d(AbstractHistogram):
238
283
 
@@ -259,10 +304,10 @@ class Histogram2d(AbstractHistogram):
259
304
  the text label for the z axis (default: "Entries/bin").
260
305
  """
261
306
 
262
- DEFAULT_PLOT_OPTIONS = dict(cmap=plt.get_cmap('hot'))
307
+ DEFAULT_PLOT_OPTIONS = dict(cmap=plt.get_cmap("hot"))
263
308
 
264
309
  def __init__(self, xedges, yedges, label: str = None, xlabel: str = None,
265
- ylabel: str = None, zlabel: str = 'Entries/bin') -> None:
310
+ ylabel: str = None, zlabel: str = "Entries/bin") -> None:
266
311
  """Constructor.
267
312
  """
268
313
  super().__init__((xedges, yedges), label, [xlabel, ylabel, zlabel])
@@ -272,9 +317,9 @@ class Histogram2d(AbstractHistogram):
272
317
  """
273
318
  # pylint: disable=arguments-differ
274
319
  if logz:
275
- vmin = kwargs.pop('vmin', None)
276
- vmax = kwargs.pop('vmax', None)
277
- kwargs.setdefault('norm', matplotlib.colors.LogNorm(vmin, vmax))
320
+ vmin = kwargs.pop("vmin", None)
321
+ vmax = kwargs.pop("vmax", None)
322
+ kwargs.setdefault("norm", matplotlib.colors.LogNorm(vmin, vmax))
278
323
  mappable = axes.pcolormesh(*self._edges, self.content.T, **kwargs)
279
324
  plt.colorbar(mappable, ax=axes, label=self.axis_labels[2])
280
325
  setup_axes(axes, xlabel=self.axis_labels[0], ylabel=self.axis_labels[1])
@@ -316,10 +316,10 @@ class FitStatus:
316
316
  if self.chisquare is None:
317
317
  return "N/A"
318
318
  if spec.endswith(Format.LATEX):
319
- return f"$\\chi^2$ = {self.chisquare:.2f} / {self.dof} dof"
319
+ return f"$\\chi^2$: {self.chisquare:.2f} / {self.dof} dof"
320
320
  if spec.endswith(Format.PRETTY):
321
- return f"χ² = {self.chisquare:.2f} / {self.dof} dof"
322
- return f"chisquare = {self.chisquare:.2f} / {self.dof} dof"
321
+ return f"χ²: {self.chisquare:.2f} / {self.dof} dof"
322
+ return f"chisquare: {self.chisquare:.2f} / {self.dof} dof"
323
323
 
324
324
  def __str__(self) -> str:
325
325
  """String formatting.
@@ -101,22 +101,43 @@ def test_plotting1d(size: int = 100000):
101
101
  plt.figure(inspect.currentframe().f_code.co_name)
102
102
  # Create the first histogram. This has no label attached, so we will have to
103
103
  # provide one at plotting time, if we want to have a corresponding legend entry.
104
+ mean = 0.
105
+ sigma = 1.
104
106
  hist1 = Histogram1d(np.linspace(-5., 5., 100), xlabel='x')
105
- hist1.fill(_RNG.normal(size=size))
107
+ hist1.fill(_RNG.normal(size=size, loc=mean, scale=sigma))
106
108
  hist1.plot(label='Standard histogram')
109
+ m, s = hist1.binned_statistics()
110
+ # Rough checks on the binned statistics---we want the mean to be within 10
111
+ # sigma/sqrt(N) and the stddev to be within 2% of the true value.
112
+ # (Note the binning has an effect on the actual values, so we cannot
113
+ # expect perfect agreement.)
114
+ assert abs((m - mean) / sigma * np.sqrt(size)) < 10.
115
+ assert abs(s / sigma - 1.) < 0.02
116
+
107
117
  # Create a second histogram, this time with a label---this should have a
108
118
  # proper entry in the legend automatically.
119
+ mean = 1.
120
+ sigma = 1.5
109
121
  hist2 = Histogram1d(np.linspace(-5., 5., 100), label='Offset histogram')
110
- hist2.fill(_RNG.normal(size=size, loc=1.))
122
+ hist2.fill(_RNG.normal(size=size, loc=mean, scale=sigma))
111
123
  hist2.plot()
124
+ m, s = hist2.binned_statistics()
125
+ assert abs((m - mean) / sigma * np.sqrt(size)) < 10.
126
+ assert abs(s / sigma - 1.) < 0.02
127
+
112
128
  # And this one should end up with no legend entry, as it has no label
129
+ mean = -1.
130
+ sigma = 0.5
113
131
  hist3 = Histogram1d(np.linspace(-5., 5., 100))
114
- hist3.fill(_RNG.normal(size=size // 2, loc=-1.))
132
+ hist3.fill(_RNG.normal(size=size, loc=mean, scale=sigma))
115
133
  hist3.plot()
134
+ m, s = hist3.binned_statistics()
135
+ assert abs((m - mean) / sigma * np.sqrt(size)) < 10.
136
+ assert abs(s / sigma - 1.) < 0.02
116
137
  plt.legend()
117
138
 
118
139
 
119
- def test_plotting2d(size: int = 100000):
140
+ def test_plotting2d(size: int = 100000, x0: float = 1., y0: float = -1.):
120
141
  """Test plotting.
121
142
  """
122
143
  plt.figure(inspect.currentframe().f_code.co_name)
@@ -124,8 +145,14 @@ def test_plotting2d(size: int = 100000):
124
145
  hist = Histogram2d(edges, edges, 'x', 'y')
125
146
  # Note we are adding different offsets to x and y so that we can see
126
147
  # the effect on the plot.
127
- hist.fill(_RNG.normal(size=size) + 1., _RNG.normal(size=size) - 1.)
148
+ hist.fill(_RNG.normal(size=size, loc=x0), _RNG.normal(size=size, loc=y0))
128
149
  hist.plot()
150
+ mx, sx = hist.binned_statistics(0)
151
+ my, sy = hist.binned_statistics(1)
152
+ assert abs((mx - x0) * np.sqrt(size)) < 10.
153
+ assert abs((my - y0) * np.sqrt(size)) < 10.
154
+ assert abs(sx - 1.) < 0.02
155
+ assert abs(sy - 1.) < 0.02
129
156
  plt.gca().set_aspect('equal')
130
157
 
131
158
 
@@ -26,7 +26,7 @@ from aptapy.plotting import plt
26
26
 
27
27
  _RNG = np.random.default_rng(313)
28
28
 
29
- TEST_HISTOGRAM = Histogram1d(np.linspace(-5., 5., 100), label="Test data")
29
+ TEST_HISTOGRAM = Histogram1d(np.linspace(-5., 5., 100), label="Random data")
30
30
  TEST_HISTOGRAM.fill(_RNG.normal(size=100000))
31
31
  NUM_SIGMA = 4.
32
32
 
@@ -1 +0,0 @@
1
- __version__ = "0.3.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes