mergeron 2024.738953.1__py3-none-any.whl → 2025.739265.0__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 (39) hide show
  1. mergeron/__init__.py +26 -6
  2. mergeron/core/__init__.py +5 -65
  3. mergeron/core/{damodaran_margin_data.py → empirical_margin_distribution.py} +74 -58
  4. mergeron/core/ftc_merger_investigations_data.py +147 -101
  5. mergeron/core/guidelines_boundaries.py +290 -1078
  6. mergeron/core/guidelines_boundary_functions.py +1128 -0
  7. mergeron/core/{guidelines_boundaries_specialized_functions.py → guidelines_boundary_functions_extra.py} +87 -55
  8. mergeron/core/pseudorandom_numbers.py +16 -22
  9. mergeron/data/__init__.py +3 -0
  10. mergeron/data/damodaran_margin_data.xls +0 -0
  11. mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
  12. mergeron/demo/__init__.py +3 -0
  13. mergeron/demo/visualize_empirical_margin_distribution.py +86 -0
  14. mergeron/gen/__init__.py +258 -246
  15. mergeron/gen/data_generation.py +473 -224
  16. mergeron/gen/data_generation_functions.py +876 -0
  17. mergeron/gen/enforcement_stats.py +355 -0
  18. mergeron/gen/upp_tests.py +171 -259
  19. mergeron-2025.739265.0.dist-info/METADATA +115 -0
  20. mergeron-2025.739265.0.dist-info/RECORD +23 -0
  21. {mergeron-2024.738953.1.dist-info → mergeron-2025.739265.0.dist-info}/WHEEL +1 -1
  22. mergeron/License.txt +0 -16
  23. mergeron/core/InCommon RSA Server CA cert chain.pem +0 -68
  24. mergeron/core/excel_helper.py +0 -257
  25. mergeron/core/proportions_tests.py +0 -520
  26. mergeron/ext/__init__.py +0 -5
  27. mergeron/ext/tol_colors.py +0 -851
  28. mergeron/gen/_data_generation_functions_nonpublic.py +0 -623
  29. mergeron/gen/investigations_stats.py +0 -709
  30. mergeron/jinja_LaTex_templates/clrrate_cis_summary_table_template.tex.jinja2 +0 -121
  31. mergeron/jinja_LaTex_templates/ftcinvdata_byhhianddelta_table_template.tex.jinja2 +0 -82
  32. mergeron/jinja_LaTex_templates/ftcinvdata_summary_table_template.tex.jinja2 +0 -57
  33. mergeron/jinja_LaTex_templates/ftcinvdata_summarypaired_table_template.tex.jinja2 +0 -104
  34. mergeron/jinja_LaTex_templates/mergeron.cls +0 -161
  35. mergeron/jinja_LaTex_templates/mergeron_table_collection_template.tex.jinja2 +0 -90
  36. mergeron/jinja_LaTex_templates/setup_tikz_tables.tex.jinja2 +0 -84
  37. mergeron-2024.738953.1.dist-info/METADATA +0 -93
  38. mergeron-2024.738953.1.dist-info/RECORD +0 -30
  39. /mergeron/{core → data}/ftc_invdata.msgpack +0 -0
