Skip to content

Commit 19d1300

Browse files
rickhondaclaude
andcommitted
Initial release: bin width estimation, all major methods
Includes: auto, freedman_diaconis, scott, sturges, rice, sqrt, doane, stone, knuth, gcd_interval, bayesian_blocks, bin() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0 parents  commit 19d1300

9 files changed

Lines changed: 700 additions & 0 deletions

File tree

.github/workflows/publish.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
jobs:
9+
publish:
10+
runs-on: ubuntu-latest
11+
environment: pypi
12+
permissions:
13+
id-token: write
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- uses: astral-sh/setup-uv@v5
19+
with:
20+
python-version: "3.12"
21+
22+
- name: Build
23+
run: uv build
24+
25+
- name: Publish
26+
uses: pypa/gh-action-pypi-publish@release/v1

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.venv/
2+
__pycache__/
3+
*.pyc
4+
*.pyo
5+
dist/
6+
*.egg-info/
7+
.pytest_cache/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Shun Richard Honda
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# binjamin
2+
3+
**Bin width estimation — every major method in one place.**
4+
5+
```python
6+
import binjamin as bj
7+
8+
# Estimate bin width
9+
bj.auto(intervals) # good default — max(FD, Sturges)
10+
bj.freedman_diaconis(intervals) # robust, no distributional assumption
11+
12+
# Bin data and get the artifact
13+
edges, counts = bj.bin(data)
14+
edges, counts = bj.bin(data, method='bayesian_blocks')
15+
```
16+
17+
## Install
18+
19+
```bash
20+
pip install binjamin
21+
```
22+
23+
## Methods
24+
25+
### Scalar — return a single bin width
26+
27+
| Method | Formula | When to use |
28+
|---|---|---|
29+
| `auto` | max(FD, Sturges) | Good general default |
30+
| `freedman_diaconis` | 2·IQR·n^(-1/3) | Unknown or skewed distribution, outliers present |
31+
| `scott` | 3.5·σ·n^(-1/3) | Near-normal data, few outliers |
32+
| `sturges` | (max−min)/(1+log₂n) | Small, near-normal datasets |
33+
| `rice` | (max−min)/(2·n^(1/3)) | No assumption, simple alternative to FD |
34+
| `sqrt` | (max−min)/√n | Quick exploratory work |
35+
| `doane` | Sturges + skewness correction | Skewed or multimodal distributions |
36+
| `stone` | Cross-validation | Unknown distribution, accuracy over speed |
37+
| `knuth` | Maximum likelihood | Uniform bins, optimal posterior |
38+
| `gcd_interval` | GCD of intervals | Integer sequences, regularly-sampled data |
39+
40+
### Variable-width — returns bin edges
41+
42+
| Method | When to use |
43+
|---|---|
44+
| `bayesian_blocks` | Non-stationary event data; density varies across the domain |
45+
46+
### Binning artifact
47+
48+
| Function | Returns |
49+
|---|---|
50+
| `bin(data, method='auto')` | `(edges, counts)` — edges and observation counts per bin |
51+
52+
## Usage
53+
54+
All scalar methods take a 1-D array-like and return a `float`:
55+
56+
```python
57+
import numpy as np
58+
import binjamin as bj
59+
60+
intervals = np.diff(np.sort(event_times)) # inter-event intervals
61+
62+
bj.freedman_diaconis(intervals) # → float
63+
bj.scott(intervals) # → float
64+
bj.knuth(intervals) # → float
65+
```
66+
67+
`bayesian_blocks` takes event positions (not intervals) and returns edges:
68+
69+
```python
70+
edges = bj.bayesian_blocks(event_times, p0=0.05)
71+
# edges: array of variable-width bin boundaries
72+
# p0: false-positive rate for new change points (lower = fewer blocks)
73+
```
74+
75+
`gcd_interval` takes integer-valued positions:
76+
77+
```python
78+
bj.gcd_interval([0, 60, 120, 180, 300]) # → 60.0
79+
```
80+
81+
## Choosing a method
82+
83+
**Start with `auto`.** If the result looks wrong:
84+
85+
- Heavy tails or outliers → `freedman_diaconis`
86+
- Known near-normal distribution → `scott`
87+
- Skewed data → `doane`
88+
- Event rate changes over time → `bayesian_blocks`
89+
- Integer sequence with known regular spacing → `gcd_interval`
90+
- Need provably optimal bins, willing to wait → `knuth` or `stone`
91+
92+
## License
93+
94+
[MIT](LICENSE)

