geom-nmf 0.1.0__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.
- geom_nmf-0.1.0/LICENSE +22 -0
- geom_nmf-0.1.0/PKG-INFO +324 -0
- geom_nmf-0.1.0/README.md +283 -0
- geom_nmf-0.1.0/pyproject.toml +104 -0
- geom_nmf-0.1.0/src/geom_nmf/__init__.py +43 -0
- geom_nmf-0.1.0/src/geom_nmf/core.py +352 -0
- geom_nmf-0.1.0/src/geom_nmf/endmembers.py +366 -0
- geom_nmf-0.1.0/src/geom_nmf/nfindr.py +324 -0
- geom_nmf-0.1.0/src/geom_nmf/plot.py +184 -0
- geom_nmf-0.1.0/src/geom_nmf/py.typed +0 -0
- geom_nmf-0.1.0/src/geom_nmf/utils.py +61 -0
- geom_nmf-0.1.0/src/geom_nmf/weights.py +150 -0
geom_nmf-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bora Jin
|
|
4
|
+
Copyright (c) 2026 QuantAQ (initial project scaffolding)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
geom_nmf-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: geom-nmf
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Geometric non-negative matrix factorization (GeomNMF) via maximum-volume simplex fitting.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: nmf,non-negative matrix factorization,source apportionment,endmember extraction,simplex,unmixing
|
|
8
|
+
Author: Bora Jin
|
|
9
|
+
Author-email: jinbora0720@gmail.com
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Provides-Extra: all
|
|
25
|
+
Provides-Extra: plot
|
|
26
|
+
Provides-Extra: progress
|
|
27
|
+
Requires-Dist: matplotlib (>=3.5) ; extra == "all"
|
|
28
|
+
Requires-Dist: matplotlib (>=3.5) ; extra == "plot"
|
|
29
|
+
Requires-Dist: numpy (>=1.21)
|
|
30
|
+
Requires-Dist: scikit-learn (>=1.0)
|
|
31
|
+
Requires-Dist: scipy (>=1.7)
|
|
32
|
+
Requires-Dist: tqdm (>=4.60) ; extra == "all"
|
|
33
|
+
Requires-Dist: tqdm (>=4.60) ; extra == "progress"
|
|
34
|
+
Project-URL: Bug Tracker, https://github.com/jinbora0720/geom-nmf/issues
|
|
35
|
+
Project-URL: Changelog, https://github.com/jinbora0720/geom-nmf/blob/main/CHANGELOG.md
|
|
36
|
+
Project-URL: Documentation, https://github.com/jinbora0720/geom-nmf/tree/main/docs
|
|
37
|
+
Project-URL: Homepage, https://github.com/jinbora0720/geom-nmf
|
|
38
|
+
Project-URL: Repository, https://github.com/jinbora0720/geom-nmf
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# geom-nmf
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+

