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.
Files changed (107) hide show
  1. nancora-0.1.0/.gitignore +17 -0
  2. nancora-0.1.0/LICENSE +21 -0
  3. nancora-0.1.0/PKG-INFO +94 -0
  4. nancora-0.1.0/README.md +57 -0
  5. nancora-0.1.0/benchmarks/README.md +10 -0
  6. nancora-0.1.0/benchmarks/__init__.py +0 -0
  7. nancora-0.1.0/benchmarks/datasets/synthetic_churn.csv +11 -0
  8. nancora-0.1.0/benchmarks/datasets/synthetic_linear.csv +11 -0
  9. nancora-0.1.0/benchmarks/datasets/synthetic_missing.csv +11 -0
  10. nancora-0.1.0/benchmarks/datasets/synthetic_outliers.csv +16 -0
  11. nancora-0.1.0/benchmarks/datasets/tiny.csv +7 -0
  12. nancora-0.1.0/benchmarks/labels/example.json +5 -0
  13. nancora-0.1.0/benchmarks/labels/synthetic_churn.json +16 -0
  14. nancora-0.1.0/benchmarks/labels/synthetic_linear.json +31 -0
  15. nancora-0.1.0/benchmarks/labels/synthetic_missing.json +16 -0
  16. nancora-0.1.0/benchmarks/labels/synthetic_outliers.json +21 -0
  17. nancora-0.1.0/benchmarks/labels/tiny.json +31 -0
  18. nancora-0.1.0/benchmarks/metrics.py +66 -0
  19. nancora-0.1.0/benchmarks/runner.py +105 -0
  20. nancora-0.1.0/docs/analysis.md +16 -0
  21. nancora-0.1.0/docs/api.md +16 -0
  22. nancora-0.1.0/docs/architecture.md +12 -0
  23. nancora-0.1.0/docs/benchmarking.md +7 -0
  24. nancora-0.1.0/docs/cli.md +8 -0
  25. nancora-0.1.0/docs/data.md +7 -0
  26. nancora-0.1.0/docs/development.md +11 -0
  27. nancora-0.1.0/docs/getting-started.md +39 -0
  28. nancora-0.1.0/docs/numpy.md +3 -0
  29. nancora-0.1.0/docs/recommendation-engine.md +13 -0
  30. nancora-0.1.0/docs/redundancy.md +5 -0
  31. nancora-0.1.0/docs/reports.md +5 -0
  32. nancora-0.1.0/docs/scoring.md +7 -0
  33. nancora-0.1.0/docs/statistics.md +5 -0
  34. nancora-0.1.0/docs/target-analysis.md +5 -0
  35. nancora-0.1.0/docs/visualization.md +5 -0
  36. nancora-0.1.0/pyproject.toml +73 -0
  37. nancora-0.1.0/src/nancora/__init__.py +33 -0
  38. nancora-0.1.0/src/nancora/analysis/__init__.py +6 -0
  39. nancora-0.1.0/src/nancora/analysis/base.py +168 -0
  40. nancora-0.1.0/src/nancora/analysis/builtin/__init__.py +14 -0
  41. nancora-0.1.0/src/nancora/analysis/builtin/_util.py +72 -0
  42. nancora-0.1.0/src/nancora/analysis/builtin/cardinality_analysis.py +66 -0
  43. nancora-0.1.0/src/nancora/analysis/builtin/categorical_distribution.py +67 -0
  44. nancora-0.1.0/src/nancora/analysis/builtin/categorical_numeric.py +72 -0
  45. nancora-0.1.0/src/nancora/analysis/builtin/correlation_analysis.py +77 -0
  46. nancora-0.1.0/src/nancora/analysis/builtin/datetime_numeric_trend.py +72 -0
  47. nancora-0.1.0/src/nancora/analysis/builtin/missingness_analysis.py +61 -0
  48. nancora-0.1.0/src/nancora/analysis/builtin/numeric_distribution.py +60 -0
  49. nancora-0.1.0/src/nancora/analysis/builtin/numeric_relationship.py +62 -0
  50. nancora-0.1.0/src/nancora/analysis/builtin/outlier_analysis.py +59 -0
  51. nancora-0.1.0/src/nancora/analysis/builtin/target_aware.py +164 -0
  52. nancora-0.1.0/src/nancora/analysis/evidence.py +125 -0
  53. nancora-0.1.0/src/nancora/analysis/pairing.py +28 -0
  54. nancora-0.1.0/src/nancora/analysis/registry.py +35 -0
  55. nancora-0.1.0/src/nancora/analysis/serialize.py +25 -0
  56. nancora-0.1.0/src/nancora/cli.py +69 -0
  57. nancora-0.1.0/src/nancora/data/__init__.py +19 -0
  58. nancora-0.1.0/src/nancora/data/io.py +45 -0
  59. nancora-0.1.0/src/nancora/data/profile.py +97 -0
  60. nancora-0.1.0/src/nancora/data/schema.py +109 -0
  61. nancora-0.1.0/src/nancora/data/transform.py +28 -0
  62. nancora-0.1.0/src/nancora/engine/__init__.py +99 -0
  63. nancora-0.1.0/src/nancora/engine/explain.py +43 -0
  64. nancora-0.1.0/src/nancora/engine/generate.py +17 -0
  65. nancora-0.1.0/src/nancora/engine/rank.py +41 -0
  66. nancora-0.1.0/src/nancora/engine/redundancy.py +128 -0
  67. nancora-0.1.0/src/nancora/engine/score.py +180 -0
  68. nancora-0.1.0/src/nancora/engine/validate.py +47 -0
  69. nancora-0.1.0/src/nancora/exceptions.py +17 -0
  70. nancora-0.1.0/src/nancora/numeric/__init__.py +5 -0
  71. nancora-0.1.0/src/nancora/numeric/arrays.py +57 -0
  72. nancora-0.1.0/src/nancora/plot/__init__.py +22 -0
  73. nancora-0.1.0/src/nancora/plot/matplotlib_backend.py +71 -0
  74. nancora-0.1.0/src/nancora/plot/plotly_backend.py +46 -0
  75. nancora-0.1.0/src/nancora/plot/spec.py +18 -0
  76. nancora-0.1.0/src/nancora/py.typed +0 -0
  77. nancora-0.1.0/src/nancora/report/__init__.py +1 -0
  78. nancora-0.1.0/src/nancora/report/html.py +57 -0
  79. nancora-0.1.0/src/nancora/report/templates/__init__.py +0 -0
  80. nancora-0.1.0/src/nancora/report/templates/report.html.j2 +65 -0
  81. nancora-0.1.0/src/nancora/result.py +253 -0
  82. nancora-0.1.0/src/nancora/stats/__init__.py +21 -0
  83. nancora-0.1.0/src/nancora/stats/tests.py +107 -0
  84. nancora-0.1.0/src/nancora/types.py +49 -0
  85. nancora-0.1.0/tests/conftest.py +20 -0
  86. nancora-0.1.0/tests/fixtures/.gitkeep +0 -0
  87. nancora-0.1.0/tests/integration/__init__.py +0 -0
  88. nancora-0.1.0/tests/integration/test_cli.py +17 -0
  89. nancora-0.1.0/tests/integration/test_explore.py +52 -0
  90. nancora-0.1.0/tests/integration/test_report.py +25 -0
  91. nancora-0.1.0/tests/regression/__init__.py +0 -0
  92. nancora-0.1.0/tests/regression/test_decisions.py +93 -0
  93. nancora-0.1.0/tests/regression/test_determinism_and_scores.py +88 -0
  94. nancora-0.1.0/tests/regression/test_golden_and_edge_cases.py +92 -0
  95. nancora-0.1.0/tests/regression/test_intelligence.py +191 -0
  96. nancora-0.1.0/tests/regression/test_result_schema.py +25 -0
  97. nancora-0.1.0/tests/unit/__init__.py +0 -0
  98. nancora-0.1.0/tests/unit/test_benchmarks.py +10 -0
  99. nancora-0.1.0/tests/unit/test_insights.py +31 -0
  100. nancora-0.1.0/tests/unit/test_io.py +13 -0
  101. nancora-0.1.0/tests/unit/test_numeric_stats.py +15 -0
  102. nancora-0.1.0/tests/unit/test_primary_ux.py +91 -0
  103. nancora-0.1.0/tests/unit/test_profile.py +13 -0
  104. nancora-0.1.0/tests/unit/test_redundancy.py +38 -0
  105. nancora-0.1.0/tests/unit/test_registry.py +19 -0
  106. nancora-0.1.0/tests/unit/test_schema.py +34 -0
  107. nancora-0.1.0/tests/unit/test_score.py +26 -0
