proxyml 0.3.0__tar.gz → 0.4.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxyml
3
- Version: 0.3.0
3
+ Version: 0.4.0
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; 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.3.0"
7
+ version = "0.4.0"
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",
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
 
@@ -184,14 +184,10 @@ def train_challenger(
184
184
  hyperparameters = extract_hyperparameters(pipeline.named_steps["estimator"])
185
185
  y_pred = pipeline.predict(X_test)
186
186
 
187
- metrics: dict[str, float] = {}
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))
187
+ metrics = score_predictions(y_test, y_pred, task=resolved_task)
193
188
 
194
189
  export = extract_export_data(pipeline, features, resolved_task)
190
+ export = replace(export, hyperparameters=hyperparameters, metrics=metrics)
195
191
 
196
192
  return TrainedChallenger(
197
193
  pipeline=pipeline,
@@ -202,6 +198,28 @@ def train_challenger(
202
198
  )
203
199
 
204
200
 
201
+ def score_champion(
202
+ labels: np.ndarray | list,
203
+ predictions: np.ndarray | list,
204
+ *,
205
+ task: Literal["classification", "regression"],
206
+ ) -> dict[str, float]:
207
+ """Score a champion model's predictions against real labels, locally.
208
+
209
+ Uses the exact same scoring code as ``train_challenger()``'s internal
210
+ fidelity metrics, so ``champion_metrics`` and a paired
211
+ ``TrainedChallenger.metrics`` are computed identically — required for an
212
+ apples-to-apples champion-vs-challenger comparison. Pass the same task
213
+ the paired challenger resolved to (e.g. ``result.task``); there's no
214
+ ``task="auto"`` here, since letting the two resolve independently risks
215
+ them silently diverging.
216
+
217
+ Returns ``{"f1":..., "accuracy":...}`` for classification or ``{"r2":...}``
218
+ for regression — the same shape as ``TrainedChallenger.metrics``.
219
+ """
220
+ return score_predictions(np.asarray(labels), np.asarray(predictions), task=task)
221
+
222
+
205
223
  def train_auto_challenger(
206
224
  data: str | Path | pd.DataFrame,
207
225
  target_col: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxyml
3
- Version: 0.3.0
3
+ Version: 0.4.0
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; extra == "local"
215
215
  Provides-Extra: dev
216
216
  Requires-Dist: build; extra == "dev"
217
217
  Requires-Dist: twine; extra == "dev"
@@ -12,4 +12,4 @@ black
12
12
  ruff
13
13
 
14
14
  [local]
15
- proxyml-core[modeling]>=0.1
15
+ proxyml-core[modeling]>=0.2
@@ -6,6 +6,7 @@ from proxyml.local import (
6
6
  Complexity,
7
7
  LADDERS,
8
8
  TrainedChallenger,
9
+ score_champion,
9
10
  train_auto_challenger,
10
11
  train_challenger,
11
12
  )
@@ -139,6 +140,47 @@ def test_train_auto_challenger_matches_manual_schema_and_train():
139
140
  )
140
141
 
141
142
 
143
+ def test_score_champion_matches_train_challenger_metric_shape_classification():
144
+ schema = _schema()
145
+ df = _df(seed=9, n=300)
146
+ target = np.where(df["age"] > 50, "senior", "junior")
147
+
148
+ result = train_challenger(df, target, schema, complexity=Complexity.MODERATE, task="classification")
149
+ champion_metrics = score_champion(target, target, task="classification")
150
+ assert set(champion_metrics) == set(result.metrics)
151
+
152
+
153
+ def test_score_champion_matches_train_challenger_metric_shape_regression():
154
+ schema = _schema()
155
+ df = _df(seed=10)
156
+ target = df["age"] * 0.5 + df["income"] * 0.0001
157
+
158
+ result = train_challenger(df, target, schema, complexity=Complexity.MODERATE, task="regression")
159
+ champion_metrics = score_champion(target, target, task="regression")
160
+ assert set(champion_metrics) == set(result.metrics)
161
+
162
+
163
+ def test_score_champion_uses_same_scoring_as_train_challenger():
164
+ # Feed score_champion the exact labels/predictions a regression fit produced
165
+ # internally, and assert numeric equality with the challenger's own r2 —
166
+ # proving the two aren't drifting copies of the same formula.
167
+ schema = _schema()
168
+ df = _df(seed=11, n=300)
169
+ target = df["age"] * 0.5 + df["income"] * 0.0001
170
+
171
+ result = train_challenger(df, target, schema, complexity=Complexity.MODERATE, task="regression")
172
+ X = df[["age", "income"]].to_numpy(dtype=object)
173
+ y_pred = result.pipeline.predict(X)
174
+ reproduced_metrics = score_champion(target, y_pred, task="regression")
175
+
176
+ # Not identical to result.metrics (that was scored on a held-out test split,
177
+ # this is scored on the full data) but both must use the same r2 formula —
178
+ # verify by comparing against sklearn directly.
179
+ from sklearn.metrics import r2_score
180
+
181
+ assert reproduced_metrics["r2"] == pytest.approx(r2_score(target, y_pred))
182
+
183
+
142
184
  def test_train_auto_challenger_passes_immutable_cols_to_get_schema():
143
185
  from unittest.mock import patch
144
186
 
File without changes
File without changes
File without changes
File without changes
File without changes