@@ -0,0 +1,355 @@
1
+ """
2
+ Methods to format and print summary statistics on merger enforcement patterns.
3
+
4
+ """
5
+
6
+ import enum
7
+ from collections.abc import Mapping
8
+
9
+ import numpy as np
10
+ from scipy.interpolate import interp1d # type: ignore
11
+
12
+ from .. import VERSION, ArrayBIGINT # noqa: TID252
13
+ from ..core import ftc_merger_investigations_data as fid # noqa: TID252
14
+ from . import INVResolution
15
+
16
+ __version__ = VERSION
17
+
18
+
19
+ @enum.unique
20
+ class IndustryGroup(enum.StrEnum):
21
+ ALL = "All Markets"
22
+ GRO = "Grocery Markets"
23
+ OIL = "Oil Markets"
24
+ CHM = "Chemical Markets"
25
+ PHM = "Pharmaceuticals Markets"
26
+ HOS = "Hospital Markets"
27
+ EDS = "Electronically-Controlled Devices and Systems Markets"
28
+ BRD = "Branded Consumer Goods Markets"
29
+ OTH = '"Other" Markets'
30
+ IIC = "Industries in Common"
31
+
32
+
33
+ @enum.unique
34
+ class OtherEvidence(enum.StrEnum):
35
+ UR = "Unrestricted on additional evidence"
36
+ HD = "Hot Documents Identified"
37
+ HN = "No Hot Documents Identified"
38
+ HU = "No Evidence on Hot Documents"
39
+ CN = "No Strong Customer Complaints"
40
+ CS = "Strong Customer Complaints"
41
+ CU = "No Evidence on Customer Complaints"
42
+ ED = "Entry Difficult"
43
+ EE = "Entry Easy"
44
+ NE = "No Entry Evidence"
45
+
46
+
47
+ @enum.unique
48
+ class StatsGrpSelector(enum.StrEnum):
49
+ FC = "ByFirmCount"
50
+ HD = "ByHHIandDelta"
51
+ DL = "ByDelta"
52
+ ZN = "ByConcZone"
53
+
54
+
55
+ @enum.unique
56
+ class StatsReturnSelector(enum.StrEnum):
57
+ CNT = "count"
58
+ RPT = "rate, point"
59
+ RIN = "rate, interval"
60
+
61
+
62
+ @enum.unique
63
+ class SortSelector(enum.StrEnum):
64
+ UCH = "unchanged"
65
+ REV = "reversed"
66
+
67
+
68
+ # Parameters and functions to interpolate selected HHI and ΔHHI values
69
+ # recorded in fractions to ranges of values in points on the HHI scale
70
+ HHI_DELTA_KNOTS = np.array(
71
+ [0, 100, 200, 300, 500, 800, 1200, 2500, 5001], dtype=np.int64
72
+ )
73
+ HHI_POST_ZONE_KNOTS = np.array([0, 1800, 2400, 10001], dtype=np.int64)
74
+ hhi_delta_ranger, hhi_zone_post_ranger = (
75
+ interp1d(_f / 1e4, _f, kind="previous", assume_sorted=True)
76
+ for _f in (HHI_DELTA_KNOTS, HHI_POST_ZONE_KNOTS)
77
+ )
78
+
79
+
80
+ HMG_PRESUMPTION_ZONE_MAP = {
81
+ HHI_POST_ZONE_KNOTS[0]: {
82
+ HHI_DELTA_KNOTS[0]: (0, 0, 0),
83
+ HHI_DELTA_KNOTS[1]: (0, 0, 0),
84
+ HHI_DELTA_KNOTS[2]: (0, 0, 0),
85
+ },
86
+ HHI_POST_ZONE_KNOTS[1]: {
87
+ HHI_DELTA_KNOTS[0]: (0, 1, 1),
88
+ HHI_DELTA_KNOTS[1]: (1, 1, 2),
89
+ HHI_DELTA_KNOTS[2]: (1, 1, 2),
90
+ },
91
+ HHI_POST_ZONE_KNOTS[2]: {
92
+ HHI_DELTA_KNOTS[0]: (0, 2, 1),
93
+ HHI_DELTA_KNOTS[1]: (1, 2, 3),
94
+ HHI_DELTA_KNOTS[2]: (2, 2, 4),
95
+ },
96
+ }
97
+
98
+ ZONE_VALS = np.unique(
99
+ np.vstack([
100
+ tuple(HMG_PRESUMPTION_ZONE_MAP[_k].values()) for _k in HMG_PRESUMPTION_ZONE_MAP
101
+ ]),
102
+ axis=0,
103
+ )
104
+
105
+ ZONE_STRINGS = {
106
+ 0: R"Green Zone (Safeharbor)",
107
+ 1: R"Yellow Zone",
108
+ 2: R"Red Zone (SLC Presumption)",
109
+ fid.TTL_KEY: "TOTAL",
110
+ }
111
+ ZONE_DETAIL_STRINGS_HHI = {
112
+ 0: Rf"HHI < {HHI_POST_ZONE_KNOTS[1]} pts.",
113
+ 1: R"HHI ∈ [{}, {}) pts. and ".format(*HHI_POST_ZONE_KNOTS[1:3]),
114
+ 2: Rf"HHI ⩾ {HHI_POST_ZONE_KNOTS[2]} pts. and ",
115
+ }
116
+ ZONE_DETAIL_STRINGS_DELTA = {
117
+ 0: "",
118
+ 1: Rf"ΔHHI < {HHI_DELTA_KNOTS[1]} pts.",
119
+ 2: Rf"ΔHHI ⩾ {HHI_DELTA_KNOTS[1]} pts.}}",
120
+ 3: R"ΔHHI ∈ [{}, {}) pts.".format(*HHI_DELTA_KNOTS[1:3]),
121
+ 4: Rf"ΔHHI ⩾ {HHI_DELTA_KNOTS[2]} pts.",
122
+ }
123
+
124
+
125
+ def enf_cnts_obs_by_group(
126
+ _invdata_array_dict: Mapping[str, Mapping[str, Mapping[str, fid.INVTableData]]],
127
+ _study_period: str,
128
+ _table_ind_grp: IndustryGroup,
129
+ _table_evid_cond: OtherEvidence,
130
+ _stats_group: StatsGrpSelector,
131
+ _enf_spec: INVResolution,
132
+ /,
133
+ ) -> ArrayBIGINT:
134
+ if _stats_group == StatsGrpSelector.HD:
135
+ raise ValueError(
136
+ f"Clearance/enforcement statistics, '{_stats_group}' not valied here."
137
+ )
138
+
139
+ match _stats_group:
140
+ case StatsGrpSelector.FC:
141
+ _cnts_func = enf_cnts_byfirmcount
142
+ _cnts_listing_func = enf_cnts_obs_byfirmcount
143
+ case StatsGrpSelector.DL:
144
+ _cnts_func = enf_cnts_bydelta
145
+ _cnts_listing_func = enf_cnts_obs_byhhianddelta
146
+ case StatsGrpSelector.ZN:
147
+ _cnts_func = enf_cnts_byconczone
148
+ _cnts_listing_func = enf_cnts_obs_byhhianddelta
149
+
150
+ return _cnts_func(
151
+ _cnts_listing_func(
152
+ _invdata_array_dict,
153
+ _study_period,
154
+ _table_ind_grp,
155
+ _table_evid_cond,
156
+ _enf_spec,
157
+ )
158
+ )
159
+
160
+
161
+ def enf_cnts_obs_byfirmcount(
162
+ _data_array_dict: Mapping[str, Mapping[str, Mapping[str, fid.INVTableData]]],
163
+ _data_period: str = "1996-2003",
164
+ _table_ind_group: IndustryGroup = IndustryGroup.ALL,
165
+ _table_evid_cond: OtherEvidence = OtherEvidence.UR,
166
+ _enf_spec: INVResolution = INVResolution.CLRN,
167
+ /,
168
+ ) -> ArrayBIGINT:
169
+ if _data_period not in _data_array_dict:
170
+ raise ValueError(
171
+ f"Invalid value of data period, {f'"{_data_period}"'}."
172
+ f"Must be one of, {tuple(_data_array_dict.keys())!r}."
173
+ )
174
+
175
+ _data_array_dict_sub = _data_array_dict[_data_period][fid.TABLE_TYPES[1]]
176
+
177
+ _table_no = table_no_lku(_data_array_dict_sub, _table_ind_group, _table_evid_cond)
178
+
179
+ _cnts_array = _data_array_dict_sub[_table_no].data_array
180
+
181
+ _ndim_in = 1
182
+ _stats_kept_indxs = []
183
+ match _enf_spec:
184
+ case INVResolution.CLRN:
185
+ _stats_kept_indxs = [-1, -2]
186
+ case INVResolution.ENFT:
187
+ _stats_kept_indxs = [-1, -3]
188
+ case INVResolution.BOTH:
189
+ _stats_kept_indxs = [-1, -3, -2]
190
+
191
+ return np.column_stack([
192
+ _cnts_array[:, :_ndim_in],
193
+ _cnts_array[:, _stats_kept_indxs],
194
+ ])
195
+
196
+
197
+ def enf_cnts_obs_byhhianddelta(
198
+ _data_array_dict: Mapping[str, Mapping[str, Mapping[str, fid.INVTableData]]],
199
+ _data_period: str = "1996-2003",
200
+ _table_ind_group: IndustryGroup = IndustryGroup.ALL,
201
+ _table_evid_cond: OtherEvidence = OtherEvidence.UR,
202
+ _enf_spec: INVResolution = INVResolution.CLRN,
203
+ /,
204
+ ) -> ArrayBIGINT:
205
+ if _data_period not in _data_array_dict:
206
+ raise ValueError(
207
+ f"Invalid value of data period, {f'"{_data_period}"'}."
208
+ f"Must be one of, {tuple(_data_array_dict.keys())!r}."
209
+ )
210
+
211
+ _data_array_dict_sub = _data_array_dict[_data_period][fid.TABLE_TYPES[0]]
212
+
213
+ _table_no = table_no_lku(_data_array_dict_sub, _table_ind_group, _table_evid_cond)
214
+
215
+ _cnts_array = _data_array_dict_sub[_table_no].data_array
216
+
217
+ _ndim_in = 2
218
+ _stats_kept_indxs = []
219
+ match _enf_spec:
220
+ case INVResolution.CLRN:
221
+ _stats_kept_indxs = [-1, -2]
222
+ case INVResolution.ENFT:
223
+ _stats_kept_indxs = [-1, -3]
224
+ case INVResolution.BOTH:
225
+ _stats_kept_indxs = [-1, -3, -2]
226
+
227
+ return np.column_stack([
228
+ _cnts_array[:, :_ndim_in],
229
+ _cnts_array[:, _stats_kept_indxs],
230
+ ])
231
+
232
+
233
+ def table_no_lku(
234
+ _data_array_dict_sub: Mapping[str, fid.INVTableData],
235
+ _table_ind_group: IndustryGroup = IndustryGroup.ALL,
236
+ _table_evid_cond: OtherEvidence = OtherEvidence.UR,
237
+ /,
238
+ ) -> str:
239
+ if _table_ind_group not in (
240
+ _igl := [_data_array_dict_sub[_v].industry_group for _v in _data_array_dict_sub]
241
+ ):
242
+ raise ValueError(
243
+ f"Invalid value for industry group, {f'"{_table_ind_group}"'}."
244
+ f"Must be one of {_igl!r}"
245
+ )
246
+
247
+ _tno = next(
248
+ _t
249
+ for _t in _data_array_dict_sub
250
+ if all((
251
+ _data_array_dict_sub[_t].industry_group == _table_ind_group,
252
+ _data_array_dict_sub[_t].additional_evidence == _table_evid_cond,
253
+ ))
254
+ )
255
+
256
+ return _tno
257
+
258
+
259
+ def enf_cnts_byfirmcount(_cnts_array: ArrayBIGINT, /) -> ArrayBIGINT:
260
+ _ndim_in = 1
261
+ return np.vstack([
262
+ np.concatenate([
263
+ (f,),
264
+ np.einsum("ij->j", _cnts_array[_cnts_array[:, 0] == f][:, _ndim_in:]),
265
+ ])
266
+ for f in np.unique(_cnts_array[:, 0])
267
+ ])
268
+
269
+
270
+ def enf_cnts_bydelta(_cnts_array: ArrayBIGINT, /) -> ArrayBIGINT:
271
+ _ndim_in = 2
272
+ return np.vstack([
273
+ np.concatenate([
274
+ (f,),
275
+ np.einsum("ij->j", _cnts_array[_cnts_array[:, 1] == f][:, _ndim_in:]),
276
+ ])
277
+ for f in HHI_DELTA_KNOTS[:-1]
278
+ ])
279
+
280
+
281
+ def enf_cnts_byconczone(_cnts_array: ArrayBIGINT, /) -> ArrayBIGINT:
282
+ # Prepare to tag clearance stats by presumption zone
283
+ _hhi_zone_post_ranged = hhi_zone_post_ranger(_cnts_array[:, 0] / 1e4)
284
+ _hhi_delta_ranged = hhi_delta_ranger(_cnts_array[:, 1] / 1e4)
285
+
286
+ # Step 1: Tag and agg. from HHI-post and Delta to zone triple
287
+ # NOTE: Although you could just map and not (partially) aggregate in this step,
288
+ # the mapped array is a copy, and is larger without partial aggregation, so
289
+ # aggregation reduces the footprint of this step in memory. Although this point
290
+ # is more relevant for generated than observed data, using the same coding pattern
291
+ # in both cases does make life easier
292
+ _ndim_in = 2
293
+ _nkeys = 3
294
+ _cnts_byhhipostanddelta = -1 * np.ones(
295
+ _nkeys + _cnts_array.shape[1] - _ndim_in, dtype=np.int64
296
+ )
297
+ _cnts_byconczone = -1 * np.ones_like(_cnts_byhhipostanddelta)
298
+ for _hhi_zone_post_lim in HHI_POST_ZONE_KNOTS[:-1]:
299
+ _level_test = _hhi_zone_post_ranged == _hhi_zone_post_lim
300
+
301
+ for _hhi_zone_delta_lim in HHI_DELTA_KNOTS[:3]:
302
+ _delta_test = (
303
+ (_hhi_delta_ranged >= _hhi_zone_delta_lim)
304
+ if _hhi_zone_delta_lim == HHI_DELTA_KNOTS[2]
305
+ else (_hhi_delta_ranged == _hhi_zone_delta_lim)
306
+ )
307
+
308
+ _zone_val = HMG_PRESUMPTION_ZONE_MAP[_hhi_zone_post_lim][
309
+ _hhi_zone_delta_lim
310
+ ]
311
+
312
+ _conc_test = _level_test & _delta_test
313
+
314
+ _cnts_byhhipostanddelta = np.vstack((
315
+ _cnts_byhhipostanddelta,
316
+ np.array(
317
+ (
318
+ *_zone_val,
319
+ *np.einsum("ij->j", _cnts_array[:, _ndim_in:][_conc_test]),
320
+ ),
321
+ dtype=np.int64,
322
+ ),
323
+ ))
324
+ _cnts_byhhipostanddelta = _cnts_byhhipostanddelta[1:]
325
+
326
+ for _zone_val in ZONE_VALS:
327
+ # Logical-and of multiple vectors:
328
+ _hhi_zone_test = (
329
+ 1
330
+ * np.column_stack([
331
+ _cnts_byhhipostanddelta[:, _idx] == _val
332
+ for _idx, _val in enumerate(_zone_val)
333
+ ])
334
+ ).prod(axis=1) == 1
335
+
336
+ _cnts_byconczone = np.vstack((
337
+ _cnts_byconczone,
338
+ np.concatenate(
339
+ (
340
+ _zone_val,
341
+ np.einsum(
342
+ "ij->j", _cnts_byhhipostanddelta[_hhi_zone_test][:, _nkeys:]
343
+ ),
344
+ ),
345
+ dtype=np.int64,
346
+ ),
347
+ ))
348
+
349
+ return _cnts_byconczone[1:]
350
+
351
+
352
+ if __name__ == "__main__":
353
+ print(
354
+ "This module provides methods to aggregate statistics on merger enforcement patterns for reporting."
355
+ )