@@ -0,0 +1,17 @@
1
+ .venv/
2
+ .venv*/
3
+ venv/
4
+ scratch/
5
+ __pycache__/
6
+ *.py[cod]
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .coverage
14
+ htmlcov/
15
+ *.png
16
+ .DS_Store
17
+
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).
@@ -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,11 @@
1
+ churned,tenure,contract
2
+ True,2,Month-to-month
3
+ True,5,Month-to-month
4
+ True,1,Month-to-month
5
+ True,3,Month-to-month
6
+ False,48,Two-year
7
+ False,60,One-year
8
+ False,36,Two-year
9
+ False,72,Two-year
10
+ True,4,Month-to-month
11
+ False,55,One-year
@@ -0,0 +1,11 @@
1
+ x,y,noise,group
2
+ 0.0,0.1,1.2,a
3
+ 1.0,2.1,-0.3,b
4
+ 2.0,4.0,0.4,a
5
+ 3.0,6.2,1.1,b
6
+ 4.0,7.9,-0.2,a
7
+ 5.0,10.1,0.8,b
8
+ 6.0,12.0,-1.0,a
9
+ 7.0,14.2,0.3,b
10
+ 8.0,15.9,0.1,a
11
+ 9.0,18.1,-0.4,b
@@ -0,0 +1,11 @@
1
+ col_a,col_b,col_c
2
+ 1.0,,3.0
3
+ ,2.0,
4
+ 3.0,,1.0
5
+ ,4.0,5.0
6
+ 5.0,,
7
+ ,6.0,7.0
8
+ 7.0,,
9
+ ,8.0,9.0
10
+ 9.0,,1.0
11
+ ,10.0,
@@ -0,0 +1,16 @@
1
+ val,group
2
+ 1.2,A
3
+ 0.9,A
4
+ 1.1,B
5
+ 0.8,A
6
+ 1.0,B
7
+ 1.3,A
8
+ 0.7,B
9
+ 1.1,A
10
+ 0.95,B
11
+ 1.05,A
12
+ 500.0,B
13
+ -450.0,A
14
+ 600.0,B
15
+ -550.0,A
16
+ 700.0,B
@@ -0,0 +1,7 @@
1
+ spend,revenue,noise,region
2
+ 10,21,3,north
3
+ 11,22,8,south
4
+ 12,25,1,north
5
+ 13,27,4,east
6
+ 14,29,9,south
7
+ 15,31,2,north
@@ -0,0 +1,5 @@
1
+ {
2
+ "schema_version": "1.0.0",
3
+ "description": "Human labels per (dataset_id, analysis_id, variables). Allowed labels: useful, essential, redundant, irrelevant, misleading. This file is empty on purpose; do not invent scores.",
4
+ "labels": []
5
+ }
@@ -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,8 @@
1
+ # CLI
2
+
3
+ ```bash
4
+ nancora explore path.csv --max-analyses 10 --out report.html
5
+ nancora analyze path.csv --target y --out report.html --json
6
+ ```
7
+
8
+ Supported suffixes: `.csv`, `.json`, `.xlsx` (extra), `.parquet` (extra).
@@ -0,0 +1,7 @@
1
+ # Data
2
+
3
+ `nc.read_csv`, `nc.read_excel`, `nc.read_json`, `nc.read_parquet` delegate to Pandas.
4
+
5
+ `nc.data.infer_schema` and `nc.data.profile` produce the typed profile the engine uses.
6
+
7
+ Transforms are few and engine-needed: `drop_constant`, `coerce_datetime`.
@@ -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.