upliftlab 0.26.2__tar.gz → 0.26.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.
Files changed (156) hide show
  1. {upliftlab-0.26.2 → upliftlab-0.26.3}/PKG-INFO +1 -1
  2. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/__init__.py +1 -1
  3. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/__main__.py +36 -16
  4. {upliftlab-0.26.2 → upliftlab-0.26.3}/pyproject.toml +1 -1
  5. {upliftlab-0.26.2 → upliftlab-0.26.3}/tests/test_engine.py +22 -0
  6. {upliftlab-0.26.2 → upliftlab-0.26.3}/upliftlab.egg-info/PKG-INFO +1 -1
  7. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/app.js +5 -5
  8. upliftlab-0.26.3/web/catalog.js +3 -0
  9. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/charts.js +1 -1
  10. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/engine-files.js +1 -1
  11. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/index.html +2 -2
  12. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/report.js +5 -5
  13. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/runtime.js +1 -1
  14. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/store.js +4 -4
  15. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/summary.js +2 -2
  16. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/views/algorithms.js +4 -4
  17. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/views/data.js +2 -2
  18. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/views/home.js +3 -3
  19. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/views/launcher.js +2 -2
  20. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/views/results.js +5 -5
  21. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/views/workspace.js +5 -5
  22. upliftlab-0.26.2/web/catalog.js +0 -3
  23. {upliftlab-0.26.2 → upliftlab-0.26.3}/LICENSE +0 -0
  24. {upliftlab-0.26.2 → upliftlab-0.26.3}/README.md +0 -0
  25. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/algorithms/__init__.py +0 -0
  26. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/algorithms/dml.py +0 -0
  27. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/algorithms/duality_r_learner.py +0 -0
  28. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/algorithms/lite/dml.py +0 -0
  29. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/algorithms/lite/duality_r_learner.py +0 -0
  30. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/algorithms/lite/r_learner.py +0 -0
  31. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/algorithms/lite/t_learner.py +0 -0
  32. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/algorithms/lite/x_learner.py +0 -0
  33. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/algorithms/r_learner.py +0 -0
  34. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/algorithms/random_baseline.py +0 -0
  35. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/algorithms/t_learner.py +0 -0
  36. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/algorithms/x_learner.py +0 -0
  37. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/arena.py +0 -0
  38. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/bridge.py +0 -0
  39. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/contract.py +0 -0
  40. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/datasets.py +0 -0
  41. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/metrics.py +0 -0
  42. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/scenario.py +0 -0
  43. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/server.py +0 -0
  44. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/templates/start_lightgbm.py +0 -0
  45. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/templates/start_pytorch.py +0 -0
  46. {upliftlab-0.26.2 → upliftlab-0.26.3}/engine/upliftlab/templates/start_sklearn.py +0 -0
  47. {upliftlab-0.26.2 → upliftlab-0.26.3}/setup.cfg +0 -0
  48. {upliftlab-0.26.2 → upliftlab-0.26.3}/upliftlab.egg-info/SOURCES.txt +0 -0
  49. {upliftlab-0.26.2 → upliftlab-0.26.3}/upliftlab.egg-info/dependency_links.txt +0 -0
  50. {upliftlab-0.26.2 → upliftlab-0.26.3}/upliftlab.egg-info/entry_points.txt +0 -0
  51. {upliftlab-0.26.2 → upliftlab-0.26.3}/upliftlab.egg-info/requires.txt +0 -0
  52. {upliftlab-0.26.2 → upliftlab-0.26.3}/upliftlab.egg-info/top_level.txt +0 -0
  53. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/data/LICENSE-criteo.md +0 -0
  54. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/editor-entry.js +0 -0
  55. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/LICENSE +0 -0
  56. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.0.woff2 +0 -0
  57. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.1.woff2 +0 -0
  58. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.10.woff2 +0 -0
  59. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.11.woff2 +0 -0
  60. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.12.woff2 +0 -0
  61. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.13.woff2 +0 -0
  62. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.14.woff2 +0 -0
  63. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.15.woff2 +0 -0
  64. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.16.woff2 +0 -0
  65. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.17.woff2 +0 -0
  66. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.18.woff2 +0 -0
  67. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.19.woff2 +0 -0
  68. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.2.woff2 +0 -0
  69. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.20.woff2 +0 -0
  70. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.21.woff2 +0 -0
  71. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.22.woff2 +0 -0
  72. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.23.woff2 +0 -0
  73. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.24.woff2 +0 -0
  74. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.25.woff2 +0 -0
  75. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.26.woff2 +0 -0
  76. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.27.woff2 +0 -0
  77. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.28.woff2 +0 -0
  78. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.29.woff2 +0 -0
  79. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.3.woff2 +0 -0
  80. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.30.woff2 +0 -0
  81. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.31.woff2 +0 -0
  82. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.32.woff2 +0 -0
  83. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.33.woff2 +0 -0
  84. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.34.woff2 +0 -0
  85. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.35.woff2 +0 -0
  86. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.36.woff2 +0 -0
  87. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.37.woff2 +0 -0
  88. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.38.woff2 +0 -0
  89. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.39.woff2 +0 -0
  90. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.4.woff2 +0 -0
  91. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.40.woff2 +0 -0
  92. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.41.woff2 +0 -0
  93. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.42.woff2 +0 -0
  94. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.43.woff2 +0 -0
  95. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.44.woff2 +0 -0
  96. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.45.woff2 +0 -0
  97. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.46.woff2 +0 -0
  98. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.47.woff2 +0 -0
  99. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.48.woff2 +0 -0
  100. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.49.woff2 +0 -0
  101. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.5.woff2 +0 -0
  102. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.50.woff2 +0 -0
  103. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.51.woff2 +0 -0
  104. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.52.woff2 +0 -0
  105. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.53.woff2 +0 -0
  106. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.54.woff2 +0 -0
  107. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.55.woff2 +0 -0
  108. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.56.woff2 +0 -0
  109. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.57.woff2 +0 -0
  110. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.58.woff2 +0 -0
  111. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.59.woff2 +0 -0
  112. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.6.woff2 +0 -0
  113. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.60.woff2 +0 -0
  114. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.61.woff2 +0 -0
  115. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.62.woff2 +0 -0
  116. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.63.woff2 +0 -0
  117. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.64.woff2 +0 -0
  118. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.65.woff2 +0 -0
  119. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.66.woff2 +0 -0
  120. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.67.woff2 +0 -0
  121. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.68.woff2 +0 -0
  122. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.69.woff2 +0 -0
  123. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.7.woff2 +0 -0
  124. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.70.woff2 +0 -0
  125. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.71.woff2 +0 -0
  126. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.72.woff2 +0 -0
  127. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.73.woff2 +0 -0
  128. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.74.woff2 +0 -0
  129. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.75.woff2 +0 -0
  130. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.76.woff2 +0 -0
  131. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.77.woff2 +0 -0
  132. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.78.woff2 +0 -0
  133. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.79.woff2 +0 -0
  134. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.8.woff2 +0 -0
  135. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.80.woff2 +0 -0
  136. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.81.woff2 +0 -0
  137. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.82.woff2 +0 -0
  138. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.83.woff2 +0 -0
  139. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.84.woff2 +0 -0
  140. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.85.woff2 +0 -0
  141. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.86.woff2 +0 -0
  142. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.87.woff2 +0 -0
  143. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.88.woff2 +0 -0
  144. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.89.woff2 +0 -0
  145. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.9.woff2 +0 -0
  146. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.90.woff2 +0 -0
  147. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/PretendardVariable.subset.91.woff2 +0 -0
  148. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/archivo-latin-ext-wdth-normal.woff2 +0 -0
  149. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/archivo-latin-wdth-normal.woff2 +0 -0
  150. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/geist-mono-latin-ext-wght-normal.woff2 +0 -0
  151. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/files/geist-mono-latin-wght-normal.woff2 +0 -0
  152. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/fonts/font.css +0 -0
  153. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/lib.js +0 -0
  154. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/styles.css +0 -0
  155. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/vendor/LICENSES.txt +0 -0
  156. {upliftlab-0.26.2 → upliftlab-0.26.3}/web/vendor/editor.js +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: upliftlab