|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
**Geometric non-negative matrix factorization (GeomNMF)** via maximum-volume simplex fitting.
|
|
48
|
+
|
|
49
|
+
GeomNMF assumes a nonnegative data matrix follows a simplex mixing model
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
Y ≈ W H, W ≥ 0, H ≥ 0, H 1 = 1
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
and estimates the source profiles `H` (endmembers) as the vertices of the maximum-volume
|
|
56
|
+
(K−1)-simplex inscribed in the convex hull of the row-normalized data. It then recovers the
|
|
57
|
+
mixing weights `W` and a source attribution matrix `Φ` giving the share of each feature
|
|
58
|
+
attributable to each source. Typical applications include source apportionment of
|
|
59
|
+
multi-pollutant air quality data and spectral unmixing.
|
|
60
|
+
|
|
61
|
+
# Getting Started
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
Install the latest version directly from GitHub:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
pip install git+https://github.com/jinbora0720/geom-nmf.git
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
With optional plotting (matplotlib) and progress-bar (tqdm) support:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
pip install "geom-nmf[all] @ git+https://github.com/jinbora0720/geom-nmf.git"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| Extra | Enables |
|
|
78
|
+
|------------|-------------------------------------------------------------|
|
|
79
|
+
| `plot` | `barplot_Phi` (installs matplotlib) |
|
|
80
|
+
| `progress` | tqdm progress bar for the max-volume search (`verbose=True`) |
|
|
81
|
+
| `all` | both |
|
|
82
|
+
|
|
83
|
+
Install from a specific branch or tag:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
pip install git+https://github.com/jinbora0720/geom-nmf.git@<branch-name-or-tag>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Quick example
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import numpy as np
|
|
93
|
+
from geom_nmf import GeomNMF, barplot_Phi, permute_to_reference
|
|
94
|
+
|
|
95
|
+
rng = np.random.default_rng(0)
|
|
96
|
+
K, J, N = 3, 6, 1000
|
|
97
|
+
H_true = rng.dirichlet(np.ones(J), size=K) # (K, J) source profiles
|
|
98
|
+
W_true = rng.dirichlet(0.5 * np.ones(K), size=N) # (N, K) mixing proportions
|
|
99
|
+
Y = rng.lognormal(size=(N, 1)) * (W_true @ H_true) # (N, J) nonnegative data
|
|
100
|
+
|
|
101
|
+
result = GeomNMF(Y, K=3)
|
|
102
|
+
|
|
103
|
+
result.H_star # (K, J) estimated source profiles, rows sum to 1
|
|
104
|
+
result.W_tilde # (N, K) source contributions on the original scale of Y
|
|
105
|
+
result.mu_tilde # (K,) mean contribution per source
|
|
106
|
+
result.Phi # (K, J) share of each feature attributable to each source
|
|
107
|
+
result.fit_diagnostics["intrinsic_rank"] # should be K - 1 for noise-free data
|
|
108
|
+
|
|
109
|
+
# sources are identified up to permutation; align with a reference if you have one
|
|
110
|
+
H_hat, mu_hat, Phi_hat, order = permute_to_reference(H_true, result.H_star, result.mu_tilde, result.Phi)
|
|
111
|
+
|
|
112
|
+
barplot_Phi(Phi_hat, feature_labels=["PM1", "PM2.5", "PM10", "CO", "NO2", "O3"])
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`Y` can be a NumPy array, a pandas DataFrame, or any 2-D array-like. A runnable version is in
|
|
116
|
+
[`examples/quickstart.py`](examples/quickstart.py).
|
|
117
|
+
|
|
118
|
+
## Public API
|
|
119
|
+
|
|
120
|
+
| Function / class | Purpose |
|
|
121
|
+
|-------------------------|------------------------------------------------------------------|
|
|
122
|
+
| `GeomNMF` | Full pipeline; returns a `GeomNMFResult` named tuple |
|
|
123
|
+
| `nfindr` | Standalone N-FINDR endmember extraction |
|
|
124
|
+
| `log_simplex_volume` | Log-volume of the simplex spanned by K points |
|
|
125
|
+
| `estimate_weights` | Recover simplex weights `W` given `Y` and `H` |
|
|
126
|
+
| `compute_Phi` | Source attribution matrix from mean weights and `H` |
|
|
127
|
+
| `permute_to_reference` | Align estimated sources to a reference ordering (Hungarian) |
|
|
128
|
+
| `barplot_Phi` | Grouped bar plot of `Φ`, optionally with bootstrap error bars |
|
|
129
|
+
|
|
130
|
+
## Choosing options
|
|
131
|
+
|
|
132
|
+
- **`candidate_method="exact"`** (default) uses convex-hull vertices and is globally optimal,
|
|
133
|
+
but the number of hull vertices grows quickly for noisy, high-dimensional data.
|
|
134
|
+
- **`candidate_method="random"`** uses extremes along random projections; cap the pool with
|
|
135
|
+
`n_candidates`.
|
|
136
|
+
- **`prune=True`** clusters candidates before the exhaustive search. It can discard the true
|
|
137
|
+
extremes, so pair it with **`refine_greedy=True`**, which runs N-FINDR swaps on the full data
|
|
138
|
+
and keeps the result only if it increases the simplex volume.
|
|
139
|
+
- A `RuntimeWarning` is raised if the exhaustive search would exceed 50 million subsets.
|
|
140
|
+
|
|
141
|
+
## Contributing
|
|
142
|
+
|
|
143
|
+
We welcome contributions! If you'd like to fix a bug, add a feature, or improve documentation, follow the steps below. Don't worry if you're new to this — it's a straightforward process once you've done it a couple of times.
|
|
144
|
+
|
|
145
|
+
This project uses [Poetry](https://python-poetry.org/) to manage dependencies and virtual environments.
|
|
146
|
+
|
|
147
|
+
### 1. Fork the repository
|
|
148
|
+
|
|
149
|
+
A "fork" is your own personal copy of the project on GitHub. To create one, click the **Fork** button in the top-right corner of the [geom-nmf repository](https://github.com/jinbora0720/geom-nmf) page.
|
|
150
|
+
|
|
151
|
+
### 2. Clone your fork
|
|
152
|
+
|
|
153
|
+
Download your fork to your local machine:
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
git clone https://github.com/<your-github-username>/geom-nmf.git
|
|
157
|
+
cd geom-nmf
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 3. Install Poetry
|
|
161
|
+
|
|
162
|
+
If you don't already have Poetry installed, choose the instructions for your operating system below.
|
|
163
|
+
|
|
164
|
+
**macOS**
|
|
165
|
+
|
|
166
|
+
The recommended way on macOS is via [Homebrew](https://brew.sh/). If you don't have Homebrew installed, install it first:
|
|
167
|
+
|
|
168
|
+
```sh
|
|
169
|
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Then install Poetry:
|
|
173
|
+
|
|
174
|
+
```sh
|
|
175
|
+
brew install poetry
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Linux / Windows (WSL)**
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
curl -sSL https://install.python-poetry.org | python3 -
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Then follow any instructions it prints to add Poetry to your `PATH`.
|
|
185
|
+
|
|
186
|
+
**Verify the installation**
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
poetry --version
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 4. Install dependencies
|
|
193
|
+
|
|
194
|
+
This installs both the main dependencies and the development dependencies (e.g. pytest, sphinx) into an isolated virtual environment managed by Poetry:
|
|
195
|
+
|
|
196
|
+
```sh
|
|
197
|
+
poetry install
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
To also install the optional coverage or documentation tools:
|
|
201
|
+
|
|
202
|
+
```sh
|
|
203
|
+
poetry install --with coverage
|
|
204
|
+
poetry install --with docs
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
You only need to run this once (and again whenever dependencies change).
|
|
208
|
+
|
|
209
|
+
To activate the virtual environment (Poetry ≥ 2.0):
|
|
210
|
+
|
|
211
|
+
```sh
|
|
212
|
+
eval $(poetry env activate)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
On Poetry 1.x, use `poetry shell` instead.
|
|
216
|
+
|
|
217
|
+
Your prompt will change to indicate you're inside the environment. To exit when you're done:
|
|
218
|
+
|
|
219
|
+
```sh
|
|
220
|
+
exit
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Alternatively, prefix any command with `poetry run` to run it inside the environment without activating it:
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
poetry run pytest tests/
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 5. Create a branch
|
|
230
|
+
|
|
231
|
+
Never make changes directly on `main`. Instead, create a new branch with a short descriptive name:
|
|
232
|
+
|
|
233
|
+
```sh
|
|
234
|
+
git checkout -b my-feature-branch
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 6. Make your changes
|
|
238
|
+
|
|
239
|
+
Edit files, write code, fix bugs — whatever your contribution involves. Keep your changes focused on one thing per branch.
|
|
240
|
+
|
|
241
|
+
### 7. Run the tests
|
|
242
|
+
|
|
243
|
+
Before submitting, make sure all tests pass:
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
poetry run pytest tests/
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
If you've added new functionality, add a corresponding test in the `tests/` directory.
|
|
250
|
+
|
|
251
|
+
Common flags:
|
|
252
|
+
|
|
253
|
+
| Flag | Description |
|
|
254
|
+
|------|-------------|
|
|
255
|
+
| `-v` | Verbose output — shows each test name and pass/fail status |
|
|
256
|
+
| `-q` | Quiet output — minimal output, just a summary |
|
|
257
|
+
| `-x` | Stop after the first failure |
|
|
258
|
+
| `-s` | Disable output capture — allows `print()` statements to show in terminal |
|
|
259
|
+
| `-k "expression"` | Run only tests whose names match the expression (e.g. `-k "test_fit"`) |
|
|
260
|
+
| `--tb=short` | Show a shortened traceback on failures (`short`, `long`, `no`, `line`) |
|
|
261
|
+
| `--cov=geom_nmf` | Report test coverage for the package (requires `pytest-cov`) |
|
|
262
|
+
|
|
263
|
+
Examples:
|
|
264
|
+
|
|
265
|
+
```sh
|
|
266
|
+
# Run all tests verbosely
|
|
267
|
+
pytest -v tests/
|
|
268
|
+
|
|
269
|
+
# Stop on first failure with full traceback
|
|
270
|
+
pytest -x --tb=long tests/
|
|
271
|
+
|
|
272
|
+
# Run only tests matching a keyword
|
|
273
|
+
pytest -v -k "test_fit" tests/
|
|
274
|
+
|
|
275
|
+
# Run with coverage report
|
|
276
|
+
pytest --cov=geom_nmf --cov-report=term-missing tests/
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### 8. Commit and push your changes
|
|
280
|
+
|
|
281
|
+
Stage and commit your changes with a clear message describing what you did:
|
|
282
|
+
|
|
283
|
+
```sh
|
|
284
|
+
git add <file(s) you changed>
|
|
285
|
+
git commit -m "Brief description of your change"
|
|
286
|
+
git push origin my-feature-branch
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### 9. Open a Pull Request
|
|
290
|
+
|
|
291
|
+
Go to your fork on GitHub. You should see a prompt to open a Pull Request — click it. Fill in a short description of what your changes do and why. A maintainer will review your PR and may leave comments or request changes before merging.
|
|
292
|
+
|
|
293
|
+
### Tips
|
|
294
|
+
|
|
295
|
+
- Keep pull requests small and focused — one feature or fix per PR is much easier to review
|
|
296
|
+
- Check that your branch is up to date with `main` before opening a PR:
|
|
297
|
+
```sh
|
|
298
|
+
git fetch origin
|
|
299
|
+
git rebase origin/main
|
|
300
|
+
```
|
|
301
|
+
- To add a new dependency: `poetry add <package>` (or `poetry add --group dev <package>` for dev-only)
|
|
302
|
+
- If you're unsure about a change, open an issue first to discuss it before writing code
|
|
303
|
+
|
|
304
|
+
## Building Documentation
|
|
305
|
+
|
|
306
|
+
The documentation is built with [Sphinx](https://www.sphinx-doc.org/) from the NumPy-style
|
|
307
|
+
docstrings in the source code.
|
|
308
|
+
|
|
309
|
+
```sh
|
|
310
|
+
poetry install --with docs
|
|
311
|
+
poetry run make -C docs html
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Then open `docs/_build/html/index.html` in your browser. The build treats warnings as errors
|
|
315
|
+
(`-W`), so broken references or malformed docstrings will fail the build.
|
|
316
|
+
|
|
317
|
+
## Building a distribution
|
|
318
|
+
|
|
319
|
+
```sh
|
|
320
|
+
poetry build # writes dist/geom_nmf-<version>.tar.gz and .whl
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
© 2026 Bora Jin. Released under the [MIT License](LICENSE).
|
|
324
|
+
|
geom_nmf-0.1.0/README.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# geom-nmf
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
**Geometric non-negative matrix factorization (GeomNMF)** via maximum-volume simplex fitting.
|
|
8
|
+
|
|
9
|
+
GeomNMF assumes a nonnegative data matrix follows a simplex mixing model
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Y ≈ W H, W ≥ 0, H ≥ 0, H 1 = 1
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
and estimates the source profiles `H` (endmembers) as the vertices of the maximum-volume
|
|
16
|
+
(K−1)-simplex inscribed in the convex hull of the row-normalized data. It then recovers the
|
|
17
|
+
mixing weights `W` and a source attribution matrix `Φ` giving the share of each feature
|
|
18
|
+
attributable to each source. Typical applications include source apportionment of
|
|
19
|
+
multi-pollutant air quality data and spectral unmixing.
|
|
20
|
+
|
|
21
|
+
# Getting Started
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Install the latest version directly from GitHub:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
pip install git+https://github.com/jinbora0720/geom-nmf.git
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
With optional plotting (matplotlib) and progress-bar (tqdm) support:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pip install "geom-nmf[all] @ git+https://github.com/jinbora0720/geom-nmf.git"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| Extra | Enables |
|
|
38
|
+
|------------|-------------------------------------------------------------|
|
|
39
|
+
| `plot` | `barplot_Phi` (installs matplotlib) |
|
|
40
|
+
| `progress` | tqdm progress bar for the max-volume search (`verbose=True`) |
|
|
41
|
+
| `all` | both |
|
|
42
|
+
|
|
43
|
+
Install from a specific branch or tag:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
pip install git+https://github.com/jinbora0720/geom-nmf.git@<branch-name-or-tag>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick example
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import numpy as np
|
|
53
|
+
from geom_nmf import GeomNMF, barplot_Phi, permute_to_reference
|
|
54
|
+
|
|
55
|
+
rng = np.random.default_rng(0)
|
|
56
|
+
K, J, N = 3, 6, 1000
|
|
57
|
+
H_true = rng.dirichlet(np.ones(J), size=K) # (K, J) source profiles
|
|
58
|
+
W_true = rng.dirichlet(0.5 * np.ones(K), size=N) # (N, K) mixing proportions
|
|
59
|
+
Y = rng.lognormal(size=(N, 1)) * (W_true @ H_true) # (N, J) nonnegative data
|
|
60
|
+
|
|
61
|
+
result = GeomNMF(Y, K=3)
|
|
62
|
+
|
|
63
|
+
result.H_star # (K, J) estimated source profiles, rows sum to 1
|
|
64
|
+
result.W_tilde # (N, K) source contributions on the original scale of Y
|
|
65
|
+
result.mu_tilde # (K,) mean contribution per source
|
|
66
|
+
result.Phi # (K, J) share of each feature attributable to each source
|
|
67
|
+
result.fit_diagnostics["intrinsic_rank"] # should be K - 1 for noise-free data
|
|
68
|
+
|
|
69
|
+
# sources are identified up to permutation; align with a reference if you have one
|
|
70
|
+
H_hat, mu_hat, Phi_hat, order = permute_to_reference(H_true, result.H_star, result.mu_tilde, result.Phi)
|
|
71
|
+
|
|
72
|
+
barplot_Phi(Phi_hat, feature_labels=["PM1", "PM2.5", "PM10", "CO", "NO2", "O3"])
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`Y` can be a NumPy array, a pandas DataFrame, or any 2-D array-like. A runnable version is in
|
|
76
|
+
[`examples/quickstart.py`](examples/quickstart.py).
|
|
77
|
+
|
|
78
|
+
## Public API
|
|
79
|
+
|
|
80
|
+
| Function / class | Purpose |
|
|
81
|
+
|-------------------------|------------------------------------------------------------------|
|
|
82
|
+
| `GeomNMF` | Full pipeline; returns a `GeomNMFResult` named tuple |
|
|
83
|
+
| `nfindr` | Standalone N-FINDR endmember extraction |
|
|
84
|
+
| `log_simplex_volume` | Log-volume of the simplex spanned by K points |
|
|
85
|
+
| `estimate_weights` | Recover simplex weights `W` given `Y` and `H` |
|
|
86
|
+
| `compute_Phi` | Source attribution matrix from mean weights and `H` |
|
|
87
|
+
| `permute_to_reference` | Align estimated sources to a reference ordering (Hungarian) |
|
|
88
|
+
| `barplot_Phi` | Grouped bar plot of `Φ`, optionally with bootstrap error bars |
|
|
89
|
+
|
|
90
|
+
## Choosing options
|
|
91
|
+
|
|
92
|
+
- **`candidate_method="exact"`** (default) uses convex-hull vertices and is globally optimal,
|
|
93
|
+
but the number of hull vertices grows quickly for noisy, high-dimensional data.
|
|
94
|
+
- **`candidate_method="random"`** uses extremes along random projections; cap the pool with
|
|
95
|
+
`n_candidates`.
|
|
96
|
+
- **`prune=True`** clusters candidates before the exhaustive search. It can discard the true
|
|
97
|
+
extremes, so pair it with **`refine_greedy=True`**, which runs N-FINDR swaps on the full data
|
|
98
|
+
and keeps the result only if it increases the simplex volume.
|
|
99
|
+
- A `RuntimeWarning` is raised if the exhaustive search would exceed 50 million subsets.
|
|
100
|
+
|
|
101
|
+
## Contributing
|
|
102
|
+
|
|
103
|
+
We welcome contributions! If you'd like to fix a bug, add a feature, or improve documentation, follow the steps below. Don't worry if you're new to this — it's a straightforward process once you've done it a couple of times.
|
|
104
|
+
|
|
105
|
+
This project uses [Poetry](https://python-poetry.org/) to manage dependencies and virtual environments.
|
|
106
|
+
|
|
107
|
+
### 1. Fork the repository
|
|
108
|
+
|
|
109
|
+
A "fork" is your own personal copy of the project on GitHub. To create one, click the **Fork** button in the top-right corner of the [geom-nmf repository](https://github.com/jinbora0720/geom-nmf) page.
|
|
110
|
+
|
|
111
|
+
### 2. Clone your fork
|
|
112
|
+
|
|
113
|
+
Download your fork to your local machine:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
git clone https://github.com/<your-github-username>/geom-nmf.git
|
|
117
|
+
cd geom-nmf
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 3. Install Poetry
|
|
121
|
+
|
|
122
|
+
If you don't already have Poetry installed, choose the instructions for your operating system below.
|
|
123
|
+
|
|
124
|
+
**macOS**
|
|
125
|
+
|
|
126
|
+
The recommended way on macOS is via [Homebrew](https://brew.sh/). If you don't have Homebrew installed, install it first:
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Then install Poetry:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
brew install poetry
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Linux / Windows (WSL)**
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
curl -sSL https://install.python-poetry.org | python3 -
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Then follow any instructions it prints to add Poetry to your `PATH`.
|
|
145
|
+
|
|
146
|
+
**Verify the installation**
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
poetry --version
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 4. Install dependencies
|
|
153
|
+
|
|
154
|
+
This installs both the main dependencies and the development dependencies (e.g. pytest, sphinx) into an isolated virtual environment managed by Poetry:
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
poetry install
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
To also install the optional coverage or documentation tools:
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
poetry install --with coverage
|
|
164
|
+
poetry install --with docs
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
You only need to run this once (and again whenever dependencies change).
|
|
168
|
+
|
|
169
|
+
To activate the virtual environment (Poetry ≥ 2.0):
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
eval $(poetry env activate)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
On Poetry 1.x, use `poetry shell` instead.
|
|
176
|
+
|
|
177
|
+
Your prompt will change to indicate you're inside the environment. To exit when you're done:
|
|
178
|
+
|
|
179
|
+
```sh
|
|
180
|
+
exit
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Alternatively, prefix any command with `poetry run` to run it inside the environment without activating it:
|
|
184
|
+
|
|
185
|
+
```sh
|
|
186
|
+
poetry run pytest tests/
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 5. Create a branch
|
|
190
|
+
|
|
191
|
+
Never make changes directly on `main`. Instead, create a new branch with a short descriptive name:
|
|
192
|
+
|
|
193
|
+
```sh
|
|
194
|
+
git checkout -b my-feature-branch
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 6. Make your changes
|
|
198
|
+
|
|
199
|
+
Edit files, write code, fix bugs — whatever your contribution involves. Keep your changes focused on one thing per branch.
|
|
200
|
+
|
|
201
|
+
### 7. Run the tests
|
|
202
|
+
|
|
203
|
+
Before submitting, make sure all tests pass:
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
poetry run pytest tests/
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
If you've added new functionality, add a corresponding test in the `tests/` directory.
|
|
210
|
+
|
|
211
|
+
Common flags:
|
|
212
|
+
|
|
213
|
+
| Flag | Description |
|
|
214
|
+
|------|-------------|
|
|
215
|
+
| `-v` | Verbose output — shows each test name and pass/fail status |
|
|
216
|
+
| `-q` | Quiet output — minimal output, just a summary |
|
|
217
|
+
| `-x` | Stop after the first failure |
|
|
218
|
+
| `-s` | Disable output capture — allows `print()` statements to show in terminal |
|
|
219
|
+
| `-k "expression"` | Run only tests whose names match the expression (e.g. `-k "test_fit"`) |
|
|
220
|
+
| `--tb=short` | Show a shortened traceback on failures (`short`, `long`, `no`, `line`) |
|
|
221
|
+
| `--cov=geom_nmf` | Report test coverage for the package (requires `pytest-cov`) |
|
|
222
|
+
|
|
223
|
+
Examples:
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
# Run all tests verbosely
|
|
227
|
+
pytest -v tests/
|
|
228
|
+
|
|
229
|
+
# Stop on first failure with full traceback
|
|
230
|
+
pytest -x --tb=long tests/
|
|
231
|
+
|
|
232
|
+
# Run only tests matching a keyword
|
|
233
|
+
pytest -v -k "test_fit" tests/
|
|
234
|
+
|
|
235
|
+
# Run with coverage report
|
|
236
|
+
pytest --cov=geom_nmf --cov-report=term-missing tests/
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### 8. Commit and push your changes
|
|
240
|
+
|
|
241
|
+
Stage and commit your changes with a clear message describing what you did:
|
|
242
|
+
|
|
243
|
+
```sh
|
|
244
|
+
git add <file(s) you changed>
|
|
245
|
+
git commit -m "Brief description of your change"
|
|
246
|
+
git push origin my-feature-branch
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### 9. Open a Pull Request
|
|
250
|
+
|
|
251
|
+
Go to your fork on GitHub. You should see a prompt to open a Pull Request — click it. Fill in a short description of what your changes do and why. A maintainer will review your PR and may leave comments or request changes before merging.
|
|
252
|
+
|
|
253
|
+
### Tips
|
|
254
|
+
|
|
255
|
+
- Keep pull requests small and focused — one feature or fix per PR is much easier to review
|
|
256
|
+
- Check that your branch is up to date with `main` before opening a PR:
|
|
257
|
+
```sh
|
|
258
|
+
git fetch origin
|
|
259
|
+
git rebase origin/main
|
|
260
|
+
```
|
|
261
|
+
- To add a new dependency: `poetry add <package>` (or `poetry add --group dev <package>` for dev-only)
|
|
262
|
+
- If you're unsure about a change, open an issue first to discuss it before writing code
|
|
263
|
+
|
|
264
|
+
## Building Documentation
|
|
265
|
+
|
|
266
|
+
The documentation is built with [Sphinx](https://www.sphinx-doc.org/) from the NumPy-style
|
|
267
|
+
docstrings in the source code.
|
|
268
|
+
|
|
269
|
+
```sh
|
|
270
|
+
poetry install --with docs
|
|
271
|
+
poetry run make -C docs html
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Then open `docs/_build/html/index.html` in your browser. The build treats warnings as errors
|
|
275
|
+
(`-W`), so broken references or malformed docstrings will fail the build.
|
|
276
|
+
|
|
277
|
+
## Building a distribution
|
|
278
|
+
|
|
279
|
+
```sh
|
|
280
|
+
poetry build # writes dist/geom_nmf-<version>.tar.gz and .whl
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
© 2026 Bora Jin. Released under the [MIT License](LICENSE).
|