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.
- ipytreecraft-0.1.0.data/data/share/jupyter/lab/settings/overrides.d/treecraft.json +10 -0
- ipytreecraft-0.1.0.dist-info/METADATA +85 -0
- ipytreecraft-0.1.0.dist-info/RECORD +21 -0
- ipytreecraft-0.1.0.dist-info/WHEEL +4 -0
- treecraft/__init__.py +23 -0
- treecraft/engine/__init__.py +1 -0
- treecraft/engine/constraint_manager.py +58 -0
- treecraft/engine/incremental_tree.py +1263 -0
- treecraft/engine/metrics.py +161 -0
- treecraft/engine/subtree_fitter.py +85 -0
- treecraft/engine/tree_state.py +427 -0
- treecraft/engine/tree_stitcher.py +280 -0
- treecraft/models/__init__.py +1 -0
- treecraft/models/enums.py +66 -0
- treecraft/models/parameter_introspector.py +374 -0
- treecraft/models/schemas.py +510 -0
- treecraft/static/widget.js +100319 -0
- treecraft/utils/__init__.py +1 -0
- treecraft/utils/export.py +191 -0
- treecraft/utils/type_inference.py +86 -0
- treecraft/widget.py +583 -0
|
@@ -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
|
+
[](https://github.com/markdedeuge/treecraft/actions/workflows/ci.yml) [](https://github.com/markdedeuge/treecraft) [](https://pypi.org/project/treecraft/) [](https://pypi.org/project/treecraft/) [](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
|
+

|
|
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,,
|
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
|