comparative-edge 0.1.0__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.
@@ -0,0 +1,499 @@
1
+ Metadata-Version: 2.4
2
+ Name: comparative-edge
3
+ Version: 0.1.0
4
+ Summary: Discover where outcomes behave reliably, validate the behavior, and compare it against an external expectation.
5
+ Author: Henry
6
+ Author-email: osas2henry@gmail.com
7
+ License: MIT
8
+ Keywords: validation,binary-outcome,edge-detection,candidate-ranges,pandas
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: numpy>=1.23
21
+ Requires-Dist: pandas>=1.5
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: keywords
28
+ Dynamic: license
29
+ Dynamic: requires-dist
30
+ Dynamic: requires-python
31
+ Dynamic: summary
32
+
33
+ # Comparative Edge
34
+
35
+ **Discover where outcomes behave reliably. Establish what your model can realistically expect. Compare that expectation with the outside world.**
36
+
37
+ Comparative Edge is a lightweight Python toolkit for discovering **validated performance ranges** from historical binary outcomes.
38
+
39
+ It is designed for situations where you have:
40
+
41
+ * a measurable value or condition,
42
+ * a binary outcome represented by `0` or `1`,
43
+ * historical observations,
44
+ * and an external expectation, benchmark, target, or opportunity that you eventually want to compare against your own model.
45
+
46
+ The central philosophy:
47
+
48
+ > **Model first. Comparison second. Decision last.**
49
+
50
+ Comparative Edge does not allow an external expectation to define what your model should believe. It first uses your own historical data to discover and validate behavioral ranges. Only after an expectation has been established should it be compared with an external reference.
51
+
52
+ ---
53
+
54
+ # The Problem
55
+
56
+ Many decisions involve two sources of information: what **your own data says should happen**, and what **the outside world says should happen**.
57
+
58
+ The outside expectation may be attractive. It may also be wrong for the conditions you're dealing with. The danger is letting that external expectation influence the model itself.
59
+
60
+ For example, imagine a company running paid advertising. Every day it records:
61
+
62
+ ```text
63
+ ad_spend outcome
64
+ ───────────────────
65
+ 50 0
66
+ 75 1
67
+ 100 1
68
+ 125 0
69
+ 150 1
70
+ 175 1
71
+ 200 0
72
+ ```
73
+
74
+ Here `1` means the desired outcome occurred and `0` means it didn't. The business wants to know whether the amount it spends is associated with a reliable outcome.
75
+
76
+ Comparative Edge can search the historical values and discover that certain spending regions behave differently:
77
+
78
+ ```text
79
+ ad spend range observed outcome
80
+ ────────────────────────────────────
81
+ <= $80 48%
82
+ $80 – $140 61%
83
+ $140 – $200 54%
84
+ >= $200 43%
85
+ ```
86
+
87
+ The important discovery isn't simply that one range has the highest percentage. The question is:
88
+
89
+ > **Which behavior is sufficiently consistent across historical periods to establish a realistic expectation?**
90
+
91
+ Once that expectation is established, it can be compared with an external one. Suppose an external campaign target assumes:
92
+
93
+ ```text
94
+ Expected outcome = 75%
95
+ ```
96
+
97
+ while your validated historical expectation is:
98
+
99
+ ```text
100
+ Your model = 61%
101
+ ```
102
+
103
+ Comparative Edge exposes the disagreement:
104
+
105
+ ```text
106
+ Your model External expectation
107
+ 61% 75%
108
+ │ │
109
+ └───────────┬───────────────┘
110
+
111
+ -14% gap
112
+ ```
113
+
114
+ The package doesn't tell the business what to do. It makes the **risk/reward difference visible before commitment**.
115
+
116
+ ---
117
+
118
+ # Core Philosophy
119
+
120
+ ## 1. Let your data establish the expectation
121
+
122
+ The model should establish its own expectation from historical evidence. An external benchmark should not decide what the model ought to produce.
123
+
124
+ ```text
125
+ Historical data → Model behavior → Validated expectation
126
+ ```
127
+
128
+ ## 2. Validate the behavior
129
+
130
+ A range that looks good across the entire dataset may not actually be reliable. Comparative Edge tests candidate ranges across multiple validation folds and eliminates ranges whose behavior falls outside the required performance band.
131
+
132
+ ```text
133
+ Candidate range
134
+
135
+ Fold 1 → Pass
136
+
137
+ Fold 2 → Pass
138
+
139
+ Fold 3 → Pass
140
+
141
+ Fold 4 → Pass
142
+
143
+ Fold 5 → Pass
144
+
145
+ Validated range
146
+ ```
147
+
148
+ The objective isn't an impressive historical result. It's **repeatable behavior**.
149
+
150
+ ## 3. Compare only after establishing your expectation
151
+
152
+ ```text
153
+ Your model → Your expectation → COMPARE ← External expectation
154
+ ```
155
+
156
+ This preserves the independence of the model.
157
+
158
+ ## 4. Use the difference to understand risk and reward
159
+
160
+ The comparison doesn't automatically determine whether an opportunity is good or bad. It tells you how far the external expectation is from what your own evidence supports. That difference can be used as a decision boundary.
161
+
162
+ ---
163
+
164
+ # Binary Outcomes
165
+
166
+ ```text
167
+ 1 = desired outcome occurred
168
+ 0 = desired outcome did not occur
169
+ ```
170
+
171
+ Applicable to many problems: sale/no sale, conversion/no conversion, success/failure, approved/rejected, retained/churned, delivery/no delivery, event/no event.
172
+
173
+ The other column represents the **value or condition** whose relationship with the outcome you want to investigate, e.g. `customer_score → conversion`, `ad_spend → sale`, `processing_time → success`, `temperature → failure`.
174
+
175
+ ---
176
+
177
+ # What the Algorithm Does
178
+
179
+ ```text
180
+ DataFrame
181
+
182
+ Select value + outcome
183
+
184
+ Generate percentile edges
185
+
186
+ Create candidate boundaries
187
+
188
+ Test candidates on validation folds
189
+
190
+ Remove weak or unstable candidates
191
+
192
+ Score surviving candidates
193
+
194
+ Return the winning validated boundary
195
+ ```
196
+
197
+ ---
198
+
199
+ # Candidate Directions
200
+
201
+ Comparative Edge doesn't assume higher values are better. It can investigate three shapes of behavior.
202
+
203
+ ## Left
204
+
205
+ Find behavior below a threshold: `value <= edge`. Example: `ad_spend <= $100`.
206
+
207
+ ## Right
208
+
209
+ Find behavior above a threshold: `value >= edge`. Example: `customer_score >= 75`.
210
+
211
+ ## Range
212
+
213
+ Find behavior between two boundaries: `low <= value <= high`. Example: `$80 <= ad_spend <= $140`.
214
+
215
+ ## All
216
+
217
+ Test all three shapes together and let them compete within the same candidate pool. Useful when you don't want to assume the relationship beforehand; the data determines whether the useful behavior occurs below a threshold, above a threshold, or inside a range.
218
+
219
+ ---
220
+
221
+ # Percentile-Based Candidate Generation
222
+
223
+ Rather than testing every possible numerical value, Comparative Edge creates candidate boundaries from percentile points, e.g. `0% - 7% - 14% - 21% - ... - 100%`. These percentile boundaries are converted into actual dataset values, and candidate ranges are constructed from those values. Duplicate percentile values are removed automatically.
224
+
225
+ ---
226
+
227
+ # Validation
228
+
229
+ Suppose `n_folds = 5`. The dataset is divided into five sequential folds. Each candidate must satisfy the configured performance requirements in every fold it's evaluated in; candidates that fail are eliminated.
230
+
231
+ ```text
232
+ Candidates
233
+
234
+ ┌─────────────┼─────────────┐
235
+ ↓ ↓ ↓
236
+ Fold 1 Fold 1 Fold 1
237
+ │ │ │
238
+ Pass Fail Pass
239
+ │ X │
240
+ Fold 2 Fold 2
241
+ │ │
242
+ Pass Pass
243
+ │ │
244
+ Fold 3 Fold 3
245
+ │ │
246
+ Pass Fail
247
+ │ X
248
+ Survives
249
+ ```
250
+
251
+ This makes the resulting expectation less dependent on a single portion of the dataset.
252
+
253
+ ---
254
+
255
+ # Target Performance
256
+
257
+ ```python
258
+ target_pct=50
259
+ target_band_width=10
260
+ ```
261
+
262
+ creates an acceptable performance band of `50% to 60%`. A candidate must fall inside this band during validation to remain a survivor. For a surviving candidate:
263
+
264
+ ```text
265
+ observed = 57%, target = 50% -> difference = +7%
266
+ observed = 46%, target = 50% -> difference = -4%
267
+ ```
268
+
269
+ The sign preserves direction: negative means below expectation, zero means matches, positive means above.
270
+
271
+ ---
272
+
273
+ # Minimum Sample Protection
274
+
275
+ ```python
276
+ min_count=20
277
+ ```
278
+
279
+ A candidate with fewer than the required observations in a validation fold is eliminated, preventing small samples from becoming apparently strong ranges.
280
+
281
+ ---
282
+
283
+ # Installation
284
+
285
+ ```bash
286
+ pip install comparative-edge
287
+ ```
288
+
289
+ ---
290
+
291
+ # Basic Usage
292
+
293
+ The DataFrame should contain exactly one binary outcome column and one numeric value column. `min_count` requires that many rows to fall inside a candidate range **within every fold**, so make sure your dataset is large enough for your chosen `n_folds` and `min_count` (roughly `n_folds * min_count` rows at minimum):
294
+
295
+ ```python
296
+ import numpy as np
297
+ import pandas as pd
298
+
299
+ rng = np.random.default_rng(7)
300
+ n = 500
301
+ ad_spend = rng.uniform(50, 300, n).round(2)
302
+ # outcome is more likely when ad_spend falls between 100 and 200
303
+ prob = np.where((ad_spend > 100) & (ad_spend < 200), 0.58, 0.30)
304
+ outcome = (rng.random(n) < prob).astype(int)
305
+
306
+ df = pd.DataFrame({"ad_spend": ad_spend, "outcome": outcome})
307
+ ```
308
+
309
+ ```python
310
+ from comparative_edge import search
311
+
312
+ winner = search(
313
+ df,
314
+ outcome_col="outcome",
315
+ target_pct=50,
316
+ range_bins=15,
317
+ target_band_width=10,
318
+ n_folds=5,
319
+ min_count=20,
320
+ )
321
+ # winner -> (126.63, 231.62)
322
+ ```
323
+
324
+ ---
325
+
326
+ # Direction
327
+
328
+ Default is `direction="all"`. You can restrict the search:
329
+
330
+ ```python
331
+ search(df, outcome_col="outcome", target_pct=50, direction="left") # below a threshold
332
+ search(df, outcome_col="outcome", target_pct=50, direction="right") # above a threshold
333
+ search(df, outcome_col="outcome", target_pct=50, direction="range") # bounded ranges only
334
+ search(df, outcome_col="outcome", target_pct=50, direction="all") # everything
335
+ ```
336
+
337
+ ---
338
+
339
+ # Parameters
340
+
341
+ | Parameter | Default | Description |
342
+ | -------------------- | ------: | ------------------------------------------------------------------ |
343
+ | `df` | n/a | Input DataFrame: exactly one value column plus `outcome_col` |
344
+ | `outcome_col` | n/a | Binary `0/1` outcome column |
345
+ | `target_pct` | n/a | **Required.** Target/reference outcome percentage, `0` to `100` |
346
+ | `range_bins` | `15` | Number of percentile boundaries used to build candidates (`>= 2`) |
347
+ | `target_band_width` | `10` | Width of the acceptable target band above `target_pct` (`>= 0`, and `target_pct + target_band_width <= 100`) |
348
+ | `n_folds` | `5` | Number of validation folds, `1` to `9` |
349
+ | `min_count` | `20` | Minimum observations required per candidate per fold (`>= 1`) |
350
+ | `direction` | `"all"` | `"left"`, `"right"`, `"range"`, or `"all"` |
351
+ | `display` | `False` | Print the analysis output |
352
+ | `verbose` | `False` | Print detailed fold-level candidate keep/drop information |
353
+
354
+ Invalid values for any parameter raise `ValueError` or `TypeError` up front, before any search runs.
355
+
356
+ ---
357
+
358
+ # Display Mode
359
+
360
+ ```python
361
+ display=True
362
+ ```
363
+
364
+ Example output:
365
+
366
+ ```text
367
+ Comparative Edge
368
+ ad_spend vs outcome
369
+
370
+ target 50-60%
371
+ direction all
372
+ range_bins 15
373
+ n_folds 5-fold
374
+ min_count 20
375
+
376
+ Results (total: 10)
377
+ Range General_pct Vs_target Coverage_pct Coverage_n
378
+ <=80.000 51.20 1.20 20.00 2
379
+ (80.000, 140.000) 58.43 8.43 40.00 4
380
+ (140.000, 200.000) 54.91 4.91 30.00 3
381
+ >=200.000 47.10 -2.90 10.00 1
382
+
383
+ Winner ad_spend (80.000, 140.000)
384
+ hit_pct = 58.43% · target 50-60% · +8.43 over floor · coverage 40.0% (4 / 10)
385
+ ```
386
+
387
+ `Coverage_pct` and `Coverage_n` show what share (and raw count) of the full dataset each surviving range covers, out of the `total` printed above the table. The winner line always shows a signed `vs_target` value: `+8.43` when the range beats the target floor, `-2.90` if it ends up below it.
388
+
389
+ ```python
390
+ verbose=True
391
+ ```
392
+
393
+ also shows how candidates were kept or eliminated in each fold.
394
+
395
+ ---
396
+
397
+ # Understanding the Result
398
+
399
+ If `search` returns `(80.0, 140.0)`, the winning validated region is approximately `80 <= value <= 140`:
400
+
401
+ ```text
402
+ Range: 80 - 140
403
+ Hit rate: 58.43%
404
+ Target: 50%
405
+ Difference: +8.43%
406
+ Coverage: 40.0% of all observations (4 / 10)
407
+ ```
408
+
409
+ This region demonstrated an observed outcome rate above the target while surviving the configured validation process, along with how much of your data that region represents.
410
+
411
+ If no candidate survives validation, `search` returns `None`. Unbounded sides come back as `None` rather than `-inf`/`inf`: a left-direction winner looks like `(None, 45.2)`, a right-direction winner like `(12.0, None)`.
412
+
413
+ ---
414
+
415
+ # Comparative Edge Is Not a Prediction Engine
416
+
417
+ It is a **validation and comparison layer**, not a replacement for your predictive model. Its role is to help answer:
418
+
419
+ > **Where does my model demonstrate reliable behavior, and how does that expectation compare with an external reference?**
420
+
421
+ It does not determine whether an external benchmark is correct, whether an opportunity should be accepted, whether a model is causally correct, or whether historical behavior will continue indefinitely. It provides evidence for those decisions.
422
+
423
+ ---
424
+
425
+ # Model First, External Expectation Second
426
+
427
+ Consider a model that expects 60% performance, and an external source claiming 75%: a 15-point disagreement. Comparative Edge doesn't respond by changing the model to 75%. Instead, the difference becomes information. Maybe the external expectation is optimistic. Maybe the model is missing a variable. Maybe the historical sample doesn't represent current conditions. Maybe the opportunity genuinely reflects a change in conditions. The package doesn't assume which explanation is correct; it makes the disagreement visible so it can be investigated.
428
+
429
+ ---
430
+
431
+ # Risk / Reward
432
+
433
+ The comparison becomes:
434
+
435
+ ```text
436
+ What my evidence supports vs What the opportunity requires
437
+ ```
438
+
439
+ A large disagreement can indicate a poor fit between the opportunity and the model's demonstrated behavior. A close agreement can indicate the opportunity is more consistent with the model's expectation. This doesn't automatically make an opportunity good or bad; it provides a **risk/reward reference point** for the decision-maker.
440
+
441
+ ---
442
+
443
+ # Applications
444
+
445
+ Comparative Edge is industry-neutral. The same framework applies anywhere an outcome can be `0` or `1`:
446
+
447
+ * **Advertising**: `ad_spend → conversion / no conversion` - does spend correspond with expected outcome?
448
+ * **Sales**: `lead_value → sale / no sale` - which value ranges consistently produce a sale?
449
+ * **Operations**: `processing_time → success / failure` - which operating ranges see more consistent success?
450
+ * **Customer Analytics**: `customer_score → retention / churn` - which score ranges show reliable retention?
451
+ * **Quality Control**: `measurement → pass / fail` - which measurement ranges correspond with acceptable outcomes?
452
+ * **Forecasting**: `forecast_value → event / no event` - where does the forecast show consistent historical behavior?
453
+
454
+ The domain changes; the underlying question stays the same:
455
+
456
+ > **What does my own evidence realistically support, and how does that compare with what is expected externally?**
457
+
458
+ ---
459
+
460
+ # Limitations
461
+
462
+ The current design expects exactly one numeric value column besides the outcome column, a binary `0`/`1` outcome, sufficient observations for validation, and numerical values that can be ordered into meaningful boundaries.
463
+
464
+ The percentile edges used to build candidates are computed from the **full** dataset before folding begins, and the winner's reported hit rate, `vs_target`, and coverage are also scored against the full dataset. Fold elimination genuinely filters out unstable candidates, but the final displayed numbers for the winner are in-sample, not held-out: treat them as a validated *screen*, not an out-of-sample performance guarantee.
465
+
466
+ Validation folds are contiguous, sequential slices of the DataFrame in its existing row order, not shuffled or randomly sampled. If your data is ordered by date or by the value column itself, folds may not be independent; shuffle beforehand if that matters for your use case.
467
+
468
+ It does not attempt to solve causal inference, multivariate feature interactions, time-series forecasting, probability calibration, model training, concept drift detection, or statistical significance testing. Those problems can complement Comparative Edge, but they're outside its core purpose.
469
+
470
+ ---
471
+
472
+ # The Philosophy Behind the Package
473
+
474
+ Comparative Edge was created around a practical problem: **how do you know whether an opportunity's expected reward is consistent with what your own model says is realistically achievable?**
475
+
476
+ The answer shouldn't begin with the opportunity. It should begin with your evidence. First establish what your data supports. Then validate whether that behavior is repeatable. Then compare it with the outside expectation. Only then decide whether the difference represents an opportunity, a warning, or a reason to investigate further.
477
+
478
+ ```text
479
+ YOUR DATA
480
+
481
+ YOUR MODEL
482
+
483
+ VALIDATED BEHAVIOR
484
+
485
+ YOUR EXPECTATION
486
+
487
+ COMPARISON
488
+
489
+
490
+ EXTERNAL EXPECTATION
491
+
492
+ RISK / REWARD VIEW
493
+
494
+ DECISION
495
+ ```
496
+
497
+ Comparative Edge isn't about making the model agree with the world. It's about **finding out where the model agrees with the world, where it disagrees, and how large that difference is.**
498
+
499
+ > **Model first. Opportunity second. Comparison before commitment.**