edgepoint 2.0.1__tar.gz → 2.0.3__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.
- {edgepoint-2.0.1 → edgepoint-2.0.3}/PKG-INFO +190 -85
- {edgepoint-2.0.1 → edgepoint-2.0.3}/README.md +189 -84
- edgepoint-2.0.3/edgepoint/main.py +713 -0
- {edgepoint-2.0.1 → edgepoint-2.0.3}/edgepoint.egg-info/PKG-INFO +190 -85
- {edgepoint-2.0.1 → edgepoint-2.0.3}/pyproject.toml +1 -1
- {edgepoint-2.0.1 → edgepoint-2.0.3}/setup.py +1 -1
- edgepoint-2.0.1/edgepoint/main.py +0 -642
- {edgepoint-2.0.1 → edgepoint-2.0.3}/LICENSE +0 -0
- {edgepoint-2.0.1 → edgepoint-2.0.3}/edgepoint/__init__.py +0 -0
- {edgepoint-2.0.1 → edgepoint-2.0.3}/edgepoint/core.py +0 -0
- {edgepoint-2.0.1 → edgepoint-2.0.3}/edgepoint/core_v1.py +0 -0
- {edgepoint-2.0.1 → edgepoint-2.0.3}/edgepoint/train_combo.py +0 -0
- {edgepoint-2.0.1 → edgepoint-2.0.3}/edgepoint/validations.py +0 -0
- {edgepoint-2.0.1 → edgepoint-2.0.3}/edgepoint.egg-info/SOURCES.txt +0 -0
- {edgepoint-2.0.1 → edgepoint-2.0.3}/edgepoint.egg-info/dependency_links.txt +0 -0
- {edgepoint-2.0.1 → edgepoint-2.0.3}/edgepoint.egg-info/requires.txt +0 -0
- {edgepoint-2.0.1 → edgepoint-2.0.3}/edgepoint.egg-info/top_level.txt +0 -0
- {edgepoint-2.0.1 → edgepoint-2.0.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: edgepoint
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: Find the point in a numeric column above which a binary outcome becomes meaningfully and reliably better.
|
|
5
5
|
Author: Henry
|
|
6
6
|
License: MIT
|
|
@@ -38,11 +38,13 @@ outcome, it answers a practical question, twice:
|
|
|
38
38
|
> becoming good enough while keeping the best coverage possible, and
|
|
39
39
|
> does that still hold on unseen data?"**
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
`edgepoint` makes a simple, less-savvy-visible property of your data easy
|
|
42
|
+
to see: the exact point along a metric where an outcome starts spiking.
|
|
43
|
+
`edgepoint` splits your data into train and test, searches train for the
|
|
44
|
+
best threshold(s), then replays those exact thresholds against test with
|
|
45
|
+
no re-optimization. What you get back is not just "the best split we could
|
|
46
|
+
find" but "the best split we could find, and here's how it actually
|
|
47
|
+
performed on data it never touched."
|
|
46
48
|
|
|
47
49
|
```python
|
|
48
50
|
from edgepoint import search
|
|
@@ -60,6 +62,52 @@ below.
|
|
|
60
62
|
|
|
61
63
|
---
|
|
62
64
|
|
|
65
|
+
## `outcome_col`: telling edgepoint what "good" means
|
|
66
|
+
|
|
67
|
+
Before `search()` scans a single edge, it needs one thing from you that
|
|
68
|
+
matters more than any parameter: `outcome_col`. This is you, the analyst,
|
|
69
|
+
labeling every row in your data as a win or a loss, a pass or a fail, a
|
|
70
|
+
positive or a negative. `edgepoint` doesn't decide what "success" looks
|
|
71
|
+
like in your business, it has no idea what a good sale, a good patient
|
|
72
|
+
outcome, or a good bet even is. You decide that, once, by pointing at a
|
|
73
|
+
column, and every other number the library produces is downstream of that
|
|
74
|
+
one judgment.
|
|
75
|
+
|
|
76
|
+
Concretely: `outcome_col` must be a column of `1`/`0`, `True`/`False`, or
|
|
77
|
+
a mix of both, one value per row, `1` (or `True`) meaning "this row counts
|
|
78
|
+
as a success" and `0` (or `False`) meaning "this row doesn't." That's it,
|
|
79
|
+
no other values are allowed, `edgepoint` raises a `ValueError` before doing
|
|
80
|
+
any work if it finds anything else in that column, rather than silently
|
|
81
|
+
guessing which values you meant as the "good" ones.
|
|
82
|
+
|
|
83
|
+
Once that column exists, everything else in `search()` is really just
|
|
84
|
+
counting, over and over, at every candidate edge in every numeric column:
|
|
85
|
+
*"of the rows at or above this point, what fraction were a `1`?"* That
|
|
86
|
+
fraction is `hit_rate`. The rest, `miss_rate`, `gap`, `coverage`, `score`,
|
|
87
|
+
are all built directly on top of that one count. Get `outcome_col` wrong,
|
|
88
|
+
too loose, too strict, mislabeled, or measuring the wrong thing entirely,
|
|
89
|
+
and every threshold `search()` proposes will be confidently optimized for
|
|
90
|
+
the wrong target. Get it right, and the rest of the pipeline (percentile
|
|
91
|
+
edges, train/test replay, combo search) is just honest, mechanical counting
|
|
92
|
+
against a target you already trust.
|
|
93
|
+
|
|
94
|
+
A few practical notes on choosing it:
|
|
95
|
+
|
|
96
|
+
- **It has to already exist as a column.** If "success" in your data is a
|
|
97
|
+
derived condition, e.g. "sale closed within 7 days" or "score improved by
|
|
98
|
+
at least 10 points", compute that as its own `0`/`1` column *before*
|
|
99
|
+
calling `search()`. `edgepoint` labels nothing on its own.
|
|
100
|
+
- **One outcome per run.** If you care about two different definitions of
|
|
101
|
+
success (say, "converted" and "converted *and* retained"), that's two
|
|
102
|
+
separate calls to `search()` with two different `outcome_col`s, not one
|
|
103
|
+
call trying to do both.
|
|
104
|
+
- **Rare outcomes still need rows on both sides.** If almost every row is a
|
|
105
|
+
`1` (or almost every row is a `0`), there's very little contrast for
|
|
106
|
+
`edgepoint` to find an edge across, and columns are likely to get skipped
|
|
107
|
+
for failing `min_coverage` on whichever side is thin.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
63
111
|
## Where this applies
|
|
64
112
|
|
|
65
113
|
Any continuous metric (or several) + binary outcome pair, for example:
|
|
@@ -77,15 +125,9 @@ Any continuous metric (or several) + binary outcome pair, for example:
|
|
|
77
125
|
|
|
78
126
|
## What you need
|
|
79
127
|
|
|
80
|
-
You only need:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
- One **binary outcome column** indicating whether each observation was a
|
|
84
|
-
success or not.
|
|
85
|
-
|
|
86
|
-
The outcome column may contain `0`/`1`, `True`/`False`, `0.0`/`1.0`, or any
|
|
87
|
-
mixture of the above. Any other value (strings, `2`, `-1`, etc.) raises a
|
|
88
|
-
`ValueError` up front, before anything runs.
|
|
128
|
+
You only need two things: one or more **numeric columns** to evaluate, and
|
|
129
|
+
the **`outcome_col`** described above, your `0`/`1` (or `True`/`False`)
|
|
130
|
+
judgment of what counts as a win on each row. A few example pairings:
|
|
89
131
|
|
|
90
132
|
| Numeric column | Outcome column |
|
|
91
133
|
| --------------------- | ------------------------------- |
|
|
@@ -147,7 +189,6 @@ df = pd.DataFrame({
|
|
|
147
189
|
})
|
|
148
190
|
```
|
|
149
191
|
|
|
150
|
-
|
|
151
192
|
### Running it
|
|
152
193
|
|
|
153
194
|
```python
|
|
@@ -156,15 +197,19 @@ from edgepoint import search
|
|
|
156
197
|
train_results_df, test_results_df, top_combos = search(
|
|
157
198
|
df,
|
|
158
199
|
outcome_col="hit",
|
|
159
|
-
|
|
200
|
+
train_split=60, # 24 train / 16 test → both ≥ min_rows
|
|
201
|
+
min_rows=15,
|
|
160
202
|
min_coverage=20,
|
|
203
|
+
mode="quick",
|
|
161
204
|
top_combos_n=2,
|
|
162
|
-
show_progress=
|
|
205
|
+
show_progress=False,
|
|
163
206
|
)
|
|
164
207
|
```
|
|
165
208
|
|
|
166
|
-
|
|
167
|
-
`show_progress=
|
|
209
|
+
With `show_progress=False` the call above runs silently and just returns
|
|
210
|
+
the three values. Turning `show_progress=True` on the exact same call
|
|
211
|
+
prints the full five-stage log so you can see how the split, the
|
|
212
|
+
single-column search, and the combo search actually got there:
|
|
168
213
|
|
|
169
214
|
```
|
|
170
215
|
EDGEPOINT.
|
|
@@ -173,101 +218,117 @@ EDGEPOINT.
|
|
|
173
218
|
🟡 STANDARD > 3 mins
|
|
174
219
|
🔴 DEEP > 10 mins
|
|
175
220
|
|
|
176
|
-
start : 2026-07-
|
|
221
|
+
start : 2026-07-17 07:10:14.
|
|
177
222
|
|
|
178
223
|
[1/5] Train / Test Split
|
|
179
224
|
--------------------------------
|
|
180
225
|
|
|
181
|
-
|
|
226
|
+
Sample Size (n) - Full = 40 . Train = 24 . Test = 16 .
|
|
227
|
+
|
|
228
|
+
Hit Rate (%) - Full = 47.50. Train = 50.00. Test = 43.75.
|
|
182
229
|
|
|
183
|
-
done in 0mins 0 secs | elapsed 0mins 1 secs |
|
|
230
|
+
done in 0mins 0 secs | elapsed 0mins 1 secs | 07:10:15.
|
|
184
231
|
|
|
185
232
|
|
|
186
|
-
[2/5] Single-Column Search (train, n=
|
|
233
|
+
[2/5] Single-Column Search (train, n=24)
|
|
187
234
|
--------------------------------
|
|
188
235
|
|
|
189
|
-
column edge_point hit_rate miss_rate
|
|
190
|
-
0 score
|
|
191
|
-
1 age
|
|
236
|
+
column edge_point hit_rate miss_rate gap n coverage
|
|
237
|
+
0 score 49.46 75.00 25.00 50.00 16 66.67
|
|
238
|
+
1 age 39.50 57.14 42.86 14.29 14 58.33
|
|
192
239
|
|
|
193
|
-
done in 0mins 0 secs | elapsed 0mins 1 secs |
|
|
240
|
+
done in 0mins 0 secs | elapsed 0mins 1 secs | 07:10:16.
|
|
194
241
|
|
|
195
242
|
|
|
196
|
-
[3/5] Single-Column Evaluation (test, n=
|
|
243
|
+
[3/5] Single-Column Evaluation (test, n=16)
|
|
197
244
|
--------------------------------
|
|
198
245
|
|
|
199
|
-
column edge_point hit_rate miss_rate gap
|
|
200
|
-
0 score
|
|
201
|
-
1 age
|
|
246
|
+
column edge_point hit_rate miss_rate gap n coverage
|
|
247
|
+
0 score 49.46 77.78 22.22 55.56 9 56.25
|
|
248
|
+
1 age 39.50 42.86 57.14 -14.29 7 43.75
|
|
202
249
|
|
|
203
|
-
done in 0mins 0 secs | elapsed 0mins 2 secs |
|
|
250
|
+
done in 0mins 0 secs | elapsed 0mins 2 secs | 07:10:16.
|
|
204
251
|
|
|
205
252
|
|
|
206
253
|
[4/5] Combo Generation & Evaluation (train, mode='quick') ..This may take a while.
|
|
207
254
|
--------------------------------
|
|
208
255
|
|
|
209
|
-
|
|
256
|
+
3 combo(s) qualified (train). sample size (n) = 24. hit rate (%) = 50.00.
|
|
210
257
|
|
|
211
258
|
combo_num combo hit_rate miss_rate gap n coverage
|
|
212
|
-
0
|
|
213
|
-
1
|
|
259
|
+
0 3 score, age 80.00 20.00 60.00 10 41.67
|
|
260
|
+
1 1 score 75.00 25.00 50.00 16 66.67
|
|
261
|
+
2 2 age 57.14 42.86 14.29 14 58.33
|
|
214
262
|
|
|
215
|
-
done in 0mins 0 secs | elapsed 0mins 2 secs |
|
|
263
|
+
done in 0mins 0 secs | elapsed 0mins 2 secs | 07:10:17.
|
|
216
264
|
|
|
217
265
|
|
|
218
266
|
[5/5] Combo Validation (test, mode='quick')
|
|
219
267
|
--------------------------------
|
|
220
268
|
|
|
221
|
-
|
|
269
|
+
3 combo(s) qualified (test). sample size (n) = 16. hit rate (%) = 43.75.
|
|
222
270
|
|
|
223
|
-
combo_num combo hit_rate coverage
|
|
224
|
-
0
|
|
225
|
-
1
|
|
271
|
+
combo_num combo hit_rate coverage n train_hit_rate train_coverage score
|
|
272
|
+
0 3 score, age 60.00 31.25 5 80.00 41.67 21.49
|
|
273
|
+
1 1 score 77.78 56.25 9 75.00 66.67 1.00
|
|
274
|
+
2 2 age 42.86 43.75 7 57.14 58.33 -29.50
|
|
226
275
|
|
|
227
|
-
done in 0mins 0 secs | elapsed 0mins 3 secs |
|
|
276
|
+
done in 0mins 0 secs | elapsed 0mins 3 secs | 07:10:17.
|
|
228
277
|
|
|
229
278
|
|
|
230
279
|
TOP 2 COMBOS (test)
|
|
231
280
|
--------------------------------
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
281
|
+
|
|
282
|
+
Sample Size (n) - Full = 40 . Train = 24 . Test = 16 .
|
|
283
|
+
Hit Rate (%) - Full = 47.50. Train = 50.00. Test = 43.75.
|
|
284
|
+
|
|
285
|
+
: combo #3 combo #1
|
|
286
|
+
Score : 49.46 49.46
|
|
287
|
+
Age : 39.5 -
|
|
235
288
|
......................................................
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
289
|
+
Hit_rate : 60.0 77.78
|
|
290
|
+
Coverage : 31.25 56.25
|
|
291
|
+
Train_hit_rate : 80.0 75.0
|
|
292
|
+
Train_coverage : 41.67 66.67
|
|
293
|
+
Combo_sample_size : 5 9
|
|
294
|
+
Test_sample_size : 16 16
|
|
295
|
+
Baseline_hit_rate (full df) : 47.50 47.50
|
|
296
|
+
Vs_baseline : 12.50 + 30.28 +
|
|
297
|
+
|
|
298
|
+
COMPLETE - total duration 0mins 4 secs (end 07:10:18).
|
|
244
299
|
```
|
|
245
300
|
|
|
246
301
|
And the three values `search()` returns:
|
|
247
302
|
|
|
248
303
|
```python
|
|
249
304
|
>>> train_results_df
|
|
250
|
-
column edge_point hit_rate miss_rate
|
|
251
|
-
0 score
|
|
252
|
-
1 age
|
|
305
|
+
column edge_point hit_rate miss_rate gap n coverage
|
|
306
|
+
0 score 49.46 75.00 25.00 50.00 16 66.67
|
|
307
|
+
1 age 39.50 57.14 42.86 14.29 14 58.33
|
|
253
308
|
|
|
254
309
|
>>> test_results_df
|
|
255
|
-
column edge_point hit_rate miss_rate gap
|
|
256
|
-
0 score
|
|
257
|
-
1 age
|
|
310
|
+
column edge_point hit_rate miss_rate gap n coverage
|
|
311
|
+
0 score 49.46 77.78 22.22 55.56 9 56.25
|
|
312
|
+
1 age 39.50 42.86 57.14 -14.29 7 43.75
|
|
258
313
|
|
|
259
314
|
>>> top_combos
|
|
260
|
-
[{'combo_num':
|
|
261
|
-
'train_hit_rate':
|
|
262
|
-
|
|
263
|
-
|
|
315
|
+
[{'combo_num': 3, 'edges': {'score': 49.46, 'age': 39.5}, 'hit_rate': 60.0, 'coverage': 31.25,
|
|
316
|
+
'train_hit_rate': 80.0, 'train_coverage': 41.67, 'combo_sample_size': 5, 'test_sample_size': 16,
|
|
317
|
+
'baseline_hit_rate': 47.5, 'vs_baseline': 12.5},
|
|
318
|
+
{'combo_num': 1, 'edges': {'score': 49.46}, 'hit_rate': 77.78, 'coverage': 56.25,
|
|
319
|
+
'train_hit_rate': 75.0, 'train_coverage': 66.67, 'combo_sample_size': 9, 'test_sample_size': 16,
|
|
320
|
+
'baseline_hit_rate': 47.5, 'vs_baseline': 30.28}]
|
|
264
321
|
```
|
|
265
322
|
|
|
266
|
-
Reading this: `score`'s threshold of
|
|
267
|
-
hit rate over
|
|
268
|
-
at
|
|
269
|
-
Adding `age`
|
|
270
|
-
|
|
323
|
+
Reading this: `score`'s threshold of 49.46 looked solid on train (75.00%
|
|
324
|
+
hit rate over 66.67% of rows) and, replayed unchanged against test, held up
|
|
325
|
+
even better at 77.78% over 56.25% of rows, comfortably ahead of the 47.5%
|
|
326
|
+
baseline hit rate. Adding `age` into the combo (`score >= 49.46 AND age >=
|
|
327
|
+
39.5`) tightens things further on train (80.00% hit rate) but shrinks
|
|
328
|
+
coverage a lot (down to 5 rows on test), and its test hit rate of 60.00%,
|
|
329
|
+
while still above baseline, is the less reliable of the two picks precisely
|
|
330
|
+
because it's resting on so few rows, exactly the kind of honest trade-off
|
|
331
|
+
`search()` is built to surface rather than hide.
|
|
271
332
|
|
|
272
333
|
---
|
|
273
334
|
|
|
@@ -384,14 +445,16 @@ list of up to `top_combos_n` dicts otherwise. Each dict:
|
|
|
384
445
|
|
|
385
446
|
```python
|
|
386
447
|
{
|
|
387
|
-
"combo_num":
|
|
388
|
-
"edges": {"score":
|
|
389
|
-
"hit_rate":
|
|
390
|
-
"coverage":
|
|
391
|
-
"train_hit_rate":
|
|
392
|
-
"train_coverage":
|
|
393
|
-
"combo_sample_size":
|
|
394
|
-
"test_sample_size":
|
|
448
|
+
"combo_num": 3,
|
|
449
|
+
"edges": {"score": 49.46, "age": 39.5}, # {column: edge_point} used by this combo
|
|
450
|
+
"hit_rate": 60.0, # test-side
|
|
451
|
+
"coverage": 31.25, # test-side
|
|
452
|
+
"train_hit_rate": 80.0, # carried over from train, unchanged
|
|
453
|
+
"train_coverage": 41.67, # carried over from train, unchanged
|
|
454
|
+
"combo_sample_size": 5, # rows clearing the combo on test
|
|
455
|
+
"test_sample_size": 16, # total test rows
|
|
456
|
+
"baseline_hit_rate": 47.5, # "do nothing" hit rate on the full df
|
|
457
|
+
"vs_baseline": 12.5, # test hit_rate - baseline_hit_rate, in points
|
|
395
458
|
}
|
|
396
459
|
```
|
|
397
460
|
|
|
@@ -482,22 +545,64 @@ independently verified zones.
|
|
|
482
545
|
|
|
483
546
|
---
|
|
484
547
|
|
|
548
|
+
## A good alternative choice, at any data size
|
|
549
|
+
|
|
550
|
+
This isn't tied to a data size at all, it scales from a few dozen rows up
|
|
551
|
+
to millions the same way: scan every candidate point, count what's above
|
|
552
|
+
and below it, replay the winner on held-out data. The core idea holds no
|
|
553
|
+
matter how much data feeds it.
|
|
554
|
+
|
|
555
|
+
**The core idea: make the spike visible.** Most datasets have some point
|
|
556
|
+
along a metric where the outcome rate stops being flat and starts climbing,
|
|
557
|
+
but that point is easy to miss just eyeballing a column. `edgepoint` scans
|
|
558
|
+
every candidate point along every numeric column and surfaces exactly
|
|
559
|
+
where that climb happens, `edge_point`, `hit_rate`, `gap`, `coverage`, in a
|
|
560
|
+
plain table. No hidden weights, no coefficients to decode, just "here's
|
|
561
|
+
the row count on each side of this line, and here's how much better things
|
|
562
|
+
get above it."
|
|
563
|
+
|
|
564
|
+
**Built to keep re-checking rather than lock in.** Data keeps arriving,
|
|
565
|
+
and a good threshold today can shift as more of it comes in. Rather than
|
|
566
|
+
fitting one answer and treating it as settled, `search()` is meant to be
|
|
567
|
+
re-run as new rows accumulate, each run rechecks the spike against a
|
|
568
|
+
fresh train/test split and tells you plainly if it moved, held, or
|
|
569
|
+
disappeared. See "Using the result correctly" above, that habit of
|
|
570
|
+
recomputing is what keeps the picture honest over time, regardless of
|
|
571
|
+
how many rows are behind it.
|
|
572
|
+
|
|
573
|
+
**Worth knowing on any dataset.** A good-looking gap can sometimes be a
|
|
574
|
+
handful of rows doing all the work rather than a real, reliable spike,
|
|
575
|
+
this shows up more easily on a smaller slice of data, but the same logic
|
|
576
|
+
applies at any scale. `edgepoint` leans against this two ways:
|
|
577
|
+
`min_coverage` refuses to let a threshold win on too few rows in the
|
|
578
|
+
first place, and the built-in shrinkage discount (see Design notes)
|
|
579
|
+
discounts small-sample gaps before they're allowed to win. Still worth a
|
|
580
|
+
glance at the `n` column yourself, if a combo's win is resting on very
|
|
581
|
+
few rows relative to the rest of your data, that's exactly the kind of
|
|
582
|
+
thing the recompute habit above is there to confirm or correct as more
|
|
583
|
+
data comes in.
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
485
587
|
## Who this is for
|
|
486
588
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
hand a threshold decision to a library's internals,
|
|
492
|
-
train/test sanity check built in rather than bolted
|
|
589
|
+
Solo builders, indie quants, and analysts who want to see exactly where a
|
|
590
|
+
metric's outcome rate spikes, in a table they can read and trust at a
|
|
591
|
+
glance, without needing a modeling toolbox to get there. This is for people
|
|
592
|
+
closer to how it was actually built: readers who'd rather audit code they
|
|
593
|
+
fully understand than hand a threshold decision to a library's internals,
|
|
594
|
+
and who want a basic train/test sanity check built in rather than bolted
|
|
595
|
+
on separately.
|
|
493
596
|
|
|
494
597
|
---
|
|
495
598
|
|
|
496
599
|
## FAQ
|
|
497
600
|
|
|
498
|
-
**
|
|
499
|
-
|
|
500
|
-
|
|
601
|
+
**What does edgepoint actually give me?**
|
|
602
|
+
A plain, readable table of exactly where an outcome starts spiking along
|
|
603
|
+
a metric, plus proof that the spike held up on data it hadn't seen. No
|
|
604
|
+
coefficients or hidden weights to decode, just an edge point, a hit rate,
|
|
605
|
+
and a coverage number.
|
|
501
606
|
|
|
502
607
|
**Is this statistically optimal?**
|
|
503
608
|
Not necessarily, and not the goal. It prioritizes interpretability and
|