mergeron 2024.738963.0__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.
- mergeron/__init__.py +26 -6
- mergeron/core/__init__.py +5 -65
- mergeron/core/{damodaran_margin_data.py → empirical_margin_distribution.py} +74 -58
- mergeron/core/ftc_merger_investigations_data.py +142 -93
- mergeron/core/guidelines_boundaries.py +289 -1077
- mergeron/core/guidelines_boundary_functions.py +1128 -0
- mergeron/core/{guidelines_boundaries_specialized_functions.py → guidelines_boundary_functions_extra.py} +76 -42
- mergeron/core/pseudorandom_numbers.py +16 -22
- mergeron/data/__init__.py +3 -0
- mergeron/data/damodaran_margin_data.xls +0 -0
- mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
- mergeron/demo/__init__.py +3 -0
- mergeron/demo/visualize_empirical_margin_distribution.py +86 -0
- mergeron/gen/__init__.py +257 -245
- mergeron/gen/data_generation.py +473 -221
- mergeron/gen/data_generation_functions.py +876 -0
- mergeron/gen/enforcement_stats.py +355 -0
- mergeron/gen/upp_tests.py +159 -259
- mergeron-2025.739265.0.dist-info/METADATA +115 -0
- mergeron-2025.739265.0.dist-info/RECORD +23 -0
- {mergeron-2024.738963.0.dist-info → mergeron-2025.739265.0.dist-info}/WHEEL +1 -1
- mergeron/License.txt +0 -16
- mergeron/core/InCommon RSA Server CA cert chain.pem +0 -68
- mergeron/core/excel_helper.py +0 -259
- mergeron/core/proportions_tests.py +0 -520
- mergeron/ext/__init__.py +0 -5
- mergeron/ext/tol_colors.py +0 -851
- mergeron/gen/_data_generation_functions_nonpublic.py +0 -621
- mergeron/gen/investigations_stats.py +0 -709
- mergeron/jinja_LaTex_templates/clrrate_cis_summary_table_template.tex.jinja2 +0 -121
- mergeron/jinja_LaTex_templates/ftcinvdata_byhhianddelta_table_template.tex.jinja2 +0 -82
- mergeron/jinja_LaTex_templates/ftcinvdata_summary_table_template.tex.jinja2 +0 -57
- mergeron/jinja_LaTex_templates/ftcinvdata_summarypaired_table_template.tex.jinja2 +0 -104
- mergeron/jinja_LaTex_templates/mergeron.cls +0 -161
- mergeron/jinja_LaTex_templates/mergeron_table_collection_template.tex.jinja2 +0 -90
- mergeron/jinja_LaTex_templates/setup_tikz_tables.tex.jinja2 +0 -84
- mergeron-2024.738963.0.dist-info/METADATA +0 -108
- mergeron-2024.738963.0.dist-info/RECORD +0 -30
- /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
|
+
)
|