mergeron 2024.738930.0__py3-none-any.whl → 2024.738936.2__py3-none-any.whl

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.

Potentially problematic release.


This version of mergeron might be problematic. Click here for more details.

Files changed (30) hide show
  1. mergeron/core/__init__.py +1 -1
  2. mergeron/core/guidelines_standards.py +247 -57
  3. mergeron/core/proportions_tests.py +19 -19
  4. mergeron/core/pseudorandom_numbers.py +30 -29
  5. mergeron/examples/__init__.py +1 -1
  6. mergeron/examples/concentration_as_diversion.py +75 -88
  7. mergeron/examples/investigations_stats_obs_tables.py +119 -111
  8. mergeron/examples/investigations_stats_sim_tables.py +108 -87
  9. mergeron/examples/plotSafeHarbs_symbolically.py +2 -2
  10. mergeron/examples/safeharbor_boundaries_for_mergers_with_asymmetric_shares.py +35 -28
  11. mergeron/examples/safeharbor_boundaries_for_symmetric_firm_mergers.py +6 -13
  12. mergeron/examples/sound_guppi_safeharbor.py +23 -18
  13. mergeron/examples/testIntrinsicClearanceRates.py +5 -5
  14. mergeron/examples/visualize_empirical_margin_distribution.py +1 -1
  15. mergeron/examples/{visualize_guidelines_tests_scatterplots.py → visualize_guidelines_tests.py} +42 -48
  16. mergeron/ext/__init__.py +1 -1
  17. mergeron/gen/__init__.py +1 -1
  18. mergeron/gen/data_generation.py +25 -24
  19. mergeron/gen/guidelines_tests.py +47 -47
  20. mergeron/gen/investigations_stats.py +98 -46
  21. mergeron/jinja_LaTex_templates/clrrate_cis_summary_table_template.tex.jinja2 +2 -3
  22. mergeron/jinja_LaTex_templates/ftcinvdata_byhhianddelta_table_template.tex.jinja2 +2 -3
  23. mergeron/jinja_LaTex_templates/ftcinvdata_summary_table_template.tex.jinja2 +1 -2
  24. mergeron/jinja_LaTex_templates/ftcinvdata_summarypaired_table_template.tex.jinja2 +13 -14
  25. mergeron/jinja_LaTex_templates/mergeron.cls +161 -0
  26. mergeron/jinja_LaTex_templates/mergeron_table_collection_template.tex.jinja2 +90 -0
  27. {mergeron-2024.738930.0.dist-info → mergeron-2024.738936.2.dist-info}/METADATA +15 -18
  28. mergeron-2024.738936.2.dist-info/RECORD +41 -0
  29. mergeron-2024.738930.0.dist-info/RECORD +0 -39
  30. {mergeron-2024.738930.0.dist-info → mergeron-2024.738936.2.dist-info}/WHEEL +0 -0
mergeron/core/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from importlib.metadata import version
2
2
 
3
- from .. import _PKG_NAME
3
+ from .. import _PKG_NAME # noqa: TID252
4
4
 
5
5
  __version__ = version(_PKG_NAME)
@@ -12,12 +12,12 @@ from .. import _PKG_NAME # noqa: TID252
12
12
  __version__ = version(_PKG_NAME)
13
13
 
14
14
  import decimal
15
- from dataclasses import dataclass
16
15
  from typing import Any, Literal, TypeAlias
17
16
 
18
17
  import numpy as np
19
18
  from mpmath import mp, mpf # type: ignore
20
19
  from numpy.typing import NDArray
20
+ from scipy.spatial.distance import minkowski as distance_function
21
21
 
22
22
  mp.prec = 80
23
23
  mp.trap_complex = True
@@ -26,7 +26,6 @@ HMGPubYear: TypeAlias = Literal[1992, 2010, 2023]
26
26
  GuidelinesSTD = namedtuple("GuidelinesSTD", "delta rec guppi divr cmcr ipr")
27
27
 
28
28
 
29
- @dataclass
30
29
  class GuidelinesStandards:
31
30
  """
32
31
  Guidelines standards by Guidelines publication year
@@ -170,7 +169,7 @@ def lerp(
170
169
  /,
171
170
  ) -> float | mpf | NDArray[np.float64]:
172
171
  """
173
- From the C++ standard function of the same name.
172
+ From the function of the same name in the C++ standard [2]_
174
173
 
175
174
  Constructs the weighted average, :math:`w_1 x_1 + w_2 x_2`, where
176
175
  :math:`w_1 = 1 - r` and :math:`w_2 = r`.
