dataeval-plots 0.0.13__tar.gz → 0.0.15__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.
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/PKG-INFO +40 -30
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/README.md +39 -29
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/src/dataeval_plots/_version.py +2 -2
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/src/dataeval_plots/backends/_feasibility_layout.py +4 -2
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/src/dataeval_plots/backends/_shared.py +100 -38
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/src/dataeval_plots/protocols.py +6 -1
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/.gitignore +0 -0
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/pyproject.toml +0 -0
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/src/dataeval_plots/__init__.py +0 -0
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/src/dataeval_plots/_registry.py +0 -0
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/src/dataeval_plots/backends/__init__.py +0 -0
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/src/dataeval_plots/backends/_altair.py +0 -0
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/src/dataeval_plots/backends/_base.py +0 -0
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/src/dataeval_plots/backends/_matplotlib.py +0 -0
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/src/dataeval_plots/backends/_plotly.py +0 -0
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/src/dataeval_plots/backends/_seaborn.py +0 -0
- {dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/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.
|
|
3
|
+
Version: 0.0.15
|
|
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/
|
|
@@ -208,60 +208,70 @@ subtitle that says which models the band judges:
|
|
|
208
208
|
|
|
209
209
|
The axis is split into three zones. Each zone classifies the requirements that fall inside it:
|
|
210
210
|
|
|
211
|
-
- **Demonstrated** runs from the axis minimum up to the lower confidence bound of the best tested model
|
|
212
|
-
|
|
211
|
+
- **Demonstrated** runs from the axis minimum up to the lower confidence bound of the best tested model, which the
|
|
212
|
+
label under the band names, for example "95.7% lower confidence bound". The zone reads "demonstrated".
|
|
213
213
|
- **Unknown** lies between the other two zones. The evidence neither demonstrates nor rules out these requirements.
|
|
214
|
-
- **Ruled out** runs from the upper confidence bound of the lowest applicable ceiling to the top of the axis.
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
- **Ruled out** runs from the upper confidence bound of the lowest applicable ceiling to the top of the axis. The zone
|
|
215
|
+
reads "ruled out\*" wherever the label fits.
|
|
216
|
+
|
|
217
|
+
The `*` marks a footnote: "ruled out" holds only under the recorded assumptions. Wherever "ruled out\*" appears, the
|
|
218
|
+
caption under the same band ends with that footnote, "\* Ruled out under the recorded assumptions, at the stated
|
|
219
|
+
confidence."
|
|
217
220
|
|
|
218
221
|
Each of your requirements is marked on the axis, and the marker states the determination in words:
|
|
219
222
|
|
|
220
223
|
| Marker | Meaning |
|
|
221
224
|
|--------|---------|
|
|
222
|
-
| "95
|
|
223
|
-
| "
|
|
224
|
-
| "ruled out
|
|
225
|
-
| "not ruled out ?" | The requirement also falls in the unknown zone: it is not above any ceiling's upper confidence bound, and no tested model reaches it |
|
|
225
|
+
| "95%: demonstrated ✓" | The requirement is at or below the lower confidence bound |
|
|
226
|
+
| "95%: insufficient evidence ?" | The requirement falls in the unknown zone, and the evidence cannot decide it |
|
|
227
|
+
| "95%: ruled out\* ✗" | The requirement is above the upper confidence bound of a ceiling, under the recorded assumptions (see the footnote) |
|
|
228
|
+
| "95%: not ruled out ?" | The requirement also falls in the unknown zone: it is not above any ceiling's upper confidence bound, and no tested model reaches it |
|
|
226
229
|
|
|
227
230
|
The caption under the band reports the observed score next to the lower confidence bound it supports, for example
|
|
228
|
-
"Your model scored 91.0% on
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
231
|
+
"Your model scored 91.0% on 2,000 evaluation images; given sampling uncertainty, its lower confidence bound is
|
|
232
|
+
88.5%." The sentence about the observed score appears only when an observed score exists. The caption then states
|
|
233
|
+
that the bounds apply to new images from the same population, and that confidence holds jointly across the whole
|
|
234
|
+
evaluation (for example "Bounds apply to new images from the same population, at 95% joint confidence.").
|
|
232
235
|
|
|
233
236
|
Numbers read as DataEval prints them. Bounds are given to 0.1%, a lower bound rounded down and an upper bound rounded
|
|
234
237
|
up, so rounding never overstates what the evidence shows. Requirements and confidence keep their own decimals, such as
|
|
235
|
-
"95.5%
|
|
238
|
+
"95.5%" or "at 97.5% joint confidence".
|
|
239
|
+
|
|
240
|
+
When a ceiling bounds the band, the caption ends with the footnote "\* Ruled out under the recorded assumptions, at
|
|
241
|
+
the stated confidence."
|
|
236
242
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
243
|
+
A recall requirement is titled "Required recall for Tank (at most 5% false positives)". Only a witness whose own upper
|
|
244
|
+
confidence bound on false positives is within that limit counts; the caption adds its bound after the observed
|
|
245
|
+
sentence, for example "Its upper confidence bound on false positives is 4.1% (observed 2.6% of 651 other images)." In
|
|
246
|
+
the evidence view, a witness over the limit keeps its recall bound and observed score but is drawn faint, its label
|
|
247
|
+
saying why it is not counted, for example "66.0% lower bound · not counted: false positives ≤8.3% (limit 5%)".
|
|
240
248
|
|
|
241
249
|
A ceiling that applies only to narrower models than the band judges never draws a zone. It appears as a note instead,
|
|
242
|
-
for example
|
|
250
|
+
for example "Group-based ceiling (72.1%) applies only to models that give one answer per group."
|
|
243
251
|
|
|
244
252
|
### Other outcomes
|
|
245
253
|
|
|
246
|
-
- **Ruled out.** A requirement above the lowest applicable ceiling's upper confidence bound is marked "ruled out
|
|
247
|
-
|
|
248
|
-
"
|
|
254
|
+
- **Ruled out.** A requirement above the lowest applicable ceiling's upper confidence bound is marked "ruled out\*
|
|
255
|
+
✗". The ruled-out zone covers everything above that bound, which the label under the band names, for example
|
|
256
|
+
"93.0% upper confidence bound". The caption ends with the footnote "\* Ruled out under the recorded assumptions, at
|
|
257
|
+
the stated confidence."
|
|
249
258
|
- **Evidence conflict.** When bounds from different sources contradict each other, the band draws no zones. A dashed
|
|
250
259
|
warning outline reads "Evidence conflicts: check assumptions", both clashing bounds are marked, and the caption
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
95% confidence).
|
|
260
|
+
reads "Bounds disagree: check the assumptions, such as evaluation images entering training. Even when they hold,
|
|
261
|
+
the chance that any bound fails is at most 5%." The 5% is the stated alpha, at 95% confidence.
|
|
254
262
|
- **Bounds at 0% or 100%.** A lower confidence bound of 0% demonstrates nothing and an upper confidence bound of 100%
|
|
255
263
|
rules nothing out, so neither draws a zone.
|
|
256
264
|
|
|
257
265
|
### Showing the evidence
|
|
258
266
|
|
|
259
267
|
`plot_evidence=True` replaces each band with one row per evidence source. Each row shows the observed score as a dot
|
|
260
|
-
and the confidence bound as a tick, with
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
that
|
|
264
|
-
|
|
268
|
+
and the confidence bound as a tick, with the legend "● observed · ┃ lower confidence bound" (and "· ┃ upper confidence
|
|
269
|
+
bound" when a ceiling has one). The caption says what both mean: "Observed on the evaluation images; bounds apply to
|
|
270
|
+
new images from the same population, at 95% joint confidence." In a conflict, the conflict caption follows it.
|
|
271
|
+
Evidence that does not apply to the judged models is drawn faint, and its label names the models it applies to, for
|
|
272
|
+
example "Group-based ceiling (one answer per group)", so this view has no narrower-scope notes. A source that produced
|
|
273
|
+
no bound for the quantity is listed faint as "no bound available", and when no source has a bound the view says "No
|
|
274
|
+
bound is available yet." instead of showing empty axes.
|
|
265
275
|
|
|
266
276
|
## Extending the Package
|
|
267
277
|
|
|
@@ -162,60 +162,70 @@ subtitle that says which models the band judges:
|
|
|
162
162
|
|
|
163
163
|
The axis is split into three zones. Each zone classifies the requirements that fall inside it:
|
|
164
164
|
|
|
165
|
-
- **Demonstrated** runs from the axis minimum up to the lower confidence bound of the best tested model
|
|
166
|
-
|
|
165
|
+
- **Demonstrated** runs from the axis minimum up to the lower confidence bound of the best tested model, which the
|
|
166
|
+
label under the band names, for example "95.7% lower confidence bound". The zone reads "demonstrated".
|
|
167
167
|
- **Unknown** lies between the other two zones. The evidence neither demonstrates nor rules out these requirements.
|
|
168
|
-
- **Ruled out** runs from the upper confidence bound of the lowest applicable ceiling to the top of the axis.
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
- **Ruled out** runs from the upper confidence bound of the lowest applicable ceiling to the top of the axis. The zone
|
|
169
|
+
reads "ruled out\*" wherever the label fits.
|
|
170
|
+
|
|
171
|
+
The `*` marks a footnote: "ruled out" holds only under the recorded assumptions. Wherever "ruled out\*" appears, the
|
|
172
|
+
caption under the same band ends with that footnote, "\* Ruled out under the recorded assumptions, at the stated
|
|
173
|
+
confidence."
|
|
171
174
|
|
|
172
175
|
Each of your requirements is marked on the axis, and the marker states the determination in words:
|
|
173
176
|
|
|
174
177
|
| Marker | Meaning |
|
|
175
178
|
|--------|---------|
|
|
176
|
-
| "95
|
|
177
|
-
| "
|
|
178
|
-
| "ruled out
|
|
179
|
-
| "not ruled out ?" | The requirement also falls in the unknown zone: it is not above any ceiling's upper confidence bound, and no tested model reaches it |
|
|
179
|
+
| "95%: demonstrated ✓" | The requirement is at or below the lower confidence bound |
|
|
180
|
+
| "95%: insufficient evidence ?" | The requirement falls in the unknown zone, and the evidence cannot decide it |
|
|
181
|
+
| "95%: ruled out\* ✗" | The requirement is above the upper confidence bound of a ceiling, under the recorded assumptions (see the footnote) |
|
|
182
|
+
| "95%: not ruled out ?" | The requirement also falls in the unknown zone: it is not above any ceiling's upper confidence bound, and no tested model reaches it |
|
|
180
183
|
|
|
181
184
|
The caption under the band reports the observed score next to the lower confidence bound it supports, for example
|
|
182
|
-
"Your model scored 91.0% on
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
"Your model scored 91.0% on 2,000 evaluation images; given sampling uncertainty, its lower confidence bound is
|
|
186
|
+
88.5%." The sentence about the observed score appears only when an observed score exists. The caption then states
|
|
187
|
+
that the bounds apply to new images from the same population, and that confidence holds jointly across the whole
|
|
188
|
+
evaluation (for example "Bounds apply to new images from the same population, at 95% joint confidence.").
|
|
186
189
|
|
|
187
190
|
Numbers read as DataEval prints them. Bounds are given to 0.1%, a lower bound rounded down and an upper bound rounded
|
|
188
191
|
up, so rounding never overstates what the evidence shows. Requirements and confidence keep their own decimals, such as
|
|
189
|
-
"95.5%
|
|
192
|
+
"95.5%" or "at 97.5% joint confidence".
|
|
193
|
+
|
|
194
|
+
When a ceiling bounds the band, the caption ends with the footnote "\* Ruled out under the recorded assumptions, at
|
|
195
|
+
the stated confidence."
|
|
190
196
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
A recall requirement is titled "Required recall for Tank (at most 5% false positives)". Only a witness whose own upper
|
|
198
|
+
confidence bound on false positives is within that limit counts; the caption adds its bound after the observed
|
|
199
|
+
sentence, for example "Its upper confidence bound on false positives is 4.1% (observed 2.6% of 651 other images)." In
|
|
200
|
+
the evidence view, a witness over the limit keeps its recall bound and observed score but is drawn faint, its label
|
|
201
|
+
saying why it is not counted, for example "66.0% lower bound · not counted: false positives ≤8.3% (limit 5%)".
|
|
194
202
|
|
|
195
203
|
A ceiling that applies only to narrower models than the band judges never draws a zone. It appears as a note instead,
|
|
196
|
-
for example
|
|
204
|
+
for example "Group-based ceiling (72.1%) applies only to models that give one answer per group."
|
|
197
205
|
|
|
198
206
|
### Other outcomes
|
|
199
207
|
|
|
200
|
-
- **Ruled out.** A requirement above the lowest applicable ceiling's upper confidence bound is marked "ruled out
|
|
201
|
-
|
|
202
|
-
"
|
|
208
|
+
- **Ruled out.** A requirement above the lowest applicable ceiling's upper confidence bound is marked "ruled out\*
|
|
209
|
+
✗". The ruled-out zone covers everything above that bound, which the label under the band names, for example
|
|
210
|
+
"93.0% upper confidence bound". The caption ends with the footnote "\* Ruled out under the recorded assumptions, at
|
|
211
|
+
the stated confidence."
|
|
203
212
|
- **Evidence conflict.** When bounds from different sources contradict each other, the band draws no zones. A dashed
|
|
204
213
|
warning outline reads "Evidence conflicts: check assumptions", both clashing bounds are marked, and the caption
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
95% confidence).
|
|
214
|
+
reads "Bounds disagree: check the assumptions, such as evaluation images entering training. Even when they hold,
|
|
215
|
+
the chance that any bound fails is at most 5%." The 5% is the stated alpha, at 95% confidence.
|
|
208
216
|
- **Bounds at 0% or 100%.** A lower confidence bound of 0% demonstrates nothing and an upper confidence bound of 100%
|
|
209
217
|
rules nothing out, so neither draws a zone.
|
|
210
218
|
|
|
211
219
|
### Showing the evidence
|
|
212
220
|
|
|
213
221
|
`plot_evidence=True` replaces each band with one row per evidence source. Each row shows the observed score as a dot
|
|
214
|
-
and the confidence bound as a tick, with
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
that
|
|
218
|
-
|
|
222
|
+
and the confidence bound as a tick, with the legend "● observed · ┃ lower confidence bound" (and "· ┃ upper confidence
|
|
223
|
+
bound" when a ceiling has one). The caption says what both mean: "Observed on the evaluation images; bounds apply to
|
|
224
|
+
new images from the same population, at 95% joint confidence." In a conflict, the conflict caption follows it.
|
|
225
|
+
Evidence that does not apply to the judged models is drawn faint, and its label names the models it applies to, for
|
|
226
|
+
example "Group-based ceiling (one answer per group)", so this view has no narrower-scope notes. A source that produced
|
|
227
|
+
no bound for the quantity is listed faint as "no bound available", and when no source has a bound the view says "No
|
|
228
|
+
bound is available yet." instead of showing empty axes.
|
|
219
229
|
|
|
220
230
|
## Extending the Package
|
|
221
231
|
|
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
21
|
+
__version__ = version = '0.0.15'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 0, 15)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
{dataeval_plots-0.0.13 → dataeval_plots-0.0.15}/src/dataeval_plots/backends/_feasibility_layout.py
RENAMED
|
@@ -48,7 +48,8 @@ def text_width(text: str, size: float = LABEL_PT) -> float:
|
|
|
48
48
|
|
|
49
49
|
def wrap(text: str, size: float, width: float) -> str:
|
|
50
50
|
"""Wrap to the figure's text width, never leaving a legend glyph at a line end."""
|
|
51
|
-
|
|
51
|
+
# textwrap breaks only at ASCII whitespace; a footnote's "*" stays with its first word
|
|
52
|
+
glued = text.replace("● ", "●\xa0").replace("┃ ", "┃\xa0").replace("* ", "*\xa0")
|
|
52
53
|
return textwrap.fill(glued, int((width - 2 * MARGIN) * 72 / (size * 0.55))).replace("\xa0", " ")
|
|
53
54
|
|
|
54
55
|
|
|
@@ -114,7 +115,8 @@ def blocks(bands: list[FeasibilityBand], plot_evidence: bool, width: float) -> t
|
|
|
114
115
|
ax_h = body + (max((p[3] for p in placed), default=-1) + 1) * LEVEL + 0.1
|
|
115
116
|
# Below the axes: the evidence legend, the caption paragraph, then the notes, each wrapped
|
|
116
117
|
texts = [(band.evidence_legend, 10.0, muted)] if plot_evidence else []
|
|
117
|
-
|
|
118
|
+
caption, notes = (band.evidence_caption, []) if plot_evidence else (band.caption, band.notes)
|
|
119
|
+
texts += [(" ".join(caption), CAPTION_PT, ink), *((note, 10.0, muted) for note in notes)]
|
|
118
120
|
wrapped = [(wrap(text, size, width), size, color) for text, size, color in texts]
|
|
119
121
|
out.append((band, x_range, xlabel, ax_h, placed, wrapped))
|
|
120
122
|
height += TITLE + ax_h + XAXIS + sum(text_height(text) for text, _, _ in wrapped) + GAP
|
|
@@ -684,16 +684,20 @@ _SCOPE_MODELS = {
|
|
|
684
684
|
"fixed_representation": "models restricted to the embeddings",
|
|
685
685
|
"partition_restricted": "models that give one answer per group",
|
|
686
686
|
}
|
|
687
|
+
_SCOPE_ROW = {"fixed_representation": "embeddings only", "partition_restricted": "one answer per group"}
|
|
688
|
+
# "ruled out*" never stands alone: its "*" footnote in the same band's caption says it holds only under the recorded
|
|
689
|
+
# assumptions, and an infeasible row always has a ceiling, which writes that footnote
|
|
687
690
|
_MARKER_WORDS = {
|
|
688
691
|
"demonstrated_feasible": "demonstrated ✓",
|
|
689
|
-
"unresolved": "
|
|
690
|
-
"infeasible_under_recorded_assumptions": "ruled out
|
|
692
|
+
"unresolved": "insufficient evidence ?",
|
|
693
|
+
"infeasible_under_recorded_assumptions": "ruled out* ✗",
|
|
691
694
|
"not_ruled_out": "not ruled out ?",
|
|
692
695
|
}
|
|
693
|
-
_RULED_OUT = "ruled out
|
|
696
|
+
_RULED_OUT = "ruled out*"
|
|
694
697
|
_AGGREGATION_WORDS = {None: "", "sequence_mean": "per-sequence mean ", "frame_pooled": "frame-pooled "}
|
|
695
698
|
_GROUP_CEILING = "group-based ceiling"
|
|
696
699
|
_NO_BOUND = "no bound available"
|
|
700
|
+
_FALSE_POSITIVE = "false_positive_rate"
|
|
697
701
|
|
|
698
702
|
|
|
699
703
|
def _pct(value: float, direction: str | None = None) -> str:
|
|
@@ -709,6 +713,11 @@ def _pct(value: float, direction: str | None = None) -> str:
|
|
|
709
713
|
return f"{tenths / 10:.1f}%"
|
|
710
714
|
|
|
711
715
|
|
|
716
|
+
def _confidence(value: float, direction: str) -> str:
|
|
717
|
+
"""Round a confidence down or an error share up to 0.1%, dropping a trailing ".0", as the printout does."""
|
|
718
|
+
return _pct(value, direction).replace(".0%", "%")
|
|
719
|
+
|
|
720
|
+
|
|
712
721
|
def _percent(value: float) -> str:
|
|
713
722
|
"""A threshold, confidence or alpha as DataEval prints it: no rounding to whole percents, no trailing zeros."""
|
|
714
723
|
return f"{value * 100:g}%"
|
|
@@ -750,6 +759,7 @@ class FeasibilityBand:
|
|
|
750
759
|
evidence_markers: list[tuple[float, str]] # requirement labels in the evidence view
|
|
751
760
|
caption: list[str]
|
|
752
761
|
notes: list[str]
|
|
762
|
+
evidence_caption: list[str] # the evidence view's caption, in place of the caption and notes
|
|
753
763
|
evidence: list[FeasibilityEvidenceRow]
|
|
754
764
|
evidence_legend: str
|
|
755
765
|
evidence_note: str | None # drawn in the evidence view when no source has a bound for the band
|
|
@@ -765,6 +775,16 @@ def _feasibility_range(values: Iterable[float]) -> tuple[float, float]:
|
|
|
765
775
|
return round(max(low, 0.0), 2), 1.0
|
|
766
776
|
|
|
767
777
|
|
|
778
|
+
def _not_counted(e: dict[str, Any]) -> str:
|
|
779
|
+
"""The label suffix of a recall witness that does not count: its false-positive bound is over the limit or none."""
|
|
780
|
+
if not e["uncertified"]:
|
|
781
|
+
return ""
|
|
782
|
+
fp = e["false_positive_bound"]
|
|
783
|
+
seen = "no false-positive bound" if fp is None else f"false positives ≤{_pct(fp, 'upper')}"
|
|
784
|
+
limit = "" if fp is None else f" (limit {_percent(e['false_positive_limit'])})"
|
|
785
|
+
return f" · not counted: {seen}{limit}"
|
|
786
|
+
|
|
787
|
+
|
|
768
788
|
def _feasibility_evidence(
|
|
769
789
|
evidence: list[dict[str, Any]], scope: str, unit: str
|
|
770
790
|
) -> tuple[list[FeasibilityEvidenceRow], list[str]]:
|
|
@@ -788,7 +808,7 @@ def _feasibility_evidence(
|
|
|
788
808
|
rank, judged = _SCOPE_RANK[info], _SCOPE_RANK[scope]
|
|
789
809
|
restricted = rank < judged if kind == "upper" else rank > judged
|
|
790
810
|
models = _SCOPE_MODELS[info].format(unit=unit)
|
|
791
|
-
label = _sentence_case(e["source"]) + (f" ({models})" if restricted else "")
|
|
811
|
+
label = _sentence_case(e["source"]) + (f" ({_SCOPE_ROW.get(info, models)})" if restricted else "")
|
|
792
812
|
observed = None if bound is None else e["observed"] # an observed score alone bounds nothing
|
|
793
813
|
rows.append(
|
|
794
814
|
FeasibilityEvidenceRow(
|
|
@@ -798,19 +818,18 @@ def _feasibility_evidence(
|
|
|
798
818
|
observed=observed,
|
|
799
819
|
restricted=restricted,
|
|
800
820
|
label=label,
|
|
801
|
-
bound_label=_NO_BOUND if bound is None else f"{_pct(bound, kind)} {kind} bound",
|
|
821
|
+
bound_label=_NO_BOUND if bound is None else f"{_pct(bound, kind)} {kind} bound" + _not_counted(e),
|
|
802
822
|
observed_label=None if observed is None else f"{_pct(observed)} observed",
|
|
803
823
|
bound_left=kind == "lower",
|
|
804
|
-
faint=restricted or bound is None,
|
|
824
|
+
faint=restricted or bound is None or e["uncertified"],
|
|
805
825
|
)
|
|
806
826
|
)
|
|
807
827
|
if restricted and kind == "upper" and bound is not None:
|
|
808
828
|
ceiling = _pct(bound, "upper")
|
|
809
829
|
notes.append(
|
|
810
|
-
f"
|
|
830
|
+
f"Group-based ceiling ({ceiling}) applies only to models that give one answer per group."
|
|
811
831
|
if info == "partition_restricted"
|
|
812
|
-
else f"
|
|
813
|
-
"it does not limit the models judged here."
|
|
832
|
+
else f"{_sentence_case(e['source'])} ({ceiling}) applies only to {models}."
|
|
814
833
|
)
|
|
815
834
|
return rows, list(dict.fromkeys(notes))
|
|
816
835
|
|
|
@@ -846,18 +865,29 @@ def _unit(rows: list[dict[str, Any]], evidence: list[dict[str, Any]]) -> str | N
|
|
|
846
865
|
return None
|
|
847
866
|
|
|
848
867
|
|
|
868
|
+
def _other(fp: dict[str, Any]) -> str:
|
|
869
|
+
"""The audit negatives a false-positive bound was measured on, such as ``651 other images``."""
|
|
870
|
+
return ("" if fp["n"] is None else f"{fp['n']:,} ") + f"other {fp['unit']}s"
|
|
871
|
+
|
|
872
|
+
|
|
849
873
|
def _feasibility_title(spec: dict[str, Any]) -> str:
|
|
850
874
|
"""The axis title, naming the AP class (or macro) and any aggregation so per-class bands differ."""
|
|
851
875
|
metric = spec["metric"]
|
|
852
876
|
if metric == "ap":
|
|
853
877
|
metric = "macro AP" if spec.get("class_name") is None else f"AP for class {spec['class_name']}"
|
|
878
|
+
elif metric == "recall":
|
|
879
|
+
f0 = _percent(spec["max_false_positive_rate"])
|
|
880
|
+
metric = f"recall for {spec['class_name']} (at most {f0} false positives)"
|
|
854
881
|
return "Required " + _AGGREGATION_WORDS[spec.get("aggregation")] + metric
|
|
855
882
|
|
|
856
883
|
|
|
857
884
|
def _feasibility_band(
|
|
858
|
-
rows: list[dict[str, Any]],
|
|
885
|
+
rows: list[dict[str, Any]], all_evidence: list[dict[str, Any]], alpha: float, noun: str
|
|
859
886
|
) -> FeasibilityBand:
|
|
860
887
|
"""One band; ``noun`` is the unit the printout's footer names for the whole call."""
|
|
888
|
+
# Bands and evidence rows draw the metric; a recall witness's false-positive bound only joins the caption
|
|
889
|
+
evidence = [e for e in all_evidence if e["quantity"] == "metric"]
|
|
890
|
+
false_positives = [e for e in all_evidence if e["quantity"] == _FALSE_POSITIVE]
|
|
861
891
|
scope = rows[0]["judged_scope"]
|
|
862
892
|
unit = _unit(rows, evidence) or "image"
|
|
863
893
|
# A lower bound of 0% demonstrates nothing and an upper bound of 100% rules nothing out
|
|
@@ -880,37 +910,56 @@ def _feasibility_band(
|
|
|
880
910
|
markers = []
|
|
881
911
|
for r in sorted(rows, key=lambda r: r["threshold"]):
|
|
882
912
|
words = _MARKER_WORDS.get(r["determination"])
|
|
883
|
-
markers.append((r["threshold"],
|
|
913
|
+
markers.append((r["threshold"], _percent(r["threshold"]) + (f": {words}" if words else "")))
|
|
884
914
|
|
|
915
|
+
conflict_caption = (
|
|
916
|
+
f"Bounds disagree: check the assumptions, such as evaluation {unit}s entering training. "
|
|
917
|
+
f"Even when they hold, the chance that any bound fails is at most {_confidence(alpha, 'upper')}."
|
|
918
|
+
)
|
|
885
919
|
if conflict:
|
|
886
|
-
caption = [
|
|
887
|
-
f"The bounds disagree. Check the assumptions, including whether evaluation {unit}s entered training. "
|
|
888
|
-
f"Even when assumptions hold, the probability that any bound fails is at most {_percent(alpha)}."
|
|
889
|
-
]
|
|
920
|
+
caption = [conflict_caption]
|
|
890
921
|
else:
|
|
891
922
|
caption = []
|
|
892
923
|
witness = None if best is None else _deciding(best, evidence)[0]
|
|
893
924
|
if witness and witness["observed"] is not None and w is not None:
|
|
894
|
-
on = "" if witness["n"] is None else f"{witness['n']:,} "
|
|
925
|
+
on = "the " if witness["n"] is None else f"{witness['n']:,} "
|
|
926
|
+
# A recall witness's count is the class's positives, which the printout names by class
|
|
927
|
+
spec = json.loads(rows[0]["target_spec"])
|
|
928
|
+
scored = f"{_pct(witness['observed'])} on {on}evaluation {unit}s"
|
|
929
|
+
if spec["metric"] == "recall":
|
|
930
|
+
scored = f"{_pct(witness['observed'])} recall on {on}{spec['class_name']} {unit}s"
|
|
895
931
|
caption.append(
|
|
896
|
-
f"{_sentence_case(witness['source'])} scored {
|
|
897
|
-
f"
|
|
932
|
+
f"{_sentence_case(witness['source'])} scored {scored}; given sampling uncertainty, its lower "
|
|
933
|
+
f"confidence bound is {_pct(w, 'lower')}."
|
|
898
934
|
)
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
935
|
+
fp = None
|
|
936
|
+
if witness and w is not None:
|
|
937
|
+
fp = next(
|
|
938
|
+
(
|
|
939
|
+
e
|
|
940
|
+
for e in false_positives
|
|
941
|
+
if e["evidence_id"] == witness["evidence_id"]
|
|
942
|
+
and e["target_spec"] == witness["target_spec"]
|
|
943
|
+
and e["bound"] is not None
|
|
944
|
+
),
|
|
945
|
+
None,
|
|
907
946
|
)
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
f"
|
|
911
|
-
)
|
|
947
|
+
if fp is not None:
|
|
948
|
+
seen = "" if fp["observed"] is None else f" (observed {_pct(fp['observed'])} of {_other(fp)})"
|
|
949
|
+
caption.append(f"Its upper confidence bound on false positives is {_pct(fp['bound'], 'upper')}{seen}.")
|
|
950
|
+
joint = _confidence(1 - alpha, "lower")
|
|
951
|
+
caption.append(f"Bounds apply to new {noun}s from the same population, at {joint} joint confidence.")
|
|
952
|
+
# The "*" footnote to every "ruled out*" goes last, as a footnote does
|
|
953
|
+
if deciding is not None and c is not None:
|
|
954
|
+
caption.append("* Ruled out under the recorded assumptions, at the stated confidence.")
|
|
955
|
+
evidence_caption = [
|
|
956
|
+
f"Observed on the evaluation {unit}s; bounds apply to new {unit}s from the same population, "
|
|
957
|
+
f"at {_confidence(1 - alpha, 'lower')} joint confidence."
|
|
958
|
+
]
|
|
959
|
+
if conflict:
|
|
960
|
+
evidence_caption.append(conflict_caption)
|
|
912
961
|
|
|
913
|
-
legend =
|
|
962
|
+
legend = "● observed · ┃ lower confidence bound"
|
|
914
963
|
if any(r.bound_type == "upper" and r.bound is not None for r in ev_rows):
|
|
915
964
|
legend += " · ┃ upper confidence bound"
|
|
916
965
|
|
|
@@ -926,6 +975,7 @@ def _feasibility_band(
|
|
|
926
975
|
evidence_markers=[(t, f"{_percent(t)} required") for t, _ in markers],
|
|
927
976
|
caption=caption,
|
|
928
977
|
notes=notes,
|
|
978
|
+
evidence_caption=evidence_caption,
|
|
929
979
|
evidence=ev_rows,
|
|
930
980
|
evidence_legend=legend,
|
|
931
981
|
evidence_note=None if any(r.bound is not None for r in ev_rows) else "No bound is available yet.",
|
|
@@ -952,6 +1002,10 @@ _EVIDENCE_SCHEMA = {
|
|
|
952
1002
|
"bound": pl.Float64,
|
|
953
1003
|
"n": pl.Int64,
|
|
954
1004
|
"state": pl.String,
|
|
1005
|
+
"quantity": pl.String,
|
|
1006
|
+
"uncertified": pl.Boolean,
|
|
1007
|
+
"false_positive_bound": pl.Float64,
|
|
1008
|
+
"false_positive_limit": pl.Float64,
|
|
955
1009
|
}
|
|
956
1010
|
|
|
957
1011
|
|
|
@@ -961,10 +1015,14 @@ def _evidence_rows(output: PlottableFeasibility) -> pl.DataFrame:
|
|
|
961
1015
|
rows = []
|
|
962
1016
|
for spec, target in targets.items():
|
|
963
1017
|
for eid, ev in output.evidence.items():
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1018
|
+
# An older DataEval has no quantity; its endpoints all bound the metric
|
|
1019
|
+
mine = [(e, getattr(e, "quantity", "metric")) for e in ev.endpoints if e.target_spec == spec]
|
|
1020
|
+
fp = next((e.value for e, q in mine if q == _FALSE_POSITIVE), None)
|
|
1021
|
+
f0 = json.loads(spec).get("max_false_positive_rate")
|
|
1022
|
+
for e, q in mine:
|
|
1023
|
+
stats = ev.details["endpoints"].get(spec if q == "metric" else f"{spec}#{q}") or {}
|
|
1024
|
+
# A recall witness counts only when its own false-positive upper bound is within the limit
|
|
1025
|
+
uncertified = q == "metric" and e.direction == "lower" and f0 is not None and (fp is None or fp > f0)
|
|
968
1026
|
rows.append(
|
|
969
1027
|
{
|
|
970
1028
|
"target_spec": spec,
|
|
@@ -978,10 +1036,14 @@ def _evidence_rows(output: PlottableFeasibility) -> pl.DataFrame:
|
|
|
978
1036
|
"unit": "sequence" if ev.unit == "group" else "image",
|
|
979
1037
|
"bound_type": e.direction,
|
|
980
1038
|
# A ceiling's mean is panel agreement, not an observed score
|
|
981
|
-
"observed": stats.get("mean") if e.direction == "lower" else None,
|
|
1039
|
+
"observed": stats.get("mean") if e.direction == "lower" or q == _FALSE_POSITIVE else None,
|
|
982
1040
|
"bound": e.value,
|
|
983
1041
|
"n": stats.get("n"),
|
|
984
1042
|
"state": e.state,
|
|
1043
|
+
"quantity": q,
|
|
1044
|
+
"uncertified": uncertified,
|
|
1045
|
+
"false_positive_bound": fp,
|
|
1046
|
+
"false_positive_limit": f0,
|
|
985
1047
|
}
|
|
986
1048
|
)
|
|
987
1049
|
return pl.DataFrame(rows, schema=_EVIDENCE_SCHEMA)
|
|
@@ -1012,7 +1074,7 @@ def prepare_feasibility_data(output: PlottableFeasibility) -> list[FeasibilityBa
|
|
|
1012
1074
|
groups.setdefault(json.dumps(spec, sort_keys=True), []).append(row)
|
|
1013
1075
|
evidence = _evidence_rows(output).to_dicts()
|
|
1014
1076
|
alpha = output.meta().state["alpha"]
|
|
1015
|
-
noun = _unit(rows, evidence) or "image" # the printout's footer noun
|
|
1077
|
+
noun = _unit(rows, [e for e in evidence if e["quantity"] == "metric"]) or "image" # the printout's footer noun
|
|
1016
1078
|
bands = []
|
|
1017
1079
|
for band in groups.values():
|
|
1018
1080
|
specs = {r["target_spec"] for r in band}
|
|
@@ -1042,7 +1104,7 @@ def feasibility_zones(band: FeasibilityBand) -> list[tuple[str, float, float, st
|
|
|
1042
1104
|
end = hi if band.ruled_out is None else band.ruled_out
|
|
1043
1105
|
zones = [("unknown", start, end, "unknown")]
|
|
1044
1106
|
if band.demonstrated is not None:
|
|
1045
|
-
zones.insert(0, ("demonstrated", lo, start,
|
|
1107
|
+
zones.insert(0, ("demonstrated", lo, start, "demonstrated"))
|
|
1046
1108
|
if band.ruled_out is not None:
|
|
1047
1109
|
zones.append(("ruled_out", end, hi, _RULED_OUT))
|
|
1048
1110
|
return [zone for zone in zones if zone[2] > zone[1]] # a zero-width zone has nothing to show
|
|
@@ -290,7 +290,12 @@ class PlottableDriftMVDC(Plottable, Protocol):
|
|
|
290
290
|
|
|
291
291
|
|
|
292
292
|
class FeasibilityEndpoint(Protocol):
|
|
293
|
-
"""
|
|
293
|
+
"""
|
|
294
|
+
One bound on one target, as DataEval's ``Endpoint`` records it; only the fields the plot reads.
|
|
295
|
+
|
|
296
|
+
Newer outputs also give ``quantity``, ``"metric"`` or ``"false_positive_rate"``; the plot reads it with a default
|
|
297
|
+
of ``"metric"`` so older outputs still plot.
|
|
298
|
+
"""
|
|
294
299
|
|
|
295
300
|
@property
|
|
296
301
|
def target_spec(self) -> str: ...
|
|
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
|