mergeron 2024.739104.1__py3-none-any.whl → 2024.739105.4__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 +4 -2
- mergeron/core/guidelines_boundaries.py +30 -19
- mergeron/core/guidelines_boundary_functions.py +5 -5
- mergeron/core/guidelines_boundary_functions_extra.py +3 -3
- mergeron/gen/__init__.py +31 -28
- mergeron/gen/data_generation.py +44 -33
- mergeron/gen/data_generation_functions.py +50 -43
- mergeron/gen/enforcement_stats.py +12 -12
- mergeron/gen/upp_tests.py +9 -10
- mergeron-2024.739105.4.dist-info/METADATA +115 -0
- {mergeron-2024.739104.1.dist-info → mergeron-2024.739105.4.dist-info}/RECORD +12 -12
- mergeron-2024.739104.1.dist-info/METADATA +0 -102
- {mergeron-2024.739104.1.dist-info → mergeron-2024.739105.4.dist-info}/WHEEL +0 -0
mergeron/__init__.py
CHANGED
|
@@ -9,7 +9,7 @@ from numpy.typing import NDArray
|
|
|
9
9
|
|
|
10
10
|
_PKG_NAME: str = Path(__file__).parent.stem
|
|
11
11
|
|
|
12
|
-
VERSION = "2024.
|
|
12
|
+
VERSION = "2024.739105.4"
|
|
13
13
|
|
|
14
14
|
__version__ = VERSION
|
|
15
15
|
|
|
@@ -34,9 +34,11 @@ ArrayBoolean: TypeAlias = NDArray[np.bool_]
|
|
|
34
34
|
ArrayDouble: TypeAlias = NDArray[np.double]
|
|
35
35
|
ArrayBIGINT: TypeAlias = NDArray[np.int64]
|
|
36
36
|
|
|
37
|
+
DEFAULT_REC_RATE = 0.85
|
|
38
|
+
|
|
37
39
|
|
|
38
40
|
@enum.unique
|
|
39
|
-
class
|
|
41
|
+
class RECForm(enum.StrEnum):
|
|
40
42
|
"""Recapture rate - derivation methods."""
|
|
41
43
|
|
|
42
44
|
INOUT = "inside-out"
|
|
@@ -13,7 +13,13 @@ import numpy as np
|
|
|
13
13
|
from attrs import Attribute, field, frozen, validators
|
|
14
14
|
from mpmath import mp, mpf # type: ignore
|
|
15
15
|
|
|
16
|
-
from .. import
|
|
16
|
+
from .. import ( # noqa: TID252
|
|
17
|
+
DEFAULT_REC_RATE,
|
|
18
|
+
VERSION,
|
|
19
|
+
ArrayDouble,
|
|
20
|
+
RECForm,
|
|
21
|
+
UPPAggrSelector,
|
|
22
|
+
)
|
|
17
23
|
from . import guidelines_boundary_functions as gbfn
|
|
18
24
|
|
|
19
25
|
__version__ = VERSION
|
|
@@ -86,7 +92,7 @@ class GuidelinesThresholds:
|
|
|
86
92
|
"""
|
|
87
93
|
|
|
88
94
|
def __attrs_post_init__(self, /) -> None:
|
|
89
|
-
# In the 2023
|
|
95
|
+
# In the 2023 Guidelines, the agencies do not define a
|
|
90
96
|
# negative presumption, or safeharbor. Practically speaking,
|
|
91
97
|
# given resource constraints and loss aversion, it is likely
|
|
92
98
|
# that staff only investigates mergers that meet the presumption;
|
|
@@ -128,7 +134,7 @@ class GuidelinesThresholds:
|
|
|
128
134
|
)
|
|
129
135
|
|
|
130
136
|
# imputed_presumption is relevant for 2010 Guidelines
|
|
131
|
-
# merger to
|
|
137
|
+
# merger to symmetry in numbers-equivalent of post-merger HHI
|
|
132
138
|
object.__setattr__(
|
|
133
139
|
self,
|
|
134
140
|
"imputed_presumption",
|
|
@@ -223,14 +229,14 @@ def _divr_value_validator(
|
|
|
223
229
|
|
|
224
230
|
def _rec_spec_validator(
|
|
225
231
|
_instance: DiversionRatioBoundary,
|
|
226
|
-
_attribute: Attribute[
|
|
227
|
-
_value:
|
|
232
|
+
_attribute: Attribute[RECForm],
|
|
233
|
+
_value: RECForm,
|
|
228
234
|
/,
|
|
229
235
|
) -> None:
|
|
230
|
-
if _value ==
|
|
236
|
+
if _value == RECForm.OUTIN and _instance.recapture_rate:
|
|
231
237
|
raise ValueError(
|
|
232
238
|
f"Invalid recapture specification, {_value!r}. "
|
|
233
|
-
"You may consider specifying `mergeron.
|
|
239
|
+
"You may consider specifying `mergeron.RECForm.INOUT` here, and "
|
|
234
240
|
'assigning the default recapture rate as attribute, "recapture_rate" of '
|
|
235
241
|
"this `DiversionRatioBoundarySpec` object."
|
|
236
242
|
)
|
|
@@ -262,27 +268,24 @@ class DiversionRatioBoundary:
|
|
|
262
268
|
)
|
|
263
269
|
|
|
264
270
|
recapture_rate: float = field(
|
|
265
|
-
kw_only=False, default=
|
|
271
|
+
kw_only=False, default=DEFAULT_REC_RATE, validator=validators.instance_of(float)
|
|
266
272
|
)
|
|
267
273
|
|
|
268
|
-
recapture_form:
|
|
274
|
+
recapture_form: RECForm | None = field(
|
|
269
275
|
kw_only=True,
|
|
270
|
-
default=
|
|
271
|
-
validator=(
|
|
272
|
-
validators.instance_of((type(None), RECTypes)),
|
|
273
|
-
_rec_spec_validator,
|
|
274
|
-
),
|
|
276
|
+
default=RECForm.INOUT,
|
|
277
|
+
validator=(validators.instance_of((type(None), RECForm)), _rec_spec_validator),
|
|
275
278
|
)
|
|
276
279
|
"""
|
|
277
280
|
The form of the recapture rate.
|
|
278
281
|
|
|
279
|
-
When :attr:`mergeron.
|
|
282
|
+
When :attr:`mergeron.RECForm.INOUT`, the recapture rate for
|
|
280
283
|
he product having the smaller market-share is assumed to equal the default,
|
|
281
284
|
and the recapture rate for the product with the larger market-share is
|
|
282
285
|
computed assuming MNL demand. Fixed recapture rates are specified as
|
|
283
|
-
:attr:`mergeron.
|
|
286
|
+
:attr:`mergeron.RECForm.FIXED`. (To specify that recapture rates be
|
|
284
287
|
constructed from the generated purchase-probabilities for products in
|
|
285
|
-
the market and for the outside good, specify :attr:`mergeron.
|
|
288
|
+
the market and for the outside good, specify :attr:`mergeron.RECForm.OUTIN`.)
|
|
286
289
|
|
|
287
290
|
The GUPPI boundary is a continuum of diversion ratio boundaries conditional on
|
|
288
291
|
price-cost margins, :math:`d_{ij} = g_i * p_i / (m_j * p_j)`,
|
|
@@ -373,7 +376,11 @@ class DiversionRatioBoundary:
|
|
|
373
376
|
|
|
374
377
|
|
|
375
378
|
def guppi_from_delta(
|
|
376
|
-
_delta_bound: float = 0.01,
|
|
379
|
+
_delta_bound: float = 0.01,
|
|
380
|
+
/,
|
|
381
|
+
*,
|
|
382
|
+
m_star: float = 1.00,
|
|
383
|
+
r_bar: float = DEFAULT_REC_RATE,
|
|
377
384
|
) -> float:
|
|
378
385
|
"""
|
|
379
386
|
Translate ∆HHI bound to GUPPI bound.
|
|
@@ -431,7 +438,11 @@ def critical_share_ratio(
|
|
|
431
438
|
|
|
432
439
|
|
|
433
440
|
def share_from_guppi(
|
|
434
|
-
_guppi_bound: float = 0.065,
|
|
441
|
+
_guppi_bound: float = 0.065,
|
|
442
|
+
/,
|
|
443
|
+
*,
|
|
444
|
+
m_star: float = 1.00,
|
|
445
|
+
r_bar: float = DEFAULT_REC_RATE,
|
|
435
446
|
) -> float:
|
|
436
447
|
"""
|
|
437
448
|
Symmetric-firm share for given GUPPI, margin, and recapture rate.
|
|
@@ -5,7 +5,7 @@ from typing import Any, Literal, TypedDict
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from mpmath import mp, mpf # type: ignore
|
|
7
7
|
|
|
8
|
-
from .. import VERSION, ArrayBIGINT, ArrayDouble # noqa: TID252
|
|
8
|
+
from .. import DEFAULT_REC_RATE, VERSION, ArrayBIGINT, ArrayDouble # noqa: TID252
|
|
9
9
|
|
|
10
10
|
__version__ = VERSION
|
|
11
11
|
|
|
@@ -211,7 +211,7 @@ def hhi_post_contrib_boundary(
|
|
|
211
211
|
|
|
212
212
|
def shrratio_boundary_wtd_avg(
|
|
213
213
|
_delta_star: float = 0.075,
|
|
214
|
-
_r_val: float =
|
|
214
|
+
_r_val: float = DEFAULT_REC_RATE,
|
|
215
215
|
/,
|
|
216
216
|
*,
|
|
217
217
|
agg_method: Literal[
|
|
@@ -420,7 +420,7 @@ def shrratio_boundary_wtd_avg(
|
|
|
420
420
|
|
|
421
421
|
def shrratio_boundary_xact_avg(
|
|
422
422
|
_delta_star: float = 0.075,
|
|
423
|
-
_r_val: float =
|
|
423
|
+
_r_val: float = DEFAULT_REC_RATE,
|
|
424
424
|
/,
|
|
425
425
|
*,
|
|
426
426
|
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
@@ -579,7 +579,7 @@ def shrratio_boundary_xact_avg(
|
|
|
579
579
|
|
|
580
580
|
def shrratio_boundary_min(
|
|
581
581
|
_delta_star: float = 0.075,
|
|
582
|
-
_r_val: float =
|
|
582
|
+
_r_val: float = DEFAULT_REC_RATE,
|
|
583
583
|
/,
|
|
584
584
|
*,
|
|
585
585
|
recapture_form: str = "inside-out",
|
|
@@ -645,7 +645,7 @@ def shrratio_boundary_min(
|
|
|
645
645
|
|
|
646
646
|
|
|
647
647
|
def shrratio_boundary_max(
|
|
648
|
-
_delta_star: float = 0.075, _r_val: float =
|
|
648
|
+
_delta_star: float = 0.075, _r_val: float = DEFAULT_REC_RATE, /, *, prec: int = 10
|
|
649
649
|
) -> GuidelinesBoundary:
|
|
650
650
|
"""
|
|
651
651
|
Share combinations on the minimum GUPPI boundary with symmetric
|
|
@@ -16,7 +16,7 @@ from mpmath import mp, mpf # type: ignore
|
|
|
16
16
|
from scipy.spatial.distance import minkowski as distance_function # type: ignore
|
|
17
17
|
from sympy import lambdify, simplify, solve, symbols # type: ignore
|
|
18
18
|
|
|
19
|
-
from .. import VERSION, ArrayDouble # noqa: TID252
|
|
19
|
+
from .. import DEFAULT_REC_RATE, VERSION, ArrayDouble # noqa: TID252
|
|
20
20
|
from .guidelines_boundary_functions import (
|
|
21
21
|
GuidelinesBoundary,
|
|
22
22
|
_shrratio_boundary_intcpt,
|
|
@@ -105,7 +105,7 @@ def hhi_delta_boundary_qdtr(_dh_val: float = 0.01, /) -> GuidelinesBoundaryCalla
|
|
|
105
105
|
|
|
106
106
|
def shrratio_boundary_qdtr_wtd_avg(
|
|
107
107
|
_delta_star: float = 0.075,
|
|
108
|
-
_r_val: float =
|
|
108
|
+
_r_val: float = DEFAULT_REC_RATE,
|
|
109
109
|
/,
|
|
110
110
|
*,
|
|
111
111
|
weighting: Literal["own-share", "cross-product-share"] | None = "own-share",
|
|
@@ -224,7 +224,7 @@ def shrratio_boundary_qdtr_wtd_avg(
|
|
|
224
224
|
|
|
225
225
|
def shrratio_boundary_distance(
|
|
226
226
|
_delta_star: float = 0.075,
|
|
227
|
-
_r_val: float =
|
|
227
|
+
_r_val: float = DEFAULT_REC_RATE,
|
|
228
228
|
/,
|
|
229
229
|
*,
|
|
230
230
|
agg_method: Literal["arithmetic mean", "distance"] = "arithmetic mean",
|
mergeron/gen/__init__.py
CHANGED
|
@@ -15,13 +15,14 @@ from attrs import Attribute, cmp_using, field, frozen, validators
|
|
|
15
15
|
from numpy.random import SeedSequence
|
|
16
16
|
|
|
17
17
|
from .. import ( # noqa: TID252
|
|
18
|
+
DEFAULT_REC_RATE,
|
|
18
19
|
VERSION,
|
|
19
20
|
ArrayBIGINT,
|
|
20
21
|
ArrayBoolean,
|
|
21
22
|
ArrayDouble,
|
|
22
23
|
ArrayFloat,
|
|
23
24
|
ArrayINT,
|
|
24
|
-
|
|
25
|
+
RECForm,
|
|
25
26
|
UPPAggrSelector,
|
|
26
27
|
)
|
|
27
28
|
from ..core.pseudorandom_numbers import DIST_PARMS_DEFAULT # noqa: TID252
|
|
@@ -57,7 +58,7 @@ class PriceSpec(tuple[bool, str | None], enum.ReprEnum):
|
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
@enum.unique
|
|
60
|
-
class
|
|
61
|
+
class SHRDistribution(enum.StrEnum):
|
|
61
62
|
"""Market share distributions."""
|
|
62
63
|
|
|
63
64
|
UNI = "Uniform"
|
|
@@ -98,26 +99,26 @@ class ShareSpec:
|
|
|
98
99
|
A key feature of market-share specification in this package is that
|
|
99
100
|
the draws represent markets with multiple different firm-counts.
|
|
100
101
|
Firm-counts are unspecified if the share distribution is
|
|
101
|
-
:attr:`mergeron.
|
|
102
|
+
:attr:`mergeron.SHRDistribution.UNI`, for Dirichlet-distributed market-shares,
|
|
102
103
|
the default specification is that firm-counts vary between
|
|
103
104
|
2 and 7 firms with each value equally likely.
|
|
104
105
|
|
|
105
106
|
Notes
|
|
106
107
|
-----
|
|
107
|
-
If :attr:`mergeron.gen.ShareSpec.dist_type`:code:` == `:attr:`mergeron.gen.
|
|
108
|
+
If :attr:`mergeron.gen.ShareSpec.dist_type`:code:` == `:attr:`mergeron.gen.SHRDistribution.UNI`,
|
|
108
109
|
then it is infeasible that
|
|
109
|
-
:attr:`mergeron.gen.ShareSpec.recapture_form`:code:` == `:attr:`mergeron.
|
|
110
|
+
:attr:`mergeron.gen.ShareSpec.recapture_form`:code:` == `:attr:`mergeron.RECForm.OUTIN`.
|
|
110
111
|
In other words, if firm-counts are unspecified, the recapture rate cannot be
|
|
111
112
|
estimated using outside good choice probabilities.
|
|
112
113
|
|
|
113
114
|
For a sample with explicit firm counts, market shares must
|
|
114
115
|
be specified as having a supported Dirichlet distribution
|
|
115
|
-
(see :class:`mergeron.gen.
|
|
116
|
+
(see :class:`mergeron.gen.SHRDistribution`).
|
|
116
117
|
|
|
117
118
|
"""
|
|
118
119
|
|
|
119
|
-
dist_type:
|
|
120
|
-
"""See :class:`
|
|
120
|
+
dist_type: SHRDistribution
|
|
121
|
+
"""See :class:`SHRDistribution`"""
|
|
121
122
|
|
|
122
123
|
dist_parms: ArrayDouble | None = field(
|
|
123
124
|
default=None, eq=cmp_using(eq=np.array_equal)
|
|
@@ -143,32 +144,32 @@ class ShareSpec:
|
|
|
143
144
|
|
|
144
145
|
@firm_counts_weights.validator
|
|
145
146
|
def _check_fcw(_i: ShareSpec, _a: Attribute[ArrayDouble], _v: ArrayDouble) -> None:
|
|
146
|
-
if _v is not None and _i.dist_type ==
|
|
147
|
+
if _v is not None and _i.dist_type == SHRDistribution.UNI:
|
|
147
148
|
raise ValueError(
|
|
148
149
|
"Generated data for markets with specified firm-counts or "
|
|
149
150
|
"varying firm counts are not feasible for market shares "
|
|
150
151
|
"with Uniform distribution. Consider revising the "
|
|
151
|
-
r"distribution type to {
|
|
152
|
+
r"distribution type to {SHRDistribution.DIR_FLAT}, which gives "
|
|
152
153
|
"uniformly distributed draws on the :math:`n+1` simplex "
|
|
153
154
|
"for firm-count, :math:`n`."
|
|
154
155
|
)
|
|
155
156
|
|
|
156
|
-
recapture_form:
|
|
157
|
-
"""See :class:`mergeron.
|
|
157
|
+
recapture_form: RECForm = field(default=RECForm.INOUT)
|
|
158
|
+
"""See :class:`mergeron.RECForm`"""
|
|
158
159
|
|
|
159
160
|
@recapture_form.validator
|
|
160
|
-
def _check_rf(_i: ShareSpec, _a: Attribute[
|
|
161
|
-
if _v ==
|
|
161
|
+
def _check_rf(_i: ShareSpec, _a: Attribute[RECForm], _v: RECForm) -> None:
|
|
162
|
+
if _v == RECForm.OUTIN and _i.dist_type == SHRDistribution.UNI:
|
|
162
163
|
raise ValueError(
|
|
163
164
|
"Market share specification requires estimation of recapture rate from "
|
|
164
165
|
"generated data. Either delete recapture rate specification or set it to None."
|
|
165
166
|
)
|
|
166
167
|
|
|
167
|
-
recapture_rate: float | None = field(default=
|
|
168
|
-
"""A value between 0 and 1
|
|
168
|
+
recapture_rate: float | None = field(default=DEFAULT_REC_RATE)
|
|
169
|
+
"""A value between 0 and 1.
|
|
169
170
|
|
|
170
171
|
:code:`None` if market share specification requires direct generation of
|
|
171
|
-
outside good choice probabilities (:attr:`mergeron.
|
|
172
|
+
outside good choice probabilities (:attr:`mergeron.RECForm.OUTIN`).
|
|
172
173
|
|
|
173
174
|
The recapture rate is usually calibrated to the numbers-equivalent of the
|
|
174
175
|
HHI threshold for the presumtion of harm from unilateral competitive effects
|
|
@@ -191,7 +192,7 @@ class ShareSpec:
|
|
|
191
192
|
def _check_rr(_i: ShareSpec, _a: Attribute[float], _v: float) -> None:
|
|
192
193
|
if _v and not (0 < _v <= 1):
|
|
193
194
|
raise ValueError("Recapture rate must lie in the interval, [0, 1).")
|
|
194
|
-
elif _v is None and _i.recapture_form !=
|
|
195
|
+
elif _v is None and _i.recapture_form != RECForm.OUTIN:
|
|
195
196
|
raise ValueError(
|
|
196
197
|
f"Recapture specification, {_i.recapture_form!r} requires that "
|
|
197
198
|
"the market sample specification inclues a recapture rate in the "
|
|
@@ -200,7 +201,7 @@ class ShareSpec:
|
|
|
200
201
|
|
|
201
202
|
|
|
202
203
|
@enum.unique
|
|
203
|
-
class
|
|
204
|
+
class PCMDistribution(enum.StrEnum):
|
|
204
205
|
"""Margin distributions."""
|
|
205
206
|
|
|
206
207
|
UNI = "Uniform"
|
|
@@ -210,7 +211,7 @@ class PCMDistributions(enum.StrEnum):
|
|
|
210
211
|
|
|
211
212
|
|
|
212
213
|
@enum.unique
|
|
213
|
-
class
|
|
214
|
+
class FM2Constraint(enum.StrEnum):
|
|
214
215
|
"""Firm 2 margins - derivation methods."""
|
|
215
216
|
|
|
216
217
|
IID = "i.i.d"
|
|
@@ -234,8 +235,8 @@ class PCMSpec:
|
|
|
234
235
|
|
|
235
236
|
"""
|
|
236
237
|
|
|
237
|
-
dist_type:
|
|
238
|
-
"""See :class:`
|
|
238
|
+
dist_type: PCMDistribution = field(kw_only=False, default=PCMDistribution.UNI)
|
|
239
|
+
"""See :class:`PCMDistribution`"""
|
|
239
240
|
|
|
240
241
|
dist_parms: ArrayDouble | None = field(kw_only=False, default=None)
|
|
241
242
|
"""Parameter specification for tailoring PCM distribution
|
|
@@ -260,9 +261,9 @@ class PCMSpec:
|
|
|
260
261
|
"are not valid with margin distribution, {_dist_type_pcm!r}"
|
|
261
262
|
)
|
|
262
263
|
elif (
|
|
263
|
-
_i.dist_type ==
|
|
264
|
+
_i.dist_type == PCMDistribution.BETA and len(_v) != len(("a", "b"))
|
|
264
265
|
) or (
|
|
265
|
-
_i.dist_type ==
|
|
266
|
+
_i.dist_type == PCMDistribution.BETA_BND
|
|
266
267
|
and len(_v) != len(("mu", "sigma", "max", "min"))
|
|
267
268
|
):
|
|
268
269
|
raise ValueError(
|
|
@@ -270,18 +271,20 @@ class PCMSpec:
|
|
|
270
271
|
f'for PCM with distribution, "{_i.dist_type}" is incorrect.'
|
|
271
272
|
)
|
|
272
273
|
|
|
273
|
-
elif _i.dist_type ==
|
|
274
|
+
elif _i.dist_type == PCMDistribution.EMPR and _v is not None:
|
|
274
275
|
raise ValueError(
|
|
275
276
|
f"Empirical distribution does not require additional parameters; "
|
|
276
277
|
f'"given value, {_v!r} is ignored."'
|
|
277
278
|
)
|
|
278
279
|
|
|
279
|
-
firm2_pcm_constraint:
|
|
280
|
-
|
|
280
|
+
firm2_pcm_constraint: FM2Constraint = field(
|
|
281
|
+
kw_only=False, default=FM2Constraint.IID
|
|
282
|
+
)
|
|
283
|
+
"""See :class:`FM2Constraint`"""
|
|
281
284
|
|
|
282
285
|
|
|
283
286
|
@enum.unique
|
|
284
|
-
class
|
|
287
|
+
class SSZConstant(float, enum.ReprEnum):
|
|
285
288
|
"""
|
|
286
289
|
Scale factors to offset sample size reduction.
|
|
287
290
|
|
mergeron/gen/data_generation.py
CHANGED
|
@@ -13,18 +13,18 @@ from attrs import Attribute, define, field, validators
|
|
|
13
13
|
from joblib import Parallel, cpu_count, delayed # type: ignore
|
|
14
14
|
from numpy.random import SeedSequence
|
|
15
15
|
|
|
16
|
-
from .. import VERSION,
|
|
16
|
+
from .. import DEFAULT_REC_RATE, VERSION, RECForm # noqa: TID252 # noqa
|
|
17
17
|
from ..core import guidelines_boundaries as gbl # noqa: TID252
|
|
18
18
|
from ..core.guidelines_boundaries import HMGThresholds # noqa: TID252
|
|
19
19
|
from . import (
|
|
20
|
-
|
|
20
|
+
FM2Constraint,
|
|
21
21
|
MarketDataSample,
|
|
22
|
-
|
|
22
|
+
PCMDistribution,
|
|
23
23
|
PCMSpec,
|
|
24
24
|
PriceSpec,
|
|
25
25
|
ShareSpec,
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
SHRDistribution,
|
|
27
|
+
SSZConstant,
|
|
28
28
|
UPPTestRegime,
|
|
29
29
|
UPPTestsCounts,
|
|
30
30
|
)
|
|
@@ -40,7 +40,7 @@ __version__ = VERSION
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
class SamplingFunctionKWArgs(TypedDict, total=False):
|
|
43
|
-
"Keyword arguments of
|
|
43
|
+
"Keyword arguments of sampling methods defined below"
|
|
44
44
|
|
|
45
45
|
sample_size: int
|
|
46
46
|
"""number of draws to generate"""
|
|
@@ -73,26 +73,28 @@ class MarketSample:
|
|
|
73
73
|
|
|
74
74
|
share_spec: ShareSpec = field(
|
|
75
75
|
kw_only=True,
|
|
76
|
-
default=ShareSpec(
|
|
76
|
+
default=ShareSpec(
|
|
77
|
+
SHRDistribution.UNI, None, None, RECForm.INOUT, DEFAULT_REC_RATE
|
|
78
|
+
),
|
|
77
79
|
validator=validators.instance_of(ShareSpec),
|
|
78
80
|
)
|
|
79
81
|
"""Market-share specification, see :class:`ShareSpec`"""
|
|
80
82
|
|
|
81
83
|
pcm_spec: PCMSpec = field(
|
|
82
|
-
kw_only=True, default=PCMSpec(
|
|
84
|
+
kw_only=True, default=PCMSpec(PCMDistribution.UNI, None, FM2Constraint.IID)
|
|
83
85
|
)
|
|
84
86
|
"""Margin specification, see :class:`PCMSpec`"""
|
|
85
87
|
|
|
86
88
|
@pcm_spec.validator
|
|
87
89
|
def _check_pcm(self, _a: Attribute[PCMSpec], _v: PCMSpec, /) -> None:
|
|
88
90
|
if (
|
|
89
|
-
self.share_spec.recapture_form ==
|
|
90
|
-
and _v.firm2_pcm_constraint ==
|
|
91
|
+
self.share_spec.recapture_form == RECForm.FIXED
|
|
92
|
+
and _v.firm2_pcm_constraint == FM2Constraint.MNL
|
|
91
93
|
):
|
|
92
94
|
raise ValueError(
|
|
93
95
|
f'Specification of "recapture_form", "{self.share_spec.recapture_form}" '
|
|
94
96
|
"requires Firm 2 margin must have property, "
|
|
95
|
-
f'"{
|
|
97
|
+
f'"{FM2Constraint.IID}" or "{FM2Constraint.SYM}".'
|
|
96
98
|
)
|
|
97
99
|
|
|
98
100
|
price_spec: PriceSpec = field(
|
|
@@ -100,12 +102,12 @@ class MarketSample:
|
|
|
100
102
|
)
|
|
101
103
|
"""Price specification, see :class:`PriceSpec`"""
|
|
102
104
|
|
|
103
|
-
hsr_filing_test_type:
|
|
105
|
+
hsr_filing_test_type: SSZConstant = field(
|
|
104
106
|
kw_only=True,
|
|
105
|
-
default=
|
|
106
|
-
validator=validators.instance_of(
|
|
107
|
+
default=SSZConstant.ONE,
|
|
108
|
+
validator=validators.instance_of(SSZConstant),
|
|
107
109
|
)
|
|
108
|
-
"""Method for modeling HSR filing threholds, see :class:`
|
|
110
|
+
"""Method for modeling HSR filing threholds, see :class:`SSZConstant`"""
|
|
109
111
|
|
|
110
112
|
data: MarketDataSample = field(default=None)
|
|
111
113
|
|
|
@@ -124,7 +126,7 @@ class MarketSample:
|
|
|
124
126
|
"""
|
|
125
127
|
Generate share, diversion ratio, price, and margin data for MarketSpec.
|
|
126
128
|
|
|
127
|
-
see :attr:`SamplingFunctionKWArgs` for description of parameters
|
|
129
|
+
see :attr:`SamplingFunctionKWArgs` for description of keyord parameters
|
|
128
130
|
|
|
129
131
|
Returns
|
|
130
132
|
-------
|
|
@@ -149,8 +151,8 @@ class MarketSample:
|
|
|
149
151
|
_shr_sample_size = 1.0 * sample_size
|
|
150
152
|
# Scale up sample size to offset discards based on specified criteria
|
|
151
153
|
_shr_sample_size *= _hsr_filing_test_type
|
|
152
|
-
if _dist_firm2_pcm ==
|
|
153
|
-
_shr_sample_size *=
|
|
154
|
+
if _dist_firm2_pcm == FM2Constraint.MNL:
|
|
155
|
+
_shr_sample_size *= SSZConstant.MNL_DEP
|
|
154
156
|
_shr_sample_size = int(_shr_sample_size)
|
|
155
157
|
|
|
156
158
|
# Generate share data
|
|
@@ -195,7 +197,7 @@ class MarketSample:
|
|
|
195
197
|
|
|
196
198
|
_mnl_test_rows = _mnl_test_rows * _hsr_filing_test
|
|
197
199
|
_s_size = sample_size # originally-specified sample size
|
|
198
|
-
if _dist_firm2_pcm ==
|
|
200
|
+
if _dist_firm2_pcm == FM2Constraint.MNL:
|
|
199
201
|
_mktshr_array = _mktshr_array[_mnl_test_rows][:_s_size]
|
|
200
202
|
_pcm_array = _pcm_array[_mnl_test_rows][:_s_size]
|
|
201
203
|
_price_array = _price_array[_mnl_test_rows][:_s_size]
|
|
@@ -240,13 +242,21 @@ class MarketSample:
|
|
|
240
242
|
self,
|
|
241
243
|
/,
|
|
242
244
|
*,
|
|
243
|
-
sample_size: int
|
|
244
|
-
seed_seq_list:
|
|
245
|
+
sample_size: int,
|
|
246
|
+
seed_seq_list: Sequence[SeedSequence],
|
|
245
247
|
nthreads: int,
|
|
246
|
-
save_data_to_file: SaveData
|
|
247
|
-
saved_array_name_suffix: str
|
|
248
|
+
save_data_to_file: SaveData,
|
|
249
|
+
saved_array_name_suffix: str,
|
|
248
250
|
) -> None:
|
|
249
|
-
"""
|
|
251
|
+
"""Populate :attr:`data` with generated data
|
|
252
|
+
|
|
253
|
+
see :attr:`SamplingFunctionKWArgs` for description of keyord parameters
|
|
254
|
+
|
|
255
|
+
Returns
|
|
256
|
+
-------
|
|
257
|
+
None
|
|
258
|
+
|
|
259
|
+
"""
|
|
250
260
|
|
|
251
261
|
self.data = self.gen_market_sample(
|
|
252
262
|
sample_size=sample_size, seed_seq_list=seed_seq_list, nthreads=nthreads
|
|
@@ -389,7 +399,8 @@ class MarketSample:
|
|
|
389
399
|
|
|
390
400
|
Returns
|
|
391
401
|
-------
|
|
392
|
-
Arrays of
|
|
402
|
+
Arrays of enforcement counts or clearance counts by firm count,
|
|
403
|
+
ΔHHI and concentration zone
|
|
393
404
|
|
|
394
405
|
"""
|
|
395
406
|
_sample_sz = sample_size
|
|
@@ -400,7 +411,7 @@ class MarketSample:
|
|
|
400
411
|
_thread_count = cpu_count()
|
|
401
412
|
|
|
402
413
|
if (
|
|
403
|
-
self.share_spec.recapture_form !=
|
|
414
|
+
self.share_spec.recapture_form != RECForm.OUTIN
|
|
404
415
|
and self.share_spec.recapture_rate != _enf_parm_vec.rec
|
|
405
416
|
):
|
|
406
417
|
raise ValueError(
|
|
@@ -458,12 +469,12 @@ class MarketSample:
|
|
|
458
469
|
/,
|
|
459
470
|
*,
|
|
460
471
|
sample_size: int = 10**6,
|
|
461
|
-
seed_seq_list:
|
|
462
|
-
nthreads: int,
|
|
472
|
+
seed_seq_list: Sequence[SeedSequence] | None = None,
|
|
473
|
+
nthreads: int = 16,
|
|
463
474
|
save_data_to_file: SaveData = False,
|
|
464
475
|
saved_array_name_suffix: str = "",
|
|
465
476
|
) -> None:
|
|
466
|
-
"""
|
|
477
|
+
"""Populate :attr:`enf_counts` etimated test counts.
|
|
467
478
|
|
|
468
479
|
Parameters
|
|
469
480
|
----------
|
|
@@ -478,16 +489,16 @@ class MarketSample:
|
|
|
478
489
|
merging firms
|
|
479
490
|
|
|
480
491
|
sample_size
|
|
481
|
-
|
|
492
|
+
Number of draws to simulate
|
|
482
493
|
|
|
483
494
|
seed_seq_list
|
|
484
|
-
List of
|
|
495
|
+
List of seed sequences, to assure independent samples in each thread
|
|
485
496
|
|
|
486
497
|
nthreads
|
|
487
|
-
Number of
|
|
498
|
+
Number of parallel processes to use
|
|
488
499
|
|
|
489
500
|
save_data_to_file
|
|
490
|
-
|
|
501
|
+
Whether to save data to an HDF5 file, and where to save it
|
|
491
502
|
|
|
492
503
|
saved_array_name_suffix
|
|
493
504
|
Suffix to add to the array names in the HDF5 file
|
|
@@ -11,7 +11,13 @@ import numpy as np
|
|
|
11
11
|
from attrs import evolve
|
|
12
12
|
from numpy.random import SeedSequence
|
|
13
13
|
|
|
14
|
-
from .. import
|
|
14
|
+
from .. import ( # noqa: TID252
|
|
15
|
+
DEFAULT_REC_RATE,
|
|
16
|
+
VERSION,
|
|
17
|
+
ArrayBIGINT,
|
|
18
|
+
ArrayDouble,
|
|
19
|
+
RECForm,
|
|
20
|
+
)
|
|
15
21
|
from ..core.damodaran_margin_data import mgn_data_resampler # noqa: TID252
|
|
16
22
|
from ..core.pseudorandom_numbers import ( # noqa: TID252
|
|
17
23
|
DIST_PARMS_DEFAULT,
|
|
@@ -21,17 +27,17 @@ from ..core.pseudorandom_numbers import ( # noqa: TID252
|
|
|
21
27
|
from . import (
|
|
22
28
|
EMPTY_ARRAY_DEFAULT,
|
|
23
29
|
FCOUNT_WTS_DEFAULT,
|
|
24
|
-
|
|
30
|
+
FM2Constraint,
|
|
25
31
|
MarginDataSample,
|
|
26
|
-
|
|
32
|
+
PCMDistribution,
|
|
27
33
|
PCMSpec,
|
|
28
34
|
PriceDataSample,
|
|
29
35
|
PriceSpec,
|
|
30
36
|
SeedSequenceData,
|
|
31
37
|
ShareDataSample,
|
|
32
38
|
ShareSpec,
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
SHRDistribution,
|
|
40
|
+
SSZConstant,
|
|
35
41
|
)
|
|
36
42
|
|
|
37
43
|
__version__ = VERSION
|
|
@@ -71,7 +77,7 @@ def gen_share_data(
|
|
|
71
77
|
|
|
72
78
|
_ssz = _sample_size
|
|
73
79
|
|
|
74
|
-
if _dist_type_mktshr ==
|
|
80
|
+
if _dist_type_mktshr == SHRDistribution.UNI:
|
|
75
81
|
_mkt_share_sample = gen_market_shares_uniform(
|
|
76
82
|
_ssz, _dist_parms_mktshr, _mktshr_rng_seed_seq, _nthreads
|
|
77
83
|
)
|
|
@@ -100,8 +106,8 @@ def gen_share_data(
|
|
|
100
106
|
|
|
101
107
|
# If recapture_form == "inside-out", recalculate _aggregate_purchase_prob
|
|
102
108
|
_frmshr_array = _mkt_share_sample.mktshr_array[:, :2]
|
|
103
|
-
_r_bar = _share_spec.recapture_rate or
|
|
104
|
-
if _recapture_form ==
|
|
109
|
+
_r_bar = _share_spec.recapture_rate or DEFAULT_REC_RATE
|
|
110
|
+
if _recapture_form == RECForm.INOUT:
|
|
105
111
|
_mkt_share_sample = ShareDataSample(
|
|
106
112
|
_mkt_share_sample.mktshr_array,
|
|
107
113
|
_mkt_share_sample.fcounts,
|
|
@@ -177,8 +183,8 @@ def gen_market_shares_uniform(
|
|
|
177
183
|
|
|
178
184
|
def gen_market_shares_dirichlet_multimarket(
|
|
179
185
|
_s_size: int = 10**6,
|
|
180
|
-
_recapture_form:
|
|
181
|
-
_dist_type_dir:
|
|
186
|
+
_recapture_form: RECForm = RECForm.INOUT,
|
|
187
|
+
_dist_type_dir: SHRDistribution = SHRDistribution.DIR_FLAT,
|
|
182
188
|
_dist_parms_dir: ArrayDouble | None = None,
|
|
183
189
|
_firm_count_wts: ArrayDouble | None = None,
|
|
184
190
|
_fcount_rng_seed_seq: SeedSequence | None = None,
|
|
@@ -226,9 +232,7 @@ def gen_market_shares_dirichlet_multimarket(
|
|
|
226
232
|
FCOUNT_WTS_DEFAULT if _firm_count_wts is None else _firm_count_wts
|
|
227
233
|
)
|
|
228
234
|
|
|
229
|
-
_min_choice_wt =
|
|
230
|
-
0.03 if _dist_type_dir == SHRDistributions.DIR_FLAT_CONSTR else 0.00
|
|
231
|
-
)
|
|
235
|
+
_min_choice_wt = 0.03 if _dist_type_dir == SHRDistribution.DIR_FLAT_CONSTR else 0.00
|
|
232
236
|
_fcount_keys, _choice_wts = zip(
|
|
233
237
|
*(
|
|
234
238
|
_f
|
|
@@ -246,10 +250,10 @@ def gen_market_shares_dirichlet_multimarket(
|
|
|
246
250
|
_dir_alphas_full = (
|
|
247
251
|
[1.0] * _fc_max if _dist_parms_dir is None else _dist_parms_dir[:_fc_max]
|
|
248
252
|
)
|
|
249
|
-
if _dist_type_dir ==
|
|
253
|
+
if _dist_type_dir == SHRDistribution.DIR_ASYM:
|
|
250
254
|
_dir_alphas_full = [2.0] * 6 + [1.5] * 5 + [1.25] * min(7, _fc_max)
|
|
251
255
|
|
|
252
|
-
if _dist_type_dir ==
|
|
256
|
+
if _dist_type_dir == SHRDistribution.DIR_COND:
|
|
253
257
|
|
|
254
258
|
def _gen_dir_alphas(_fcv: int) -> ArrayDouble:
|
|
255
259
|
_dat = [2.5] * 2
|
|
@@ -322,7 +326,7 @@ def gen_market_shares_dirichlet_multimarket(
|
|
|
322
326
|
def gen_market_shares_dirichlet(
|
|
323
327
|
_dir_alphas: ArrayDouble,
|
|
324
328
|
_s_size: int = 10**6,
|
|
325
|
-
_recapture_form:
|
|
329
|
+
_recapture_form: RECForm = RECForm.INOUT,
|
|
326
330
|
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
327
331
|
_nthreads: int = 16,
|
|
328
332
|
/,
|
|
@@ -338,8 +342,8 @@ def gen_market_shares_dirichlet(
|
|
|
338
342
|
sample size to be drawn
|
|
339
343
|
|
|
340
344
|
_recapture_form
|
|
341
|
-
r_1 = r_2 if
|
|
342
|
-
|
|
345
|
+
r_1 = r_2 if RECForm.FIXED, otherwise MNL-consistent. If
|
|
346
|
+
RECForm.OUTIN; the number of columns in the output share array
|
|
343
347
|
is len(_dir_alphas) - 1.
|
|
344
348
|
|
|
345
349
|
_mktshr_rng_seed_seq
|
|
@@ -357,7 +361,7 @@ def gen_market_shares_dirichlet(
|
|
|
357
361
|
if not isinstance(_dir_alphas, np.ndarray):
|
|
358
362
|
_dir_alphas = np.array(_dir_alphas)
|
|
359
363
|
|
|
360
|
-
if _recapture_form ==
|
|
364
|
+
if _recapture_form == RECForm.OUTIN:
|
|
361
365
|
_dir_alphas = np.concatenate((_dir_alphas, _dir_alphas[-1:]))
|
|
362
366
|
|
|
363
367
|
_mktshr_seed_seq_ch = (
|
|
@@ -391,7 +395,7 @@ def gen_market_shares_dirichlet(
|
|
|
391
395
|
|
|
392
396
|
# If recapture_form == 'inside_out', further calculations downstream
|
|
393
397
|
_aggregate_purchase_prob = np.nan * np.empty((_s_size, 1))
|
|
394
|
-
if _recapture_form ==
|
|
398
|
+
if _recapture_form == RECForm.OUTIN:
|
|
395
399
|
_aggregate_purchase_prob = 1 - _mktshr_array[:, [-1]]
|
|
396
400
|
_mktshr_array = _mktshr_array[:, :-1] / _aggregate_purchase_prob
|
|
397
401
|
|
|
@@ -404,7 +408,7 @@ def gen_market_shares_dirichlet(
|
|
|
404
408
|
|
|
405
409
|
|
|
406
410
|
def gen_divr_array(
|
|
407
|
-
_recapture_form:
|
|
411
|
+
_recapture_form: RECForm,
|
|
408
412
|
_recapture_rate: float | None,
|
|
409
413
|
_frmshr_array: ArrayDouble,
|
|
410
414
|
_aggregate_purchase_prob: ArrayDouble = EMPTY_ARRAY_DEFAULT,
|
|
@@ -413,7 +417,7 @@ def gen_divr_array(
|
|
|
413
417
|
"""
|
|
414
418
|
Given merging-firm shares and related parameters, return diverion ratios.
|
|
415
419
|
|
|
416
|
-
If recapture is specified as :attr:`mergeron.
|
|
420
|
+
If recapture is specified as :attr:`mergeron.RECForm.OUTIN`, then the
|
|
417
421
|
choice-probability for the outside good must be supplied.
|
|
418
422
|
|
|
419
423
|
Parameters
|
|
@@ -445,7 +449,7 @@ def gen_divr_array(
|
|
|
445
449
|
"""
|
|
446
450
|
|
|
447
451
|
_divr_array: ArrayDouble
|
|
448
|
-
if _recapture_form ==
|
|
452
|
+
if _recapture_form == RECForm.FIXED:
|
|
449
453
|
_divr_array = _recapture_rate * _frmshr_array[:, ::-1] / (1 - _frmshr_array) # type: ignore
|
|
450
454
|
|
|
451
455
|
else:
|
|
@@ -475,7 +479,7 @@ def gen_margin_price_data(
|
|
|
475
479
|
_aggregate_purchase_prob: ArrayDouble,
|
|
476
480
|
_pcm_spec: PCMSpec,
|
|
477
481
|
_price_spec: PriceSpec,
|
|
478
|
-
_hsr_filing_test_type:
|
|
482
|
+
_hsr_filing_test_type: SSZConstant,
|
|
479
483
|
_pcm_rng_seed_seq: SeedSequence,
|
|
480
484
|
_pr_rng_seed_seq: SeedSequence | None = None,
|
|
481
485
|
_nthreads: int = 16,
|
|
@@ -505,7 +509,7 @@ def gen_margin_price_data(
|
|
|
505
509
|
|
|
506
510
|
_hsr_filing_test_type
|
|
507
511
|
Enum specifying restriction, if any, to impose on market data sample
|
|
508
|
-
to model HSR filing requirements; see :class:`mergeron.gen.
|
|
512
|
+
to model HSR filing requirements; see :class:`mergeron.gen.SSZConstant`.
|
|
509
513
|
|
|
510
514
|
_pcm_rng_seed_seq
|
|
511
515
|
Seed sequence for generating margin data.
|
|
@@ -552,7 +556,7 @@ def gen_margin_price_data(
|
|
|
552
556
|
# generate the margin data
|
|
553
557
|
# generate price and margin data
|
|
554
558
|
_frmshr_array_plus = np.hstack((_frmshr_array, _nth_firm_share))
|
|
555
|
-
_pcm_spec_here = evolve(_pcm_spec, firm2_pcm_constraint=
|
|
559
|
+
_pcm_spec_here = evolve(_pcm_spec, firm2_pcm_constraint=FM2Constraint.IID)
|
|
556
560
|
_margin_data = _gen_margin_data(
|
|
557
561
|
_frmshr_array_plus,
|
|
558
562
|
np.ones_like(_frmshr_array_plus, np.float64),
|
|
@@ -569,7 +573,7 @@ def gen_margin_price_data(
|
|
|
569
573
|
_price_array_here = 1 / (1 - _pcm_array)
|
|
570
574
|
_price_array = _price_array_here[:, :2]
|
|
571
575
|
_nth_firm_price = _price_array_here[:, [-1]]
|
|
572
|
-
if _pcm_spec.firm2_pcm_constraint ==
|
|
576
|
+
if _pcm_spec.firm2_pcm_constraint == FM2Constraint.MNL:
|
|
573
577
|
# Generate i.i.d. PCMs then take PCM0 and construct PCM1
|
|
574
578
|
# Regenerate MNL test
|
|
575
579
|
_purchase_prob_array = _aggregate_purchase_prob * _frmshr_array
|
|
@@ -616,7 +620,7 @@ def gen_margin_price_data(
|
|
|
616
620
|
_test_rev_ratio, _test_rev_ratio_inv = 10, 1 / 10
|
|
617
621
|
|
|
618
622
|
match _hsr_filing_test_type:
|
|
619
|
-
case
|
|
623
|
+
case SSZConstant.HSR_TEN:
|
|
620
624
|
# See, https://www.ftc.gov/enforcement/premerger-notification-program/
|
|
621
625
|
# -> Procedures For Submitting Post-Consummation Filings
|
|
622
626
|
# -> Key Elements to Determine Whether a Post Consummation Filing is Required
|
|
@@ -627,7 +631,7 @@ def gen_margin_price_data(
|
|
|
627
631
|
_rev_ratio = (_rev_array.min(axis=1) / _rev_array.max(axis=1)).round(4)
|
|
628
632
|
_hsr_filing_test = _rev_ratio >= _test_rev_ratio_inv
|
|
629
633
|
# del _rev_array, _rev_ratio
|
|
630
|
-
case
|
|
634
|
+
case SSZConstant.HSR_NTH:
|
|
631
635
|
# To get around the 10-to-1 ratio restriction, specify that the nth firm test:
|
|
632
636
|
# if the smaller merging firm matches or exceeds the n-th firm in size, and
|
|
633
637
|
# the larger merging firm has at least 10 times the size of the nth firm,
|
|
@@ -642,12 +646,15 @@ def gen_margin_price_data(
|
|
|
642
646
|
dtype=np.int64,
|
|
643
647
|
)
|
|
644
648
|
== _rev_ratio_to_nth.shape[1]
|
|
645
|
-
)
|
|
649
|
+
)
|
|
646
650
|
|
|
647
651
|
# del _nth_firm_rev, _rev_ratio_to_nth
|
|
648
652
|
case _:
|
|
649
653
|
# Otherwise, all draws meet the filing test
|
|
650
654
|
_hsr_filing_test = np.ones(len(_frmshr_array), dtype=bool)
|
|
655
|
+
_hsr_filing_test = _hsr_filing_test | (
|
|
656
|
+
_frmshr_array.min(axis=1) >= _test_rev_ratio_inv
|
|
657
|
+
)
|
|
651
658
|
|
|
652
659
|
return _margin_data, PriceDataSample(_price_array, _hsr_filing_test)
|
|
653
660
|
|
|
@@ -669,28 +676,28 @@ def _gen_margin_data(
|
|
|
669
676
|
_dist_type: Literal["Beta", "Uniform"]
|
|
670
677
|
_pcm_array = (
|
|
671
678
|
np.empty((len(_frmshr_array), 1), dtype=np.float64)
|
|
672
|
-
if _pcm_spec.firm2_pcm_constraint ==
|
|
679
|
+
if _pcm_spec.firm2_pcm_constraint == FM2Constraint.SYM
|
|
673
680
|
else np.empty_like(_frmshr_array, dtype=np.float64)
|
|
674
681
|
)
|
|
675
682
|
|
|
676
683
|
_beta_min, _beta_max = [None] * 2 # placeholder
|
|
677
|
-
if _dist_type_pcm ==
|
|
684
|
+
if _dist_type_pcm == PCMDistribution.EMPR:
|
|
678
685
|
_pcm_array = mgn_data_resampler(
|
|
679
686
|
_pcm_array.shape, # type: ignore
|
|
680
687
|
seed_sequence=_pcm_rng_seed_seq,
|
|
681
688
|
)
|
|
682
689
|
else:
|
|
683
|
-
_dist_type = "Uniform" if _dist_type_pcm ==
|
|
684
|
-
if _dist_type_pcm ==
|
|
690
|
+
_dist_type = "Uniform" if _dist_type_pcm == PCMDistribution.UNI else "Beta"
|
|
691
|
+
if _dist_type_pcm == PCMDistribution.BETA:
|
|
685
692
|
if _dist_parms_pcm is None:
|
|
686
693
|
_dist_parms_pcm = np.ones(2, np.float64)
|
|
687
694
|
|
|
688
|
-
elif _dist_type_pcm ==
|
|
695
|
+
elif _dist_type_pcm == PCMDistribution.BETA_BND: # Bounded beta
|
|
689
696
|
if _dist_parms_pcm is None:
|
|
690
697
|
_dist_parms_pcm = np.array([0, 1, 0, 1], np.float64)
|
|
691
698
|
_dist_parms = beta_located_bound(_dist_parms_pcm)
|
|
692
699
|
else:
|
|
693
|
-
# _dist_type_pcm ==
|
|
700
|
+
# _dist_type_pcm == PCMDistribution.UNI
|
|
694
701
|
_dist_parms = (
|
|
695
702
|
DIST_PARMS_DEFAULT if _dist_parms_pcm is None else _dist_parms_pcm
|
|
696
703
|
)
|
|
@@ -705,20 +712,20 @@ def _gen_margin_data(
|
|
|
705
712
|
_pcm_rng.fill()
|
|
706
713
|
del _pcm_rng
|
|
707
714
|
|
|
708
|
-
if _dist_type_pcm ==
|
|
715
|
+
if _dist_type_pcm == PCMDistribution.BETA_BND:
|
|
709
716
|
_beta_min, _beta_max = _dist_parms_pcm[2:]
|
|
710
717
|
_pcm_array = (_beta_max - _beta_min) * _pcm_array + _beta_min
|
|
711
718
|
del _beta_min, _beta_max
|
|
712
719
|
|
|
713
|
-
if _dist_firm2_pcm ==
|
|
720
|
+
if _dist_firm2_pcm == FM2Constraint.SYM:
|
|
714
721
|
_pcm_array = np.column_stack((_pcm_array,) * _frmshr_array.shape[1])
|
|
715
|
-
if _dist_firm2_pcm ==
|
|
722
|
+
if _dist_firm2_pcm == FM2Constraint.MNL:
|
|
716
723
|
# Impose FOCs from profit-maximization with MNL demand
|
|
717
|
-
if _dist_type_pcm ==
|
|
724
|
+
if _dist_type_pcm == PCMDistribution.EMPR:
|
|
718
725
|
print(
|
|
719
726
|
"NOTE: Estimated Firm 2 parameters will not be consistent with "
|
|
720
727
|
"the empirical distribution of margins in the source data. For "
|
|
721
|
-
"consistency, respecify pcm_spec.firm2_pcm_constraint =
|
|
728
|
+
"consistency, respecify pcm_spec.firm2_pcm_constraint = FM2Constraint.IID."
|
|
722
729
|
)
|
|
723
730
|
_purchase_prob_array = _aggregate_purchase_prob * _frmshr_array
|
|
724
731
|
|
|
@@ -800,7 +807,7 @@ def beta_located_bound(_dist_parms: ArrayDouble, /) -> ArrayDouble:
|
|
|
800
807
|
|
|
801
808
|
def parse_seed_seq_list(
|
|
802
809
|
_sseq_list: Sequence[SeedSequence] | None,
|
|
803
|
-
_mktshr_dist_type:
|
|
810
|
+
_mktshr_dist_type: SHRDistribution,
|
|
804
811
|
_price_spec: PriceSpec,
|
|
805
812
|
/,
|
|
806
813
|
) -> SeedSequenceData:
|
|
@@ -841,7 +848,7 @@ def parse_seed_seq_list(
|
|
|
841
848
|
else (None, SeedSequence(pool_size=8))
|
|
842
849
|
)
|
|
843
850
|
|
|
844
|
-
_seed_count = 2 if _mktshr_dist_type ==
|
|
851
|
+
_seed_count = 2 if _mktshr_dist_type == SHRDistribution.UNI else 3
|
|
845
852
|
|
|
846
853
|
if _sseq_list:
|
|
847
854
|
if len(_sseq_list) < _seed_count:
|
|
@@ -33,7 +33,7 @@ __version__ = VERSION
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
@enum.unique
|
|
36
|
-
class
|
|
36
|
+
class IndustryGroup(enum.StrEnum):
|
|
37
37
|
ALL = "All Markets"
|
|
38
38
|
GRO = "Grocery Markets"
|
|
39
39
|
OIL = "Oil Markets"
|
|
@@ -47,7 +47,7 @@ class INDGRPConstants(enum.StrEnum):
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
@enum.unique
|
|
50
|
-
class
|
|
50
|
+
class OtherEvidence(enum.StrEnum):
|
|
51
51
|
HD = "Hot Documents Identified"
|
|
52
52
|
CC = "Strong Customer Complaints"
|
|
53
53
|
NE = "No Entry Evidence"
|
|
@@ -236,8 +236,8 @@ ZONE_VALS = np.unique(
|
|
|
236
236
|
def enf_stats_output(
|
|
237
237
|
_data_array_dict: fid.INVData,
|
|
238
238
|
_data_period: str = "1996-2003",
|
|
239
|
-
_table_ind_group:
|
|
240
|
-
_table_evid_cond:
|
|
239
|
+
_table_ind_group: IndustryGroup = IndustryGroup.ALL,
|
|
240
|
+
_table_evid_cond: OtherEvidence = OtherEvidence.UR,
|
|
241
241
|
_stats_group: StatsGrpSelector = StatsGrpSelector.FC,
|
|
242
242
|
_enf_spec: INVResolution = INVResolution.CLRN,
|
|
243
243
|
/,
|
|
@@ -293,8 +293,8 @@ def enf_stats_output(
|
|
|
293
293
|
def enf_stats_listing_by_group(
|
|
294
294
|
_invdata_array_dict: Mapping[str, Mapping[str, Mapping[str, fid.INVTableData]]],
|
|
295
295
|
_study_period: str,
|
|
296
|
-
_table_ind_grp:
|
|
297
|
-
_table_evid_cond:
|
|
296
|
+
_table_ind_grp: IndustryGroup,
|
|
297
|
+
_table_evid_cond: OtherEvidence,
|
|
298
298
|
_stats_group: StatsGrpSelector,
|
|
299
299
|
_enf_spec: INVResolution,
|
|
300
300
|
/,
|
|
@@ -329,8 +329,8 @@ def enf_stats_listing_by_group(
|
|
|
329
329
|
def enf_cnts_listing_byfirmcount(
|
|
330
330
|
_data_array_dict: Mapping[str, Mapping[str, Mapping[str, fid.INVTableData]]],
|
|
331
331
|
_data_period: str = "1996-2003",
|
|
332
|
-
_table_ind_group:
|
|
333
|
-
_table_evid_cond:
|
|
332
|
+
_table_ind_group: IndustryGroup = IndustryGroup.ALL,
|
|
333
|
+
_table_evid_cond: OtherEvidence = OtherEvidence.UR,
|
|
334
334
|
_enf_spec: INVResolution = INVResolution.CLRN,
|
|
335
335
|
/,
|
|
336
336
|
) -> ArrayBIGINT:
|
|
@@ -365,8 +365,8 @@ def enf_cnts_listing_byfirmcount(
|
|
|
365
365
|
def enf_cnts_listing_byhhianddelta(
|
|
366
366
|
_data_array_dict: Mapping[str, Mapping[str, Mapping[str, fid.INVTableData]]],
|
|
367
367
|
_data_period: str = "1996-2003",
|
|
368
|
-
_table_ind_group:
|
|
369
|
-
_table_evid_cond:
|
|
368
|
+
_table_ind_group: IndustryGroup = IndustryGroup.ALL,
|
|
369
|
+
_table_evid_cond: OtherEvidence = OtherEvidence.UR,
|
|
370
370
|
_enf_spec: INVResolution = INVResolution.CLRN,
|
|
371
371
|
/,
|
|
372
372
|
) -> ArrayBIGINT:
|
|
@@ -400,8 +400,8 @@ def enf_cnts_listing_byhhianddelta(
|
|
|
400
400
|
|
|
401
401
|
def table_no_lku(
|
|
402
402
|
_data_array_dict_sub: Mapping[str, fid.INVTableData],
|
|
403
|
-
_table_ind_group:
|
|
404
|
-
_table_evid_cond:
|
|
403
|
+
_table_ind_group: IndustryGroup = IndustryGroup.ALL,
|
|
404
|
+
_table_evid_cond: OtherEvidence = OtherEvidence.UR,
|
|
405
405
|
/,
|
|
406
406
|
) -> str:
|
|
407
407
|
if _table_ind_group not in (
|
mergeron/gen/upp_tests.py
CHANGED
|
@@ -20,7 +20,7 @@ from .. import ( # noqa
|
|
|
20
20
|
ArrayDouble,
|
|
21
21
|
ArrayFloat,
|
|
22
22
|
ArrayINT,
|
|
23
|
-
|
|
23
|
+
RECForm,
|
|
24
24
|
UPPAggrSelector,
|
|
25
25
|
)
|
|
26
26
|
from ..core import guidelines_boundaries as gbl # noqa: TID252
|
|
@@ -94,24 +94,23 @@ def enf_cnts(
|
|
|
94
94
|
|
|
95
95
|
_stats_rowlen = 6
|
|
96
96
|
# Clearance/enforcement counts --- by firm count
|
|
97
|
-
|
|
98
|
-
if
|
|
99
|
-
|
|
100
|
-
_max_firm_count = max(_firm_counts_list)
|
|
97
|
+
_firmcounts_list = np.unique(_fcounts)
|
|
98
|
+
if _firmcounts_list is not None and np.all(_firmcounts_list >= 0):
|
|
99
|
+
_max_firmcount = max(_firmcounts_list)
|
|
101
100
|
|
|
102
101
|
_enf_cnts_sim_byfirmcount_array = -1 * np.ones(_stats_rowlen, np.int64)
|
|
103
|
-
for
|
|
104
|
-
|
|
102
|
+
for _firmcount in np.arange(2, _max_firmcount + 1):
|
|
103
|
+
_firmcount_test = _fcounts == _firmcount
|
|
105
104
|
|
|
106
105
|
_enf_cnts_sim_byfirmcount_array = np.vstack((
|
|
107
106
|
_enf_cnts_sim_byfirmcount_array,
|
|
108
107
|
np.array([
|
|
109
|
-
|
|
110
|
-
np.einsum("ij->", 1 *
|
|
108
|
+
_firmcount,
|
|
109
|
+
np.einsum("ij->", 1 * _firmcount_test),
|
|
111
110
|
*[
|
|
112
111
|
np.einsum(
|
|
113
112
|
"ij->",
|
|
114
|
-
1 * (
|
|
113
|
+
1 * (_firmcount_test & getattr(_upp_test_arrays, _f)),
|
|
115
114
|
)
|
|
116
115
|
for _f in _upp_test_arrays.__dataclass_fields__
|
|
117
116
|
],
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: mergeron
|
|
3
|
+
Version: 2024.739105.4
|
|
4
|
+
Summary: Merger Policy Analysis using Python
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: merger policy analysis,merger guidelines,merger screening,policy presumptions,concentration standards,upward pricing pressure,GUPPI
|
|
7
|
+
Author: Murthy Kambhampaty
|
|
8
|
+
Author-email: smk@capeconomics.com
|
|
9
|
+
Requires-Python: >=3.12,<4.0
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
|
+
Requires-Dist: aenum (>=3.1.15,<4.0.0)
|
|
22
|
+
Requires-Dist: attrs (>=23.2)
|
|
23
|
+
Requires-Dist: bs4 (>=0.0.1)
|
|
24
|
+
Requires-Dist: certifi (>=2023.11.17)
|
|
25
|
+
Requires-Dist: google-re2 (>=1.1)
|
|
26
|
+
Requires-Dist: jinja2 (>=3.1)
|
|
27
|
+
Requires-Dist: joblib (>=1.3)
|
|
28
|
+
Requires-Dist: matplotlib (>=3.8)
|
|
29
|
+
Requires-Dist: mpmath (>=1.3)
|
|
30
|
+
Requires-Dist: msgpack (>=1.0)
|
|
31
|
+
Requires-Dist: msgpack-numpy (>=0.4)
|
|
32
|
+
Requires-Dist: numpy (>=1.26,<2)
|
|
33
|
+
Requires-Dist: scipy (>=1.12)
|
|
34
|
+
Requires-Dist: sympy (>=1.12)
|
|
35
|
+
Requires-Dist: tables (>=3.8)
|
|
36
|
+
Requires-Dist: types-beautifulsoup4 (>=4.11.2)
|
|
37
|
+
Requires-Dist: urllib3 (>=2.2.2,<3.0.0)
|
|
38
|
+
Requires-Dist: xlrd (>=2.0.1,<3.0.0)
|
|
39
|
+
Requires-Dist: xlsxwriter (>=3.1)
|
|
40
|
+
Description-Content-Type: text/x-rst
|
|
41
|
+
|
|
42
|
+
mergeron: Merger Policy Analysis using Python
|
|
43
|
+
=============================================
|
|
44
|
+
|
|
45
|
+
Analyze the sets of mergers conforming to concentration and diversion ratio bounds. Analyze intrinsic enforcement rates, and intrinsic clearance rates, under concentration, diversion ratio, GUPPI, CMCR, and IPR bounds using generated data with specified distributions of market shares, price-cost margins, firm counts, and prices, optionally imposing restrictions implied by statutory filing thresholds and/or Bertrand-Nash oligopoly with MNL demand. Download and analyze merger investigations data published by the U.S. Federal Trade Commission in various reports on extended merger investigations (Second Requests) during 1996 to 2011.
|
|
46
|
+
|
|
47
|
+
Here, enforcement rates derived with merger enforcement as being exogenous to firm conduct are defined as intrinsic enforcement rates, and similarly intrinsic clearance rates. Depending on the merger enforcement regime, or merger control regime, intrinsic enforcement rates may also not be the complement of intrinsic clearance rates, i.e, it is not necessarily true that the intrinsic clearance rate estimate for a given enforcement regime is 1 minus the intrinsic enforcement rate. In contrast, observed enforcement rates reflect the deterrent effects of merger enforcement on firm conduct as well as the effects of merger screening on the level of enforcement; and, by definition, the observed clearance rate is 1 minus the observed enforcement rate.
|
|
48
|
+
|
|
49
|
+
Introduction
|
|
50
|
+
------------
|
|
51
|
+
|
|
52
|
+
Module :code:`.core.guidelines_boundaries` includes classes for specifying concentration bounds (:code:`.core.guidelines_boundaries.ConcentrationBoundary`) and diversion-ratio bounds (:code:`.core.guidelines_boundaries.DiversionRatioBoundary`), with automatic generation of boundary (as an array of share-pairs) and area. This module also includes a function for generating plots of concentration and diversion-ratio boundaries, and functions for mapping GUPPI standards to concentration (ΔHHI) standards, and vice-versa.
|
|
53
|
+
|
|
54
|
+
Module :code:`.gen.data_generation` includes the :code:`.gen.data_generation.MarketSample` which provides for a rich specification of shares and diversion ratios (:code:`.gen.data_generation.MarketSample.share_spec`), margins (:code:`.gen.data_generation.MarketSample.pcm_spec`, prices (:code:`.gen.data_generation.MarketSample.price_spec`), and HSR filing requirements (:code:`.gen.data_generation.MarketSample.hsr_filing_test_type`), and with methods for, (i) generating sample data (:code:`.gen.data_generation.MarketSample.generate_sample`), and (ii) estimating enforcement or clearance rates under specified enforcement regimes given a method of aggregating diversion ratio or GUPPI estimates for the firms in a merger (:code:`.gen.data_generation.MarketSample.estimate_enf_counts`). While the latter populate the properties, :code:`.gen.data_generation.MarketSample.data`
|
|
55
|
+
and :code:`.gen.data_generation.MarketSample.enf_counts`, respectively, the underlying methods for generating standalone :code:`MarketDataSample` and :code:`UPPTestCounts` objects are included in the class definition, with helper functions defined in the modules, :code:`.gen.data_generation_functions` and :code:`.gen.upp_tests`. Notably, market shares are generated for a sample of markets with firm-count distributed as specified in :code:`.gen.data_generation.MarketSample.share_spec.firm_count_weights`, with defaults as discussed below (also see, :code:`.gen.ShareSpec.firm_count_weights`.
|
|
56
|
+
|
|
57
|
+
By default, merging-firm shares are drawn with uniform distribution over the space :math:`s_1 + s_2 \leqslant 1` for an unspecified number of firms. Alternatively, shares may be drawn from the Dirichlet distribution, with specified shape parameters (see property `dist_type` of :code:`.gen.data_generation.MarketSample.share_spec`, of type, :code:`.gen.SHRDistribution`). When drawing shares from the Dirichlet distribution, the user specifies the `firm_count_weights` property of :code:`.gen.data_generation.MarketSample.share_spec`, as a vector of weights specifying the frequency distribution over sequential firm counts, e.g., :code:`[133, 184, 134, 52, 32, 10, 12, 4, 3]` to specify shares drawn from Dirichlet distributions with 2 to 10 pre-merger firms distributed as in data for FTC merger investigations during 1996--2003 (See, for example, Table 4.1 of `FTC, Horizontal Merger Investigations Data, Fiscal Years 1996--2003 (Revised: August 31, 2004) <https://www.ftc.gov/sites/default/files/documents/reports/horizontal-merger-investigation-data-fiscal-years-1996-2003/040831horizmergersdata96-03.pdf>`_). If the property `firm_count_weights` is not explicitly assigned a value when defining :code:`.gen.data_generation.MarketSample.share_spec`, the default values is used, which results in a sample of markets with 2 to 7 firms with relative frequency in inverse proportion to firm-count, with 2-firm markets being 6 times as likely to be drawn as 7-firm markets.
|
|
58
|
+
|
|
59
|
+
Recapture rates can be specified as, "proportional", "inside-out", "outside-in" (see :code:`.RECForm`). The "inside-out" specification (assigning :code:`.RECForm.INOUT` to the `recapture_form` property of :code:`.gen.data_generation.MarketSample.share_spec`) results in recapture ratios consistent with merging-firms' in-market shares and a default recapture rate. The "outside-in" specification (assigning :code:`.RECForm.INOUT` to the `recapture_form` property of :code:`.gen.data_generation.MarketSample.share_spec`) yields diversion ratios from purchase probabilities drawn at random for :math:`N+1` goods, from which are derived market shares and recapture rates for the :math:`N` goods in the putative market (see, :code:`.gen.ShareSpec`). The "outside-in" specification is invalid when the distribution of markets over firm-count is unspecified, i.e., when the property `dist_type` of :code:`.gen.data_generation.MarketSample.share_spec` is assigned :code:`.gen.ShareDistributions.UNI`, raising a :code:`ValueError` exception. The "proportional" form (`recapture_form` = :code:`.RECForm.FIXED`) is often used in the literature, as an approximation to the "inside-out" calibration. See, for example, Coate (2011).
|
|
60
|
+
|
|
61
|
+
Price-cost-margins may be specified as having uniform distribution, Beta distribution (including a bounded Beta distribution with specified mean and variance), or an empirical distribution (see, :code:`.gen.PCMSpec`). The empirical margin distribution is based on resampling margin data published by Prof. Damodaran of NYU Stern School of Business (see Notes), using an estimated Gaussian KDE. The second merging firm's margin (per the property `firm2_pcm_constraint` of :code:`.gen.data_generation.MarketSample.pcm_spec`) may be specified as symmetric, i.i.d., or subject to equilibrium conditions for (profit-maximization in) Bertrand-Nash oligopoly with MNL demand (:code:`.gen.FM2Constraint`).
|
|
62
|
+
|
|
63
|
+
Prices may be specified as symmetric or asymmetric, and in the latter case, the direction of correlation between merging firm prices, if any, can also be specified (see, :code:`.gen.PriceSpec`). Prices may also be defined by imposing cost symmetry on firms in the sample, with fixed unit marginal costs normalized to 1 unit, such that price equal :math:`1 / (1 - \pmb{m})`, where :math:`\pmb{m}` represents the array of margins for firms in the sample.
|
|
64
|
+
|
|
65
|
+
The market sample may be restricted to mergers meeting the HSR filing requirement under two alternative approaches: in the one, the smaller of the two merging firms meets the lower HSR size threshold ($10 million, as adjusted) and the larger of the two merging firms meets the size test if it's share is no less than 10 times the share of the smaller firm. In the other, the :math:`n`-th firm's size is maintained as $10 million, as adjusted (see, :code:`.gen.SSZConstant`), and a merger meets the HSR filing test if either, (a.) the smaller merging firm is no smaller than the n-th firm and the larger merging firm is at 10-times as large as the n-th firm, or (b.) the smaller merging firm's market share is in excess of 10%; in effect this version of the test maintains that if the smaller merging firm's market share exceeds 10%, the value of the transaction exceeds $200 million, as adjusted, and the size-of-person test is eliminated (see, FTC (2008, p. 12); the above are simplifications of the statutory HSR filing requirements). The second assumption avoids the unfortunate assumption in the first that, within the resulting sample, the larger merging firm be at least 10 times as large as the smaller merging firm, as a consequence of the full definition of the HSR filing requirement.
|
|
66
|
+
|
|
67
|
+
The full specification of a market sample is given in a :code:`.gen.data_generation.MarketSample` object, including the above parameters. Data are drawn by invoking :code:`.gen.data_generation.MarketSample.generate_sample` which adds a :code:`data` property of class, :code:`.gen.MarketDataSample`. Enforcement or clearance counts are computed by invoking :code:`.gen.data_generation.MarketSample.estimate_enf_counts`, which adds an :code:`enf_counts` property of class :code:`.gen.UPPTestsCounts`. For fast, parallel generation of enforcement or clearance counts over large market data samples that ordinarily would exceed available limits on machine memory, the user can invoke the method :code:`.gen.data_generation.MarketSample.estimate_enf_counts` on a :code:`.gen.data_generation.MarketSample` object without first invoking :code:`.gen.data_generation.MarketSample.generate_sample`. Note, however, that this strategy does not retain the market sample in memory in the interests of conserving memory and maintaining high performance (the user can specify that the market sample and enforcement statistics be stored to permanent storage; when saving to current PCIe NVMe storage, the performance penalty is slight, but can be considerable if saving to SATA storage).
|
|
68
|
+
|
|
69
|
+
Enforcement statistics based on FTC investigations data and test data are printed to screen or rendered to LaTex files (for processing into publication-quality tables) using methods provided in :code:`.gen.enforcement_stats`.
|
|
70
|
+
|
|
71
|
+
Programs demonstrating the use of this package are included in the sub-package, :code:`.demo`.
|
|
72
|
+
|
|
73
|
+
This package includes a class, :code:`.core.pseudorandom_numbers.MulithreadedRNG` for generating random numbers with selected continuous distribution over specified parameters, and with CPU multithreading on machines with multiple virtual, logical, or physical CPU cores. This class is an adaptation from the documentation of the :code:`numpy` package, from the discussion on `multithreaded random-number generation <https://numpy.org/doc/stable/reference/random/multithreading.html>_`; the version included here permits selection of the distribution with pre-tests to catch and inform on common errors. To access these directly:
|
|
74
|
+
|
|
75
|
+
.. code-block:: python
|
|
76
|
+
|
|
77
|
+
import mergeron.core.pseudorandom_numbers as prng
|
|
78
|
+
|
|
79
|
+
Documentation for this package is in the form of the API Reference. Documentation for individual functions and classes is accessible within a python shell. For example:
|
|
80
|
+
|
|
81
|
+
.. code-block:: python
|
|
82
|
+
|
|
83
|
+
import mergeron.core.market_sample as market_sample
|
|
84
|
+
|
|
85
|
+
help(market_sample.MarketSample)
|
|
86
|
+
|
|
87
|
+
.. rubric:: References
|
|
88
|
+
|
|
89
|
+
.. _coate2011:
|
|
90
|
+
|
|
91
|
+
Coate, M. B. (2011). Benchmarking the upward pricing pressure model with Federal Trade
|
|
92
|
+
Commission evidence. Journal of Competition Law & Economics, 7(4), 825--846. URL: https://doi.org/10.1093/joclec/nhr014.
|
|
93
|
+
|
|
94
|
+
.. _ftc_premerger_guide2:
|
|
95
|
+
|
|
96
|
+
FTC Premerger Notification Office. “To File or Not to File: When You Must File a Premerger Notification Report Form”. 2008 (September, revised). URL: https://www.ftc.gov/sites/default/files/attachments/premerger-introductory-guides/guide2.pdf
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
.. image:: https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json
|
|
100
|
+
:alt: Poetry
|
|
101
|
+
:target: https://python-poetry.org/
|
|
102
|
+
|
|
103
|
+
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
|
|
104
|
+
:alt: Ruff
|
|
105
|
+
:target: https://github.com/astral-sh/ruff
|
|
106
|
+
|
|
107
|
+
.. image:: https://www.mypy-lang.org/static/mypy_badge.svg
|
|
108
|
+
:alt: Checked with mypy
|
|
109
|
+
:target: https://mypy-lang.org/
|
|
110
|
+
|
|
111
|
+
.. image:: https://img.shields.io/badge/License-MIT-yellow.svg
|
|
112
|
+
:alt: License: MIT
|
|
113
|
+
:target: https://opensource.org/licenses/MIT
|
|
114
|
+
|
|
115
|
+
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
mergeron/License.txt,sha256=7iX-y0EyjkbVJKJLS4ZKzuuE1wd0lryfsD_IytLG8lQ,1246
|
|
2
|
-
mergeron/__init__.py,sha256=
|
|
2
|
+
mergeron/__init__.py,sha256=1Sk0at6M9B-U0jssF0eVp_PPvAfcR_vwkglbRZA782M,1479
|
|
3
3
|
mergeron/core/__init__.py,sha256=KtjBlZOl7jwBCAUhrTJB9PdrN39YLYytNiSUSM_gRmA,62
|
|
4
4
|
mergeron/core/damodaran_margin_data.py,sha256=rMrgN1Qtw572a0ftY97OOj4otq8ldlLrcOi-bcE-org,8554
|
|
5
5
|
mergeron/core/ftc_merger_investigations_data.py,sha256=qGAjjXEyqwS1PKKxvJGsSkr0sfI--4oyLss9I1qCNR4,28247
|
|
6
|
-
mergeron/core/guidelines_boundaries.py,sha256=
|
|
7
|
-
mergeron/core/guidelines_boundary_functions.py,sha256=
|
|
8
|
-
mergeron/core/guidelines_boundary_functions_extra.py,sha256=
|
|
6
|
+
mergeron/core/guidelines_boundaries.py,sha256=sEvIIaOvWl6tMDYeZCIr8EsBioXOn9RSXKyKlmxnH-k,15610
|
|
7
|
+
mergeron/core/guidelines_boundary_functions.py,sha256=GGn5mwBWmxkqcat4Ya0D-J6-7ujosgCCK3eJ9RFWASI,29749
|
|
8
|
+
mergeron/core/guidelines_boundary_functions_extra.py,sha256=HDwwKZDWlrj3Tw-I0gHm0TCSDcIyb9jDfwbuDvK55B8,11322
|
|
9
9
|
mergeron/core/pseudorandom_numbers.py,sha256=cJEWDTfy9CUTzR_di6Fm1Vl1Le6xWoU8wFHbYVMEuLI,9225
|
|
10
10
|
mergeron/data/__init__.py,sha256=KtjBlZOl7jwBCAUhrTJB9PdrN39YLYytNiSUSM_gRmA,62
|
|
11
11
|
mergeron/data/damodaran_margin_data.xls,sha256=Qggl1p5nkOMJI8YUXhkwXQRz-OhRSqBTzz57N0JQyYA,79360
|
|
@@ -20,12 +20,12 @@ mergeron/data/jinja2_LaTeX_templates/mergeron_table_collection_template.tex.jinj
|
|
|
20
20
|
mergeron/data/jinja2_LaTeX_templates/setup_tikz_tables.tex,sha256=1hw3RINDtBrh9ZEToMIiNFIu9rozcPwRly69-5O_0UQ,3207
|
|
21
21
|
mergeron/demo/__init__.py,sha256=KtjBlZOl7jwBCAUhrTJB9PdrN39YLYytNiSUSM_gRmA,62
|
|
22
22
|
mergeron/demo/visualize_empirical_margin_distribution.py,sha256=v1xFJumBX2Ooye82kSSgly-_GpFVkYSDqBwM__rcmZY,2363
|
|
23
|
-
mergeron/gen/__init__.py,sha256=
|
|
24
|
-
mergeron/gen/data_generation.py,sha256=
|
|
25
|
-
mergeron/gen/data_generation_functions.py,sha256=
|
|
26
|
-
mergeron/gen/enforcement_stats.py,sha256=
|
|
27
|
-
mergeron/gen/upp_tests.py,sha256=
|
|
23
|
+
mergeron/gen/__init__.py,sha256=0rfcWpKDhYE_jNsw6xKTGFJqgNtfJ-5JFxHS89CIEuI,16575
|
|
24
|
+
mergeron/gen/data_generation.py,sha256=ZwcVoAfqGTwVBL7PRil_A9kZU8DQK0eCHtsBFA1QElA,16773
|
|
25
|
+
mergeron/gen/data_generation_functions.py,sha256=bP3E0IPXINRc8s0dUxS_Wqo1byVzheZLX811A17WNbU,28571
|
|
26
|
+
mergeron/gen/enforcement_stats.py,sha256=4YQYOeU3dqrOLejhK4chGZMO9ZoID9ZiJZ1V95eSboQ,27370
|
|
27
|
+
mergeron/gen/upp_tests.py,sha256=yzEwWK1bVfjtBYMwXnL5uEWWRiR0_9y0wmjNMB-O3rU,12589
|
|
28
28
|
mergeron/py.typed,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
29
|
-
mergeron-2024.
|
|
30
|
-
mergeron-2024.
|
|
31
|
-
mergeron-2024.
|
|
29
|
+
mergeron-2024.739105.4.dist-info/METADATA,sha256=IuhsMhd4tzkwBVvcCIXo93l1FKZtlss3HSnK4u4Ymcw,13940
|
|
30
|
+
mergeron-2024.739105.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
31
|
+
mergeron-2024.739105.4.dist-info/RECORD,,
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: mergeron
|
|
3
|
-
Version: 2024.739104.1
|
|
4
|
-
Summary: Merger Policy Analysis using Python
|
|
5
|
-
License: MIT
|
|
6
|
-
Keywords: merger policy analysis,merger guidelines,merger screening,policy presumptions,concentration standards,upward pricing pressure,GUPPI
|
|
7
|
-
Author: Murthy Kambhampaty
|
|
8
|
-
Author-email: smk@capeconomics.com
|
|
9
|
-
Requires-Python: >=3.12,<4.0
|
|
10
|
-
Classifier: Development Status :: 4 - Beta
|
|
11
|
-
Classifier: Environment :: Console
|
|
12
|
-
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
-
Classifier: Intended Audience :: Science/Research
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Classifier: Operating System :: OS Independent
|
|
16
|
-
Classifier: Programming Language :: Python
|
|
17
|
-
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
-
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
|
-
Requires-Dist: aenum (>=3.1.15,<4.0.0)
|
|
22
|
-
Requires-Dist: attrs (>=23.2)
|
|
23
|
-
Requires-Dist: bs4 (>=0.0.1)
|
|
24
|
-
Requires-Dist: certifi (>=2023.11.17)
|
|
25
|
-
Requires-Dist: google-re2 (>=1.1)
|
|
26
|
-
Requires-Dist: jinja2 (>=3.1)
|
|
27
|
-
Requires-Dist: joblib (>=1.3)
|
|
28
|
-
Requires-Dist: matplotlib (>=3.8)
|
|
29
|
-
Requires-Dist: mpmath (>=1.3)
|
|
30
|
-
Requires-Dist: msgpack (>=1.0)
|
|
31
|
-
Requires-Dist: msgpack-numpy (>=0.4)
|
|
32
|
-
Requires-Dist: numpy (>=1.26,<2)
|
|
33
|
-
Requires-Dist: scipy (>=1.12)
|
|
34
|
-
Requires-Dist: sympy (>=1.12)
|
|
35
|
-
Requires-Dist: tables (>=3.8)
|
|
36
|
-
Requires-Dist: types-beautifulsoup4 (>=4.11.2)
|
|
37
|
-
Requires-Dist: urllib3 (>=2.2.2,<3.0.0)
|
|
38
|
-
Requires-Dist: xlrd (>=2.0.1,<3.0.0)
|
|
39
|
-
Requires-Dist: xlsxwriter (>=3.1)
|
|
40
|
-
Description-Content-Type: text/x-rst
|
|
41
|
-
|
|
42
|
-
mergeron: Merger Policy Analysis using Python
|
|
43
|
-
=============================================
|
|
44
|
-
|
|
45
|
-
Analyze the sets of mergers conforming to concentration and diversion ratio bounds. Analyze intrinsic enforcement rates, and intrinsic clearance rates, under concentration, diversion ratio, GUPPI, CMCR, and IPR bounds using generated data with specified distributions of market shares, price-cost margins, firm counts, and prices, optionally imposing restrictions implied by statutory filing thresholds and/or Bertrand-Nash oligopoly with MNL demand. Download and analyze merger investigations data published by the U.S. Federal Trade Commission in various reports on extended merger investigations (Second Requests) during 1996 to 2011.
|
|
46
|
-
|
|
47
|
-
Here, enforcement rates derived with merger enforcement as being exogenous to firm conduct are defined as intrinsic enforcement rates, and similarly intrinsic clearance rates. Depending on the merger enforcement regime, or merger control regime, intrinsic enforcement rates may also not be the complement of intrinsic clearance rates, i.e, it is not necessarily true that the intrinsic clearance rate estimate for a given enforcement regime is 1 minus the intrinsic enforcement rate. In contrast, observed enforcement rates reflect the deterrent effects of merger enforcement on firm conduct as well as the effects of merger screening on the level of enforcement; and, by definition, the observed clearance rate is 1 minus the observed enforcement rate.
|
|
48
|
-
|
|
49
|
-
Introduction
|
|
50
|
-
------------
|
|
51
|
-
|
|
52
|
-
Module :code:`mergeron.core.guidelines_boundaries` includes classes for specifying concentration bounds (:code:`mergeron.core.guidelines_boundaries.ConcentrationBoundary`) and diversion-ratio bounds (:code:`mergeron.core.guidelines_boundaries.DiversionRatioBoundary`), with automatic generation of boundary (as an array of share-pairs) and area. This module also includes a function for generating plots of concentration and diversion-ratio boundaries, and functions for mapping GUPPI standards to concentration (ΔHHI) standards, and vice-versa.
|
|
53
|
-
|
|
54
|
-
Module :code:`mergeron.gen.market_sample` includes the :code:`mergeron.gen.market_sample.MarketSample` with methods for, (i) generating sample data under a rich specification of shares, diversion ratios, margins, prices, and HSR filing requirements, and (ii) for estimating enforcement or clearance rates under specified enforcement regimes given a method of aggregating diversion ratio or GUPPI estimates for the firms in a merger. Notably. share are generated not just for markets with a fixed number of firms, but for markets with multiple firm-count weights, which may be left unspecified or explicitly specified.
|
|
55
|
-
|
|
56
|
-
Unless otherwise specified, merging-firm shares are drawn with uniform distribution over the space :math:`s_1 + s_2 \leqslant 1` for an unspecified number of firms. Alternatively, shares may be drawn from the Dirichlet distribution, with specified shape parameters (see :code:`mergeron.gen.ShareConstants`. When drawing shares from the Dirichlet distribution, the user passes, using :code:`mergeron.gen.MarketSpec.ShareSpec.firm_count_weights`, a vector of weights specifying the frequency distribution over sequential firm counts, e.g., :code:`[133, 184, 134, 52, 32, 10, 12, 4, 3]` to specify shares drawn from Dirichlet distributions with 2 to 10 pre-merger firms distributed as in data for FTC merger investigations during 1996--2003 (See, for example, Table 4.1 of `FTC, Horizontal Merger Investigations Data, Fiscal Years 1996--2003 (Revised: August 31, 2004) <https://www.ftc.gov/sites/default/files/documents/reports/horizontal-merger-investigation-data-fiscal-years-1996-2003/040831horizmergersdata96-03.pdf>`_). If :code:`mergeron.gen.MarketSpec.ShareSpec.firm_count_weights` is not assigned a value when defining :code:`mergeron.gen.MarketSpec.ShareSpec` (which has type, :code:`mergeron.gen.ShareSpec`), the default values is used, with results in a sample of markets with 2 to 6 firms with equal relative frequency.
|
|
57
|
-
|
|
58
|
-
Recapture rates can be specified as, "proportional", "inside-out", "outside-in" (see :code:`mergeron.RECConstants`. The "inside-out" specification results in recapture ratios consistent with merging-firms' in-market shares and a default recapture rate. The "outside-in" specification yields diversion ratios from purchase probabilities drawn at random for :math:`N+1` goods, from which are derived market shares and recapture rates for the :math:`N` goods in the putative market (see, :code:`mergeron.gen.DiversionRatioSpec`). The "outside-in" specification is invalid when the distribution of markets over firm-count is unspecified, i.e., when :code:`mergeron.gen.MarketSpec.ShareSpec.dist_type ==`:code:`mergeron.gen.ShareConstants.UNI`.
|
|
59
|
-
|
|
60
|
-
Price-cost-margins may be specified as having uniform distribution, Beta distribution (including a bounded Beta distribution with specified mean and variance), or an empirical distribution. The empirical margin distribution is based on resampling margin data published by Prof. Damodaran of NYU Stern School of Business (see Notes), using an estimated Gaussian KDE. The second merging firm's margin may be specified as symmetric, i.i.d., or subject to equilibrium conditions for (profit-maximization in) Bertrand-Nash oligopoly with MNL demand (see, :code:`mergeron.gen.PCMSpec`).
|
|
61
|
-
|
|
62
|
-
Prices may be specified as symmetric or asymmetric, and in the latter case, the direction of correlation between merging firm prices, if any, can also be specified (see, :code:`mergeron.gen.PriceSpec`).
|
|
63
|
-
|
|
64
|
-
The market sample may be restricted to mergers meeting the HSR filing requirement under two alternative approaches: in the one, the smaller of the two merging firms meets the HSR filing threshold for the smaller (acquired) firm. In the other, the :math:`n`-th firm's size matches the size requirement for the smaller merging firm (see, :code:`mergeron.gen.SSZConstants`). The second assumption avoids the unfortunate assumption in the first that, within the resulting sample, the larger merging firm be at least 10 times as large as the smaller merging firm, as a consequence of the full definition of the HSR filing requirement.
|
|
65
|
-
|
|
66
|
-
The full specification of a market sample is given in a :code:`mergeron.gen.market_sample.MarketSample` object, including the above parameters. Data are drawn by invoking :code:`mergeron.gen.market_sample.MarketSample.generate_sample` which adds a :code:`data` property of class, :code:`mergeron.gen.MarketDataSample`. Enforcement or clearance counts are computed by invoking :code:`mergeron.gen.market_sample.MarketSample.estimate_enf_counts`, which adds an :code:`enf_counts` property of class :code:`mergeron.gen.UPPTestsCounts`. For fast, parallel generation of enforcement or clearance counts over large market data samples that ordinarily would exceed available limits on machine memory, the user can invoke the method :code:`estimate_enf_counts` on a :code:`mergeron.gen.market_sample.MarketSample` object without first invoking :code:`generate_sample`. Note, however, that this strategy does not retain the market sample in memory in the interests of conserving memory and maintaining high performance (the user can specify that the market sample and enforcement statistics be stored to permanent storage; when saving to current PCIe NVMe storage, the performance penalty is slight, but can be considerable if saving to SATA storage).
|
|
67
|
-
|
|
68
|
-
Enforcement statistics based on FTC investigations data and test data are printed to screen or rendered to LaTex files (for processing into publication-quality tables) using methods provided in :code:`mergeron.gen.enforcement_stats`.
|
|
69
|
-
|
|
70
|
-
Programs demonstrating the use of this package are included in the sub-package, :code:`mergeron.demo`.
|
|
71
|
-
|
|
72
|
-
This package includes a class, :code:`mergeron.core.pseudorandom_numbers.MulithreadedRNG` for generating random numbers with selected continuous distribution over specified parameters, and with CPU multithreading on machines with multiple virtual, logical, or physical CPU cores. This class is an adaptation from the documentation of the :code:`numpy` package, from the discussion on `multithreaded random-number generation <https://numpy.org/doc/stable/reference/random/multithreading.html>_`; the version included here permits selection of the distribution with pre-tests to catch and inform on common errors. To access these directly:
|
|
73
|
-
|
|
74
|
-
.. code-block:: python
|
|
75
|
-
|
|
76
|
-
import mergeron.core.pseudorandom_numbers as prng
|
|
77
|
-
|
|
78
|
-
Documentation for this package is in the form of the API Reference. Documentation for individual functions and classes is accessible within a python shell. For example:
|
|
79
|
-
|
|
80
|
-
.. code-block:: python
|
|
81
|
-
|
|
82
|
-
import mergeron.core.market_sample as market_sample
|
|
83
|
-
|
|
84
|
-
help(market_sample.MarketSample)
|
|
85
|
-
|
|
86
|
-
.. image:: https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json
|
|
87
|
-
:alt: Poetry
|
|
88
|
-
:target: https://python-poetry.org/
|
|
89
|
-
|
|
90
|
-
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
|
|
91
|
-
:alt: Ruff
|
|
92
|
-
:target: https://github.com/astral-sh/ruff
|
|
93
|
-
|
|
94
|
-
.. image:: https://www.mypy-lang.org/static/mypy_badge.svg
|
|
95
|
-
:alt: Checked with mypy
|
|
96
|
-
:target: https://mypy-lang.org/
|
|
97
|
-
|
|
98
|
-
.. image:: https://img.shields.io/badge/License-MIT-yellow.svg
|
|
99
|
-
:alt: License: MIT
|
|
100
|
-
:target: https://opensource.org/licenses/MIT
|
|
101
|
-
|
|
102
|
-
|
|
File without changes
|