dataeval-plots 0.0.16__tar.gz → 0.0.18__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.
Files changed (17) hide show
  1. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/PKG-INFO +1 -1
  2. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/src/dataeval_plots/_version.py +2 -2
  3. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/src/dataeval_plots/backends/_shared.py +93 -4
  4. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/src/dataeval_plots/protocols.py +44 -3
  5. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/.gitignore +0 -0
  6. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/README.md +0 -0
  7. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/pyproject.toml +0 -0
  8. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/src/dataeval_plots/__init__.py +0 -0
  9. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/src/dataeval_plots/_registry.py +0 -0
  10. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/src/dataeval_plots/backends/__init__.py +0 -0
  11. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/src/dataeval_plots/backends/_altair.py +0 -0
  12. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/src/dataeval_plots/backends/_base.py +0 -0
  13. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/src/dataeval_plots/backends/_feasibility_layout.py +0 -0
  14. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/src/dataeval_plots/backends/_matplotlib.py +0 -0
  15. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/src/dataeval_plots/backends/_plotly.py +0 -0
  16. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/src/dataeval_plots/backends/_seaborn.py +0 -0
  17. {dataeval_plots-0.0.16 → dataeval_plots-0.0.18}/src/dataeval_plots/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: dataeval-plots
3
- Version: 0.0.16
3
+ Version: 0.0.18
4
4
  Summary: DataEval companion package for plotting utilities
5
5
  Project-URL: Homepage, https://dataeval.ai/
6
6
  Project-URL: Repository, https://github.com/aria-ml/dataeval/
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.0.16'
22
- __version_tuple__ = version_tuple = (0, 0, 16)
21
+ __version__ = version = '0.0.18'
22
+ __version_tuple__ = version_tuple = (0, 0, 18)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -15,6 +15,8 @@ import numpy as np
15
15
  import polars as pl
16
16
  from numpy.typing import NDArray
17
17
 
18
+ from dataeval_plots.protocols import PlottableFeasibilityBounds
19
+
18
20
  if TYPE_CHECKING:
19
21
  from dataeval_plots.protocols import (
20
22
  PlottableBalance,
@@ -22,6 +24,7 @@ if TYPE_CHECKING:
22
24
  PlottableDrift,
23
25
  PlottableDriftMVDC,
24
26
  PlottableFeasibility,
27
+ PlottableFeasibilityDeterminations,
25
28
  )
26
29
 
