mergeron 2024.738973.0__py3-none-any.whl → 2024.739079.10__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 +28 -3
- mergeron/core/__init__.py +2 -77
- mergeron/core/damodaran_margin_data.py +66 -52
- mergeron/core/excel_helper.py +39 -37
- mergeron/core/ftc_merger_investigations_data.py +66 -35
- mergeron/core/guidelines_boundaries.py +261 -234
- mergeron/core/guidelines_boundary_functions.py +182 -27
- mergeron/core/guidelines_boundary_functions_extra.py +17 -14
- mergeron/core/proportions_tests.py +2 -4
- mergeron/core/pseudorandom_numbers.py +6 -11
- mergeron/data/__init__.py +3 -0
- mergeron/data/damodaran_margin_data.xls +0 -0
- mergeron/data/damodaran_margin_data_dict.msgpack +0 -0
- mergeron/{jinja_LaTex_templates/setup_tikz_tables.tex.jinja2 → data/jinja2_LaTeX_templates/setup_tikz_tables.tex} +45 -50
- mergeron/demo/__init__.py +3 -0
- mergeron/demo/visualize_empirical_margin_distribution.py +88 -0
- mergeron/ext/__init__.py +2 -4
- mergeron/ext/tol_colors.py +3 -3
- mergeron/gen/__init__.py +53 -46
- mergeron/gen/_data_generation_functions.py +28 -93
- mergeron/gen/data_generation.py +20 -24
- mergeron/gen/{investigations_stats.py → enforcement_stats.py} +59 -57
- mergeron/gen/market_sample.py +6 -10
- mergeron/gen/upp_tests.py +29 -26
- mergeron-2024.739079.10.dist-info/METADATA +109 -0
- mergeron-2024.739079.10.dist-info/RECORD +36 -0
- mergeron/core/InCommon RSA Server CA cert chain.pem +0 -68
- mergeron-2024.738973.0.dist-info/METADATA +0 -108
- mergeron-2024.738973.0.dist-info/RECORD +0 -32
- /mergeron/{core → data}/ftc_invdata.msgpack +0 -0
- /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/clrrate_cis_summary_table_template.tex.jinja2 +0 -0
- /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/ftcinvdata_byhhianddelta_table_template.tex.jinja2 +0 -0
- /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/ftcinvdata_summary_table_template.tex.jinja2 +0 -0
- /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/ftcinvdata_summarypaired_table_template.tex.jinja2 +0 -0
- /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/mergeron.cls +0 -0
- /mergeron/{jinja_LaTex_templates → data/jinja2_LaTeX_templates}/mergeron_table_collection_template.tex.jinja2 +0 -0
- {mergeron-2024.738973.0.dist-info → mergeron-2024.739079.10.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plot the empirical distribution derived using the Gaussian KDE with
|
|
3
|
+
margin data downloaded from Prof. Damodaran's website at NYU.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import warnings
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from matplotlib.ticker import StrMethodFormatter
|
|
13
|
+
from numpy.random import PCG64DXSM, Generator, SeedSequence
|
|
14
|
+
from scipy import stats # type: ignore
|
|
15
|
+
|
|
16
|
+
import mergeron.core.damodaran_margin_data as dmgn
|
|
17
|
+
from mergeron import DATA_DIR
|
|
18
|
+
from mergeron.core.guidelines_boundary_functions import boundary_plot
|
|
19
|
+
|
|
20
|
+
SAMPLE_SIZE = 10**6
|
|
21
|
+
BIN_COUNT = 25
|
|
22
|
+
mgn_data_obs, mgn_data_wts, mgn_data_stats = dmgn.mgn_data_builder()
|
|
23
|
+
print(repr(mgn_data_obs))
|
|
24
|
+
print(repr(mgn_data_stats))
|
|
25
|
+
|
|
26
|
+
plt, mgn_fig, mgn_ax, set_axis_def = boundary_plot(mktshares_plot_flag=False)
|
|
27
|
+
mgn_fig.set_figheight(6.5)
|
|
28
|
+
mgn_fig.set_figwidth(9.0)
|
|
29
|
+
|
|
30
|
+
_, mgn_bins, _ = mgn_ax.hist(
|
|
31
|
+
x=mgn_data_obs,
|
|
32
|
+
weights=mgn_data_wts,
|
|
33
|
+
bins=BIN_COUNT,
|
|
34
|
+
alpha=0.4,
|
|
35
|
+
density=True,
|
|
36
|
+
label="Downloaded data",
|
|
37
|
+
color="#004488", # Paul Tol's High Contrast Blue
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
with warnings.catch_warnings():
|
|
41
|
+
warnings.filterwarnings("ignore", category=UserWarning)
|
|
42
|
+
# Don't warn regarding the below; ticklabels have been fixed before this point
|
|
43
|
+
mgn_ax.set_yticklabels([
|
|
44
|
+
f"{float(_g.get_text()) * np.diff(mgn_bins)[-1]:.0%}"
|
|
45
|
+
for _g in mgn_ax.get_yticklabels()
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
mgn_kde = stats.gaussian_kde(mgn_data_obs, weights=mgn_data_wts, bw_method="silverman")
|
|
49
|
+
mgn_kde.set_bandwidth(bw_method=mgn_kde.factor / 3.0)
|
|
50
|
+
|
|
51
|
+
mgn_xvec = np.linspace(0, BIN_COUNT, 10**5) / BIN_COUNT
|
|
52
|
+
mgn_ax.plot(
|
|
53
|
+
mgn_xvec,
|
|
54
|
+
mgn_kde(mgn_xvec),
|
|
55
|
+
color="#004488",
|
|
56
|
+
rasterized=True,
|
|
57
|
+
label="Estimated Density",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
mgn_ax.hist(
|
|
61
|
+
x=mgn_kde.resample(
|
|
62
|
+
SAMPLE_SIZE, seed=Generator(PCG64DXSM(SeedSequence(pool_size=8)))
|
|
63
|
+
)[0],
|
|
64
|
+
color="#DDAA33", # Paul Tol's High Contrast Yellow
|
|
65
|
+
alpha=0.6,
|
|
66
|
+
bins=BIN_COUNT,
|
|
67
|
+
density=True,
|
|
68
|
+
label="Generated data",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
mgn_ax.legend(
|
|
72
|
+
loc="best",
|
|
73
|
+
fancybox=False,
|
|
74
|
+
shadow=False,
|
|
75
|
+
frameon=True,
|
|
76
|
+
facecolor="white",
|
|
77
|
+
edgecolor="white",
|
|
78
|
+
framealpha=1,
|
|
79
|
+
fontsize="small",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
mgn_ax.set_xlim(0.0, 1.0)
|
|
83
|
+
mgn_ax.xaxis.set_major_formatter(StrMethodFormatter("{x:>3.0%}"))
|
|
84
|
+
mgn_ax.set_xlabel("Price Cost Margin", fontsize=10)
|
|
85
|
+
mgn_ax.set_ylabel("Relative Frequency", fontsize=10)
|
|
86
|
+
|
|
87
|
+
mgn_fig.tight_layout()
|
|
88
|
+
plt.savefig(DATA_DIR / f"{Path(__file__).stem}.pdf")
|
mergeron/ext/__init__.py
CHANGED
mergeron/ext/tol_colors.py
CHANGED
|
@@ -796,7 +796,7 @@ def main() -> None:
|
|
|
796
796
|
schemes: Sequence[str] = tol_cset()
|
|
797
797
|
fig, axes = plt.subplots(ncols=len(schemes), figsize=(9, 3))
|
|
798
798
|
fig.subplots_adjust(top=0.9, bottom=0.02, left=0.02, right=0.92)
|
|
799
|
-
for ax, scheme in zip(axes, schemes):
|
|
799
|
+
for ax, scheme in zip(axes, schemes): # type: ignore
|
|
800
800
|
cset = tol_cset(scheme)
|
|
801
801
|
names = cset._fields # type: ignore
|
|
802
802
|
colors = list(cset)
|
|
@@ -813,7 +813,7 @@ def main() -> None:
|
|
|
813
813
|
gradient = np.vstack((gradient, gradient))
|
|
814
814
|
fig, axes = plt.subplots(nrows=len(schemes))
|
|
815
815
|
fig.subplots_adjust(top=0.98, bottom=0.02, left=0.2, right=0.99)
|
|
816
|
-
for ax, scheme in zip(axes, schemes):
|
|
816
|
+
for ax, scheme in zip(axes, schemes): # type: ignore
|
|
817
817
|
pos = list(ax.get_position().bounds)
|
|
818
818
|
ax.set_axis_off()
|
|
819
819
|
ax.imshow(gradient, aspect=4, cmap=tol_cmap(scheme))
|
|
@@ -832,7 +832,7 @@ def main() -> None:
|
|
|
832
832
|
gradient = np.vstack((gradient, gradient))
|
|
833
833
|
fig, axes = plt.subplots(nrows=23)
|
|
834
834
|
fig.subplots_adjust(top=0.98, bottom=0.02, left=0.25, right=0.99)
|
|
835
|
-
for lut, ax in enumerate(axes, start=1):
|
|
835
|
+
for lut, ax in enumerate(axes, start=1): # type: ignore
|
|
836
836
|
pos = list(ax.get_position().bounds)
|
|
837
837
|
ax.set_axis_off()
|
|
838
838
|
ax.imshow(gradient, aspect=4, cmap=tol_cmap("rainbow_discrete", lut))
|
mergeron/gen/__init__.py
CHANGED
|
@@ -7,28 +7,26 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
import enum
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
-
from
|
|
11
|
-
from typing import ClassVar, Protocol, TypeVar
|
|
10
|
+
from typing import ClassVar, Protocol
|
|
12
11
|
|
|
13
12
|
import numpy as np
|
|
14
|
-
from attrs import Attribute, define, field, frozen, validators
|
|
15
|
-
from numpy.typing import
|
|
13
|
+
from attrs import Attribute, cmp_using, define, field, frozen, validators
|
|
14
|
+
from numpy.typing import NDArray
|
|
16
15
|
|
|
17
|
-
from .. import
|
|
16
|
+
from .. import VERSION, RECConstants, UPPAggrSelector # noqa: TID252
|
|
18
17
|
from ..core.pseudorandom_numbers import DIST_PARMS_DEFAULT # noqa: TID252
|
|
19
18
|
|
|
20
|
-
__version__ =
|
|
19
|
+
__version__ = VERSION
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
EMPTY_ARRAY_DEFAULT = np.zeros(2)
|
|
24
|
-
FCOUNT_WTS_DEFAULT =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
TI = TypeVar("TI", bound=NBitBase)
|
|
23
|
+
FCOUNT_WTS_DEFAULT = np.divide(
|
|
24
|
+
(_nr := np.arange(1, 6)[::-1]), _nr.sum(), dtype=np.float64
|
|
25
|
+
)
|
|
28
26
|
|
|
29
27
|
|
|
30
28
|
@enum.unique
|
|
31
|
-
class
|
|
29
|
+
class PriceConstants(tuple[bool, str | None], enum.ReprEnum):
|
|
32
30
|
"""Price specification.
|
|
33
31
|
|
|
34
32
|
Whether prices are symmetric and, if not, the direction of correlation, if any.
|
|
@@ -38,7 +36,7 @@ class PRIConstants(tuple[bool, str | None], enum.ReprEnum):
|
|
|
38
36
|
ZERO = (False, None)
|
|
39
37
|
NEG = (False, "negative share-correlation")
|
|
40
38
|
POS = (False, "positive share-correlation")
|
|
41
|
-
CSY = (False, "market-wide cost-symmetry")
|
|
39
|
+
# TODO: CSY = (False, "market-wide cost-symmetry")
|
|
42
40
|
|
|
43
41
|
|
|
44
42
|
@enum.unique
|
|
@@ -46,7 +44,7 @@ class SHRConstants(enum.StrEnum):
|
|
|
46
44
|
"""Market share distributions."""
|
|
47
45
|
|
|
48
46
|
UNI = "Uniform"
|
|
49
|
-
"""Uniform distribution over
|
|
47
|
+
R"""Uniform distribution over :math:`s_1 + s_2 \leqslant 1`"""
|
|
50
48
|
|
|
51
49
|
DIR_FLAT = "Flat Dirichlet"
|
|
52
50
|
"""Shape parameter for all merging-firm-shares is unity (1)"""
|
|
@@ -87,48 +85,56 @@ class ShareSpec:
|
|
|
87
85
|
"""
|
|
88
86
|
|
|
89
87
|
recapture_form: RECConstants
|
|
90
|
-
"""
|
|
88
|
+
"""See :class:`mergeron.RECConstants`"""
|
|
91
89
|
|
|
92
90
|
recapture_rate: float | None
|
|
93
|
-
"""A value between 0 and 1.
|
|
91
|
+
"""A value between 0 and 1, typically 0.8.
|
|
94
92
|
|
|
95
|
-
None if market share specification requires direct generation of
|
|
96
|
-
outside good choice probabilities (RECConstants.OUTIN).
|
|
93
|
+
:code:`None` if market share specification requires direct generation of
|
|
94
|
+
outside good choice probabilities (:attr:`mergeron.RECConstants.OUTIN`).
|
|
97
95
|
|
|
98
96
|
The recapture rate is usually calibrated to the numbers-equivalent of the
|
|
99
|
-
HHI threshold for the presumtion of harm from unilateral
|
|
100
|
-
in published merger guidelines. Accordingly,
|
|
97
|
+
HHI threshold for the presumtion of harm from unilateral competitive effects
|
|
98
|
+
in published merger guidelines. Accordingly, the recapture rate rounded to
|
|
99
|
+
the nearest 5% is:
|
|
100
|
+
|
|
101
|
+
* 0.85, **7-to-6 merger from symmetry**; US Guidelines, 1992, 2023
|
|
102
|
+
* 0.80, **6-to-5 merger to symmetry**; EU Guidelines for horizontal mergers, 2004
|
|
103
|
+
* 0.80, 5-to-4 merger from symmetry
|
|
104
|
+
* 0.80, **5-to-4 merger to symmetry**; US Guidelines, 2010
|
|
101
105
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
* 0.78, **5-to-4 merger to symmetry**; US Guidelines, 2010
|
|
106
|
+
Highlighting indicates hypothetical mergers in the neighborhood of (the boundary of)
|
|
107
|
+
the Guidelines presumption of harm. (In the EU Guidelines, concentration measures serve as
|
|
108
|
+
screens for further investigation, rather than as the basis for presumptions of harm or
|
|
109
|
+
presumptions no harm.)
|
|
107
110
|
|
|
108
|
-
Highlighting indicates hypothetical mergers close to the boundary of the presumption.
|
|
109
111
|
"""
|
|
110
112
|
|
|
111
113
|
dist_type: SHRConstants
|
|
112
|
-
"""
|
|
114
|
+
"""See :class:`mergeron.gen.SHRConstants`"""
|
|
113
115
|
|
|
114
|
-
dist_parms: NDArray[np.float64] | None
|
|
116
|
+
dist_parms: NDArray[np.float64] | None = field(
|
|
117
|
+
default=None, eq=cmp_using(eq=np.array_equal)
|
|
118
|
+
)
|
|
115
119
|
"""Parameters for tailoring market-share distribution
|
|
116
120
|
|
|
117
121
|
For Uniform distribution, bounds of the distribution; defaults to `(0, 1)`;
|
|
118
|
-
for Beta distribution, shape parameters, defaults to `(1, 1)`;
|
|
119
|
-
for Bounded-Beta distribution, vector of (min, max, mean, std. deviation), non-optional;
|
|
120
122
|
for Dirichlet-type distributions, a vector of shape parameters of length
|
|
121
123
|
no less than the length of firm-count weights below; defaults depend on
|
|
122
124
|
type of Dirichlet-distribution specified.
|
|
123
125
|
|
|
124
126
|
"""
|
|
125
|
-
firm_counts_weights: NDArray[np.float64 | np.int64] | None
|
|
126
|
-
|
|
127
|
+
firm_counts_weights: NDArray[np.float64 | np.int64] | None = field(
|
|
128
|
+
default=None, eq=cmp_using(eq=np.array_equal)
|
|
129
|
+
)
|
|
130
|
+
"""Relative or absolute frequencies of firm counts
|
|
127
131
|
|
|
128
132
|
|
|
129
133
|
Given frequencies are exogenous to generated market data sample;
|
|
130
|
-
defaults to FCOUNT_WTS_DEFAULT, which specifies
|
|
131
|
-
with weights in descending order from 5 to 1.
|
|
134
|
+
for Dirichlet-type distributions, defaults to FCOUNT_WTS_DEFAULT, which specifies
|
|
135
|
+
firm-counts of 2 to 6 with weights in descending order from 5 to 1.
|
|
136
|
+
|
|
137
|
+
"""
|
|
132
138
|
|
|
133
139
|
|
|
134
140
|
@enum.unique
|
|
@@ -167,10 +173,10 @@ class PCMSpec:
|
|
|
167
173
|
"""
|
|
168
174
|
|
|
169
175
|
firm2_pcm_constraint: FM2Constants
|
|
170
|
-
"""See FM2Constants"""
|
|
176
|
+
"""See :class:`mergeron.gen.FM2Constants`"""
|
|
171
177
|
|
|
172
178
|
dist_type: PCMConstants
|
|
173
|
-
"""See PCMConstants"""
|
|
179
|
+
"""See :class:`mergeron.gen.PCMConstants`"""
|
|
174
180
|
|
|
175
181
|
dist_parms: NDArray[np.float64] | None
|
|
176
182
|
"""Parameter specification for tailoring PCM distribution
|
|
@@ -179,6 +185,7 @@ class PCMSpec:
|
|
|
179
185
|
for Beta distribution, shape parameters, defaults to `(1, 1)`;
|
|
180
186
|
for Bounded-Beta distribution, vector of (min, max, mean, std. deviation), non-optional;
|
|
181
187
|
for empirical distribution based on Damodaran margin data, optional, ignored
|
|
188
|
+
|
|
182
189
|
"""
|
|
183
190
|
|
|
184
191
|
|
|
@@ -303,31 +310,31 @@ class MarketSpec:
|
|
|
303
310
|
|
|
304
311
|
share_spec: ShareSpec = field(
|
|
305
312
|
kw_only=True,
|
|
306
|
-
default=ShareSpec(RECConstants.INOUT, 0.
|
|
313
|
+
default=ShareSpec(RECConstants.INOUT, 0.85, SHRConstants.UNI, None, None),
|
|
307
314
|
validator=[validators.instance_of(ShareSpec), _share_spec_validator],
|
|
308
315
|
)
|
|
309
|
-
"""Market-share specification, see
|
|
316
|
+
"""Market-share specification, see :class:`mergeron.gen.ShareSpec`"""
|
|
310
317
|
|
|
311
318
|
pcm_spec: PCMSpec = field(
|
|
312
319
|
kw_only=True,
|
|
313
320
|
default=PCMSpec(FM2Constants.IID, PCMConstants.UNI, None),
|
|
314
321
|
validator=[validators.instance_of(PCMSpec), _pcm_spec_validator],
|
|
315
322
|
)
|
|
316
|
-
"""Margin specification, see
|
|
323
|
+
"""Margin specification, see :class:`mergeron.gen.PCMSpec`"""
|
|
317
324
|
|
|
318
|
-
price_spec:
|
|
325
|
+
price_spec: PriceConstants = field(
|
|
319
326
|
kw_only=True,
|
|
320
|
-
default=
|
|
321
|
-
validator=validators.instance_of(
|
|
327
|
+
default=PriceConstants.SYM,
|
|
328
|
+
validator=validators.instance_of(PriceConstants),
|
|
322
329
|
)
|
|
323
|
-
"""Price specification, see
|
|
330
|
+
"""Price specification, see :class:`mergeron.gen.PriceConstants`"""
|
|
324
331
|
|
|
325
332
|
hsr_filing_test_type: SSZConstants = field(
|
|
326
333
|
kw_only=True,
|
|
327
334
|
default=SSZConstants.ONE,
|
|
328
335
|
validator=validators.instance_of(SSZConstants),
|
|
329
336
|
)
|
|
330
|
-
"""Method for modeling HSR filing threholds, see SSZConstants"""
|
|
337
|
+
"""Method for modeling HSR filing threholds, see :class:`mergeron.gen.SSZConstants`"""
|
|
331
338
|
|
|
332
339
|
|
|
333
340
|
@enum.unique
|
|
@@ -456,8 +463,8 @@ class UPPTestsRaw:
|
|
|
456
463
|
|
|
457
464
|
A test success is a draw ("market") that meeets the
|
|
458
465
|
specified test criterion, and a test failure is
|
|
459
|
-
one that does not; test criteria are
|
|
460
|
-
|
|
466
|
+
one that does not; test criteria are evaluated in
|
|
467
|
+
:func:`enforcement_stats.gen_upp_arrays`.
|
|
461
468
|
"""
|
|
462
469
|
|
|
463
470
|
guppi_test_simple: NDArray[np.bool_]
|
|
@@ -479,7 +486,7 @@ class UPPTestsRaw:
|
|
|
479
486
|
class UPPTestsCounts:
|
|
480
487
|
"""Counts of markets resolved as specified
|
|
481
488
|
|
|
482
|
-
Resolution may be either
|
|
489
|
+
Resolution may be either :attr:`INVResolution.ENFT` or :attr:`INVResolution.CLRN`.
|
|
483
490
|
"""
|
|
484
491
|
|
|
485
492
|
by_firm_count: NDArray[np.int64]
|
|
@@ -4,36 +4,33 @@ Non-public functions called in data_generation.py
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
from importlib.metadata import version
|
|
8
7
|
from typing import Literal
|
|
9
8
|
|
|
10
9
|
import numpy as np
|
|
11
10
|
from numpy.random import SeedSequence
|
|
12
11
|
from numpy.typing import NDArray
|
|
13
12
|
|
|
14
|
-
from .. import
|
|
15
|
-
from ..core.damodaran_margin_data import
|
|
13
|
+
from .. import VERSION, RECConstants # noqa: TID252
|
|
14
|
+
from ..core.damodaran_margin_data import mgn_data_resampler # noqa: TID252
|
|
16
15
|
from ..core.pseudorandom_numbers import ( # noqa: TID252
|
|
17
16
|
DIST_PARMS_DEFAULT,
|
|
18
17
|
MultithreadedRNG,
|
|
19
18
|
prng,
|
|
20
19
|
)
|
|
21
20
|
from . import (
|
|
22
|
-
EMPTY_ARRAY_DEFAULT,
|
|
23
21
|
FCOUNT_WTS_DEFAULT,
|
|
24
|
-
TF,
|
|
25
22
|
FM2Constants,
|
|
26
23
|
MarginDataSample,
|
|
27
24
|
MarketSpec,
|
|
28
25
|
PCMConstants,
|
|
26
|
+
PriceConstants,
|
|
29
27
|
PriceDataSample,
|
|
30
|
-
PRIConstants,
|
|
31
28
|
ShareDataSample,
|
|
32
29
|
SHRConstants,
|
|
33
30
|
SSZConstants,
|
|
34
31
|
)
|
|
35
32
|
|
|
36
|
-
__version__ =
|
|
33
|
+
__version__ = VERSION
|
|
37
34
|
|
|
38
35
|
|
|
39
36
|
def _gen_share_data(
|
|
@@ -100,7 +97,7 @@ def _gen_share_data(
|
|
|
100
97
|
|
|
101
98
|
# If recapture_form == "inside-out", recalculate _aggregate_purchase_prob
|
|
102
99
|
_frmshr_array = _mkt_share_sample.mktshr_array[:, :2]
|
|
103
|
-
_r_bar = _mkt_sample_spec.share_spec.recapture_rate or 0.
|
|
100
|
+
_r_bar = _mkt_sample_spec.share_spec.recapture_rate or 0.85
|
|
104
101
|
if _recapture_form == RECConstants.INOUT:
|
|
105
102
|
_mkt_share_sample = ShareDataSample(
|
|
106
103
|
_mkt_share_sample.mktshr_array,
|
|
@@ -114,7 +111,7 @@ def _gen_share_data(
|
|
|
114
111
|
|
|
115
112
|
def _gen_market_shares_uniform(
|
|
116
113
|
_s_size: int = 10**6,
|
|
117
|
-
_dist_parms_mktshr: NDArray[np.
|
|
114
|
+
_dist_parms_mktshr: NDArray[np.float64] | None = DIST_PARMS_DEFAULT,
|
|
118
115
|
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
119
116
|
_nthreads: int = 16,
|
|
120
117
|
/,
|
|
@@ -139,8 +136,8 @@ def _gen_market_shares_uniform(
|
|
|
139
136
|
"""
|
|
140
137
|
|
|
141
138
|
_frmshr_array = np.empty((_s_size, 2), dtype=np.float64)
|
|
142
|
-
_dist_parms_mktshr: NDArray[np.
|
|
143
|
-
DIST_PARMS_DEFAULT if _dist_parms_mktshr is None else _dist_parms_mktshr
|
|
139
|
+
_dist_parms_mktshr: NDArray[np.float64] = (
|
|
140
|
+
DIST_PARMS_DEFAULT if _dist_parms_mktshr is None else _dist_parms_mktshr
|
|
144
141
|
)
|
|
145
142
|
_mrng = MultithreadedRNG(
|
|
146
143
|
_frmshr_array,
|
|
@@ -150,7 +147,7 @@ def _gen_market_shares_uniform(
|
|
|
150
147
|
nthreads=_nthreads,
|
|
151
148
|
)
|
|
152
149
|
_mrng.fill()
|
|
153
|
-
# Convert draws on U[0, 1] to Uniformly-distributed draws on simplex, s_1 + s_2
|
|
150
|
+
# Convert draws on U[0, 1] to Uniformly-distributed draws on simplex, s_1 + s_2 <= 1
|
|
154
151
|
_frmshr_array = np.sort(_frmshr_array, axis=1)
|
|
155
152
|
_frmshr_array = np.column_stack((
|
|
156
153
|
_frmshr_array[:, 0],
|
|
@@ -179,8 +176,8 @@ def _gen_market_shares_dirichlet_multisample(
|
|
|
179
176
|
_s_size: int = 10**6,
|
|
180
177
|
_recapture_form: RECConstants = RECConstants.INOUT,
|
|
181
178
|
_dist_type_dir: SHRConstants = SHRConstants.DIR_FLAT,
|
|
182
|
-
_dist_parms_dir: NDArray[np.
|
|
183
|
-
_firm_count_wts: NDArray[np.
|
|
179
|
+
_dist_parms_dir: NDArray[np.float64] | None = None,
|
|
180
|
+
_firm_count_wts: NDArray[np.float64] | None = None,
|
|
184
181
|
_fcount_rng_seed_seq: SeedSequence | None = None,
|
|
185
182
|
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
186
183
|
_nthreads: int = 16,
|
|
@@ -218,7 +215,7 @@ def _gen_market_shares_dirichlet_multisample(
|
|
|
218
215
|
|
|
219
216
|
"""
|
|
220
217
|
|
|
221
|
-
_firm_count_wts: NDArray[np.
|
|
218
|
+
_firm_count_wts: NDArray[np.float64] = (
|
|
222
219
|
FCOUNT_WTS_DEFAULT if _firm_count_wts is None else _firm_count_wts
|
|
223
220
|
)
|
|
224
221
|
|
|
@@ -314,7 +311,7 @@ def _gen_market_shares_dirichlet_multisample(
|
|
|
314
311
|
|
|
315
312
|
|
|
316
313
|
def _gen_market_shares_dirichlet(
|
|
317
|
-
_dir_alphas: NDArray[np.
|
|
314
|
+
_dir_alphas: NDArray[np.float64],
|
|
318
315
|
_s_size: int = 10**6,
|
|
319
316
|
_recapture_form: RECConstants = RECConstants.INOUT,
|
|
320
317
|
_mktshr_rng_seed_seq: SeedSequence | None = None,
|
|
@@ -412,18 +409,18 @@ def _gen_price_data(
|
|
|
412
409
|
|
|
413
410
|
_pr_max_ratio = 5.0
|
|
414
411
|
match _mkt_sample_spec.price_spec:
|
|
415
|
-
case
|
|
412
|
+
case PriceConstants.SYM:
|
|
416
413
|
_nth_firm_price = np.ones((len(_frmshr_array), 1))
|
|
417
|
-
case
|
|
414
|
+
case PriceConstants.POS:
|
|
418
415
|
_price_array, _nth_firm_price = (
|
|
419
416
|
np.ceil(_p * _pr_max_ratio) for _p in (_frmshr_array, _nth_firm_share)
|
|
420
417
|
)
|
|
421
|
-
case
|
|
418
|
+
case PriceConstants.NEG:
|
|
422
419
|
_price_array, _nth_firm_price = (
|
|
423
420
|
np.ceil((1 - _p) * _pr_max_ratio)
|
|
424
421
|
for _p in (_frmshr_array, _nth_firm_share)
|
|
425
422
|
)
|
|
426
|
-
case
|
|
423
|
+
case PriceConstants.ZERO:
|
|
427
424
|
_price_array_gen = prng(_seed_seq).choice(
|
|
428
425
|
1 + np.arange(_pr_max_ratio), size=(len(_frmshr_array), 3)
|
|
429
426
|
)
|
|
@@ -458,10 +455,10 @@ def _gen_price_data(
|
|
|
458
455
|
_hsr_filing_test = _rev_ratio >= _test_rev_ratio_inv
|
|
459
456
|
# del _rev_array, _rev_ratio
|
|
460
457
|
case SSZConstants.HSR_NTH:
|
|
461
|
-
# To get around the 10-to-1 ratio restriction, specify that the nth firm
|
|
462
|
-
#
|
|
463
|
-
#
|
|
464
|
-
#
|
|
458
|
+
# To get around the 10-to-1 ratio restriction, specify that the nth firm test:
|
|
459
|
+
# if the smaller merging firm matches or exceeds the n-th firm in size, and
|
|
460
|
+
# the larger merging firm has at least 10 times the size of the nth firm,
|
|
461
|
+
# the size test is considered met.
|
|
465
462
|
# Alternatively, if the smaller merging firm has 10% or greater share,
|
|
466
463
|
# the value of transaction test is considered met.
|
|
467
464
|
_rev_ratio_to_nth = np.round(np.sort(_rev_array, axis=1) / _nth_firm_rev, 4)
|
|
@@ -483,33 +480,31 @@ def _gen_price_data(
|
|
|
483
480
|
|
|
484
481
|
|
|
485
482
|
def _gen_pcm_data(
|
|
486
|
-
_frmshr_array: NDArray[np.
|
|
483
|
+
_frmshr_array: NDArray[np.float64],
|
|
484
|
+
_price_array: NDArray[np.float64],
|
|
485
|
+
_aggregate_purchase_prob: NDArray[np.float64],
|
|
487
486
|
_mkt_sample_spec: MarketSpec,
|
|
488
|
-
_price_array: NDArray[np.floating[TF]],
|
|
489
|
-
_aggregate_purchase_prob: NDArray[np.floating[TF]],
|
|
490
487
|
_pcm_rng_seed_seq: SeedSequence,
|
|
491
488
|
_nthreads: int = 16,
|
|
492
489
|
/,
|
|
493
490
|
) -> MarginDataSample:
|
|
494
|
-
_recapture_form = _mkt_sample_spec.share_spec.recapture_form
|
|
495
491
|
_dist_type_pcm, _dist_firm2_pcm, _dist_parms_pcm = (
|
|
496
492
|
getattr(_mkt_sample_spec.pcm_spec, _f)
|
|
497
493
|
for _f in ("dist_type", "firm2_pcm_constraint", "dist_parms")
|
|
498
494
|
)
|
|
499
|
-
_dist_type: Literal["Beta", "Uniform"] = (
|
|
500
|
-
"Uniform" if _dist_type_pcm == PCMConstants.UNI else "Beta"
|
|
501
|
-
)
|
|
502
495
|
|
|
496
|
+
_dist_type: Literal["Beta", "Uniform"]
|
|
503
497
|
_pcm_array = np.empty((len(_frmshr_array), 2), dtype=np.float64)
|
|
504
498
|
_mnl_test_array = np.empty((len(_frmshr_array), 2), dtype=int)
|
|
505
499
|
|
|
506
500
|
_beta_min, _beta_max = [None] * 2 # placeholder
|
|
507
501
|
if _dist_type_pcm == PCMConstants.EMPR:
|
|
508
|
-
_pcm_array =
|
|
502
|
+
_pcm_array = mgn_data_resampler(
|
|
509
503
|
_pcm_array.shape, # type: ignore
|
|
510
504
|
seed_sequence=_pcm_rng_seed_seq,
|
|
511
505
|
)
|
|
512
506
|
else:
|
|
507
|
+
_dist_type = "Uniform" if _dist_type_pcm == PCMConstants.UNI else "Beta"
|
|
513
508
|
if _dist_type_pcm == PCMConstants.BETA:
|
|
514
509
|
if _dist_parms_pcm is None:
|
|
515
510
|
_dist_parms_pcm = np.ones(2, np.float64)
|
|
@@ -562,66 +557,6 @@ def _gen_pcm_data(
|
|
|
562
557
|
return MarginDataSample(_pcm_array, _mnl_test_array)
|
|
563
558
|
|
|
564
559
|
|
|
565
|
-
def _gen_divr_array(
|
|
566
|
-
_recapture_form: RECConstants,
|
|
567
|
-
_recapture_rate: float | None,
|
|
568
|
-
_frmshr_array: NDArray[np.float64],
|
|
569
|
-
_aggregate_purchase_prob: NDArray[np.float64] = EMPTY_ARRAY_DEFAULT,
|
|
570
|
-
/,
|
|
571
|
-
) -> NDArray[np.float64]:
|
|
572
|
-
"""
|
|
573
|
-
Given merging-firm shares and related parameters, return diverion ratios.
|
|
574
|
-
|
|
575
|
-
If recapture is specified as "Outside-in" (RECConstants.OUTIN), then the
|
|
576
|
-
choice-probability for the outside good must be supplied.
|
|
577
|
-
|
|
578
|
-
Parameters
|
|
579
|
-
----------
|
|
580
|
-
_recapture_form
|
|
581
|
-
Enum specifying Fixed (proportional), Inside-out, or Outside-in
|
|
582
|
-
|
|
583
|
-
_recapture_rate
|
|
584
|
-
If recapture is proportional or inside-out, the recapture rate
|
|
585
|
-
for the firm with the smaller share.
|
|
586
|
-
|
|
587
|
-
_frmshr_array
|
|
588
|
-
Merging-firm shares.
|
|
589
|
-
|
|
590
|
-
_aggregate_purchase_prob
|
|
591
|
-
1 minus probability that the outside good is chosen; converts
|
|
592
|
-
market shares to choice probabilities by multiplication.
|
|
593
|
-
|
|
594
|
-
Returns
|
|
595
|
-
-------
|
|
596
|
-
Merging-firm diversion ratios for mergers in the sample.
|
|
597
|
-
|
|
598
|
-
"""
|
|
599
|
-
|
|
600
|
-
_divr_array: NDArray[np.float64]
|
|
601
|
-
if _recapture_form == RECConstants.FIXED:
|
|
602
|
-
_divr_array = _recapture_rate * _frmshr_array[:, ::-1] / (1 - _frmshr_array) # type: ignore
|
|
603
|
-
|
|
604
|
-
else:
|
|
605
|
-
_purchprob_array = _aggregate_purchase_prob * _frmshr_array
|
|
606
|
-
_divr_array = _purchprob_array[:, ::-1] / (1 - _purchprob_array)
|
|
607
|
-
|
|
608
|
-
_divr_assert_test = (
|
|
609
|
-
(np.round(np.einsum("ij->i", _frmshr_array), 15) == 1)
|
|
610
|
-
| (np.argmin(_frmshr_array, axis=1) == np.argmax(_divr_array, axis=1))
|
|
611
|
-
)[:, None]
|
|
612
|
-
if not all(_divr_assert_test):
|
|
613
|
-
raise ValueError(
|
|
614
|
-
"{} {} {} {}".format(
|
|
615
|
-
"Data construction fails tests:",
|
|
616
|
-
"the index of min(s_1, s_2) must equal",
|
|
617
|
-
"the index of max(d_12, d_21), for all draws.",
|
|
618
|
-
"unless frmshr_array sums to 1.00.",
|
|
619
|
-
)
|
|
620
|
-
)
|
|
621
|
-
|
|
622
|
-
return _divr_array
|
|
623
|
-
|
|
624
|
-
|
|
625
560
|
def _beta_located(
|
|
626
561
|
_mu: float | NDArray[np.float64], _sigma: float | NDArray[np.float64], /
|
|
627
562
|
) -> NDArray[np.float64]:
|
|
@@ -647,7 +582,7 @@ def _beta_located(
|
|
|
647
582
|
return np.array([_mu * _mul, (1 - _mu) * _mul], dtype=np.float64)
|
|
648
583
|
|
|
649
584
|
|
|
650
|
-
def beta_located_bound(_dist_parms: NDArray[np.
|
|
585
|
+
def beta_located_bound(_dist_parms: NDArray[np.float64], /) -> NDArray[np.float64]:
|
|
651
586
|
R"""
|
|
652
587
|
Return shape parameters for a non-standard beta, given the mean, stddev, range
|
|
653
588
|
|