Skip to content

Commit 1b4cdcf

Browse files
authored
Merge pull request #10 from KhiopsML/alpha-feedback
feat: solve bug reported in the alpha feedback and added some features like the cumulative arg.
2 parents 6bdd503 + 75b8e30 commit 1b4cdcf

14 files changed

Lines changed: 897 additions & 184 deletions

File tree

README.md

Lines changed: 52 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ Khisto is a Python library for creating histograms using the **Khiops optimal bi
1212
- **Matplotlib Integration**: `khisto.matplotlib.hist` works like `plt.hist`.
1313
- **Minimal Dependencies**: Only requires NumPy (matplotlib optional for plotting).
1414

15+
| Standard Gaussian | Heavy-tailed Pareto |
16+
| --- | --- |
17+
| ![Adaptive Gaussian histogram](docs/images/gaussian-quick-start.png) | ![Adaptive Pareto histogram](docs/images/pareto-quick-start.png) |
18+
19+
## Reproducing The Example Distributions
20+
21+
The complete runnable script is available in `scripts/generate_distribution_examples.py`.
22+
23+
Run it from the repository root to regenerate both example distributions and the figure files used in this README:
24+
25+
```bash
26+
python scripts/generate_distribution_examples.py
27+
```
28+
1529
## Installation
1630

1731
```bash
@@ -32,8 +46,8 @@ pip install "khisto[matplotlib]"
3246
import numpy as np
3347
from khisto import histogram
3448

35-
# Generate data
36-
data = np.random.normal(0, 1, 1000)
49+
# Generate 10,000 samples from a standard Gaussian distribution.
50+
data = np.random.normal(0, 1, 10000)
3751

3852
# Compute optimal histogram (drop-in replacement for np.histogram)
3953
hist, bin_edges = histogram(data)
@@ -48,86 +62,48 @@ hist, bin_edges = histogram(data, max_bins=10)
4862
hist, bin_edges = histogram(data, range=(-2, 2))
4963
```
5064

51-
### Matplotlib Integration
65+
Using 10,000 samples keeps the adaptive refinement visible while remaining fast to compute.
66+
67+
Heavy-tailed example:
5268

5369
```python
5470
import numpy as np
5571
import matplotlib.pyplot as plt
5672
from khisto.matplotlib import hist
5773

58-
data = np.random.normal(0, 1, 1000)
74+
# Generate 10,000 samples from a Pareto distribution, shifted to start at 1 for better log-log visualization
75+
shape = 3
76+
long_tail_data = np.random.pareto(shape, size=10000) + 1
5977

60-
# Create optimal histogram plot
61-
n, bins, patches = hist(data)
62-
plt.show()
63-
64-
# With density
65-
n, bins, patches = hist(data, density=True)
66-
plt.xlabel('Value')
67-
plt.ylabel('Density')
78+
# Plot an adaptive histogram on logarithmic axes.
79+
n, bins, patches = hist(long_tail_data, density=True)
80+
plt.xscale("log")
81+
plt.yscale("log")
6882
plt.show()
6983
```
7084

71-
## API Reference
72-
73-
### `khisto.histogram`
85+
### Matplotlib Integration
7486

7587
```python
76-
def histogram(
77-
a: ArrayLike,
78-
range: Optional[tuple[float, float]] = None,
79-
max_bins: Optional[int] = None,
80-
density: bool = False,
81-
) -> tuple[ndarray, ndarray]
82-
```
83-
84-
Compute an optimal histogram using the Khiops binning algorithm.
85-
86-
**Parameters:**
87-
- `a`: Input data. The histogram is computed over the flattened array.
88-
- `range`: The lower and upper range of the bins. Values outside are ignored.
89-
- `max_bins`: Maximum number of bins. If None, the algorithm selects optimal.
90-
- `density`: If True, return probability density. If False, return counts.
88+
import numpy as np
89+
import matplotlib.pyplot as plt
90+
from khisto.matplotlib import hist
9191

92-
**Returns:**
93-
- `hist`: The values of the histogram (counts or density).
94-
- `bin_edges`: The bin edges (length = len(hist) + 1).
92+
# Generate 10,000 samples from a standard Gaussian distribution.
93+
data = np.random.normal(0, 1, 10000)
9594

