chartrank 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.
@@ -0,0 +1,40 @@
1
+ # --- Python bytecode and caches ---
2
+ __pycache__/
3
+ *.py[cod]
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+
8
+ # --- Build and packaging artifacts ---
9
+ build/
10
+ dist/
11
+ *.egg-info/
12
+
13
+ # --- Test coverage reports ---
14
+ .coverage
15
+ .coverage.*
16
+ coverage.xml
17
+ htmlcov/
18
+
19
+ # --- Virtual environments ---
20
+ .venv/
21
+ venv/
22
+
23
+ # --- Documentation build output (MkDocs) ---
24
+ site/
25
+
26
+ # --- Notebooks and editors ---
27
+ .ipynb_checkpoints/
28
+ .vscode/
29
+ .idea/
30
+
31
+ # --- OS files ---
32
+ .DS_Store
33
+ Thumbs.db
34
+
35
+ # --- Local working notes (not part of the project) ---
36
+ .claude/
37
+ PROJECT_GUIDE.md
38
+ PREMIER_MESSAGE.md
39
+ LISEZ-MOI_KIT.md
40
+ docs/journal/
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-11-15
11
+
12
+ Early preview to reserve the name on PyPI. Not ready for use.
13
+
14
+ ### Added
15
+
16
+ - `profile(df)`: minimal column profile (dtype, share of missing values,
17
+ number of distinct values).
18
+ - Package structure (`src/` layout, hatchling build, empty modules).
19
+ - Community files: README (EN/FR), license, contributing guide, code of conduct,
20
+ issue templates.
21
+ - Continuous integration (GitHub Actions): ruff, mypy, and pytest with coverage
22
+ ≥ 85 % on Python 3.11 to 3.14 (Linux) and 3.11 (Windows, macOS), with
23
+ network access disabled during tests.
24
+ - Documentation skeleton (MkDocs Material, English and French) published to
25
+ GitHub Pages; new `[docs]` extra.
26
+ - Release workflow: a `v*` tag publishes to PyPI with Trusted Publishing;
27
+ a manual run publishes to TestPyPI.
28
+
29
+ [Unreleased]: https://github.com/Adeshinan/chartrank/compare/v0.1.0...HEAD
30
+ [0.1.0]: https://github.com/Adeshinan/chartrank/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Faïd Adéshinan ALAGBE
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.
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.5
2
+ Name: chartrank
3
+ Version: 0.1.0
4
+ Summary: Early preview, not ready for use. Suggests and explains the right chart for your data and intent.
5
+ Project-URL: Homepage, https://github.com/Adeshinan/chartrank
6
+ Project-URL: Issues, https://github.com/Adeshinan/chartrank/issues
7
+ Project-URL: Changelog, https://github.com/Adeshinan/chartrank/blob/main/CHANGELOG.md
8
+ Author: Faïd Adéshinan ALAGBE
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: chart-recommendation,data-visualization,eda,pandas,plotly
12
+ Classifier: Development Status :: 2 - Pre-Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Scientific/Engineering :: Visualization
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: numpy>=1.26
24
+ Requires-Dist: pandas>=2.2
25
+ Requires-Dist: plotly>=5.24
26
+ Requires-Dist: pyyaml>=6.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: build>=1.2; extra == 'dev'
29
+ Requires-Dist: mkdocs-material>=9.5; extra == 'dev'
30
+ Requires-Dist: mkdocs-static-i18n>=1.2; extra == 'dev'
31
+ Requires-Dist: mypy>=1.11; extra == 'dev'
32
+ Requires-Dist: pandas-stubs>=2.2; extra == 'dev'
33
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
34
+ Requires-Dist: pytest-socket>=0.7; extra == 'dev'
35
+ Requires-Dist: pytest>=8.0; extra == 'dev'
36
+ Requires-Dist: ruff>=0.6; extra == 'dev'
37
+ Requires-Dist: twine>=6.0; extra == 'dev'
38
+ Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
39
+ Provides-Extra: docs
40
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
41
+ Requires-Dist: mkdocs-static-i18n>=1.2; extra == 'docs'
42
+ Provides-Extra: static
43
+ Requires-Dist: kaleido>=1.0; extra == 'static'
44
+ Description-Content-Type: text/markdown
45
+
46
+ # ChartRank
47
+
48
+ [![CI](https://github.com/Adeshinan/chartrank/actions/workflows/ci.yml/badge.svg)](https://github.com/Adeshinan/chartrank/actions/workflows/ci.yml)
49
+ [![Docs](https://github.com/Adeshinan/chartrank/actions/workflows/docs.yml/badge.svg)](https://adeshinan.github.io/chartrank/)
50
+ ![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)
51
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
52
+
53
+ **Suggests and explains the right chart for your data and intent.**
54
+
55
+ > 🚧 Work in progress — v1.0 planned for December 2026.
56
+
57
+ Give ChartRank one or two columns and an intent (`compare`, `distribution`,
58
+ `relationship`, `composition`, `trend`). It proposes 3 to 5 charts drawn on your
59
+ own data, marks the recommended one with ★, explains each with numbers computed
60
+ from your data ("good for / avoid if"), and gives you the image or a standalone
61
+ Python script to reproduce it. Deterministic, local, no AI.
62
+
63
+ ```python
64
+ import chartrank as cr
65
+
66
+ g = cr.suggest(df, x="Pclass", y="Fare", intent="compare")
67
+ g.show()
68
+ print(g.best.why)
69
+ print(g.best.code)
70
+ ```
71
+
72
+ *This API is the v1.0 target and is not available yet.*
73
+
74
+ [Documentation](https://adeshinan.github.io/chartrank/) ·
75
+ [Version française](README.fr.md) · License: [MIT](LICENSE)
@@ -0,0 +1,30 @@
1
+ # ChartRank
2
+
3
+ [![CI](https://github.com/Adeshinan/chartrank/actions/workflows/ci.yml/badge.svg)](https://github.com/Adeshinan/chartrank/actions/workflows/ci.yml)
4
+ [![Docs](https://github.com/Adeshinan/chartrank/actions/workflows/docs.yml/badge.svg)](https://adeshinan.github.io/chartrank/fr/)
5
+ ![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)
6
+ [![Licence : MIT](https://img.shields.io/badge/licence-MIT-green)](LICENSE)
7
+
8
+ **Propose et explique le bon graphique selon vos données et votre intention.**
9
+
10
+ > 🚧 En cours de développement — v1.0 prévue en décembre 2026.
11
+
12
+ Donnez à ChartRank une ou deux colonnes et une intention (`compare`,
13
+ `distribution`, `relationship`, `composition`, `trend`). Il propose 3 à 5
14
+ graphiques dessinés sur vos données, marque la recommandation ★, explique chacun
15
+ avec des chiffres calculés sur vos données (« bon pour / à éviter si ») et fournit
16
+ l'image ou un script Python autonome pour le refaire. Déterministe, local, sans IA.
17
+
18
+ ```python
19
+ import chartrank as cr
20
+
21
+ g = cr.suggest(df, x="Pclass", y="Fare", intent="compare")
22
+ g.show()
23
+ print(g.best.why)
24
+ print(g.best.code)
25
+ ```
26
+
27
+ *Cette API est l'objectif de la v1.0 et n'est pas encore disponible.*
28
+
29
+ [Documentation](https://adeshinan.github.io/chartrank/fr/) ·
30
+ [English version](README.md) · Licence : [MIT](LICENSE)
@@ -0,0 +1,30 @@
1
+ # ChartRank
2
+
3
+ [![CI](https://github.com/Adeshinan/chartrank/actions/workflows/ci.yml/badge.svg)](https://github.com/Adeshinan/chartrank/actions/workflows/ci.yml)
4
+ [![Docs](https://github.com/Adeshinan/chartrank/actions/workflows/docs.yml/badge.svg)](https://adeshinan.github.io/chartrank/)
5
+ ![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
7
+
8
+ **Suggests and explains the right chart for your data and intent.**
9
+
10
+ > 🚧 Work in progress — v1.0 planned for December 2026.
11
+
12
+ Give ChartRank one or two columns and an intent (`compare`, `distribution`,
13
+ `relationship`, `composition`, `trend`). It proposes 3 to 5 charts drawn on your
14
+ own data, marks the recommended one with ★, explains each with numbers computed
15
+ from your data ("good for / avoid if"), and gives you the image or a standalone
16
+ Python script to reproduce it. Deterministic, local, no AI.
17
+
18
+ ```python
19
+ import chartrank as cr
20
+
21
+ g = cr.suggest(df, x="Pclass", y="Fare", intent="compare")
22
+ g.show()
23
+ print(g.best.why)
24
+ print(g.best.code)
25
+ ```
26
+
27
+ *This API is the v1.0 target and is not available yet.*
28
+
29
+ [Documentation](https://adeshinan.github.io/chartrank/) ·
30
+ [Version française](README.fr.md) · License: [MIT](LICENSE)
@@ -0,0 +1,101 @@
1
+ # ---------------------------------------------------------------------------
2
+ # Build system: which tool turns the source tree into an installable package.
3
+ # ---------------------------------------------------------------------------
4
+ [build-system]
5
+ requires = ["hatchling>=1.25"]
6
+ build-backend = "hatchling.build"
7
+
8
+ # ---------------------------------------------------------------------------
9
+ # Package metadata (PEP 621): what PyPI and pip read.
10
+ # ---------------------------------------------------------------------------
11
+ [project]
12
+ name = "chartrank"
13
+ dynamic = ["version"] # read from src/chartrank/__init__.py (see [tool.hatch.version])
14
+ description = "Early preview, not ready for use. Suggests and explains the right chart for your data and intent."
15
+ readme = "README.md"
16
+ license = "MIT"
17
+ license-files = ["LICENSE"]
18
+ authors = [{ name = "Faïd Adéshinan ALAGBE" }]
19
+ requires-python = ">=3.11"
20
+ dependencies = [
21
+ "pandas>=2.2",
22
+ "numpy>=1.26",
23
+ "plotly>=5.24",
24
+ "pyyaml>=6.0",
25
+ ]
26
+ keywords = ["data-visualization", "plotly", "pandas", "eda", "chart-recommendation"]
27
+ classifiers = [
28
+ "Development Status :: 2 - Pre-Alpha",
29
+ "Intended Audience :: Science/Research",
30
+ "Intended Audience :: Developers",
31
+ "Programming Language :: Python :: 3",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Programming Language :: Python :: 3.13",
35
+ "Programming Language :: Python :: 3.14",
36
+ "Topic :: Scientific/Engineering :: Visualization",
37
+ "Typing :: Typed",
38
+ ]
39
+
40
+ [project.optional-dependencies]
41
+ static = ["kaleido>=1.0"] # PNG / SVG export
42
+ dev = [
43
+ "pytest>=8.0",
44
+ "pytest-cov>=5.0",
45
+ "pytest-socket>=0.7",
46
+ "ruff>=0.6",
47
+ "mypy>=1.11",
48
+ "pandas-stubs>=2.2",
49
+ "types-PyYAML>=6.0",
50
+ "build>=1.2",
51
+ "twine>=6.0",
52
+ "chartrank[docs]", # dev also installs the docs tools
53
+ ]
54
+ docs = [
55
+ "mkdocs-material>=9.5",
56
+ "mkdocs-static-i18n>=1.2",
57
+ ]
58
+
59
+ [project.urls]
60
+ Homepage = "https://github.com/Adeshinan/chartrank"
61
+ Issues = "https://github.com/Adeshinan/chartrank/issues"
62
+ Changelog = "https://github.com/Adeshinan/chartrank/blob/main/CHANGELOG.md"
63
+
64
+ # ---------------------------------------------------------------------------
65
+ # Hatchling settings
66
+ # ---------------------------------------------------------------------------
67
+ [tool.hatch.version]
68
+ path = "src/chartrank/__init__.py"
69
+
70
+ [tool.hatch.build.targets.wheel]
71
+ packages = ["src/chartrank"]
72
+
73
+ [tool.hatch.build.targets.sdist]
74
+ # Source archive: code, tests and the files a packager needs, nothing else.
75
+ include = ["src/", "tests/", "README.md", "README.fr.md", "LICENSE", "CHANGELOG.md"]
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Tools
79
+ # ---------------------------------------------------------------------------
80
+ [tool.ruff]
81
+ line-length = 88
82
+ target-version = "py311"
83
+ extend-exclude = ["docs/spec"] # reference documents, not project code
84
+
85
+ [tool.ruff.lint]
86
+ # E/W: style, F: real errors, I: import order, UP: modern syntax, B: likely bugs
87
+ select = ["E", "W", "F", "I", "UP", "B"]
88
+
89
+ [tool.mypy]
90
+ python_version = "3.11"
91
+ strict = true
92
+
93
+ [tool.pytest.ini_options]
94
+ testpaths = ["src", "tests"]
95
+ # -ra: summary of skipped/failed tests; --disable-socket: no network (ENF-05);
96
+ # --doctest-modules: the examples in docstrings are run as tests.
97
+ addopts = "-ra --disable-socket --doctest-modules"
98
+
99
+ [tool.coverage.run]
100
+ source = ["chartrank"]
101
+ branch = true
@@ -0,0 +1,7 @@
1
+ """ChartRank: suggests and explains the right chart for your data and intent."""
2
+
3
+ from chartrank.profile import profile
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ __all__ = ["__version__", "profile"]
@@ -0,0 +1,4 @@
1
+ """Chart catalog.
2
+
3
+ Declarative table mapping (variable types, intent) to candidate charts.
4
+ """
@@ -0,0 +1 @@
1
+ """Command-line interface: profile, suggest and rank commands."""
@@ -0,0 +1 @@
1
+ """Code generation: turn a ChartSpec into a standalone, commented Python script."""
@@ -0,0 +1,4 @@
1
+ """Data dictionary.
2
+
3
+ Load and validate YAML or dict metadata (units, order, labels, roles).
4
+ """
@@ -0,0 +1 @@
1
+ """Gallery: object returned by suggest(), with show(), best and indexing."""
@@ -0,0 +1,4 @@
1
+ """Guard rules.
2
+
3
+ Best-practice checks (log scale, top N, overplotting...) applied to candidates.
4
+ """
@@ -0,0 +1,50 @@
1
+ """Column profiling: semantic types and indicators.
2
+
3
+ Version 0.1 is minimal: for each column, its pandas dtype, the share of missing
4
+ values and the number of distinct values. Semantic types come in task 1.1.
5
+ """
6
+
7
+ import pandas as pd
8
+
9
+
10
+ def profile(df: pd.DataFrame) -> pd.DataFrame:
11
+ """Describe each column of ``df``.
12
+
13
+ Args:
14
+ df: The data to describe. It is never modified.
15
+
16
+ Returns:
17
+ One row per column (same order as ``df``), with:
18
+
19
+ - ``dtype``: the pandas dtype, as text (e.g. ``"float64"``);
20
+ - ``missing``: share of missing values, between 0 and 1;
21
+ - ``distinct``: number of distinct non-missing values.
22
+
23
+ Raises:
24
+ TypeError: If ``df`` is not a pandas DataFrame.
25
+
26
+ Example:
27
+ >>> import pandas as pd
28
+ >>> import chartrank as cr
29
+ >>> cr.profile(pd.DataFrame({"Age": [22.0, None, 38.0, 22.0]}))
30
+ dtype missing distinct
31
+ Age float64 0.25 2
32
+ """
33
+ if not isinstance(df, pd.DataFrame):
34
+ raise TypeError(
35
+ f"profile() expects a pandas DataFrame, got {type(df).__name__}. "
36
+ "Convert your data first, e.g. pd.DataFrame(data) or pd.read_csv(path)."
37
+ )
38
+
39
+ n_rows = len(df)
40
+ rows = {
41
+ column: {
42
+ "dtype": str(df[column].dtype),
43
+ "missing": df[column].isna().sum() / n_rows if n_rows else 0.0,
44
+ "distinct": df[column].nunique(dropna=True),
45
+ }
46
+ for column in df.columns
47
+ }
48
+ return pd.DataFrame.from_dict(
49
+ rows, orient="index", columns=["dtype", "missing", "distinct"]
50
+ )
File without changes
@@ -0,0 +1 @@
1
+ """Recommender: combines catalog and guards, ranks candidates, picks the star chart."""
@@ -0,0 +1 @@
1
+ """Rendering backends that turn a ChartSpec into a figure."""
@@ -0,0 +1 @@
1
+ """Plotly backend: ChartSpec to plotly.graph_objects.Figure, with the common theme."""
@@ -0,0 +1 @@
1
+ """ChartSpec: neutral, engine-independent description of a chart."""
@@ -0,0 +1,4 @@
1
+ """Target analysis.
2
+
3
+ Task detection and association measures between variables and a target.
4
+ """
@@ -0,0 +1,7 @@
1
+ """Smoke tests: the package can be imported and exposes its version."""
2
+
3
+ import chartrank
4
+
5
+
6
+ def test_version_is_exposed() -> None:
7
+ assert chartrank.__version__ == "0.1.0"
@@ -0,0 +1,12 @@
1
+ """The test suite runs without network access (ENF-05)."""
2
+
3
+ import socket
4
+
5
+ import pytest
6
+ from pytest_socket import SocketBlockedError
7
+
8
+
9
+ @pytest.mark.filterwarnings("ignore:A test tried to use socket.socket")
10
+ def test_network_is_blocked() -> None:
11
+ with pytest.raises(SocketBlockedError):
12
+ socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -0,0 +1,62 @@
1
+ """Tests for the minimal profile (task 0.4): dtype, missing share, distinct count."""
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import pytest
6
+
7
+ import chartrank as cr
8
+
9
+
10
+ @pytest.fixture
11
+ def passengers() -> pd.DataFrame:
12
+ """A tiny Titanic-like DataFrame: 4 passengers, a few missing values."""
13
+ return pd.DataFrame(
14
+ {
15
+ "Pclass": [1, 3, 3, 2],
16
+ "Sex": ["female", "male", "male", None],
17
+ "Age": [38.0, np.nan, 26.0, np.nan],
18
+ }
19
+ )
20
+
21
+
22
+ def test_one_row_per_column_in_original_order(passengers: pd.DataFrame) -> None:
23
+ result = cr.profile(passengers)
24
+ assert list(result.index) == ["Pclass", "Sex", "Age"]
25
+ assert list(result.columns) == ["dtype", "missing", "distinct"]
26
+
27
+
28
+ def test_dtype(passengers: pd.DataFrame) -> None:
29
+ result = cr.profile(passengers)
30
+ assert result.loc["Pclass", "dtype"] == "int64"
31
+ assert result.loc["Age", "dtype"] == "float64"
32
+
33
+
34
+ def test_missing_is_a_share_between_0_and_1(passengers: pd.DataFrame) -> None:
35
+ result = cr.profile(passengers)
36
+ assert result.loc["Pclass", "missing"] == 0.0
37
+ assert result.loc["Sex", "missing"] == 0.25 # 1 missing out of 4
38
+ assert result.loc["Age", "missing"] == 0.5 # 2 missing out of 4
39
+
40
+
41
+ def test_distinct_ignores_missing_values(passengers: pd.DataFrame) -> None:
42
+ result = cr.profile(passengers)
43
+ assert result.loc["Pclass", "distinct"] == 3 # 1, 2, 3
44
+ assert result.loc["Sex", "distinct"] == 2 # female, male (None not counted)
45
+ assert result.loc["Age", "distinct"] == 2 # 38, 26
46
+
47
+
48
+ def test_empty_dataframe_has_no_missing_values() -> None:
49
+ result = cr.profile(pd.DataFrame({"Fare": pd.Series([], dtype="float64")}))
50
+ assert result.loc["Fare", "missing"] == 0.0
51
+ assert result.loc["Fare", "distinct"] == 0
52
+
53
+
54
+ def test_input_dataframe_is_not_modified(passengers: pd.DataFrame) -> None:
55
+ before = passengers.copy()
56
+ cr.profile(passengers)
57
+ pd.testing.assert_frame_equal(passengers, before)
58
+
59
+
60
+ def test_rejects_non_dataframe_with_clear_message() -> None:
61
+ with pytest.raises(TypeError, match="expects a pandas DataFrame"):
62
+ cr.profile([1, 2, 3]) # type: ignore[arg-type]