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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edgepoint
3
- Version: 2.0.2
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
- Rather than fitting a predictive model, `edgepoint` splits your data into
42
- train and test, searches train for the best threshold(s), then replays those
43
- exact thresholds against test with no re-optimization. What you get back is
44
- not just "the best split we could find" but "the best split we could find,
45
- and here's how it actually performed on data it never touched."
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
- - One or more **numeric columns** to evaluate.
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
- Not primarily ML researchers with an established modeling toolbox already
509
- in hand, they don't need this to solve the raw math. This is for people
510
- closer to how it was actually built: solo builders, indie quants, and
511
- analysts who would rather read and trust code they can fully audit than
512
- hand a threshold decision to a library's internals, and who want a basic
513
- train/test sanity check built in rather than bolted on separately.
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
- **Does this replace machine learning?**
520
- No. Machine learning predicts outcomes; `edgepoint` proposes interpretable
521
- operational thresholds and checks them against held-out data.
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