Skip to content

Commit 909e9ec

Browse files
committed
Refactor: histogram function and related tests for improved density handling
- Updated the parameter in the function to be optional, defaulting to None for backward compatibility. - Enhanced documentation for the function to clarify behavior of the parameter. - Modified the function in the matplotlib interface to default to density=True, aligning with the new histogram behavior. - Removed unsupported cumulative histogram functionality from the function. - Updated tests for the histogram to reflect changes in density handling and removed tests for cumulative histograms.
1 parent 3e99878 commit 909e9ec

6 files changed

Lines changed: 41 additions & 80 deletions

File tree

docs/API.md

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,20 @@ khisto.histogram(
2323
a: ArrayLike,
2424
range: Optional[tuple[float, float]] = None,
2525
max_bins: Optional[int] = None,
26-
density: bool = False,
26+
density: Optional[bool] = None,
2727
) -> tuple[NDArray[np.floating], NDArray[np.floating]]
2828
```
2929

3030
Compute an optimal histogram using the Khiops binning algorithm.
3131

32-
Drop-in replacement for [`numpy.histogram`](https://numpy.org/doc/stable/reference/generated/numpy.histogram.html), using optimal binning instead of fixed-width bins.
33-
3432
#### Parameters
3533

3634
| Parameter | Type | Default | Description |
3735
|-----------|------|---------|-------------|
3836
| `a` | `ArrayLike` | required | Input data. The histogram is computed over the flattened array. |
3937
| `range` | `tuple[float, float]` | `None` | Lower and upper range of the bins. Values outside are ignored. |
4038
| `max_bins` | `int` | `None` | Maximum number of bins. If not provided, the optimal number is determined automatically. |
41-
| `density` | `bool` | `False` | If `True`, return probability density values; otherwise return counts. |
39+
| `density` | `bool` | `None` | If `False` or `None`, return counts; if `True`, return probability density values. |
4240

4341
#### Returns
4442

@@ -49,7 +47,7 @@ Drop-in replacement for [`numpy.histogram`](https://numpy.org/doc/stable/referen
4947

5048
#### See Also
5149

52-
- [`numpy.histogram`](https://numpy.org/doc/stable/reference/generated/numpy.histogram.html) — NumPy's standard histogram function.
50+
- [`numpy.histogram`](https://numpy.org/doc/stable/reference/generated/numpy.histogram.html) — NumPy's histogram function (`bins` and `weights` parameters are not supported).
5351

5452
#### Examples
5553

@@ -206,37 +204,34 @@ khisto.matplotlib.hist(
206204
x: ArrayLike,
207205
range: Optional[tuple[float, float]] = None,
208206
max_bins: Optional[int] = None,
209-
density: bool = False,
207+
density: bool = True,
210208
ax: Optional[Axes] = None,
211209
**kwargs,
212210
) -> tuple[NDArray[np.floating], NDArray[np.floating], Any]
213211
```
214212

215213
Compute and plot an optimal histogram.
216214

217-
Drop-in replacement for [`matplotlib.pyplot.hist`](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.hist.html) using Khisto's optimal binning algorithm.
218-
219215
#### Parameters
220216

221217
| Parameter | Type | Default | Description |
222218
|-----------|------|---------|-------------|
223-
| `x` | `ArrayLike` | required | Input data. |
224-
| `range` | `tuple[float, float]` | `None` | Lower and upper range of the bins. Values outside are ignored. |
219+
| `x` | `ArrayLike` | required | Input data. The histogram is computed over the flattened array. |
225220
| `max_bins` | `int` | `None` | Maximum number of bins. If `None`, uses optimal binning. |
226-
| `ax` | `Axes` | `None` | Axes to plot on. If `None`, uses current axes. |
227-
| `**kwargs` | | | Other parameters passed to matplotlib. See [`matplotlib.pyplot.hist`](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.hist.html). |
221+
222+
Other parameters are passed to matplotlib. See [`matplotlib.pyplot.hist`](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.hist.html) for styling options.
228223

229224
#### Returns
230225

231226
| Return | Type | Description |
232227
|--------|------|-------------|
233-
| `n` | `NDArray[np.floating]` | The values of the histogram bins. |
228+
| `n` | `NDArray[np.floating]` | The values of the histogram bins (probability density by default). |
234229
| `bins` | `NDArray[np.floating]` | The bin edges. |
235230
| `patches` | `Any` | Container of individual artists (bars or StepPatch). |
236231