3
- Version: 0.26.2
3
+ Version: 0.26.3
4
4
  Summary: Find the uplift model that gives the most incremental outcome for the same budget, on your own experiment data
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://uplift-lab.vercel.app
@@ -2,4 +2,4 @@
2
2
 
3
3
  The same package runs in the browser (Pyodide) and natively (`python -m upliftlab serve`).
4
4
  """
5
- __version__ = '0.26.2'
5
+ __version__ = '0.26.3'
@@ -77,28 +77,48 @@ def main(argv=None):
77
77
  print(f'{dataset} · {len(algorithms)} algorithms · {args.cycles} cycles · device {devices[0]}', file=sys.stderr)
78
78
  bridge.set_reporter(lambda m: (lambda d: d.get('phase') == 'done' and print(f" c{d['cycle']} {d['arm']} done", file=sys.stderr))(json.loads(m)))
79
79
  result = bridge.run_all(dataset, algorithms, args.cycles, args.explore, args.target, args.seed, args.mode, args.budget, args.base_learner, args.efficiency)
80
- by = result['summary']['by']
81
- measure = ('incremental' if by.lower().endswith('incr') else 'gain') + (' (truth)' if by.startswith('truth') else ' (observed)')
82
- volume = by.lower().endswith('incr') # ranked by incremental outcome: the band is the gap to first place, as on the screen
83
- print(f"\n{'rank':<5}{'algorithm':<26}{measure:>22}{'vs 1st, 95% CI' if volume else 'observed 95% CI':>30}{'AUCC':>8}{'Qini':>8}{'fit ms':>9}")
84
- for s in sorted(result['summary']['arms'], key=lambda s: s['rank']):
85
- g, ci, auc, ms = s[by], s['ci'], s['aucc'], s['trainMs']
86
- gain = '—' if g is None else f'{g:,.0f}' if volume else f'{g:+.1%}'
87
- q = '—' if s['qini'] is None else f"{s['qini']:.3f}"
88
- gap = s.get('vsLeader') or {}
89
- if volume:
90
- band = '1st' if s['rank'] == 1 else '—' if gap.get('diff') is None or gap.get('ci', [None])[0] is None else f"{gap['diff']:+,.0f} [{gap['ci'][0]:+,.0f}, {gap['ci'][1]:+,.0f}]"
91
- else:
92
- band = '—' if ci[0] is None else f'[{ci[0]:+.1%}, {ci[1]:+.1%}]'
93
- auc = '—' if auc is None else f'{auc:.3f}'
94
- ms = '—' if ms is None else f'{ms:.0f}'
95
- print(f"{s['rank']:<5}{s['name'][:25]:<26}{gain:>22}{band:>30}{auc:>8}{q:>8}{ms:>9}")
80
+ print('\n' + '\n'.join(table(result)))
96
81
  print('\n' + conclusion(result, args.mode))
97
82
  if args.out:
98
83
  Path(args.out).write_text(json.dumps({'format': 'upliftlab.run/1', **result}, ensure_ascii=False))
99
84
  print(f'\nwrote {args.out}', file=sys.stderr)
100
85
 
101
86
 
87
+ def table(result):
88
+ """The ranking as lines of text. The second header line names each column's basis. The ranked value, its gap to first
89
+ place, AUCC and Qini use the ranking's basis; the 95% CI is always observed, since only observed data has sampling
90
+ error. With synthetic data that adds the observed gap as its own column, which is what an experiment would have shown."""
91
+ arms, by = result['summary']['arms'], result['summary']['by']
92
+ truth, volume = by.startswith('truth'), by.lower().endswith('incr')
93
+ basis, lead = ('truth' if truth else 'observed'), min(arms, key=lambda s: s['rank'])
94
+ dash = '—'
95
+ fix = lambda v, f: dash if v is None else format(v, f) # noqa: E731
96
+
97
+ def observed_gap(s):
98
+ gap = s.get('vsLeader') or {}
99
+ if s['rank'] == 1:
100
+ return '1st'
101
+ if gap.get('diff') is None or (gap.get('ci') or [None])[0] is None:
102
+ return dash
103
+ return f"{gap['diff']:+,.0f} [{gap['ci'][0]:+,.0f}, {gap['ci'][1]:+,.0f}]"
104
+
105
+ columns = [('incremental' if volume else 'gain', basis, 13, lambda s: fix(s[by], ',.0f' if volume else '+.1%'))]
106
+ if volume and truth:
107
+ columns.append(('vs 1st', 'truth', 10, lambda s: '1st' if s['rank'] == 1 else dash if s[by] is None or lead[by] is None else f'{s[by] - lead[by]:+,.0f}'))
108
+ if volume:
109
+ columns.append(('vs 1st, 95% CI', 'observed', 26, observed_gap))
110
+ else:
111
+ columns.append(('95% CI', 'observed', 20, lambda s: dash if s['ci'][0] is None else f"[{s['ci'][0]:+.1%}, {s['ci'][1]:+.1%}]"))
112
+ columns += [('AUCC', basis, 10, lambda s: fix(s['auccTruth' if truth else 'aucc'], '.3f')),
113
+ ('Qini', basis, 10, lambda s: fix(s['qiniTruth' if truth else 'qini'], '.3f')),
114
+ ('fit ms', '', 8, lambda s: fix(s['trainMs'], '.0f'))]
115
+ lines = [f"{'rank':<5}{'algorithm':<26}" + ''.join(f'{name:>{w}}' for name, _, w, _ in columns),
116
+ ' ' * 31 + ''.join(f'{b:>{w}}' for _, b, w, _ in columns)]
117
+ for s in sorted(arms, key=lambda s: s['rank']):
118
+ lines.append(f"{s['rank']:<5}{s['name'][:25]:<26}" + ''.join(f'{cell(s):>{w}}' for _, _, w, cell in columns))
119
+ return lines
120
+
121
+
102
122
  def conclusion(result, mode):
103
123
  """One line under the table: who won, by how much over Random, on which basis."""
104
124
  arms, by = result['summary']['arms'], result['summary']['by']
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "upliftlab"
7
- version = "0.26.2"
7
+ version = "0.26.3"
8
8
  description = "Find the uplift model that gives the most incremental outcome for the same budget, on your own experiment data"
9
9
  requires-python = ">=3.10"
10
10
  readme = "README.md"
@@ -17,6 +17,7 @@ from upliftlab.arena import Run, knapsack, select, best_cutoff # noqa: E402
17
17
  from upliftlab.arena import cost_estimator # noqa: E402
18
18
  from upliftlab.metrics import aucc, cost_curve, pooled, qini, qini_draws, qini_of_draws, uplift_at # noqa: E402
19
19
  from upliftlab.scenario import ReplayScenario, SyntheticScenario # noqa: E402
20
+ from upliftlab.__main__ import table # noqa: E402
20
21
 
21
22
  BUILTINS = {a['id']: {'id': a['id'], 'name': a['name'], 'code': a['code']} for a in bridge.builtins()}
22
23
  TEMPLATES = {t['id']: t for t in bridge.templates()}
@@ -216,6 +217,27 @@ def test_ranking_does_not_depend_on_the_units_of_cost_or_outcome(algorithm):
216
217
  assert other_gain == pytest.approx(gain, abs=1e-9), units
217
218
 
218
219
 
220
+ def test_the_command_line_table_ranks_and_measures_gaps_on_one_basis():
221
+ def arm(rank, name, truth, observed, gap=None):
222
+ return {'rank': rank, 'name': name, 'truthIncR': truth, 'incR': observed, 'ci': [None, None], 'trainMs': 5., 'aucc': .51, 'auccTruth': .61, 'qini': .02, 'qiniTruth': .04,
223
+ 'vsLeader': None if gap is None else {'diff': gap, 'ci': [gap - 100, gap + 100], 'tied': False}}
224
+ synthetic = {'summary': {'by': 'truthIncR', 'arms': [arm(1, 'R-Learner', 2342., 2500.), arm(2, 'Random', 2172., 2139., gap=-361.)]}}
225
+ head, basis, first, second = table(synthetic)
226
+ assert head.split()[2:] == ['incremental', 'vs', '1st', 'vs', '1st,', '95%', 'CI', 'AUCC', 'Qini', 'fit', 'ms']
227
+ assert basis.split() == ['truth', 'truth', 'observed', 'truth', 'truth'], 'every column names its basis'
228
+ assert second.split()[2:6] == ['2,172', '-170', '-361', '[-461,'], 'the gap to first place is on the ranking basis, the observed one is its own column'
229
+ assert '0.610' in second and '0.040' in second, 'AUCC and Qini follow the ranking basis'
230
+ observed = {'summary': {'by': 'incR', 'arms': [arm(1, 'R-Learner', None, 2500.), arm(2, 'Random', None, 2139., gap=-361.)]}}
231
+ head, basis, first, second = table(observed)
232
+ assert basis.split() == ['observed', 'observed', 'observed', 'observed'] and second.split()[2:4] == ['2,139', '-361']
233
+ assert '0.510' in second and '0.020' in second
234
+ run = call('run_all', dataset='synthetic/base', algorithms=pick('r_learner', 'random_baseline'), cycles=3, mode='budget')
235
+ lines, s = table(run), sorted(run['summary']['arms'], key=lambda a: a['rank'])
236
+ assert lines[3].split()[3] == f"{s[1]['truthIncR'] - s[0]['truthIncR']:+,.0f}"
237
+ by_gain = table(call('run_all', dataset='synthetic/base', algorithms=pick('r_learner', 'random_baseline'), cycles=3))
238
+ assert by_gain[0].split()[2:4] == ['gain', '95%'] and by_gain[1].split() == ['truth', 'observed', 'truth', 'truth']
239
+
240
+
219
241
  def test_qini_and_uplift_at_k_follow_their_definitions():
220
242
  line = [{'f': f, 'cost': f * 10, 'value': f * 4} for f in (0, .25, .5, .75, 1)]
221
243
  assert qini(line) == pytest.approx(0), 'a straight line is random targeting'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: upliftlab
3
- Version: 0.26.2
3
+ Version: 0.26.3
4
4
  Summary: Find the uplift model that gives the most incremental outcome for the same budget, on your own experiment data
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://uplift-lab.vercel.app
@@ -1,9 +1,9 @@
1
1
  // Shell: header, router and the engine chip. Views live in ./views and read from ./store.
2
- import {$, esc, go} from './lib.js?v=0.26.2';
3
- import {state, subscribe, boot, running} from './store.js?v=0.26.2';
4
- import {workspaceView} from './views/workspace.js?v=0.26.2';
5
- import {algorithmsView} from './views/algorithms.js?v=0.26.2';
6
- import {dataView} from './views/data.js?v=0.26.2';
2
+ import {$, esc, go} from './lib.js?v=0.26.3';
3
+ import {state, subscribe, boot, running} from './store.js?v=0.26.3';
4
+ import {workspaceView} from './views/workspace.js?v=0.26.3';
5
+ import {algorithmsView} from './views/algorithms.js?v=0.26.3';
6
+ import {dataView} from './views/data.js?v=0.26.3';
7
7
 
8
8
  const routes = [
9
9
  [/^\/?$/, workspaceView, () => ({})],