ipytreecraft 0.1.0__py3-none-any.whl

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,10 @@
1
+ {
2
+ "@jupyterlab/notebook-extension:tracker": {
3
+ "windowingMode": "none"
4
+ },
5
+ "@jupyter-widgets/jupyterlab-manager:plugin": {
6
+ "saveState": true
7
+ }
8
+ }
9
+
10
+
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.4
2
+ Name: ipytreecraft
3
+ Version: 0.1.0
4
+ Summary: Incremental, interactive decision tree builder for risk specialists
5
+ Author-email: Mark <mark@example.com>
6
+ Classifier: Framework :: Jupyter
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Programming Language :: Python :: 3
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: joblib>=1.3
11
+ Requires-Dist: numpy>=1.26
12
+ Requires-Dist: pandas>=2.0
13
+ Requires-Dist: pydantic>=2.0
14
+ Requires-Dist: scikit-learn<2.0,>=1.5
15
+ Provides-Extra: api
16
+ Requires-Dist: fastapi>=0.110; extra == 'api'
17
+ Requires-Dist: python-multipart>=0.0.9; extra == 'api'
18
+ Requires-Dist: slowapi>=0.1.9; extra == 'api'
19
+ Requires-Dist: uvicorn[standard]>=0.29; extra == 'api'
20
+ Provides-Extra: dev
21
+ Requires-Dist: httpx>=0.27; extra == 'dev'
22
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
23
+ Requires-Dist: pytest>=8.0; extra == 'dev'
24
+ Requires-Dist: ruff>=0.4; extra == 'dev'
25
+ Provides-Extra: jupyter
26
+ Requires-Dist: anywidget>=0.9.13; extra == 'jupyter'
27
+ Requires-Dist: jupyterlab>=4.0; extra == 'jupyter'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # Treecraft
31
+
32
+ [![CI](https://github.com/markdedeuge/treecraft/actions/workflows/ci.yml/badge.svg)](https://github.com/markdedeuge/treecraft/actions/workflows/ci.yml) [![Coverage](https://img.shields.io/badge/coverage-96%25-success)](https://github.com/markdedeuge/treecraft) [![PyPI version](https://img.shields.io/pypi/v/treecraft.svg)](https://pypi.org/project/treecraft/) [![Downloads](https://img.shields.io/pypi/dm/treecraft.svg)](https://pypi.org/project/treecraft/) [![License](https://img.shields.io/badge/license-MIT-success)](https://github.com/markdedeuge/treecraft/blob/main/LICENSE)
33
+
34
+ Treecraft is an interactive decision tree framework. Treecraft allows analysts to manually evaluate, override, and prune decision boundaries utilizing an interactive UI interface, while automatically refitting subtrees optimally beneath manual split points.
35
+
36
+ ![Treecraft Interface](docs/treecraftui.png)
37
+
38
+ The framework extends standard Scikit-Learn. Modifications made visually in the interface alter splits in the underlying tree graph. The resulting model can then be exported as a standard, fitted `DecisionTreeClassifier` or `DecisionTreeRegressor` compatible with standard sklearn pipelines.
39
+
40
+ ## Installation
41
+
42
+ This module provides an interactive interface built upon `anywidget` for integration into Jupyter ecosystems.
43
+
44
+ Install from source locally via standard python package deployment:
45
+
46
+ ```bash
47
+ pip install -e .
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ Initiate the widget by supplying a `pandas.DataFrame` and declaring the target feature.
53
+
54
+ ```python
55
+ import pandas as pd
56
+ from sklearn.datasets import load_iris
57
+ from treecraft.widget import TreecraftWidget
58
+
59
+ data = load_iris()
60
+ df = pd.DataFrame(data.data, columns=data.feature_names)
61
+ df['target'] = data.target
62
+
63
+ # Instantiate the interactive widget
64
+ widget = TreecraftWidget(df=df, target='target', model_id='iris_model')
65
+ widget
66
+ ```
67
+
68
+ Once the interactive visualization opens, users can apply initial hyperparameters, manually navigate tree partitions, append/remove split points, and verify node performance across configurable train/validation splits.
69
+
70
+ ### Pipeline Extraction
71
+
72
+ After modifying the boundaries visually to conform to the required constraints, extract the native Scikit-Learn estimator.
73
+
74
+ ```python
75
+ from sklearn.pipeline import make_pipeline
76
+
77
+ # Extract the fitted Scikit-Learn estimator structure
78
+ clf = widget.export_sklearn()
79
+ pipeline = make_pipeline(clf)
80
+
81
+ # Execute predictions
82
+ X_test = df.drop(columns=['target'])
83
+ predictions = pipeline.predict(X_test)
84
+ ```
85
+
@@ -0,0 +1,21 @@
1
+ treecraft/__init__.py,sha256=4OzJlLicc8p4dFR5iXtQZ_0A0r6LAABbEJz9yBcdlhw,530
2
+ treecraft/widget.py,sha256=_Ea3bhUmvNsGg39sbSwA8iA77g91ejBDHrBGqv9ae_c,20610
3
+ treecraft/engine/__init__.py,sha256=Bi742cK5TaARof69Mct6pTLrLWMf6cyNEeNOAOh-ZWc,49
4
+ treecraft/engine/constraint_manager.py,sha256=23qQsGqTJLLuj7iHhA8I_F4ip5QF_DxulPZdZ3pvjrs,2108
5
+ treecraft/engine/incremental_tree.py,sha256=Aw49INnQtvlYXAs6qijlfva4oF4P9SPibi6i4HZWevo,51143
6
+ treecraft/engine/metrics.py,sha256=3Bmh1pYtnvZLK95_1x5_eChOpvNalj4UFuJz0c2yet0,5281
7
+ treecraft/engine/subtree_fitter.py,sha256=4_X6ucSbyo-xOv5ZWjLYrap_6bRhNNpb4qWlwa0LYSo,2691
8
+ treecraft/engine/tree_state.py,sha256=ggjloDrydLoxIizVomlMWngHbfkezq2OTk0xWi6r0Kg,16318
9
+ treecraft/engine/tree_stitcher.py,sha256=NVlhFqwSRBeO4SqZEgqjPWT_spphr3DoDcQ-p-bEVco,11000
10
+ treecraft/models/__init__.py,sha256=OG-5u75I0WMtqIXLNi-zI7xNPggiv6msefUmGVEGz3E,47
11
+ treecraft/models/enums.py,sha256=9ne2S2lot7CB5E39pRiygOZwgSjRQPCO1NtL0NYNQco,1480
12
+ treecraft/models/parameter_introspector.py,sha256=Pv2JQmESldpo-gCnPhkRT79aTXXZ02rSwZKP4uX9yTA,12817
13
+ treecraft/models/schemas.py,sha256=gZD8luL6IYs3eevfp_liv6yl0KutESiS94RlSHmww7U,15005
14
+ treecraft/utils/__init__.py,sha256=dOYPix5mbfd6guG-bxYgZTiDkSEQQyTBIJjlejehVs8,39
15
+ treecraft/utils/export.py,sha256=Q340B3qJ_z--8YIjN8MfHw2bHBhiGZ6cX6jXNvz7RKs,6148
16
+ treecraft/utils/type_inference.py,sha256=U4AehDQzMa5sEa5nID-fEMyufSXX3tauwycBLLWuXMU,3009
17
+ treecraft/static/widget.js,sha256=KyNSFMXwinTt8pTAe8sNUTdFgMOQzzVPz-if6a36gLE,4472839
18
+ ipytreecraft-0.1.0.data/data/share/jupyter/lab/settings/overrides.d/treecraft.json,sha256=POfn1Hhafiec2hUk4LEZZvDFPZDpYDu3p14fVZTcyhE,161
19
+ ipytreecraft-0.1.0.dist-info/METADATA,sha256=UW5OeS3_Mk0wWB0iFgjrGUy9kLTMH4QiyzsxV_Yn9cQ,3561
20
+ ipytreecraft-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
21
+ ipytreecraft-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
treecraft/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ """Treecraft: Incremental, interactive decision tree builder."""
2
+
3
+ from treecraft.engine.incremental_tree import IncrementalTreeEngine
4
+ from treecraft.models.schemas import (
5
+ CrossValidationParams,
6
+ DatasetMeta,
7
+ SplitOverride,
8
+ TrainTestSplitParams,
9
+ TreeConfig,
10
+ )
11
+ from treecraft.widget import TreecraftWidget
12
+
13
+ __version__ = "0.1.0"
14
+
15
+ __all__ = [
16
+ "IncrementalTreeEngine",
17
+ "TreecraftWidget",
18
+ "DatasetMeta",
19
+ "TreeConfig",
20
+ "TrainTestSplitParams",
21
+ "CrossValidationParams",
22
+ "SplitOverride",
23
+ ]
@@ -0,0 +1 @@
1
+ """Core engine for incremental tree building."""
@@ -0,0 +1,58 @@
1
+ """ConstraintManager — tracks pinned nodes and manual split overrides."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from treecraft.models.schemas import SplitOverride
6
+
7
+
8
+ class ConstraintManager:
9
+ """Manages which tree nodes have user-pinned split overrides.
10
+
11
+ A pinned node's split (feature + threshold/categories) is frozen
12
+ during incremental re-training — the algorithm only optimises the
13
+ subtrees below pinned nodes.
14
+ """
15
+
16
+ def __init__(self) -> None:
17
+ self._pinned: dict[int, SplitOverride] = {}
18
+
19
+ def pin_node(self, node_id: int, override: SplitOverride) -> None:
20
+ """Pin *node_id* with the given split override."""
21
+ self._pinned[node_id] = override
22
+
23
+ def unpin_node(self, node_id: int) -> None:
24
+ """Remove the pin from *node_id* (no-op if not pinned)."""
25
+ self._pinned.pop(node_id, None)
26
+
27
+ def get_pinned_nodes(self) -> dict[int, SplitOverride]:
28
+ """Return a copy of all pinned node overrides."""
29
+ return dict(self._pinned)
30
+
31
+ def is_frozen(self, node_id: int) -> bool:
32
+ """Return ``True`` if *node_id* is pinned."""
33
+ return node_id in self._pinned
34
+
35
+ def clear(self) -> None:
36
+ """Remove all pins."""
37
+ self._pinned.clear()
38
+
39
+ def remap_ids(self, old_to_new: dict[int, int]) -> None:
40
+ """Remap pinned node IDs after a stitch operation.
41
+
42
+ Pins whose old ID does not appear in *old_to_new* are
43
+ discarded (the node was removed during the stitch).
44
+ """
45
+ remapped: dict[int, SplitOverride] = {}
46
+ for old_id, override in self._pinned.items():
47
+ new_id = old_to_new.get(old_id)
48
+ if new_id is not None:
49
+ # Update the node_id inside the override as well.
50
+ override = override.model_copy(update={"node_id": new_id})
51
+ remapped[new_id] = override
52
+ self._pinned = remapped
53
+
54
+ def copy(self) -> ConstraintManager:
55
+ """Return a deep copy of this manager."""
56
+ cm = ConstraintManager()
57
+ cm._pinned = {k: v.model_copy() for k, v in self._pinned.items()}
58
+ return cm