96-
### `khisto.matplotlib.hist`
95+
# Density is usually the most interpretable view with variable-width bins.
96+
n, bins, patches = hist(data, density=True)
97+
plt.xlabel('Value')
98+
plt.ylabel('Density')
99+
plt.show()
97100

98-
```python
99-
def hist(
100-
x: ArrayLike,
101-
range: Optional[tuple[float, float]] = None,
102-
max_bins: Optional[int] = None,
103-
density: bool = False,
104-
histtype: str = "bar",
105-
orientation: Literal["vertical", "horizontal"] = "vertical",
106-
log: bool = False,
107-
color: Optional[str] = None,
108-
label: Optional[str] = None,
109-
ax: Optional[Axes] = None,
110-
**kwargs,
111-
) -> tuple[ndarray, ndarray, Any]
101+
# Cumulative density follows matplotlib semantics.
102+
n, bins, patches = hist(data, density=True, cumulative=True)
103+
plt.ylabel('Cumulative probability')
104+
plt.show()
112105
```
113106

114-
Plot an optimal histogram using matplotlib.
115-
116-
**Parameters:**
117-
- `x`: Input data.
118-
- `range`: The lower and upper range of the bins.
119-
- `max_bins`: Maximum number of bins.
120-
- `density`: If True, plot probability density.
121-
- `histtype`: Type of histogram (`"bar"`, `"step"`, `"stepfilled"`).
122-
- `orientation`: `"vertical"` or `"horizontal"`.
123-
- `log`: If True, set log scale on the value axis.
124-
- `ax`: Matplotlib axes to plot on.
125-
126-
**Returns:**
127-
- `n`: The histogram values.
128-
- `bins`: The bin edges.
129-
- `patches`: The matplotlib patches.
130-
131107
## How It Works
132108