pyproject.toml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
[project]
2+
name = "binjamin"
3+
version = "0.1.0"
4+
description = "Bin width estimation — all major methods in one place"
5+
readme = "README.md"
6+
license = { text = "MIT" }
7+
authors = [
8+
{ name = "Shun Richard Honda", email = "shun.honda@adelic.org" }
9+
]
10+
keywords = ["binning", "histogram", "bin-width", "freedman-diaconis", "bayesian-blocks"]
11+
classifiers = [
12+
"Development Status :: 3 - Alpha",
13+
"Intended Audience :: Developers",
14+
"Intended Audience :: Science/Research",
15+
"License :: OSI Approved :: MIT License",
16+
"Programming Language :: Python :: 3",
17+
"Topic :: Scientific/Engineering :: Mathematics",
18+
"Topic :: Scientific/Engineering :: Information Analysis",
19+
]
20+
requires-python = ">=3.12"
21+
dependencies = [
22+
"numpy>=1.24",
23+
]
24+
25+
[project.urls]
26+
Homepage = "https://github.com/adelic-ai/binjamin"
27+
28+
[build-system]
29+
requires = ["hatchling"]
30+
build-backend = "hatchling.build"
31+
32+
[tool.hatch.build.targets.wheel]
33+
packages = ["src/binjamin"]
34+
35+
[dependency-groups]
36+
dev = [
37+
"pytest>=8.0",
38+
"twine>=6.2.0",
39+
]

src/binjamin/__init__.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
binjamin — bin width estimation
3+
4+
All major methods in one place. Every function takes a 1-D array of values
5+
(or inter-event intervals) and returns a scalar bin width estimate.
6+
7+
Suggested import:
8+
9+
import binjamin as bj
10+
11+
bj.auto(intervals)
12+
bj.freedman_diaconis(intervals)
13+
bj.bayesian_blocks(orders)
14+
15+
Methods returning a scalar bin width
16+
-------------------------------------
17+
auto — max(freedman_diaconis, sturges); numpy default
18+
freedman_diaconis — 2·IQR·n^(-1/3); robust, no distributional assumption
19+
scott — 3.5·σ·n^(-1/3); assumes normality
20+
sturges — (max-min)/(1+log₂n); assumes normality, underbins large n
21+
rice — (max-min)/(2·n^(1/3)); simple, no assumption
22+
sqrt — (max-min)/√n; simplest
23+
doane — Sturges adjusted for skewness
24+
stone — cross-validation leave-one-out; slower, more accurate
25+
knuth — maximum likelihood; optimal for uniform bins
26+
gcd_interval — GCD of intervals; exact but brittle
27+
28+
Methods returning variable-width edges
29+
----------------------------------------
30+
bayesian_blocks — dynamic programming; best for non-stationary event data
31+
"""
32+
33+
from .methods import (
34+
auto,
35+
freedman_diaconis,
36+
scott,
37+
sturges,
38+
rice,
39+
sqrt,
40+
doane,
41+
stone,
42+
knuth,
43+
gcd_interval,
44+
bayesian_blocks,
45+
bin,
46+
)
47+
48+
__all__ = [
49+
"auto",
50+
"freedman_diaconis",
51+
"scott",
52+
"sturges",
53+
"rice",
54+
"sqrt",
55+
"doane",
56+
"stone",
57+
"knuth",
58+
"gcd_interval",
59+
"bayesian_blocks",
60+
"bin",
61+
]

0 commit comments

Comments
 (0)