@@ -180,21 +179,35 @@ def lerp(
180
179
  _x1, _x2
181
180
  bounds :math:`x_1, x_2` to interpolate between.
182
181
  _r
183
- interpolation weight :math:`r` assigned to :math:`x_2`.
182
+ interpolation weight :math:`r` assigned to :math:`x_2`
184
183
 
185
184
  Returns
186
185
  -------
187
- The linear interpolation, or weighted average, :math:`x_1 (1 - r) + x_2 r`.
186
+ The linear interpolation, or weighted average,
187
+ :math:`x_1 + r \\cdot (x_1 - x_2) \\equiv (1 - r) \\cdot x_1 + r \\cdot x_2`.
188
188
 
189
189
  Raises
190
190
  ------
191
191
  ValueError
192
192
  If the interpolation weight is not in the interval, :math:`[0, 1]`.
193
193
 
194
+ References
195
+ ----------
196
+
197
+ .. [2] C++ Reference, https://en.cppreference.com/w/cpp/numeric/lerp
198
+
194
199
  """
200
+
195
201
  if not 0 <= _r <= 1:
196
202
  raise ValueError("Specified interpolation weight must lie in [0, 1].")
197
- return _x2 * _r + _x1 * (1 - _r)
203
+ elif _r == 0:
204
+ return _x1
205
+ elif _r == 1:
206
+ return _x2
207
+ elif _r == 0.5:
208
+ return 1 / 2 * (_x1 + _x2)
209
+ else:
210
+ return _r * _x2 + (1 - _r) * _x1
198
211
 
199
212
 
200
213
  def gbd_from_dsf(
@@ -337,9 +350,7 @@ def boundary_plot(*, mktshares_plot_flag: bool = True) -> tuple[Any, ...]:
337
350
  _fig = plt.figure(figsize=(5, 5), dpi=600)
338
351
  _ax_out = _fig.add_subplot()
339
352
 
340
- def _set_axis_def(
341
- _ax1: mpa._axes.Axes, /, mktshares_plot_flag: bool = False
342
- ) -> mpa.Axes:
353
+ def _set_axis_def(_ax1: mpa.Axes, /, mktshares_plot_flag: bool = False) -> mpa.Axes:
343
354
  # Set the width of axis gridlines, and tick marks:
344
355
  # both axes, both major and minor ticks
345
356
  # Frame, grid, and facecolor
@@ -556,7 +567,7 @@ def delta_hhi_boundary(
556
567
 
557
568
 
558
569
  def combined_share_boundary(
559
- _s_incpt: float = 0.0625, /, *, bdry_dps: int = 10
570
+ _s_intcpt: float = 0.0625, /, *, bdry_dps: int = 10
560
571
  ) -> tuple[NDArray[np.float64], float]:
561
572
  """
562
573
  Share combinations on the merging-firms' combined share boundary.
@@ -567,7 +578,7 @@ def combined_share_boundary(
567
578
 
568
579
  Parameters
569
580
  ----------
570
- _s_incpt:
581
+ _s_intcpt:
571
582
  Merging-firms' combined share.
572
583
  bdry_dps
573
584
  Number of decimal places for rounding reported shares.
@@ -577,16 +588,16 @@ def combined_share_boundary(
577
588
  Array of share-pairs, area under boundary.
578
589
 
579
590
  """
580
- _s_incpt = mpf(f"{_s_incpt}")
581
- _s_mid = _s_incpt / 2
591
+ _s_intcpt = mpf(f"{_s_intcpt}")
592
+ _s_mid = _s_intcpt / 2
582
593
 
583
- _s1_pts = (0, _s_mid, _s_incpt)
594
+ _s1_pts = (0, _s_mid, _s_intcpt)
584
595
  return (
585
596
  np.column_stack((
586
597
  np.array(_s1_pts, np.float64),
587
598
  np.array(_s1_pts[::-1], np.float64),
588
599
  )),
589
- round(float(_s_incpt * _s_mid), bdry_dps),
600
+ round(float(_s_intcpt * _s_mid), bdry_dps),
590
601
  )
591
602
 
592
603
 
@@ -654,17 +665,17 @@ def shrratio_mgnsym_boundary_max(
654
665
  # of function call with other shrratio_mgnsym_boundary functions
655
666
  del _r_val
656
667
  _delta_star = mpf(f"{_delta_star}")
657
- _s_incpt = _delta_star
668
+ _s_intcpt = _delta_star
658
669
  _s_mid = _delta_star / (1 + _delta_star)
659
670
 
660
- _s1_pts = (0, _s_mid, _s_incpt)
671
+ _s1_pts = (0, _s_mid, _s_intcpt)
661
672
 
662
673
  return (
663
674
  np.column_stack((
664
675
  np.array(_s1_pts, np.float64),
665
676
  np.array(_s1_pts[::-1], np.float64),
666
677
  )),
667
- round(float(_s_incpt * _s_mid), gbd_dps), # simplified calculation
678
+ round(float(_s_intcpt * _s_mid), gbd_dps), # simplified calculation
668
679
  )
669
680
 
670
681
 
@@ -714,7 +725,7 @@ def shrratio_mgnsym_boundary_min(
714
725
  )
715
726
 
716
727
  _delta_star = mpf(f"{_delta_star}")
717
- _s_incpt = mpf("1.00")
728
+ _s_intcpt = mpf("1.00")
718
729
  _s_mid = _delta_star / (1 + _delta_star)
719
730
 
720
731
  if recapture_spec == "inside-out":
@@ -729,14 +740,14 @@ def shrratio_mgnsym_boundary_min(
729
740
  _smin_nr / _guppi_bdry_env_dr,
730
741
  _s_mid,
731
742
  _smax_nr / _guppi_bdry_env_dr,
732
- _s_incpt,
743
+ _s_intcpt,
733
744
  ),
734
745
  np.float64,
735
746
  )
736
747
 
737
748
  _gbd_area = (_smin_nr + (_smax_nr - _smin_nr) * _s_mid) / _guppi_bdry_env_dr
738
749
  else:
739
- _s1_pts, _gbd_area = np.array((0, _s_mid, _s_incpt), np.float64), _s_mid
750
+ _s1_pts, _gbd_area = np.array((0, _s_mid, _s_intcpt), np.float64), _s_mid
740
751
 
741
752
  return np.column_stack((_s1_pts, _s1_pts[::-1])), round(float(_gbd_area), gbd_dps)
742
753
 
@@ -747,7 +758,7 @@ def shrratio_mgnsym_boundary_wtd_avg(
747
758
  /,
748
759
  *,
749
760
  avg_method: Literal["arithmetic", "geometric", "distance"] = "arithmetic",
750
- wgtng_policy: Literal["own-share", "cross-product-share"] = "own-share",
761
+ wgtng_policy: Literal["own-share", "cross-product-share"] | None = "own-share",
751
762
  recapture_spec: Literal["inside-out", "proportional"] = "inside-out",
752
763
  gbd_dps: int = 5,
753
764
  ) -> tuple[NDArray[np.float64], float]:
@@ -819,9 +830,9 @@ def shrratio_mgnsym_boundary_wtd_avg(
819
830
  Parameters
820
831
  ----------
821
832
  _delta_star
822
- GUPPI bound
833
+ corollary to GUPPI bound (:math:`\\overline{g} / (m^* \\cdot \\overline{r})`)
823
834
  _r_val
824
- Recapture ratio.
835
+ recapture ratio
825
836
  avg_method
826
837
  Whether "arithmetic", "geometric", or "distance".
827
838
  wgtng_policy
@@ -840,17 +851,18 @@ def shrratio_mgnsym_boundary_wtd_avg(
840
851
 
841
852
  if _delta_star > 1:
842
853
  raise ValueError(
843
- "Invalid combination specified; "
844
- "Margin-adjusted benchmark share ratio cannot exceed 1."
854
+ "Margin-adjusted benchmark share ratio, `_delta_star` cannot exceed 1."
845
855
  )
846
856
 
847
857
  _delta_star = mpf(f"{_delta_star}")
848
858
  _s_mid = _delta_star / (1 + _delta_star)
849
859
 
860
+ # initial conditions
850
861
  _gbdry_points = [(_s_mid, _s_mid)]
851
862
  _s_1_pre, _s_2_pre = _s_mid, _s_mid
852
863
  _s_2_oddval, _s_2_oddsum, _s_2_evnsum = True, 0, 0
853
864
 
865
+ # parameters for iteration
854
866
  _gbd_step_sz = mp.power(10, -gbd_dps)
855
867
  _theta = _gbd_step_sz * (10 if wgtng_policy == "cross-product-share" else 1)
856
868
  for _s_1 in mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz):
@@ -871,8 +883,12 @@ def shrratio_mgnsym_boundary_wtd_avg(
871
883
  else _s_1 / (1 - _s_2)
872
884
  )
873
885
 
874
- _r = mp.fdiv(
875
- _s_1 if wgtng_policy == "cross-product-share" else _s_2, _s_1 + _s_2
886
+ _r = (
887
+ mp.fdiv(
888
+ _s_1 if wgtng_policy == "cross-product-share" else _s_2, _s_1 + _s_2
889
+ )
890
+ if wgtng_policy
891
+ else 0.5
876
892
  )
877
893
 
878
894
  match avg_method:
@@ -884,12 +900,12 @@ def shrratio_mgnsym_boundary_wtd_avg(
884
900
  _delta_test = lerp(_de_1, _de_2, _r)
885
901
 
886
902
  if wgtng_policy == "cross-product-share":
887
- test_flag, incr_decr = (_delta_test > _delta_star, -1)
903
+ _test_flag, _incr_decr = (_delta_test > _delta_star, -1)
888
904
  else:
889
- test_flag, incr_decr = (_delta_test < _delta_star, 1)
905
+ _test_flag, _incr_decr = (_delta_test < _delta_star, 1)
890
906
 
891
- if test_flag:
892
- _s_2 += incr_decr * _gbd_step_sz
907
+ if _test_flag:
908
+ _s_2 += _incr_decr * _gbd_step_sz
893
909
  else:
894
910
  break
895
911
 
@@ -917,13 +933,19 @@ def shrratio_mgnsym_boundary_wtd_avg(
917
933
  # Area under boundary
918
934
  _gbdry_area_total = 2 * _gbd_prtlarea - mp.power(_s_mid, 2)
919
935
 
920
- _gbdry_points = np.row_stack((
921
- _gbdry_points,
922
- (
923
- mpf("0.0"),
924
- _delta_star if wgtng_policy == "cross-product-share" else mpf("1.0"),
925
- ),
926
- )).astype(np.float64)
936
+ match wgtng_policy:
937
+ case "cross-product-share":
938
+ _s_intcpt = _delta_star
939
+ case "own-product-share":
940
+ _s_intcpt = mpf("1.0")
941
+ case None if avg_method == "distance":
942
+ _s_intcpt = _delta_star * mp.sqrt("2")
943
+ case _:
944
+ _s_intcpt = _s_2_pre
945
+
946
+ _gbdry_points = np.row_stack((_gbdry_points, (mpf("0.0"), _s_intcpt))).astype(
947
+ np.float64
948
+ )
927
949
  # Points defining boundary to point-of-symmetry
928
950
  return (
929
951
  np.row_stack((np.flip(_gbdry_points, 0), np.flip(_gbdry_points[1:], 1))),
@@ -1010,13 +1032,13 @@ def shrratio_mgnsym_boundary_xact_avg(
1010
1032
  _gbdry_points_start = np.array([(_s_mid, _s_mid)])
1011
1033
  _s_1 = np.array(mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz), np.float64)
1012
1034
  if recapture_spec == "inside-out":
1013
- _s_incpt = mp.fdiv(
1035
+ _s_intcpt = mp.fdiv(
1014
1036
  mp.fsub(
1015
1037
  2 * _delta_star * _r_val + 1, mp.fabs(2 * _delta_star * _r_val - 1)
1016
1038
  ),
1017
1039
  2 * mpf(f"{_r_val}"),
1018
1040
  )
1019
- _nr_t1 = 1 + 2 * _delta_star * _r_val * (1 - _s_1) - _s_1 * (1 - _r_val)
1041
+ _nr_t1 = 1 + 2 * _delta_star * _r_val * (1 - _s_1) - _s_1 * (1 - _r_val) # type: ignore
1020
1042
 
1021
1043
  _nr_sqrt_mdr = 4 * _delta_star * _r_val
1022
1044
  _nr_sqrt_mdr2 = _nr_sqrt_mdr * _r_val
@@ -1041,9 +1063,9 @@ def shrratio_mgnsym_boundary_xact_avg(
1041
1063
 
1042
1064
  _nr_t2_s1 = _nr_sqrt_s1sq + _nr_sqrt_s1 + _nr_sqrt_nos1
1043
1065
 
1044
- if not np.isclose(
1045
- np.einsum("i->", _nr_t2_mdr.astype(np.float64)), # from mpf to float64
1046
- np.einsum("i->", _nr_t2_s1.astype(np.float64)),
1066
+ if not np.isclose( # type: ignore
1067
+ np.einsum("i->", _nr_t2_mdr.astype(np.float64)), # type: ignore from mpf to float64
1068
+ np.einsum("i->", _nr_t2_s1.astype(np.float64)), # type: ignore from mpf to float64
1047
1069
  rtol=0,
1048
1070
  atol=0.5 * gbd_dps,
1049
1071
  ):
@@ -1055,7 +1077,7 @@ def shrratio_mgnsym_boundary_xact_avg(
1055
1077
  _s_2 = (_nr_t1 - np.sqrt(_nr_t2_s1)) / (2 * _r_val)
1056
1078
 
1057
1079
  else:
1058
- _s_incpt = mp.fsub(_delta_star + 1 / 2, mp.fabs(_delta_star - 1 / 2))
1080
+ _s_intcpt = mp.fsub(_delta_star + 1 / 2, mp.fabs(_delta_star - 1 / 2))
1059
1081
  _s_2 = (
1060
1082
  (1 / 2)
1061
1083
  + _delta_star
@@ -1070,7 +1092,7 @@ def shrratio_mgnsym_boundary_xact_avg(
1070
1092
  )
1071
1093
 
1072
1094
  _gbdry_points_inner = np.column_stack((_s_1, _s_2))
1073
- _gbdry_points_end = np.array([(mpf("0.0"), _s_incpt)], np.float64)
1095
+ _gbdry_points_end = np.array([(mpf("0.0"), _s_intcpt)], np.float64)
1074
1096
 
1075
1097
  _gbdry_points = np.row_stack((
1076
1098
  _gbdry_points_end,
@@ -1112,6 +1134,8 @@ def shrratio_mgnsym_boundary_avg(
1112
1134
  Share combinations along the average GUPPI boundary, with
1113
1135
  symmetric merging-firm margins.
1114
1136
 
1137
+ Faster than calling shrratio_mgnsym_boundary_avg(wgtng_policy=None).
1138
+
1115
1139
  Parameters
1116
1140
  ----------
1117
1141
  _delta_star
@@ -1119,7 +1143,7 @@ def shrratio_mgnsym_boundary_avg(
1119
1143
  _r_val
1120
1144
  Recapture ratio.
1121
1145
  avg_method
1122
- Whether "arithmetic", "geometric", or "root-mean-square".
1146
+ Whether "arithmetic", "geometric", or "distance".
1123
1147
  recapture_spec
1124
1148
  Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
1125
1149
  value for both merging firms ("proportional").
@@ -1138,7 +1162,7 @@ def shrratio_mgnsym_boundary_avg(
1138
1162
  "Margin-adjusted benchmark share ratio cannot exceed 1."
1139
1163
  )
1140
1164
 
1141
- if avg_method not in (_avgmthds := ("arithmetic", "geometric", "root-mean-square")):
1165
+ if avg_method not in (_avgmthds := ("arithmetic", "geometric", "distance")):
1142
1166
  raise ValueError(
1143
1167
  f"Averarging method, {f'"{avg_method}"'} is invalid. "
1144
1168
  f"Must be one of, {_avgmthds!r}."
@@ -1152,18 +1176,21 @@ def shrratio_mgnsym_boundary_avg(
1152
1176
 
1153
1177
  _delta_star = mpf(f"{_delta_star}")
1154
1178
  _s_mid = _delta_star / (1 + _delta_star)
1155
- _gbd_step_sz = mp.power(10, -gbd_dps)
1156
1179
 
1180
+ # initial conditions
1157
1181
  _s_2 = _s_mid
1158
1182
  _s_2_oddval = True
1159
1183
  _s_2_oddsum = 0
1160
1184
  _s_2_evnsum = 0
1161
1185
  _gbdry_points = [(_s_mid, _s_mid)]
1186
+
1187
+ # parameters for iteration
1188
+ _gbd_step_sz = mp.power(10, -gbd_dps)
1162
1189
  for _s_1 in mp.arange(_s_mid, 0, -_gbd_step_sz):
1163
1190
  _s_1 -= _gbd_step_sz
1164
1191
  while True:
1165
- _shratio_12 = _s_2 / (1 - _s_1)
1166
- _shratio_21 = (
1192
+ _delta_12 = _s_2 / (1 - _s_1)
1193
+ _delta_21 = (
1167
1194
  _s_1 / (1 - _s_2)
1168
1195
  if recapture_spec == "proportional"
1169
1196
  else _s_1 / (1 - lerp(_s_1, _s_2, _r_val))
@@ -1171,13 +1198,21 @@ def shrratio_mgnsym_boundary_avg(
1171
1198
 
1172
1199
  match avg_method:
1173
1200
  case "geometric":
1174
- _small_d = mp.sqrt(_shratio_12 * _shratio_21)
1201
+ _delta_test = mp.sqrt(_delta_12 * _delta_21)
1175
1202
  case "distance":
1176
- _small_d = mp.sqrt(mp.fdiv((_shratio_12**2 + _shratio_21**2), "2"))
1203
+ # _delta_test = mp.sqrt(mp.fdiv((_delta_12**2 + _delta_21**2), "2"))
1204
+ _delta_test = mp.sqrt(
1205
+ mp.fdiv(
1206
+ mp.fsum(
1207
+ mp.power(f"{_g}", "2") for _g in (_delta_12, _delta_21)
1208
+ ),
1209
+ "2",
1210
+ )
1211
+ )
1177
1212
  case _:
1178
- _small_d = mp.fdiv(_shratio_12 + _shratio_21, "2")
1213
+ _delta_test = mp.fdiv(_delta_12 + _delta_21, "2")
1179
1214
 
1180
- if _small_d < _delta_star:
1215
+ if _delta_test < _delta_star:
1181
1216
  _s_2 += _gbd_step_sz
1182
1217
  else:
1183
1218
  break
@@ -1190,12 +1225,12 @@ def shrratio_mgnsym_boundary_avg(
1190
1225
 
1191
1226
  # Starting at _s_id - _gbd_step_sz means _s_1 is not always
1192
1227
  # an even multiple of _gbd_step_sz
1193
- _s_incpt = _s_2
1228
+ _s_intcpt = _s_2
1194
1229
 
1195
1230
  _gbd_prtlarea = 2 * _gbd_step_sz * (
1196
1231
  mp.fmul(4 / 3, _s_2_oddsum)
1197
1232
  + mp.fmul(2 / 3, _s_2_evnsum)
1198
- + mp.fmul(1 / 3, _s_mid + _s_incpt)
1233
+ + mp.fmul(1 / 3, _s_mid + _s_intcpt)
1199
1234
  ) - mp.power(_s_mid, 2)
1200
1235
 
1201
1236
  _gbdry_points = np.array(_gbdry_points, np.float64)
@@ -1203,3 +1238,158 @@ def shrratio_mgnsym_boundary_avg(
1203
1238
  np.row_stack((np.flip(_gbdry_points, 0), np.flip(_gbdry_points[1:], 1))),
1204
1239
  round(float(_gbd_prtlarea), gbd_dps),
1205
1240
  )
1241
+
1242
+
1243
+ def shrratio_mgnsym_boundary_distance(
1244
+ _delta_star: float = 0.075,
1245
+ _r_val: float = 0.80,
1246
+ /,
1247
+ *,
1248
+ avg_method: Literal["arithmetic", "distance"] = "arithmetic",
1249
+ wgtng_policy: Literal["own-share", "cross-product-share"] | None = "own-share",
1250
+ recapture_spec: Literal["inside-out", "proportional"] = "inside-out",
1251
+ gbd_dps: int = 5,
1252
+ ) -> tuple[NDArray[np.float64], float]:
1253
+ """
1254
+ Share combinations for the GUPPI boundaries using various aggregators with symmetric merging-firm margins.
1255
+
1256
+ Parameters
1257
+ ----------
1258
+ _delta_star
1259
+ corollary to GUPPI bound (:math:`\\overline{g} / (m^* \\cdot \\overline{r})`)
1260
+ _r_val
1261
+ recapture ratio
1262
+ avg_method
1263
+ Whether "arithmetic", "geometric", or "distance".
1264
+ wgtng_policy
1265
+ Whether "own-share" or "cross-product-share".
1266
+ recapture_spec
1267
+ Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
1268
+ value for both merging firms ("proportional").
1269
+ gbd_dps
1270
+ Number of decimal places for rounding returned shares and area.
1271
+
1272
+ Returns
1273
+ -------
1274
+ Array of share-pairs, area under boundary.
1275
+
1276
+ """
1277
+
1278
+ if _delta_star > 1:
1279
+ raise ValueError(
1280
+ "Margin-adjusted benchmark share ratio, `_delta_star` cannot exceed 1."
1281
+ )
1282
+
1283
+ _delta_star = mpf(f"{_delta_star}")
1284
+ _s_mid = _delta_star / (1 + _delta_star)
1285
+
1286
+ # initial conditions
1287
+ _gbdry_points = [(_s_mid, _s_mid)]
1288
+ _s_1_pre, _s_2_pre = _s_mid, _s_mid
1289
+ _s_2_oddval, _s_2_oddsum, _s_2_evnsum = True, 0, 0
1290
+
1291
+ # parameters for iteration
1292
+ _weights_base = (mpf("0.5"),) * 2
1293
+ _gbd_step_sz = mp.power(10, -gbd_dps)
1294
+ _theta = _gbd_step_sz * (10 if wgtng_policy == "cross-product-share" else 1)
1295
+ for _s_1 in mp.arange(_s_mid - _gbd_step_sz, 0, -_gbd_step_sz):
1296
+ # The wtd. avg. GUPPI is not always convex to the origin, so we
1297
+ # increment _s_2 after each iteration in which our algorithm
1298
+ # finds (s1, s2) on the boundary
1299
+ _s_2 = _s_2_pre * (1 + _theta)
1300
+
1301
+ if (_s_1 + _s_2) > mpf("0.99875"):
1302
+ # 1: # We lose accuracy at 3-9s and up
1303
+ break
1304
+
1305
+ while True:
1306
+ _de_1 = _s_2 / (1 - _s_1)
1307
+ _de_2 = (
1308
+ _s_1 / (1 - lerp(_s_1, _s_2, _r_val))
1309
+ if recapture_spec == "inside-out"
1310
+ else _s_1 / (1 - _s_2)
1311
+ )
1312
+
1313
+ _weights_i = (
1314
+ (
1315
+ _w1 := mp.fdiv(
1316
+ _s_2 if wgtng_policy == "cross-product-share" else _s_1,
1317
+ _s_1 + _s_2,
1318
+ ),
1319
+ 1 - _w1,
1320
+ )
1321
+ if wgtng_policy
1322
+ else _weights_base
1323
+ )
1324
+
1325
+ match avg_method:
1326
+ case "arithmetic":
1327
+ _delta_test = distance_function(
1328
+ (_de_1, _de_2), (0.0, 0.0), p=1, w=_weights_i
1329
+ )
1330
+ case "distance":
1331
+ _delta_test = distance_function(
1332
+ (_de_1, _de_2), (0.0, 0.0), p=2, w=_weights_i
1333
+ )
1334
+
1335
+ if wgtng_policy == "cross-product-share":
1336
+ _test_flag, _incr_decr = (_delta_test > _delta_star, -1)
1337
+ else:
1338
+ _test_flag, _incr_decr = (_delta_test < _delta_star, 1)
1339
+
1340
+ if _test_flag:
1341
+ _s_2 += _incr_decr * _gbd_step_sz
1342
+ else:
1343
+ break
1344
+
1345
+ # Build-up boundary points
1346
+ _gbdry_points.append((_s_1, _s_2))
1347
+
1348
+ # Build up area terms
1349
+ _s_2_oddsum += _s_2 if _s_2_oddval else 0
1350
+ _s_2_evnsum += _s_2 if not _s_2_oddval else 0
1351
+ _s_2_oddval = not _s_2_oddval
1352
+
1353
+ # Hold share points
1354
+ _s_2_pre = _s_2
1355
+ _s_1_pre = _s_1
1356
+
1357
+ _gbd_prtlarea = _gbd_step_sz * (
1358
+ (4 * _s_2_oddsum + 2 * _s_2_evnsum + _s_mid + _delta_star) / 3
1359
+ if wgtng_policy == "cross-product-share"
1360
+ else (
1361
+ (4 * _s_2_oddsum + 2 * _s_2_evnsum + _s_mid + _s_2_pre) / 3
1362
+ + _s_1_pre * (1 + _s_2_pre) / 2
1363
+ )
1364
+ )
1365
+
1366
+ # Area under boundary
1367
+ _gbdry_area_total = 2 * _gbd_prtlarea - mp.power(_s_mid, 2)
1368
+
1369
+ match wgtng_policy:
1370
+ case "cross-product-share":
1371
+ _s_intcpt = _delta_star
1372
+ case "own-product-share":
1373
+ _s_intcpt = mpf("1.0")
1374
+ case None if avg_method == "distance":
1375
+ _s_intcpt = _delta_star * mp.sqrt("2")
1376
+ case None if avg_method == "arithmetic" and recapture_spec == "inside-out":
1377
+ _s_intcpt = mp.fdiv(
1378
+ mp.fsub(
1379
+ 2 * _delta_star * _r_val + 1, mp.fabs(2 * _delta_star * _r_val - 1)
1380
+ ),
1381
+ 2 * mpf(f"{_r_val}"),
1382
+ )
1383
+ case None if avg_method == "arithmetic":
1384
+ _s_intcpt = mp.fsub(_delta_star + 1 / 2, mp.fabs(_delta_star - 1 / 2))
1385
+ case _:
1386
+ _s_intcpt = _s_2_pre
1387
+
1388
+ _gbdry_points = np.row_stack((_gbdry_points, (mpf("0.0"), _s_intcpt))).astype(
1389
+ np.float64
1390
+ )
1391
+ # Points defining boundary to point-of-symmetry
1392
+ return (
1393
+ np.row_stack((np.flip(_gbdry_points, 0), np.flip(_gbdry_points[1:], 1))),
1394
+ round(float(_gbdry_area_total), gbd_dps),
1395
+ )
@@ -21,12 +21,12 @@ from numpy.typing import NBitBase, NDArray
21
21
  from scipy.optimize import OptimizeResult, root # type: ignore
22
22
  from scipy.stats import beta, chi2, norm # type: ignore
23
23
 
24
- T = TypeVar("T", bound=NBitBase)
24
+ TI = TypeVar("TI", bound=NBitBase)
25
25
 
26
26
 
27
27
  def propn_ci(
28
- _npos: NDArray[np.integer[T]] | int = 4,
29
- _nobs: NDArray[np.integer[T]] | int = 10,
28
+ _npos: NDArray[np.integer[TI]] | int = 4,
29
+ _nobs: NDArray[np.integer[TI]] | int = 10,
30
30
  /,
31
31
  *,
32
32
  alpha: float = 0.05,
@@ -41,7 +41,7 @@ def propn_ci(
41
41
  ]:
42
42
  """Returns point estimates and confidence interval for a proportion
43
43
 
44
- Methods "Clopper-Pearson" and "Exact" are synoymous [2]_. Similarly,
44
+ Methods "Clopper-Pearson" and "Exact" are synoymous [3]_. Similarly,
45
45
  "Wilson" and "Score" are synonyms here.
46
46
 
47
47
  Parameters
@@ -66,7 +66,7 @@ def propn_ci(
66
66
  References
67
67
  ----------
68
68
 
69
- .. [2] Alan Agresti & Brent A. Coull (1998) Approximate is Better
69
+ .. [3] Alan Agresti & Brent A. Coull (1998) Approximate is Better
70
70
  than “Exact” for Interval Estimation of Binomial Proportions,
71
71
  The American Statistician, 52:2, 119-126,
72
72
  https://doi.org/10.1080/00031305.1998.10480550
@@ -74,9 +74,9 @@ def propn_ci(
74
74
  """
75
75
 
76
76
  for _f in _npos, _nobs:
77
- if not isinstance(_f, float | int | np.floating | np.integer):
77
+ if not isinstance(_f, int | np.integer):
78
78
  raise ValueError(
79
- f"Count, {_f!r} must have type that is a subtype of np.floating or np.integer."
79
+ f"Count, {_f!r} must have type that is a subtype of np.integer."
80
80
  )
81
81
 
82
82
  if not _nobs:
@@ -131,7 +131,7 @@ def propn_ci(
131
131
 
132
132
 
133
133
  def propn_ci_multinomial(
134
- _counts: NDArray[np.integer[T]],
134
+ _counts: NDArray[np.integer[TI]],
135
135
  /,
136
136
  *,
137
137
  alpha: float = 0.05,
@@ -200,9 +200,9 @@ def propn_diff_ci(
200
200
  ) -> tuple[float, float, float, float]:
201
201
  R"""Confidence intervals for differences in binomial proportions.
202
202
 
203
- Methods available are Agresti-Caffo [3]_, Mee [4]_, Meitinen-Nurminen [4]_ [5]_
204
- and Newcombe (aka, Score method) [4]_. See also, source code for the
205
- R-language function BinomDiffCI, in the module StatsAndCIs [6]_.
203
+ Methods available are Agresti-Caffo [4]_, Mee [5]_, Meitinen-Nurminen [5]_ [6]_
204
+ and Newcombe (aka, Score method) [5]_. See also, source code for the
205
+ R-language function BinomDiffCI, in the module StatsAndCIs [7]_.
206
206
 
207
207
  Parameters
208
208
  ----------
@@ -222,19 +222,19 @@ def propn_diff_ci(
222
222
  References
223
223
  ----------
224
224
 
225
- .. [3] Agresti, A., & Caffo, T. (2000). Simple and Effective
225
+ .. [4] Agresti, A., & Caffo, T. (2000). Simple and Effective
226
226
  Confidence Intervals for Proportions and Differences of Proportions
227
227
  Result from Adding Two Successes and Two Failures.
228
228
  The American Statistician, 54(4), 280--288. https://doi.org/10.2307/2685779
229
229
 
230
- .. [4] Newcombe, R.G. (1998). Two-sided confidence intervals for
230
+ .. [5] Newcombe, R.G. (1998). Two-sided confidence intervals for
231
231
  the single proportion: comparison of seven methods. Statist. Med., 17: 857-872.
232
232
  https://doi.org/10.1002/(SICI)1097-0258(19980430)17:8%3C857::AID-SIM777%3E3.0.CO;2-E
233
233
 
234
- .. [5] Miettinen, O. and Nurminen, M. (1985). Comparative analysis of two rates.
234
+ .. [6] Miettinen, O. and Nurminen, M. (1985). Comparative analysis of two rates.
235
235
  Statist. Med., 4: 213-226. https://doi.org/10.1002/sim.4780040211; Appendix I
236
236
 
237
- .. [6] StatsAndCIs.r, function BinomDiffCI, method, "mn"
237
+ .. [7] StatsAndCIs.r, function BinomDiffCI, method, "mn"
238
238
  https://github.com/cran/DescTools/blob/master/R/StatsAndCIs.r
239
239
  (R source code is distributed under the CC-BY license.)
240
240
 
@@ -349,9 +349,9 @@ def _propn_diff_ci_mn(
349
349
 
350
350
  """
351
351
  for _f in _npos1, _nobs1, _npos1, _nobs2:
352
- if not isinstance(_f, float | int | np.floating | np.integer):
352
+ if not isinstance(_f, int | np.integer):
353
353
  raise ValueError(
354
- f"Count, {_f!r} must have type that is a subtype of np.floating or np.integer."
354
+ f"Count, {_f!r} must have type that is a subtype of np.integer."
355
355
  )
356
356
 
357
357
  _chi_sq_cr = chi2.ppf(1 - alpha, 1)
@@ -442,7 +442,7 @@ def _propn_diff_chisq_mn(
442
442
 
443
443
 
444
444
  def propn_ci_diff_multinomial(
445
- _counts: NDArray[np.integer[T]], /, *, alpha: float = 0.05
445
+ _counts: NDArray[np.integer[TI]], /, *, alpha: float = 0.05
446
446
  ) -> NDArray[np.float64]:
447
447
  """Estimate confidence intervals of pair-wise differences in multinomial proportions
448
448
 
@@ -483,7 +483,7 @@ class MultinomialDiffTest(NamedTuple):
483
483
 
484
484
 
485
485
  def propn_diff_multinomial_chisq(
486
- _counts: NDArray[np.integer[T]], /, *, alpha: float = 0.05
486
+ _counts: NDArray[np.integer[TI]], /, *, alpha: float = 0.05
487
487
  ) -> MultinomialDiffTest:
488
488
  """Chi-square test for homogeneity of differences in multinomial proportions.
489
489