proxyml 0.3.0__tar.gz → 0.4.1__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.
- {proxyml-0.3.0 → proxyml-0.4.1}/PKG-INFO +2 -2
- {proxyml-0.3.0 → proxyml-0.4.1}/pyproject.toml +2 -2
- {proxyml-0.3.0 → proxyml-0.4.1}/src/proxyml/__init__.py +5 -0
- {proxyml-0.3.0 → proxyml-0.4.1}/src/proxyml/local/__init__.py +2 -0
- {proxyml-0.3.0 → proxyml-0.4.1}/src/proxyml/local/challenger.py +30 -8
- {proxyml-0.3.0 → proxyml-0.4.1}/src/proxyml.egg-info/PKG-INFO +2 -2
- {proxyml-0.3.0 → proxyml-0.4.1}/src/proxyml.egg-info/requires.txt +1 -1
- {proxyml-0.3.0 → proxyml-0.4.1}/tests/test_local_challenger.py +58 -0
- {proxyml-0.3.0 → proxyml-0.4.1}/LICENSE +0 -0
- {proxyml-0.3.0 → proxyml-0.4.1}/README.md +0 -0
- {proxyml-0.3.0 → proxyml-0.4.1}/setup.cfg +0 -0
- {proxyml-0.3.0 → proxyml-0.4.1}/src/proxyml/client.py +0 -0
- {proxyml-0.3.0 → proxyml-0.4.1}/src/proxyml/schema_builder.py +0 -0
- {proxyml-0.3.0 → proxyml-0.4.1}/src/proxyml.egg-info/SOURCES.txt +0 -0
- {proxyml-0.3.0 → proxyml-0.4.1}/src/proxyml.egg-info/dependency_links.txt +0 -0
- {proxyml-0.3.0 → proxyml-0.4.1}/src/proxyml.egg-info/top_level.txt +0 -0
- {proxyml-0.3.0 → proxyml-0.4.1}/tests/test_client.py +0 -0
- {proxyml-0.3.0 → proxyml-0.4.1}/tests/test_dependency_boundaries.py +0 -0
- {proxyml-0.3.0 → proxyml-0.4.1}/tests/test_schema_builder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxyml
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Python SDK for calling the ProxyML API
|
|
5
5
|
Author-email: ProxyML <contact@proxyml.ai>
|
|
6
6
|
License: Apache License
|
|
@@ -211,7 +211,7 @@ Requires-Dist: pandas>=1.5
|
|
|
211
211
|
Requires-Dist: orjson>=3.9
|
|
212
212
|
Requires-Dist: proxyml-core>=0.1
|
|
213
213
|
Provides-Extra: local
|
|
214
|
-
Requires-Dist: proxyml-core[modeling]>=0.1; extra == "local"
|
|
214
|
+
Requires-Dist: proxyml-core[modeling]>=0.2.1; extra == "local"
|
|
215
215
|
Provides-Extra: dev
|
|
216
216
|
Requires-Dist: build; extra == "dev"
|
|
217
217
|
Requires-Dist: twine; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "proxyml"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
description = "Python SDK for calling the ProxyML API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {file = "LICENSE"}
|
|
@@ -35,7 +35,7 @@ dependencies = [
|
|
|
35
35
|
|
|
36
36
|
[project.optional-dependencies]
|
|
37
37
|
local = [
|
|
38
|
-
"proxyml-core[modeling]>=0.1",
|
|
38
|
+
"proxyml-core[modeling]>=0.2.1",
|
|
39
39
|
]
|
|
40
40
|
dev = [
|
|
41
41
|
"build",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from importlib.metadata import version as _pkg_version
|
|
2
|
+
|
|
1
3
|
from proxyml.client import (
|
|
2
4
|
health_check,
|
|
3
5
|
put_schema,
|
|
@@ -27,7 +29,10 @@ from proxyml.client import (
|
|
|
27
29
|
)
|
|
28
30
|
from proxyml.schema_builder import get_schema
|
|
29
31
|
|
|
32
|
+
__version__ = _pkg_version("proxyml")
|
|
33
|
+
|
|
30
34
|
__all__ = [
|
|
35
|
+
"__version__",
|
|
31
36
|
"health_check",
|
|
32
37
|
"put_schema",
|
|
33
38
|
"fetch_schema",
|
|
@@ -10,6 +10,7 @@ from proxyml.local.challenger import (
|
|
|
10
10
|
Complexity,
|
|
11
11
|
Rung,
|
|
12
12
|
TrainedChallenger,
|
|
13
|
+
score_champion,
|
|
13
14
|
train_auto_challenger,
|
|
14
15
|
train_challenger,
|
|
15
16
|
)
|
|
@@ -17,6 +18,7 @@ from proxyml.local.challenger import (
|
|
|
17
18
|
__all__ = [
|
|
18
19
|
"train_challenger",
|
|
19
20
|
"train_auto_challenger",
|
|
21
|
+
"score_champion",
|
|
20
22
|
"Complexity",
|
|
21
23
|
"Rung",
|
|
22
24
|
"TrainedChallenger",
|
|
@@ -10,7 +10,7 @@ training target was real ground truth or a black box's predictions.
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
-
from dataclasses import dataclass
|
|
13
|
+
from dataclasses import dataclass, replace
|
|
14
14
|
from enum import Enum
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
from typing import Any, Callable, Literal
|
|
@@ -19,7 +19,6 @@ import numpy as np
|
|
|
19
19
|
import pandas as pd
|
|
20
20
|
from sklearn.base import BaseEstimator
|
|
21
21
|
from sklearn.linear_model import LogisticRegressionCV, RidgeCV
|
|
22
|
-
from sklearn.metrics import f1_score, r2_score
|
|
23
22
|
from sklearn.model_selection import train_test_split
|
|
24
23
|
from sklearn.pipeline import Pipeline
|
|
25
24
|
|
|
@@ -34,6 +33,7 @@ from proxyml_core.modeling.estimators import (
|
|
|
34
33
|
)
|
|
35
34
|
from proxyml_core.modeling.extract import extract_export_data
|
|
36
35
|
from proxyml_core.modeling.preprocess import build_preprocessor
|
|
36
|
+
from proxyml_core.modeling.scoring import score_predictions
|
|
37
37
|
from proxyml_core.schema import Feature, FeatureSchema
|
|
38
38
|
|
|
39
39
|
|
|
@@ -60,6 +60,8 @@ def _simple_classifier() -> BaseEstimator:
|
|
|
60
60
|
max_iter=500,
|
|
61
61
|
cv=5,
|
|
62
62
|
n_jobs=-1,
|
|
63
|
+
scoring="accuracy",
|
|
64
|
+
use_legacy_attributes=False,
|
|
63
65
|
)
|
|
64
66
|
|
|
65
67
|
|
|
@@ -76,6 +78,8 @@ def _flexible_classifier() -> BaseEstimator:
|
|
|
76
78
|
max_iter=1000,
|
|
77
79
|
cv=5,
|
|
78
80
|
n_jobs=-1,
|
|
81
|
+
scoring="accuracy",
|
|
82
|
+
use_legacy_attributes=False,
|
|
79
83
|
)
|
|
80
84
|
|
|
81
85
|
|
|
@@ -184,14 +188,10 @@ def train_challenger(
|
|
|
184
188
|
hyperparameters = extract_hyperparameters(pipeline.named_steps["estimator"])
|
|
185
189
|
y_pred = pipeline.predict(X_test)
|
|
186
190
|
|
|
187
|
-
metrics
|
|
188
|
-
if classification:
|
|
189
|
-
metrics["f1"] = float(f1_score(y_test, y_pred, average="weighted", zero_division=0))
|
|
190
|
-
metrics["accuracy"] = float((y_test == y_pred).mean())
|
|
191
|
-
else:
|
|
192
|
-
metrics["r2"] = float(r2_score(y_test, y_pred))
|
|
191
|
+
metrics = score_predictions(y_test, y_pred, task=resolved_task)
|
|
193
192
|
|
|
194
193
|
export = extract_export_data(pipeline, features, resolved_task)
|
|
194
|
+
export = replace(export, hyperparameters=hyperparameters, metrics=metrics)
|
|
195
195
|
|
|
196
196
|
return TrainedChallenger(
|
|
197
197
|
pipeline=pipeline,
|
|
@@ -202,6 +202,28 @@ def train_challenger(
|
|
|
202
202
|
)
|
|
203
203
|
|
|
204
204
|
|
|
205
|
+
def score_champion(
|
|
206
|
+
labels: np.ndarray | list,
|
|
207
|
+
predictions: np.ndarray | list,
|
|
208
|
+
*,
|
|
209
|
+
task: Literal["classification", "regression"],
|
|
210
|
+
) -> dict[str, float]:
|
|
211
|
+
"""Score a champion model's predictions against real labels, locally.
|
|
212
|
+
|
|
213
|
+
Uses the exact same scoring code as ``train_challenger()``'s internal
|
|
214
|
+
fidelity metrics, so ``champion_metrics`` and a paired
|
|
215
|
+
``TrainedChallenger.metrics`` are computed identically — required for an
|
|
216
|
+
apples-to-apples champion-vs-challenger comparison. Pass the same task
|
|
217
|
+
the paired challenger resolved to (e.g. ``result.task``); there's no
|
|
218
|
+
``task="auto"`` here, since letting the two resolve independently risks
|
|
219
|
+
them silently diverging.
|
|
220
|
+
|
|
221
|
+
Returns ``{"f1":..., "accuracy":...}`` for classification or ``{"r2":...}``
|
|
222
|
+
for regression — the same shape as ``TrainedChallenger.metrics``.
|
|
223
|
+
"""
|
|
224
|
+
return score_predictions(np.asarray(labels), np.asarray(predictions), task=task)
|
|
225
|
+
|
|
226
|
+
|
|
205
227
|
def train_auto_challenger(
|
|
206
228
|
data: str | Path | pd.DataFrame,
|
|
207
229
|
target_col: str,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxyml
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Python SDK for calling the ProxyML API
|
|
5
5
|
Author-email: ProxyML <contact@proxyml.ai>
|
|
6
6
|
License: Apache License
|
|
@@ -211,7 +211,7 @@ Requires-Dist: pandas>=1.5
|
|
|
211
211
|
Requires-Dist: orjson>=3.9
|
|
212
212
|
Requires-Dist: proxyml-core>=0.1
|
|
213
213
|
Provides-Extra: local
|
|
214
|
-
Requires-Dist: proxyml-core[modeling]>=0.1; extra == "local"
|
|
214
|
+
Requires-Dist: proxyml-core[modeling]>=0.2.1; extra == "local"
|
|
215
215
|
Provides-Extra: dev
|
|
216
216
|
Requires-Dist: build; extra == "dev"
|
|
217
217
|
Requires-Dist: twine; extra == "dev"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
import pandas as pd
|
|
3
5
|
import pytest
|
|
@@ -6,6 +8,7 @@ from proxyml.local import (
|
|
|
6
8
|
Complexity,
|
|
7
9
|
LADDERS,
|
|
8
10
|
TrainedChallenger,
|
|
11
|
+
score_champion,
|
|
9
12
|
train_auto_challenger,
|
|
10
13
|
train_challenger,
|
|
11
14
|
)
|
|
@@ -62,6 +65,20 @@ def test_train_challenger_classification():
|
|
|
62
65
|
assert result.export.classes is not None
|
|
63
66
|
|
|
64
67
|
|
|
68
|
+
def test_train_challenger_classification_rungs_fit_without_sklearn_deprecation_warnings():
|
|
69
|
+
# scoring="accuracy" and use_legacy_attributes=False on the SIMPLE/FLEXIBLE
|
|
70
|
+
# classifiers pin today's sklearn defaults explicitly, matching what
|
|
71
|
+
# get_default_classifier() (the MODERATE rung) already does.
|
|
72
|
+
schema = _schema()
|
|
73
|
+
df = _df(seed=12, n=200)
|
|
74
|
+
target = np.where(df["age"] > 50, "senior", "junior")
|
|
75
|
+
|
|
76
|
+
with warnings.catch_warnings():
|
|
77
|
+
warnings.simplefilter("error", FutureWarning)
|
|
78
|
+
train_challenger(df, target, schema, complexity=Complexity.SIMPLE, task="classification")
|
|
79
|
+
train_challenger(df, target, schema, complexity=Complexity.FLEXIBLE, task="classification")
|
|
80
|
+
|
|
81
|
+
|
|
65
82
|
def test_train_challenger_simple_rung_is_more_regularized_than_flexible():
|
|
66
83
|
schema = _schema()
|
|
67
84
|
df = _df(seed=3)
|
|
@@ -139,6 +156,47 @@ def test_train_auto_challenger_matches_manual_schema_and_train():
|
|
|
139
156
|
)
|
|
140
157
|
|
|
141
158
|
|
|
159
|
+
def test_score_champion_matches_train_challenger_metric_shape_classification():
|
|
160
|
+
schema = _schema()
|
|
161
|
+
df = _df(seed=9, n=300)
|
|
162
|
+
target = np.where(df["age"] > 50, "senior", "junior")
|
|
163
|
+
|
|
164
|
+
result = train_challenger(df, target, schema, complexity=Complexity.MODERATE, task="classification")
|
|
165
|
+
champion_metrics = score_champion(target, target, task="classification")
|
|
166
|
+
assert set(champion_metrics) == set(result.metrics)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_score_champion_matches_train_challenger_metric_shape_regression():
|
|
170
|
+
schema = _schema()
|
|
171
|
+
df = _df(seed=10)
|
|
172
|
+
target = df["age"] * 0.5 + df["income"] * 0.0001
|
|
173
|
+
|
|
174
|
+
result = train_challenger(df, target, schema, complexity=Complexity.MODERATE, task="regression")
|
|
175
|
+
champion_metrics = score_champion(target, target, task="regression")
|
|
176
|
+
assert set(champion_metrics) == set(result.metrics)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_score_champion_uses_same_scoring_as_train_challenger():
|
|
180
|
+
# Feed score_champion the exact labels/predictions a regression fit produced
|
|
181
|
+
# internally, and assert numeric equality with the challenger's own r2 —
|
|
182
|
+
# proving the two aren't drifting copies of the same formula.
|
|
183
|
+
schema = _schema()
|
|
184
|
+
df = _df(seed=11, n=300)
|
|
185
|
+
target = df["age"] * 0.5 + df["income"] * 0.0001
|
|
186
|
+
|
|
187
|
+
result = train_challenger(df, target, schema, complexity=Complexity.MODERATE, task="regression")
|
|
188
|
+
X = df[["age", "income"]].to_numpy(dtype=object)
|
|
189
|
+
y_pred = result.pipeline.predict(X)
|
|
190
|
+
reproduced_metrics = score_champion(target, y_pred, task="regression")
|
|
191
|
+
|
|
192
|
+
# Not identical to result.metrics (that was scored on a held-out test split,
|
|
193
|
+
# this is scored on the full data) but both must use the same r2 formula —
|
|
194
|
+
# verify by comparing against sklearn directly.
|
|
195
|
+
from sklearn.metrics import r2_score
|
|
196
|
+
|
|
197
|
+
assert reproduced_metrics["r2"] == pytest.approx(r2_score(target, y_pred))
|
|
198
|
+
|
|
199
|
+
|
|
142
200
|
def test_train_auto_challenger_passes_immutable_cols_to_get_schema():
|
|
143
201
|
from unittest.mock import patch
|
|
144
202
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|