nancora 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.
- nancora-0.1.0/.gitignore +17 -0
- nancora-0.1.0/LICENSE +21 -0
- nancora-0.1.0/PKG-INFO +94 -0
- nancora-0.1.0/README.md +57 -0
- nancora-0.1.0/benchmarks/README.md +10 -0
- nancora-0.1.0/benchmarks/__init__.py +0 -0
- nancora-0.1.0/benchmarks/datasets/synthetic_churn.csv +11 -0
- nancora-0.1.0/benchmarks/datasets/synthetic_linear.csv +11 -0
- nancora-0.1.0/benchmarks/datasets/synthetic_missing.csv +11 -0
- nancora-0.1.0/benchmarks/datasets/synthetic_outliers.csv +16 -0
- nancora-0.1.0/benchmarks/datasets/tiny.csv +7 -0
- nancora-0.1.0/benchmarks/labels/example.json +5 -0
- nancora-0.1.0/benchmarks/labels/synthetic_churn.json +16 -0
- nancora-0.1.0/benchmarks/labels/synthetic_linear.json +31 -0
- nancora-0.1.0/benchmarks/labels/synthetic_missing.json +16 -0
- nancora-0.1.0/benchmarks/labels/synthetic_outliers.json +21 -0
- nancora-0.1.0/benchmarks/labels/tiny.json +31 -0
- nancora-0.1.0/benchmarks/metrics.py +66 -0
- nancora-0.1.0/benchmarks/runner.py +105 -0
- nancora-0.1.0/docs/analysis.md +16 -0
- nancora-0.1.0/docs/api.md +16 -0
- nancora-0.1.0/docs/architecture.md +12 -0
- nancora-0.1.0/docs/benchmarking.md +7 -0
- nancora-0.1.0/docs/cli.md +8 -0
- nancora-0.1.0/docs/data.md +7 -0
- nancora-0.1.0/docs/development.md +11 -0
- nancora-0.1.0/docs/getting-started.md +39 -0
- nancora-0.1.0/docs/numpy.md +3 -0
- nancora-0.1.0/docs/recommendation-engine.md +13 -0
- nancora-0.1.0/docs/redundancy.md +5 -0
- nancora-0.1.0/docs/reports.md +5 -0
- nancora-0.1.0/docs/scoring.md +7 -0
- nancora-0.1.0/docs/statistics.md +5 -0
- nancora-0.1.0/docs/target-analysis.md +5 -0
- nancora-0.1.0/docs/visualization.md +5 -0
- nancora-0.1.0/pyproject.toml +73 -0
- nancora-0.1.0/src/nancora/__init__.py +33 -0
- nancora-0.1.0/src/nancora/analysis/__init__.py +6 -0
- nancora-0.1.0/src/nancora/analysis/base.py +168 -0
- nancora-0.1.0/src/nancora/analysis/builtin/__init__.py +14 -0
- nancora-0.1.0/src/nancora/analysis/builtin/_util.py +72 -0
- nancora-0.1.0/src/nancora/analysis/builtin/cardinality_analysis.py +66 -0
- nancora-0.1.0/src/nancora/analysis/builtin/categorical_distribution.py +67 -0
- nancora-0.1.0/src/nancora/analysis/builtin/categorical_numeric.py +72 -0
- nancora-0.1.0/src/nancora/analysis/builtin/correlation_analysis.py +77 -0
- nancora-0.1.0/src/nancora/analysis/builtin/datetime_numeric_trend.py +72 -0
- nancora-0.1.0/src/nancora/analysis/builtin/missingness_analysis.py +61 -0
- nancora-0.1.0/src/nancora/analysis/builtin/numeric_distribution.py +60 -0
- nancora-0.1.0/src/nancora/analysis/builtin/numeric_relationship.py +62 -0
- nancora-0.1.0/src/nancora/analysis/builtin/outlier_analysis.py +59 -0
- nancora-0.1.0/src/nancora/analysis/builtin/target_aware.py +164 -0
- nancora-0.1.0/src/nancora/analysis/evidence.py +125 -0
- nancora-0.1.0/src/nancora/analysis/pairing.py +28 -0
- nancora-0.1.0/src/nancora/analysis/registry.py +35 -0
- nancora-0.1.0/src/nancora/analysis/serialize.py +25 -0
- nancora-0.1.0/src/nancora/cli.py +69 -0
- nancora-0.1.0/src/nancora/data/__init__.py +19 -0
- nancora-0.1.0/src/nancora/data/io.py +45 -0
- nancora-0.1.0/src/nancora/data/profile.py +97 -0
- nancora-0.1.0/src/nancora/data/schema.py +109 -0
- nancora-0.1.0/src/nancora/data/transform.py +28 -0
- nancora-0.1.0/src/nancora/engine/__init__.py +99 -0
- nancora-0.1.0/src/nancora/engine/explain.py +43 -0
- nancora-0.1.0/src/nancora/engine/generate.py +17 -0
- nancora-0.1.0/src/nancora/engine/rank.py +41 -0
- nancora-0.1.0/src/nancora/engine/redundancy.py +128 -0
- nancora-0.1.0/src/nancora/engine/score.py +180 -0
- nancora-0.1.0/src/nancora/engine/validate.py +47 -0
- nancora-0.1.0/src/nancora/exceptions.py +17 -0
- nancora-0.1.0/src/nancora/numeric/__init__.py +5 -0
- nancora-0.1.0/src/nancora/numeric/arrays.py +57 -0
- nancora-0.1.0/src/nancora/plot/__init__.py +22 -0
- nancora-0.1.0/src/nancora/plot/matplotlib_backend.py +71 -0
- nancora-0.1.0/src/nancora/plot/plotly_backend.py +46 -0
- nancora-0.1.0/src/nancora/plot/spec.py +18 -0
- nancora-0.1.0/src/nancora/py.typed +0 -0
- nancora-0.1.0/src/nancora/report/__init__.py +1 -0
- nancora-0.1.0/src/nancora/report/html.py +57 -0
- nancora-0.1.0/src/nancora/report/templates/__init__.py +0 -0
- nancora-0.1.0/src/nancora/report/templates/report.html.j2 +65 -0
- nancora-0.1.0/src/nancora/result.py +253 -0
- nancora-0.1.0/src/nancora/stats/__init__.py +21 -0
- nancora-0.1.0/src/nancora/stats/tests.py +107 -0
- nancora-0.1.0/src/nancora/types.py +49 -0
- nancora-0.1.0/tests/conftest.py +20 -0
- nancora-0.1.0/tests/fixtures/.gitkeep +0 -0
- nancora-0.1.0/tests/integration/__init__.py +0 -0
- nancora-0.1.0/tests/integration/test_cli.py +17 -0
- nancora-0.1.0/tests/integration/test_explore.py +52 -0
- nancora-0.1.0/tests/integration/test_report.py +25 -0
- nancora-0.1.0/tests/regression/__init__.py +0 -0
- nancora-0.1.0/tests/regression/test_decisions.py +93 -0
- nancora-0.1.0/tests/regression/test_determinism_and_scores.py +88 -0
- nancora-0.1.0/tests/regression/test_golden_and_edge_cases.py +92 -0
- nancora-0.1.0/tests/regression/test_intelligence.py +191 -0
- nancora-0.1.0/tests/regression/test_result_schema.py +25 -0
- nancora-0.1.0/tests/unit/__init__.py +0 -0
- nancora-0.1.0/tests/unit/test_benchmarks.py +10 -0
- nancora-0.1.0/tests/unit/test_insights.py +31 -0
- nancora-0.1.0/tests/unit/test_io.py +13 -0
- nancora-0.1.0/tests/unit/test_numeric_stats.py +15 -0
- nancora-0.1.0/tests/unit/test_primary_ux.py +91 -0
- nancora-0.1.0/tests/unit/test_profile.py +13 -0
- nancora-0.1.0/tests/unit/test_redundancy.py +38 -0
- nancora-0.1.0/tests/unit/test_registry.py +19 -0
- nancora-0.1.0/tests/unit/test_schema.py +34 -0
- nancora-0.1.0/tests/unit/test_score.py +26 -0
nancora-0.1.0/.gitignore
ADDED
nancora-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nancora
|
|
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.
|
nancora-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: nancora
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python data-science toolkit that recommends which analyses are worth attention.
|
|
5
|
+
Project-URL: Homepage, https://github.com/nancora/nancora
|
|
6
|
+
Author: Nancora
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: data-analysis,eda,recommendation,statistics
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: jinja2>=3.1
|
|
20
|
+
Requires-Dist: matplotlib>=3.7
|
|
21
|
+
Requires-Dist: numpy>=1.24
|
|
22
|
+
Requires-Dist: pandas>=2.0
|
|
23
|
+
Requires-Dist: plotly>=5.18
|
|
24
|
+
Requires-Dist: scipy>=1.10
|
|
25
|
+
Requires-Dist: typer>=0.12
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
28
|
+
Requires-Dist: pandas-stubs>=2.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
32
|
+
Provides-Extra: excel
|
|
33
|
+
Requires-Dist: openpyxl>=3.1; extra == 'excel'
|
|
34
|
+
Provides-Extra: parquet
|
|
35
|
+
Requires-Dist: pyarrow>=14; extra == 'parquet'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# Nancora
|
|
39
|
+
|
|
40
|
+
Nancora is a Python data-science toolkit that **recommends which analyses are worth attention**.
|
|
41
|
+
|
|
42
|
+
It does not reimplement Pandas, NumPy, SciPy, Matplotlib, or Plotly. Those libraries do the computation. Nancora profiles data, generates analysis candidates, scores them with an **explainable heuristic**, removes redundant work, and returns a machine-readable result plus an HTML report.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import nancora as nc
|
|
46
|
+
|
|
47
|
+
df = nc.read_csv("data.csv")
|
|
48
|
+
|
|
49
|
+
# 1. Unsupervised exploration
|
|
50
|
+
result = nc.explore(df)
|
|
51
|
+
|
|
52
|
+
# 2. Target-aware exploration
|
|
53
|
+
result = nc.explore(df, target="target_column")
|
|
54
|
+
|
|
55
|
+
# Jupyter Notebook Experience: Simply evaluate `result` for clean, rich HTML rendering!
|
|
56
|
+
|
|
57
|
+
# 3. Clean structured properties
|
|
58
|
+
result.profile # Dataset shape, column kinds, quality stats
|
|
59
|
+
result.recommendations # Prioritized list of top analytical recommendations
|
|
60
|
+
result.insights # Human-readable insights from key evidence
|
|
61
|
+
result.visualizations # Matplotlib figures for top recommendations
|
|
62
|
+
result.decision_trace # Complete explanation of selected & skipped analyses
|
|
63
|
+
|
|
64
|
+
# 4. Useful serialization
|
|
65
|
+
result.to_dict()
|
|
66
|
+
result.to_json()
|
|
67
|
+
result.save("report.html")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Nancora follows a strict philosophy: **SELECT → PRIORITIZE → EXPLAIN → VISUALIZE** (not flood the user with unnecessary charts).
|
|
71
|
+
Scores are ranking heuristics (0–100), not scientific truth. Association is not causation.
|
|
72
|
+
|
|
73
|
+
## Install
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install nancora
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
For development installation:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install -e ".[dev]"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## CLI
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
nancora explore data.csv --out report.html
|
|
89
|
+
nancora analyze data.csv --target y --out report.html
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## What Nancora is not
|
|
93
|
+
|
|
94
|
+
No LLM chatbot, AutoML, cloud platform, plugin marketplace, or deep-learning stack. See [LIMITATIONS.md](LIMITATIONS.md) and [docs/architecture.md](docs/architecture.md).
|
nancora-0.1.0/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Nancora
|
|
2
|
+
|
|
3
|
+
Nancora is a Python data-science toolkit that **recommends which analyses are worth attention**.
|
|
4
|
+
|
|
5
|
+
It does not reimplement Pandas, NumPy, SciPy, Matplotlib, or Plotly. Those libraries do the computation. Nancora profiles data, generates analysis candidates, scores them with an **explainable heuristic**, removes redundant work, and returns a machine-readable result plus an HTML report.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
import nancora as nc
|
|
9
|
+
|
|
10
|
+
df = nc.read_csv("data.csv")
|
|
11
|
+
|
|
12
|
+
# 1. Unsupervised exploration
|
|
13
|
+
result = nc.explore(df)
|
|
14
|
+
|
|
15
|
+
# 2. Target-aware exploration
|
|
16
|
+
result = nc.explore(df, target="target_column")
|
|
17
|
+
|
|
18
|
+
# Jupyter Notebook Experience: Simply evaluate `result` for clean, rich HTML rendering!
|
|
19
|
+
|
|
20
|
+
# 3. Clean structured properties
|
|
21
|
+
result.profile # Dataset shape, column kinds, quality stats
|
|
22
|
+
result.recommendations # Prioritized list of top analytical recommendations
|
|
23
|
+
result.insights # Human-readable insights from key evidence
|
|
24
|
+
result.visualizations # Matplotlib figures for top recommendations
|
|
25
|
+
result.decision_trace # Complete explanation of selected & skipped analyses
|
|
26
|
+
|
|
27
|
+
# 4. Useful serialization
|
|
28
|
+
result.to_dict()
|
|
29
|
+
result.to_json()
|
|
30
|
+
result.save("report.html")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Nancora follows a strict philosophy: **SELECT → PRIORITIZE → EXPLAIN → VISUALIZE** (not flood the user with unnecessary charts).
|
|
34
|
+
Scores are ranking heuristics (0–100), not scientific truth. Association is not causation.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install nancora
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For development installation:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e ".[dev]"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## CLI
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
nancora explore data.csv --out report.html
|
|
52
|
+
nancora analyze data.csv --target y --out report.html
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## What Nancora is not
|
|
56
|
+
|
|
57
|
+
No LLM chatbot, AutoML, cloud platform, plugin marketplace, or deep-learning stack. See [LIMITATIONS.md](LIMITATIONS.md) and [docs/architecture.md](docs/architecture.md).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Benchmarks
|
|
2
|
+
|
|
3
|
+
This directory is a **framework**, not a published leaderboard.
|
|
4
|
+
|
|
5
|
+
- `datasets/` — tiny synthetic CSVs committed with the repo
|
|
6
|
+
- `labels/` — optional human labels (`useful`, `essential`, `redundant`, `irrelevant`, `misleading`)
|
|
7
|
+
- `runner.py` — runs `explore` and computes metrics **only when labels exist**
|
|
8
|
+
- `metrics.py` — precision@k, essential recall, redundancy reduction, coverage, runtime, information efficiency
|
|
9
|
+
|
|
10
|
+
Empty `labels` arrays mean the runner prints `no labels; skip scoring`. Do not fabricate results.
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dataset_id": "synthetic_churn",
|
|
3
|
+
"description": "Synthetic benchmark dataset with boolean churn target",
|
|
4
|
+
"labels": [
|
|
5
|
+
{
|
|
6
|
+
"analysis_id": "categorical_numeric",
|
|
7
|
+
"variables": ["churned", "tenure"],
|
|
8
|
+
"kind": "essential"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"analysis_id": "categorical_distribution",
|
|
12
|
+
"variables": ["churned"],
|
|
13
|
+
"kind": "useful"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dataset_id": "synthetic_linear",
|
|
3
|
+
"description": "Linear relationship benchmark dataset",
|
|
4
|
+
"labels": [
|
|
5
|
+
{
|
|
6
|
+
"analysis_id": "numeric_relationship",
|
|
7
|
+
"variables": ["x", "y"],
|
|
8
|
+
"kind": "essential"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"analysis_id": "correlation_analysis",
|
|
12
|
+
"variables": ["noise", "x", "y"],
|
|
13
|
+
"kind": "essential"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"analysis_id": "categorical_numeric",
|
|
17
|
+
"variables": ["group", "x"],
|
|
18
|
+
"kind": "useful"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"analysis_id": "numeric_distribution",
|
|
22
|
+
"variables": ["x"],
|
|
23
|
+
"kind": "useful"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"analysis_id": "missingness_analysis",
|
|
27
|
+
"variables": ["x", "y", "noise", "group"],
|
|
28
|
+
"kind": "useful"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dataset_id": "synthetic_missing",
|
|
3
|
+
"description": "Synthetic benchmark dataset with high missingness",
|
|
4
|
+
"labels": [
|
|
5
|
+
{
|
|
6
|
+
"analysis_id": "missingness_analysis",
|
|
7
|
+
"variables": ["col_a", "col_b", "col_c"],
|
|
8
|
+
"kind": "essential"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"analysis_id": "numeric_distribution",
|
|
12
|
+
"variables": ["col_a"],
|
|
13
|
+
"kind": "useful"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dataset_id": "synthetic_outliers",
|
|
3
|
+
"description": "Synthetic benchmark dataset with strong outliers",
|
|
4
|
+
"labels": [
|
|
5
|
+
{
|
|
6
|
+
"analysis_id": "outlier_analysis",
|
|
7
|
+
"variables": ["val"],
|
|
8
|
+
"kind": "essential"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"analysis_id": "numeric_distribution",
|
|
12
|
+
"variables": ["val"],
|
|
13
|
+
"kind": "useful"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"analysis_id": "categorical_numeric",
|
|
17
|
+
"variables": ["group", "val"],
|
|
18
|
+
"kind": "useful"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dataset_id": "tiny",
|
|
3
|
+
"description": "Tiny business benchmark dataset",
|
|
4
|
+
"labels": [
|
|
5
|
+
{
|
|
6
|
+
"analysis_id": "numeric_relationship",
|
|
7
|
+
"variables": ["revenue", "spend"],
|
|
8
|
+
"kind": "essential"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"analysis_id": "correlation_analysis",
|
|
12
|
+
"variables": ["noise", "revenue", "spend"],
|
|
13
|
+
"kind": "essential"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"analysis_id": "categorical_numeric",
|
|
17
|
+
"variables": ["region", "spend"],
|
|
18
|
+
"kind": "useful"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"analysis_id": "numeric_distribution",
|
|
22
|
+
"variables": ["spend"],
|
|
23
|
+
"kind": "useful"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"analysis_id": "missingness_analysis",
|
|
27
|
+
"variables": ["spend", "revenue", "noise", "region"],
|
|
28
|
+
"kind": "useful"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Benchmark metrics. Computed only from labels — never fabricated."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def precision_at_k(selected: list[tuple[str, tuple[str, ...]]], useful: set[tuple[str, tuple[str, ...]]], k: int) -> float | None:
|
|
9
|
+
if not useful:
|
|
10
|
+
return None
|
|
11
|
+
top = selected[:k]
|
|
12
|
+
if not top:
|
|
13
|
+
return 0.0
|
|
14
|
+
hits = sum(1 for item in top if item in useful)
|
|
15
|
+
return hits / len(top)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def recall_essential(
|
|
19
|
+
selected: list[tuple[str, tuple[str, ...]]], essential: set[tuple[str, tuple[str, ...]]]
|
|
20
|
+
) -> float | None:
|
|
21
|
+
if not essential:
|
|
22
|
+
return None
|
|
23
|
+
hits = sum(1 for item in essential if item in set(selected))
|
|
24
|
+
return hits / len(essential)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def redundancy_reduction(n_generated_pairs: int, n_rejected_redundant: int) -> float | None:
|
|
28
|
+
if n_generated_pairs <= 0:
|
|
29
|
+
return None
|
|
30
|
+
return n_rejected_redundant / n_generated_pairs
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def coverage_of_essential(
|
|
34
|
+
selected: list[tuple[str, tuple[str, ...]]], essential: set[tuple[str, tuple[str, ...]]]
|
|
35
|
+
) -> float | None:
|
|
36
|
+
return recall_essential(selected, essential)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def information_efficiency(selected: list[tuple[str, tuple[str, ...]]]) -> float | None:
|
|
40
|
+
if not selected:
|
|
41
|
+
return None
|
|
42
|
+
units = {(aid, var) for aid, vars_ in selected for var in vars_}
|
|
43
|
+
return len(units) / len(selected)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
import json
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def load_labels(path: str | Path) -> list[dict]:
|
|
51
|
+
p = Path(path)
|
|
52
|
+
if not p.is_file():
|
|
53
|
+
return []
|
|
54
|
+
payload = json.loads(p.read_text(encoding="utf-8"))
|
|
55
|
+
return list(payload.get("labels") or [])
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def evaluate_labels(labels: list[dict], data_dir: str | Path) -> dict[str, Any]:
|
|
59
|
+
if not labels:
|
|
60
|
+
return {"status": "no_labels"}
|
|
61
|
+
return {"status": "ok"}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def summarize_metrics(payload: dict[str, Any]) -> dict[str, Any]:
|
|
65
|
+
return payload
|
|
66
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Run labeled recommendation checks. Skips metrics when labels are absent."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
14
|
+
sys.path.insert(0, str(ROOT / "src"))
|
|
15
|
+
|
|
16
|
+
from nancora.engine import explore # noqa: E402
|
|
17
|
+
from nancora.types import RejectReason # noqa: E402
|
|
18
|
+
|
|
19
|
+
from benchmarks.metrics import ( # noqa: E402
|
|
20
|
+
coverage_of_essential,
|
|
21
|
+
information_efficiency,
|
|
22
|
+
precision_at_k,
|
|
23
|
+
recall_essential,
|
|
24
|
+
redundancy_reduction,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
LABELS_DIR = Path(__file__).parent / "labels"
|
|
28
|
+
DATA_DIR = Path(__file__).parent / "datasets"
|
|
29
|
+
REDUNDANT_REASONS = {
|
|
30
|
+
RejectReason.EXACT_DUPLICATE.value,
|
|
31
|
+
RejectReason.SYMMETRIC_DUPLICATE.value,
|
|
32
|
+
RejectReason.SIMILAR_ANALYSIS.value,
|
|
33
|
+
RejectReason.COVERAGE_OVERLAP.value,
|
|
34
|
+
RejectReason.DOMINATED.value,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _key(analysis_id: str, variables: list[str] | tuple[str, ...]) -> tuple[str, tuple[str, ...]]:
|
|
39
|
+
return analysis_id, tuple(sorted(variables))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def load_labels(dataset_id: str) -> list[dict]:
|
|
43
|
+
path = LABELS_DIR / f"{dataset_id}.json"
|
|
44
|
+
if not path.exists():
|
|
45
|
+
return []
|
|
46
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
47
|
+
return list(payload.get("labels") or [])
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def run_dataset(csv_path: Path) -> dict:
|
|
51
|
+
dataset_id = csv_path.stem
|
|
52
|
+
labels = load_labels(dataset_id)
|
|
53
|
+
df = pd.read_csv(csv_path)
|
|
54
|
+
started = time.perf_counter()
|
|
55
|
+
result = explore(df, max_analyses=10, rng_seed=0)
|
|
56
|
+
runtime = time.perf_counter() - started
|
|
57
|
+
selected = [_key(c.analysis_id, c.variables) for c in result.selected]
|
|
58
|
+
report = {
|
|
59
|
+
"dataset_id": dataset_id,
|
|
60
|
+
"runtime_seconds": round(runtime, 6),
|
|
61
|
+
"n_selected": len(result.selected),
|
|
62
|
+
"n_rejected": len(result.rejected),
|
|
63
|
+
"labels_present": bool(labels),
|
|
64
|
+
}
|
|
65
|
+
if not labels:
|
|
66
|
+
report["metrics"] = None
|
|
67
|
+
report["note"] = "no labels; skip scoring"
|
|
68
|
+
return report
|
|
69
|
+
|
|
70
|
+
by_kind: dict[str, set] = {k: set() for k in ("useful", "essential", "redundant", "irrelevant", "misleading")}
|
|
71
|
+
for row in labels:
|
|
72
|
+
by_kind.setdefault(row["kind"], set()).add(_key(row["analysis_id"], row["variables"]))
|
|
73
|
+
|
|
74
|
+
n_pairs = sum(1 for c in result.selected + result.rejected if len(c.variables) == 2)
|
|
75
|
+
n_red = sum(
|
|
76
|
+
1
|
|
77
|
+
for c in result.rejected
|
|
78
|
+
if c.reject_reason and c.reject_reason.value in REDUNDANT_REASONS
|
|
79
|
+
)
|
|
80
|
+
useful = by_kind["useful"] | by_kind["essential"]
|
|
81
|
+
report["metrics"] = {
|
|
82
|
+
"precision_at_5": precision_at_k(selected, useful, 5),
|
|
83
|
+
"recall_essential": recall_essential(selected, by_kind["essential"]),
|
|
84
|
+
"redundancy_reduction": redundancy_reduction(n_pairs, n_red),
|
|
85
|
+
"analytical_coverage": coverage_of_essential(selected, by_kind["essential"]),
|
|
86
|
+
"information_efficiency": information_efficiency(selected),
|
|
87
|
+
"runtime_seconds": round(runtime, 6),
|
|
88
|
+
}
|
|
89
|
+
return report
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def main(argv: list[str] | None = None) -> int:
|
|
93
|
+
parser = argparse.ArgumentParser(description="Nancora benchmark runner")
|
|
94
|
+
parser.parse_args(argv)
|
|
95
|
+
datasets = sorted(DATA_DIR.glob("*.csv"))
|
|
96
|
+
if not datasets:
|
|
97
|
+
print("no datasets found")
|
|
98
|
+
return 0
|
|
99
|
+
for path in datasets:
|
|
100
|
+
print(json.dumps(run_dataset(path), indent=2, sort_keys=True))
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Analysis registry
|
|
2
|
+
|
|
3
|
+
Builtin IDs:
|
|
4
|
+
|
|
5
|
+
1. numeric_distribution
|
|
6
|
+
2. categorical_distribution
|
|
7
|
+
3. numeric_relationship
|
|
8
|
+
4. categorical_numeric
|
|
9
|
+
5. datetime_numeric_trend
|
|
10
|
+
6. correlation_analysis
|
|
11
|
+
7. outlier_analysis
|
|
12
|
+
8. missingness_analysis
|
|
13
|
+
9. cardinality_analysis
|
|
14
|
+
10. target_aware
|
|
15
|
+
|
|
16
|
+
Register more with `@nc.register_analysis` on a class implementing `propose`, `compute_evidence`, and `plot_spec`. There is no plugin marketplace in the MVP.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Public API
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
import nancora as nc
|
|
5
|
+
|
|
6
|
+
nc.read_csv / read_excel / read_json / read_parquet
|
|
7
|
+
nc.explore(df, max_analyses=10, rng_seed=0)
|
|
8
|
+
nc.analyze(df, target=..., max_analyses=10, rng_seed=0)
|
|
9
|
+
nc.register_analysis
|
|
10
|
+
nc.list_analyses()
|
|
11
|
+
nc.get_analysis(id)
|
|
12
|
+
|
|
13
|
+
nc.data / nc.numpy / nc.stats / nc.plot / nc.analysis
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`AnalysisResult`: `summary`, `profile`, `recommendations`, `insights`, `rejected`, `visualize`, `to_dict`, `to_json`, `save`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
Pipeline: data → schema → profile → candidates → evidence → scoring → redundancy → ranking → recommendations → visualization → insights → report.
|
|
4
|
+
|
|
5
|
+
Scientific libraries are engines, not products Nancora reimplements.
|
|
6
|
+
|
|
7
|
+
- Pandas: frames and IO
|
|
8
|
+
- NumPy: arrays and univariate numeric summaries
|
|
9
|
+
- SciPy: named association tests
|
|
10
|
+
- Matplotlib / Plotly: PlotSpec backends
|
|
11
|
+
|
|
12
|
+
The recommendation engine is the product differentiator. See `src/nancora/engine/`.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Benchmarking
|
|
2
|
+
|
|
3
|
+
`benchmarks/runner.py` loads synthetic datasets and optional human labels.
|
|
4
|
+
|
|
5
|
+
Label values: `useful`, `essential`, `redundant`, `irrelevant`, `misleading`.
|
|
6
|
+
|
|
7
|
+
If a dataset has no labels, the runner **skips metrics** rather than inventing scores. Do not commit fabricated precision/recall.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Development
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
pip install -e ".[dev]"
|
|
5
|
+
pytest
|
|
6
|
+
ruff check src tests
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Python 3.10+. Src layout. CI runs Ruff and Pytest on 3.10 and 3.12.
|
|
10
|
+
|
|
11
|
+
Decision tests live in `tests/regression/test_decisions.py`. Prefer obvious synthetic relationships over threshold-tuning.
|