27
30
  MethodType = Literal[
@@ -1181,12 +1184,19 @@ def _feasibility_band(
1181
1184
  )
1182
1185
 
1183
1186
 
1184
- # These mirror DataEval's performance/_feasibility.py: _SOURCES, _source_name, _unit_noun and FeasibilityOutput._bounds
1187
+ # The readable name of each source: by DataEval 1.2.0's evidence id (these mirror its performance/_feasibility.py:
1188
+ # _SOURCES, _source_name, _unit_noun and FeasibilityOutput._bounds), and by the `source` value of the determinations and
1189
+ # bounds shape. Any other value shows its own text.
1185
1190
  _SOURCE_NAMES = {
1186
1191
  "replica_ceiling/v1": "repeated labels",
1187
1192
  "builtin_witness/v1:knn": "built-in k-NN",
1188
1193
  "builtin_witness/v1:probe": "built-in linear probe",
1189
1194
  "user_witness/v1": "your model",
1195
+ "local_panels": _GROUP_CEILING,
1196
+ "replicas": "repeated labels",
1197
+ "knn": "built-in k-NN",
1198
+ "linear_probe": "built-in linear probe",
1199
+ "user_model": "your model",
1190
1200
  }
1191
1201
  _EVIDENCE_SCHEMA = {
1192
1202
  "target_spec": pl.String,
@@ -1208,7 +1218,7 @@ _EVIDENCE_SCHEMA = {
1208
1218
  }
1209
1219
 
1210
1220
 
1211
- def _evidence_rows(output: PlottableFeasibility) -> pl.DataFrame:
1221
+ def _evidence_rows(output: PlottableFeasibilityDeterminations) -> pl.DataFrame:
1212
1222
  """One row per endpoint on a target in ``determinations``, in their order, then the order of ``evidence``."""
1213
1223
  targets = {r["target_spec"]: r for r in output.determinations.iter_rows(named=True)}
1214
1224
  rows = []
@@ -1248,6 +1258,85 @@ def _evidence_rows(output: PlottableFeasibility) -> pl.DataFrame:
1248
1258
  return pl.DataFrame(rows, schema=_EVIDENCE_SCHEMA)
1249
1259
 
1250
1260
 
1261
+ def _bounds_inputs(output: PlottableFeasibilityBounds) -> tuple[list[dict[str, Any]], pl.DataFrame]:
1262
+ """
1263
+ Read the determinations and bounds shape as the determination rows and evidence frame the bands are built from.
1264
+
1265
+ The rows use the determinations and evidence shape's vocabulary, so everything downstream reads either shape
1266
+ alike: an unresolved determination with limitation ``conflict`` is an ``evidence_conflict``, and ``envelope`` is a
1267
+ ``ceiling``.
1268
+ """
1269
+ determinations = list(output.determinations.iter_rows(named=True))
1270
+ bounds = list(output.bounds.iter_rows(named=True))
1271
+ rows: list[dict[str, Any]] = []
1272
+ evidence: list[dict[str, Any]] = []
1273
+ for d in determinations:
1274
+ # ponytail: a private join key in the old reader's vocabulary; typed keys replace it when that reader goes
1275
+ spec = json.dumps(
1276
+ {
1277
+ "metric": d["metric"],
1278
+ "scope": d["judged_scope"],
1279
+ "class_name": d["class_name"],
1280
+ "aggregation": d["aggregation"],
1281
+ "ap_functional": d["ap_functional"],
1282
+ "max_false_positive_rate": d["max_false_positive_rate"],
1283
+ "threshold": d["threshold"],
1284
+ },
1285
+ sort_keys=True,
1286
+ )
1287
+ conflict = d["determination"] == "unresolved" and d["limitation"] == "conflict"
1288
+ rows.append(
1289
+ {
1290
+ "target_spec": spec,
1291
+ "metric": d["metric"],
1292
+ "threshold": d["threshold"],
1293
+ "judged_scope": d["judged_scope"],
1294
+ "determination": "evidence_conflict" if conflict else d["determination"],
1295
+ "ceiling": d["envelope"],
1296
+ "ceiling_scope": d["envelope_scope"],
1297
+ "witness": d["witness"],
1298
+ "witness_source": d["witness_source"],
1299
+ }
1300
+ )
1301
+ mine = [b for b in bounds if b["requirement"] == d["requirement"]]
1302
+ f0 = d["max_false_positive_rate"]
1303
+ for b in mine:
1304
+ q = _FALSE_POSITIVE if b["quantity"] == _FALSE_POSITIVE else "metric"
1305
+ fp = next(
1306
+ (x["bound"] for x in mine if x["source"] == b["source"] and x["quantity"] == _FALSE_POSITIVE), None
1307
+ )
1308
+ # A recall witness counts only when its own false-positive upper bound is within the limit
1309
+ uncertified = q == "metric" and b["bound_type"] == "lower" and f0 is not None and (fp is None or fp > f0)
1310
+ evidence.append(
1311
+ {
1312
+ "target_spec": spec,
1313
+ "metric": d["metric"],
1314
+ "threshold": d["threshold"],
1315
+ "source": _SOURCE_NAMES.get(b["source"], b["source"]),
1316
+ "evidence_id": b["source"],
1317
+ "information_scope": b["information_scope"],
1318
+ "unit": b["unit"],
1319
+ "bound_type": b["bound_type"],
1320
+ "observed": b["observed"],
1321
+ "bound": b["bound"],
1322
+ "n": b["n"],
1323
+ "state": b["state"],
1324
+ "quantity": q,
1325
+ "uncertified": uncertified,
1326
+ "false_positive_bound": fp,
1327
+ "false_positive_limit": f0,
1328
+ }
1329
+ )
1330
+ return rows, pl.DataFrame(evidence, schema=_EVIDENCE_SCHEMA)
1331
+
1332
+
1333
+ def _feasibility_inputs(output: PlottableFeasibility) -> tuple[list[dict[str, Any]], pl.DataFrame]:
1334
+ """The determination rows and evidence frame of a feasibility output in either shape."""
1335
+ if isinstance(output, PlottableFeasibilityBounds):
1336
+ return _bounds_inputs(output)
1337
+ return list(output.determinations.iter_rows(named=True)), _evidence_rows(output)
1338
+
1339
+
1251
1340
  def prepare_feasibility_data(output: PlottableFeasibility) -> list[FeasibilityBand]:
1252
1341
  """
1253
1342
  Prepare feasibility data for plotting across all backends.
@@ -1265,13 +1354,13 @@ def prepare_feasibility_data(output: PlottableFeasibility) -> list[FeasibilityBa
1265
1354
  list[FeasibilityBand]
1266
1355
  One band per quantity, in the order the determinations first name it
1267
1356
  """
1268
- rows = list(output.determinations.iter_rows(named=True))
1357
+ rows, evidence_frame = _feasibility_inputs(output)
1269
1358
  groups: dict[str, list[dict[str, Any]]] = {}
1270
1359
  for row in rows:
1271
1360
  spec = json.loads(row["target_spec"])
1272
1361
  spec.pop("threshold", None)
1273
1362
  groups.setdefault(json.dumps(spec, sort_keys=True), []).append(row)
1274
- evidence = _evidence_rows(output).to_dicts()
1363
+ evidence = evidence_frame.to_dicts()
1275
1364
  alpha = output.meta().state["alpha"]
1276
1365
  noun = _unit(rows, [e for e in evidence if e["quantity"] == "metric"]) or "image" # the printout's footer noun
1277
1366
  bands = []
@@ -13,7 +13,9 @@ The protocol hierarchy:
13
13
  - PlottableStats
14
14
  - PlottableDriftMVDC
15
15
  - PlottableDrift
16
- - PlottableFeasibility
16
+ - PlottableFeasibilityDeterminations
17
+ - PlottableFeasibilityBounds
18
+ - PlottableFeasibility (either of the two feasibility shapes)
17
19
  """
18
20
 
19
21
  from __future__ import annotations
@@ -358,8 +360,8 @@ class FeasibilityEvidence(Protocol):
358
360
 
359
361
 
360
362
  @runtime_checkable
361
- class PlottableFeasibility(Plottable, Protocol):
362
- """Protocol for feasibility plot outputs.
363
+ class PlottableFeasibilityDeterminations(Plottable, Protocol):
364
+ """Protocol for feasibility plot outputs in the ``determinations`` and ``evidence`` shape.
363
365
 
364
366
  Required attributes:
365
367
  - determinations: One row per target (DataFrame)
@@ -382,6 +384,45 @@ class PlottableFeasibility(Plottable, Protocol):
382
384
  def plot_type(self) -> Literal["feasibility"]: ...
383
385
 
384
386
 
387
+ @runtime_checkable
388
+ class PlottableFeasibilityBounds(Plottable, Protocol):
389
+ """Protocol for feasibility plot outputs in the ``determinations`` and ``bounds`` shape.
390
+
391
+ Required attributes:
392
+ - determinations: One row per requirement (DataFrame)
393
+ - bounds: One row per source, requirement and quantity (DataFrame)
394
+ - meta().state["alpha"]: The call's error budget
395
+ - plot_type -> 'feasibility'
396
+
397
+ Structures:
398
+ - determinations columns: requirement, metric, threshold, judged_scope, class_name, aggregation, ap_functional,
399
+ max_false_positive_rate, determination, limitation, envelope, envelope_scope, witness, witness_source
400
+ - bounds columns: requirement, source, role, quantity, bound_type, information_scope, unit, state, bound,
401
+ observed, n; ``requirement`` joins a bound to its determinations row
402
+
403
+ Enum columns are read as strings, with these values:
404
+ - determination: demonstrated_feasible, infeasible_under_recorded_assumptions, not_ruled_out, unresolved
405
+ - limitation: only ``conflict`` is read; with determination ``unresolved`` it marks a conflict between a witness
406
+ and an envelope
407
+ - judged_scope, envelope_scope, information_scope: full_input, fixed_representation, partition_restricted
408
+ - source, witness_source: local_panels, replicas, knn, linear_probe, user_model (any other value shows its text)
409
+ - role: envelope, witness. bound_type: lower, upper
410
+ - quantity: a metric name (accuracy, ap, recall, recall_at_fp, mota) or false_positive_rate
411
+ - state: unavailable, uninformative, informative. unit: image, sequence
412
+ - aggregation: frame_pooled, sequence_mean. ap_functional: population_ap_integral, population_ap_101
413
+ """
414
+
415
+ @property
416
+ def determinations(self) -> pl.DataFrame: ...
417
+ @property
418
+ def bounds(self) -> pl.DataFrame: ...
419
+ @property
420
+ def plot_type(self) -> Literal["feasibility"]: ...
421
+
422
+
423
+ PlottableFeasibility = PlottableFeasibilityDeterminations | PlottableFeasibilityBounds
424
+ """A feasibility output in either shape; DataEval 1.2.0 gives the first, later releases the second."""
425
+
385
426
  # Type alias for all plottable types
386
427
  PlottableType = (
387
428
  Dataset