edgepoint 2.0.2__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.2/edgepoint.egg-info → edgepoint-2.0.3}/PKG-INFO +108 -24
- edgepoint-2.0.2/PKG-INFO → edgepoint-2.0.3/README.md +618 -562
- edgepoint-2.0.2/README.md → edgepoint-2.0.3/edgepoint.egg-info/PKG-INFO +646 -534
- {edgepoint-2.0.2 → edgepoint-2.0.3}/pyproject.toml +1 -1
- {edgepoint-2.0.2 → edgepoint-2.0.3}/setup.py +1 -1
- {edgepoint-2.0.2 → edgepoint-2.0.3}/LICENSE +0 -0
- {edgepoint-2.0.2 → edgepoint-2.0.3}/edgepoint/__init__.py +0 -0
- {edgepoint-2.0.2 → edgepoint-2.0.3}/edgepoint/core.py +0 -0
- {edgepoint-2.0.2 → edgepoint-2.0.3}/edgepoint/core_v1.py +0 -0
- {edgepoint-2.0.2 → edgepoint-2.0.3}/edgepoint/main.py +0 -0
- {edgepoint-2.0.2 → edgepoint-2.0.3}/edgepoint/train_combo.py +0 -0
- {edgepoint-2.0.2 → edgepoint-2.0.3}/edgepoint/validations.py +0 -0
- {edgepoint-2.0.2 → edgepoint-2.0.3}/edgepoint.egg-info/SOURCES.txt +0 -0
- {edgepoint-2.0.2 → edgepoint-2.0.3}/edgepoint.egg-info/dependency_links.txt +0 -0
- {edgepoint-2.0.2 → edgepoint-2.0.3}/edgepoint.egg-info/requires.txt +0 -0
- {edgepoint-2.0.2 → edgepoint-2.0.3}/edgepoint.egg-info/top_level.txt +0 -0
- {edgepoint-2.0.2 → 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
|
| --------------------- | ------------------------------- |
|
|
@@ -503,22 +545,64 @@ independently verified zones.
|
|
|
503
545
|
|
|
504
546
|
---
|
|
505
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
|
+
|
|
506
587
|
## Who this is for
|
|
507
588
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
hand a threshold decision to a library's internals,
|
|
513
|
-
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.
|
|
514
596
|
|
|
515
597
|
---
|
|
516
598
|
|
|
517
599
|
## FAQ
|
|
518
600
|
|
|
519
|
-
**
|
|
520
|
-
|
|
521
|
-
|
|
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.
|
|
522
606
|
|
|
523
607
|
**Is this statistically optimal?**
|
|
524
608
|
Not necessarily, and not the goal. It prioritizes interpretability and
|