133109
Khisto uses the Khiops optimal binning algorithm based on the MODL (Minimum Optimal Description Length) principle. Instead of using fixed-width bins like traditional histograms, it:
@@ -138,6 +114,11 @@ Khisto uses the Khiops optimal binning algorithm based on the MODL (Minimum Opti
138114

139115
This results in histograms that better represent the underlying distribution, with finer bins in dense regions and wider bins in sparse regions.
140116

117+
The method implemented in Khiops is comprehensively detailed in [2] and further extended in [1].
118+
119+
- [1] M. Boullé. Floating-point histograms for exploratory analysis of large scale real-world data sets. Intelligent Data Analysis, 28(5):1347-1394, 2024
120+
- [2] V. Zelaya Mendizábal, M. Boullé, F. Rossi. Fast and fully-automated histograms for large-scale data sets. Computational Statistics & Data Analysis, 180:0-0, 2023
121+
141122
## Development
142123

143124
```bash
@@ -146,12 +127,16 @@ git clone https://github.com/khiops/khisto-python.git
146127
cd khisto-python
147128

148129
# Install with dev dependencies
149-
pip install -e ".[matplotlib]"
130+
uv sync --group dev --extra all
150131

151132
# Run tests
152-
pytest
133+
uv run pytest
153134
```
154135

136+
## Documentation
137+
138+
See the [API](docs/API.md) and [API Comparison](docs/API_COMPARISON.md) for detailed information on available functions, parameters, and how Khisto compares to standard histogram implementations.
139+
155140
## License
156141

157142
[BSD 3-Clause Clear License](LICENSE)

docs/API.md

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Complete API reference for the Khisto library.
1111
- [HistogramResult](#histogramresult)
1212
- [Matplotlib API](#matplotlib-api)
1313
- [hist](#hist)
14+
- [How It Works](#how-it-works)
1415

1516
---
1617

@@ -23,7 +24,7 @@ khisto.histogram(
2324
a: ArrayLike,
2425
range: Optional[tuple[float, float]] = None,
2526
max_bins: Optional[int] = None,
26-
density: Optional[bool] = None,
27+
density: bool = False,
2728
) -> tuple[NDArray[np.floating], NDArray[np.floating]]
2829
```
2930

@@ -33,10 +34,10 @@ Compute an optimal histogram using the Khiops binning algorithm.
3334

3435
| Parameter | Type | Default | Description |
3536
|-----------|------|---------|-------------|
36-
| `a` | `ArrayLike` | required | Input data. The histogram is computed over the flattened array. |
37+
| `a` | `ArrayLike` | required | Input data. The input is converted to a floating-point array and flattened to one dimension. |
3738
| `range` | `tuple[float, float]` | `None` | Lower and upper range of the bins. Values outside are ignored. |
3839
| `max_bins` | `int` | `None` | Maximum number of bins. If not provided, the optimal number is determined automatically. |
39-
| `density` | `bool` | `None` | If `False` or `None`, return counts; if `True`, return probability density values. |
40+
| `density` | `bool` | `False` | If `False`, return counts; if `True`, return probability density values. |
4041

4142
#### Returns
4243

@@ -47,7 +48,7 @@ Compute an optimal histogram using the Khiops binning algorithm.
4748

4849
#### See Also
4950

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

5253
#### Examples
5354

@@ -81,6 +82,14 @@ hist, bin_edges = histogram(data, max_bins=5)
8182
print(f"Number of bins: {len(hist)}") # <= 5
8283
```
8384

85+
Concatenating nested inputs into a single dataset:
86+
87+
```python
88+
data = [np.array([0.0, 1.0]), np.array([2.0, 3.0, 4.0])]
89+
hist, bin_edges = histogram(data)
90+
print(hist.sum()) # 5
91+
```
92+
8493
---
8594

8695
## Core API
@@ -176,7 +185,8 @@ import numpy as np
176185
from khisto.core import compute_histogram
177186

178187
data = np.random.normal(0, 1, 1000)
179-
result = compute_histogram(data)
188+
results = compute_histogram(data)
189+
result = next(r for r in results if r.is_best)
180190

181191
# Access bin information
182192
print(f"Bin edges: {result.bin_edges}")
@@ -204,8 +214,8 @@ khisto.matplotlib.hist(
204214
x: ArrayLike,
205215
range: Optional[tuple[float, float]] = None,
206216
max_bins: Optional[int] = None,
207-
density: bool = True,
208-
ax: Optional[Axes] = None,
217+
density: bool = False,
218+
cumulative: bool | float = False,
209219
**kwargs,
210220
) -> tuple[NDArray[np.floating], NDArray[np.floating], Any]
211221
```
@@ -216,10 +226,12 @@ Compute and plot an optimal histogram.
216226

217227
| Parameter | Type | Default | Description |
218228
|-----------|------|---------|-------------|
219-
| `x` | `ArrayLike` | required | Input data. The histogram is computed over the flattened array. |
229+
| `x` | `ArrayLike` | required | Input data, or a sequence of array-like objects. Nested inputs are concatenated and histogrammed as one dataset. |
220230
| `max_bins` | `int` | `None` | Maximum number of bins. If `None`, uses optimal binning. |
231+
| `density` | `bool` | `False` | If `True`, return and plot probability densities. If `False`, return counts. |
232+
| `cumulative` | `bool or float` | `False` | Cumulative mode, following `matplotlib.pyplot.hist`. Negative values accumulate in reverse order. |
221233

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.
234+
Other parameters are passed to matplotlib for styling. `ax` can be provided to draw on a specific axes. The `bins`, `weights`, and `stacked` arguments are not supported.
223235

224236
#### Returns
225237

@@ -231,8 +243,8 @@ Other parameters are passed to matplotlib. See [`matplotlib.pyplot.hist`](https:
231243

232244
#### See Also
233245

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).
235-
- [`khisto.histogram`](#histogram) — Underlying histogram computation.
246+
- [`matplotlib.pyplot.hist`](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.hist.html) — Matplotlib's histogram function.
247+
- [`khisto.histogram`](#histogram) — Underlying non-cumulative histogram computation.
236248

237249
#### Examples
238250

@@ -243,47 +255,52 @@ import numpy as np
243255
import matplotlib.pyplot as plt
244256
from khisto.matplotlib import hist
245257

246-
data = np.random.normal(0, 1, 1000)
258+
data = np.random.normal(0, 1, 10000)
247259

248-
# Default is density=True
249-
n, bins, patches = hist(data)
260+
# Density is usually the clearest view with variable-width bins.
261+
n, bins, patches = hist(data, density=True)
250262
plt.xlabel('Value')
251263
plt.ylabel('Density')
252264
plt.title('Optimal Histogram')
253265
plt.show()
254266
```
255267

256-
Frequency plot:
268+
Cumulative density:
257269

258270
```python
259-
n, bins, patches = hist(data, density=False)
260-
plt.xlabel('Value')
261-
plt.ylabel('Count')
271+
n, bins, patches = hist(data, density=True, cumulative=True)
272+
plt.ylabel('Cumulative probability')
262273
plt.show()
263274
```
264275

265-
Step histogram:
276+
Heavy-tailed Pareto example:
266277

267278
```python
268-
n, bins, patches = hist(data, histtype='step', color='blue', label='Data')
269-
plt.legend()
279+
shape = 3
280+
long_tail_data = np.random.pareto(shape, size=10000) + 1
281+
282+
n, bins, patches = hist(long_tail_data, density=True)
283+
plt.xscale('log')
284+
plt.yscale('log')
270285
plt.show()
271286
```
272287

273-
Using specific axes:
288+
---
274289

275-
```python
276-
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
290+
## How It Works
277291

278-
hist(data, ax=ax1)
279-
ax1.set_title('Counts')
292+
Khisto uses the Khiops optimal binning algorithm based on the MODL (Minimum Optimal Description Length) principle. Instead of using fixed-width bins like traditional histograms, it:
280293

281-
hist(data, density=True, ax=ax2)
282-
ax2.set_title('Density')
294+
1. Analyzes the data distribution
295+
2. Finds bin boundaries that minimize information loss
296+
3. Creates variable-width bins that adapt to data density
283297

284-
plt.tight_layout()
285-
plt.show()
286-
```
298+
This results in histograms that better represent the underlying distribution, with finer bins in dense regions and wider bins in sparse regions.
299+
300+
The method implemented in Khiops is comprehensively detailed in [2] and further extended in [1].
301+
302+
- [1] M. Boullé. Floating-point histograms for exploratory analysis of large scale real-world data sets. Intelligent Data Analysis, 28(5):1347-1394, 2024
303+
- [2] V. Zelaya Mendizábal, M. Boullé, F. Rossi. Fast and fully-automated histograms for large-scale data sets. Computational Statistics & Data Analysis, 180:0-0, 2023
287304

288305
---
289306

docs/API_COMPARISON.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ khisto.matplotlib.hist(
114114
range=None,
115115
max_bins=None,
116116
density=False,
117+
cumulative=False,
117118
histtype='bar',
118119
orientation='vertical',
119120
log=False,
@@ -130,11 +131,11 @@ khisto.matplotlib.hist(
130131
|---------|------------|--------|
131132
| **Binning** | Fixed-width | Optimal variable-width |
132133
| **Bins param** | `bins` | `max_bins` |
133-
| **Axes param** | Implicit (current) | Explicit `ax` parameter |
134-
| **Cumulative** | Supported | Not supported |
134+
| **Axes param** | Implicit (current) | Optional `ax` parameter |
135+
| **Cumulative** | Supported | Supported |
135136
| **Stacked** | Supported | Not supported |
136137
| **Weights** | Supported | Not supported |
137-
| **Multiple datasets** | Supported | Single dataset only |
138+
| **Multiple datasets** | Supported | Sequences are concatenated into one dataset |
138139

139140
#### Usage Comparison
140141

@@ -167,6 +168,10 @@ plt.show()
167168
plt.hist(data, density=True)
168169
hist(data, density=True)
169170

171+
# cumulative view
172+
plt.hist(data, density=True, cumulative=True)
173+
hist(data, density=True, cumulative=True)
174+
170175
# histogram type
171176
plt.hist(data, histtype='step')
172177
hist(data, histtype='step')
@@ -224,7 +229,7 @@ n, bins, patches = hist(data, max_bins=30) # max_bins is optional
224229
| Density ||||
225230
| Range ||||
226231
| Weights ||||
227-
| Cumulative ||| |
232+
| Cumulative ||| |
228233
| Plotting ||||
229234
| Step histogram ||||
230235
| Horizontal ||||
36.8 KB
Loading

docs/images/pareto-quick-start.png

30.5 KB
Loading

0 commit comments

Comments
 (0)