237232
#### See Also
238233

239-
- [`matplotlib.pyplot.hist`](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.hist.html)Full documentation of supported parameters.
234+
- [`matplotlib.pyplot.hist`](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.hist.html)Matplotlib's histogram function (`bins`, `weights`, `cumulative`, and stacked/multiple dataset features are not supported).
240235
- [`khisto.histogram`](#histogram) — Underlying histogram computation.
241236

242237
#### Examples
@@ -250,19 +245,20 @@ from khisto.matplotlib import hist
250245

251246
data = np.random.normal(0, 1, 1000)
252247

248+
# Default is density=True
253249
n, bins, patches = hist(data)
254250
plt.xlabel('Value')
255-
plt.ylabel('Count')
251+
plt.ylabel('Density')
256252
plt.title('Optimal Histogram')
257253
plt.show()
258254
```
259255

260-
Density plot:
256+
Frequency plot:
261257

262258
```python
263-
n, bins, patches = hist(data, density=True)
259+
n, bins, patches = hist(data, density=False)
264260
plt.xlabel('Value')
265-
plt.ylabel('Density')
261+
plt.ylabel('Count')
266262
plt.show()
267263
```
268264

sandbox/khisto_demo.ipynb

Lines changed: 3 additions & 3 deletions
Large diffs are not rendered by default.

src/khisto/array/histogram/api.py

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -55,58 +55,43 @@ def histogram(
5555
a: ArrayLike,
5656
range: Optional[tuple[float, float]] = None,
5757
max_bins: Optional[int] = None,
58-
density: bool = False,
58+
density: Optional[bool] = None,
5959
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
6060
"""Compute an optimal histogram using the Khiops binning algorithm.
6161
62-
This function is a drop-in replacement for numpy.histogram, but uses
63-
optimal binning instead of equal-width bins.
64-
6562
Parameters
6663
----------
6764
a : array_like
6865
Input data. The histogram is computed over the flattened array.
6966
range : tuple of (float, float), optional
7067
The lower and upper range of the bins. Values outside the range are
71-
ignored. The first element of the range must be less than or equal
72-
to the second. If not provided, the range is simply
73-
``(a.min(), a.max())``.
68+
ignored. If not provided, the range is ``(a.min(), a.max())``.
7469
max_bins : int, optional
7570
Maximum number of bins. If not provided, the algorithm selects
7671
the optimal number of bins automatically.
77-
density : bool, default False
78-
If True, return probability density values; otherwise return counts.
72+
density : bool, optional
73+
If False, the result will contain the number of samples in each bin.
74+
If True, the result is the value of the probability density function
75+
at the bin, normalized such that the integral over the range is 1.
76+
Default is None, which behaves as False for backwards compatibility.
7977
8078
Returns
8179
-------
8280
hist : ndarray
83-
The values of the histogram. If density is True, these are
84-
probability density values; otherwise, they are counts.
81+
The values of the histogram.
8582
bin_edges : ndarray
8683
The bin edges (length(hist) + 1).
8784
8885
See Also
8986
--------
90-
numpy.histogram : NumPy's standard histogram function.
87+
numpy.histogram : NumPy's histogram function (``bins`` and ``weights``
88+
parameters are not supported).
9189
9290
Notes
9391
-----
9492
Unlike numpy.histogram, this function uses optimal binning which may
9593
produce bins of unequal width. The bins are determined by the Khiops
9694
algorithm to best represent the underlying data distribution.
97-
98-
Examples
99-
--------
100-
>>> import numpy as np
101-
>>> from khisto.array import histogram
102-
>>> data = np.random.normal(0, 1, 1000)
103-
>>> hist, bin_edges = histogram(data)
104-
>>> # Density histogram
105-
>>> density_hist, edges = histogram(data, density=True)
106-
>>> # Constrained number of bins
107-
>>> hist, edges = histogram(data, max_bins=10)
108-
>>> # Filter to a specific range (values outside are ignored)
109-
>>> hist, edges = histogram(data, range=(-2, 2))
11095
"""
11196
# Convert to numpy array and flatten
11297
arr = np.asarray(a, dtype=np.float64).ravel()
@@ -119,6 +104,7 @@ def histogram(
119104
results = compute_histogram(arr)
120105
result = _select_histogram(results, max_bins=max_bins)
121106

107+
# Treat None as False for backwards compatibility (like NumPy)
122108
if density:
123109
return result.density.copy(), result.bin_edges.copy()
124110
else:

src/khisto/matplotlib/hist.py

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ def hist(
1919
x: ArrayLike,
2020
range: Optional[tuple[float, float]] = None,
2121
max_bins: Optional[int] = None,
22-
density: bool = False,
23-
cumulative: bool = False,
22+
density: bool = True,
2423
histtype: str = "bar",
2524
orientation: Literal["vertical", "horizontal"] = "vertical",
2625
log: bool = False,
@@ -35,45 +34,30 @@ def hist(
3534
) -> tuple[np.ndarray, np.ndarray, Any]:
3635
"""Compute and plot an optimal histogram.
3736
38-
Drop-in replacement for `matplotlib.pyplot.hist` using Khisto's optimal
39-
binning algorithm. Most parameters match matplotlib's hist function.
40-
4137
Parameters
4238
----------
4339
x : array_like
44-
Input data.
45-
range : tuple of (float, float), optional
46-
The lower and upper range of the bins. Values outside the range are
47-
ignored. If not provided, the range is ``(x.min(), x.max())``.
40+
Input data. The histogram is computed over the flattened array.
4841
max_bins : int, optional
49-
Maximum number of bins. If None, uses optimal binning.
50-
ax : Axes, optional
51-
Axes to plot on. If None, uses current axes.
52-
**kwargs
53-
Other parameters are passed to matplotlib. See `matplotlib.pyplot.hist`.
42+
Maximum number of bins. If not provided, the algorithm selects
43+
the optimal number of bins automatically.
5444
5545
Returns
5646
-------
5747
n : ndarray
58-
Histogram values.
48+
Histogram values (probability density by default).
5949
bins : ndarray
6050
Bin edges.
6151
patches
6252
Container with the bar patches.
6353
6454
See Also
6555
--------
66-
matplotlib.pyplot.hist : Full documentation of supported parameters.
67-
khisto.histogram : Underlying histogram computation.
68-
69-
Examples
70-
--------
71-
>>> from khisto.matplotlib import hist
72-
>>> hist(data)
73-
>>> hist(data, density=True, max_bins=10)
56+
matplotlib.pyplot.hist : Matplotlib's histogram function. The ``bins``,
57+
``weights``, ``cumulative``, and stacked/multiple dataset features
58+
are not supported.
59+
khisto.array.histogram : Underlying histogram computation.
7460
"""
75-
if cumulative:
76-
raise NotImplementedError("Cumulative histograms are not yet supported")
7761

7862
# Get axes
7963
if ax is None:

tests/array/test_histogram.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def test_histogram_with_max_bins(self, normal_data):
119119

120120
def test_histogram_density_parameter(self, normal_data):
121121
"""Test density parameter behavior."""
122-
# Without density (counts)
122+
# Without density (counts) - explicit False same as None default
123123
hist_counts, edges1 = histogram(normal_data, density=False)
124124

125125
# With density

tests/plot/test_matplotlib_histogram.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ def test_without_ax(self, normal_data):
4242
plt.close()
4343

4444
def test_density_histogram(self, normal_data):
45-
"""Test density histogram."""
45+
"""Test density histogram (default behavior)."""
4646
fig, ax = plt.subplots()
47-
n, bins, patches = hist(normal_data, density=True, ax=ax)
47+
n, bins, patches = hist(normal_data, ax=ax)
4848

4949
# Density should integrate to 1
5050
bin_widths = np.diff(bins)
@@ -53,7 +53,7 @@ def test_density_histogram(self, normal_data):
5353
plt.close(fig)
5454

5555
def test_frequency_histogram(self, normal_data):
56-
"""Test frequency histogram."""
56+
"""Test frequency histogram (explicit density=False)."""
5757
fig, ax = plt.subplots()
5858
n, bins, patches = hist(normal_data, density=False, ax=ax)
5959

@@ -119,11 +119,6 @@ def test_stepfilled_histtype(self, normal_data):
119119
assert isinstance(n, np.ndarray)
120120
plt.close(fig)
121121

122-
def test_cumulative_not_supported(self, normal_data):
123-
"""Test that cumulative raises NotImplementedError."""
124-
with pytest.raises(NotImplementedError):
125-
hist(normal_data, cumulative=True)
126-
127122

128123
class TestHistReturnValues:
129124
"""Test return values match matplotlib.pyplot.hist interface."""

0 commit comments

Comments
 (0)