mergeron 2025.739319.3__py3-none-any.whl → 2025.739341.9__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 +21 -23
- mergeron/core/__init__.py +21 -5
- mergeron/core/empirical_margin_distribution.py +216 -160
- mergeron/core/ftc_merger_investigations_data.py +31 -35
- mergeron/core/guidelines_boundaries.py +27 -20
- mergeron/core/guidelines_boundary_functions.py +22 -32
- mergeron/core/guidelines_boundary_functions_extra.py +15 -30
- mergeron/core/pseudorandom_numbers.py +21 -18
- mergeron/data/__init__.py +13 -11
- mergeron/data/damodaran_margin_data_serialized.zip +0 -0
- mergeron/gen/__init__.py +32 -41
- mergeron/gen/data_generation.py +19 -23
- mergeron/gen/data_generation_functions.py +27 -38
- mergeron/gen/enforcement_stats.py +154 -32
- mergeron/gen/upp_tests.py +4 -9
- mergeron-2025.739341.9.dist-info/METADATA +94 -0
- mergeron-2025.739341.9.dist-info/RECORD +20 -0
- {mergeron-2025.739319.3.dist-info → mergeron-2025.739341.9.dist-info}/WHEEL +1 -1
- mergeron/data/damodaran_margin_data.xls +0 -0
- mergeron/demo/__init__.py +0 -3
- mergeron/demo/visualize_empirical_margin_distribution.py +0 -94
- mergeron-2025.739319.3.dist-info/METADATA +0 -174
- mergeron-2025.739319.3.dist-info/RECORD +0 -22
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Functions for generating synthetic data under specified distributions.
|
|
3
3
|
|
|
4
|
-
Uses multiple CPUs when available, with PCG64DXSM as the PRNG
|
|
5
|
-
|
|
4
|
+
Uses multiple CPUs when available, with PCG64DXSM as the PRNG. [#]_
|
|
5
|
+
|
|
6
|
+
References
|
|
7
|
+
----------
|
|
8
|
+
|
|
9
|
+
.. [#] See,
|
|
10
|
+
https://numpy.org/doc/stable/reference/random/generated/numpy.random.PCG64DXSM.html
|
|
6
11
|
|
|
7
12
|
"""
|
|
8
13
|
|
|
@@ -14,7 +19,7 @@ from typing import Literal
|
|
|
14
19
|
|
|
15
20
|
import numpy as np
|
|
16
21
|
from attrs import Attribute, Converter, define, field
|
|
17
|
-
from numpy.random import
|
|
22
|
+
from numpy.random import Generator, SeedSequence
|
|
18
23
|
|
|
19
24
|
from .. import ( # noqa: TID252
|
|
20
25
|
NTHREADS,
|
|
@@ -24,6 +29,7 @@ from .. import ( # noqa: TID252
|
|
|
24
29
|
this_yaml,
|
|
25
30
|
yaml_rt_mapper,
|
|
26
31
|
)
|
|
32
|
+
from . import DEFAULT_BITGENERATOR
|
|
27
33
|
|
|
28
34
|
__version__ = VERSION
|
|
29
35
|
|
|
@@ -32,24 +38,24 @@ DEFAULT_BETA_DIST_PARMS: ArrayFloat = np.array([1.0, 1.0], float)
|
|
|
32
38
|
|
|
33
39
|
|
|
34
40
|
def prng(_s: SeedSequence | None = None, /) -> np.random.Generator:
|
|
35
|
-
"""
|
|
41
|
+
"""Return a psure-random number generator.
|
|
36
42
|
|
|
37
43
|
Parameters
|
|
38
44
|
----------
|
|
39
45
|
_s
|
|
40
|
-
SeedSequence, for generating random numbers
|
|
46
|
+
SeedSequence, for generating repeatable, non-overlapping random numbers.
|
|
41
47
|
|
|
42
48
|
Returns
|
|
43
49
|
-------
|
|
44
|
-
A numpy random
|
|
50
|
+
A numpy random generator.
|
|
45
51
|
|
|
46
52
|
"""
|
|
47
|
-
return Generator(
|
|
53
|
+
return Generator(
|
|
54
|
+
DEFAULT_BITGENERATOR(SeedSequence(pool_size=8) if _s is None else _s)
|
|
55
|
+
)
|
|
48
56
|
|
|
49
57
|
|
|
50
58
|
# Add yaml representer, constructor for SeedSequence
|
|
51
|
-
|
|
52
|
-
|
|
53
59
|
(_, _) = (
|
|
54
60
|
this_yaml.representer.add_representer(
|
|
55
61
|
SeedSequence, lambda _r, _d: _r.represent_mapping("!SeedSequence", _d.state)
|
|
@@ -63,14 +69,14 @@ def prng(_s: SeedSequence | None = None, /) -> np.random.Generator:
|
|
|
63
69
|
def gen_seed_seq_list_default(
|
|
64
70
|
_len: int = 3, /, *, generated_entropy: Sequence[int] | None = None
|
|
65
71
|
) -> tuple[SeedSequence, ...]:
|
|
66
|
-
"""
|
|
67
|
-
Return specified number of SeedSequences, for generating random variates
|
|
72
|
+
R"""
|
|
73
|
+
Return specified number of SeedSequences, for generating random variates.
|
|
68
74
|
|
|
69
75
|
Initializes a specified number of SeedSequences based on a set of
|
|
70
76
|
10 generated "seeds" in a hard-coded list. If the required number of
|
|
71
77
|
random variates is larger than 10, the user must first generate
|
|
72
78
|
a sufficient number of seeds to draw upon for initializing SeedSequences.
|
|
73
|
-
The generated
|
|
79
|
+
The generated entropy can be reused in subsequent calls to this function.
|
|
74
80
|
|
|
75
81
|
Parameters
|
|
76
82
|
----------
|
|
@@ -85,20 +91,18 @@ def gen_seed_seq_list_default(
|
|
|
85
91
|
Returns
|
|
86
92
|
-------
|
|
87
93
|
A list of numpy SeedSequence objects, which can be used to seed prng() or to spawn
|
|
88
|
-
seed sequences that can be used as seeds to generate non-overlapping streams in parallel.
|
|
94
|
+
seed sequences that can be used as seeds to generate non-overlapping streams in parallel. [#]_
|
|
89
95
|
|
|
90
96
|
Raises
|
|
91
97
|
------
|
|
92
98
|
ValueError
|
|
93
|
-
When, :math
|
|
99
|
+
When, :math:`\_sseq\_list\_len > max(10, len(generated\_entropy))`.
|
|
94
100
|
|
|
95
101
|
References
|
|
96
102
|
----------
|
|
97
|
-
*See*, https://numpy.org/doc/stable/reference/random/parallel.html
|
|
98
|
-
|
|
103
|
+
.. [#] *See*, https://numpy.org/doc/stable/reference/random/parallel.html
|
|
99
104
|
|
|
100
105
|
"""
|
|
101
|
-
|
|
102
106
|
generated_entropy = generated_entropy or [
|
|
103
107
|
92156365243929466422624541055805800714117298857186959727264899187749727119124,
|
|
104
108
|
45508962760932900824607908382088764294813063250106926349700153055300051503944,
|
|
@@ -214,7 +218,6 @@ class MultithreadedRNG:
|
|
|
214
218
|
|
|
215
219
|
def fill(self) -> None:
|
|
216
220
|
"""Fill the provided output array with random number draws as specified."""
|
|
217
|
-
|
|
218
221
|
if not len(self.dist_parms) or np.array_equal(
|
|
219
222
|
self.dist_parms, DEFAULT_DIST_PARMS
|
|
220
223
|
):
|
mergeron/data/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Data useful for empirical analysis of merger enforcement policy
|
|
2
|
+
Data useful for empirical analysis of merger enforcement policy.
|
|
3
3
|
|
|
4
4
|
These data are processed for further analysis within relevant
|
|
5
5
|
submodules of the parent package. Thus, direct access is
|
|
@@ -14,39 +14,41 @@ __version__ = VERSION
|
|
|
14
14
|
|
|
15
15
|
data_resources = resources.files(f"{_PKG_NAME}.data")
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
DAMODARAN_MARGIN_DATA = data_resources / "damodaran_margin_data_serialized.zip"
|
|
18
18
|
"""
|
|
19
|
-
|
|
19
|
+
Included copy of Prof. Damodaran's margin data.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Use for replication/testing.
|
|
22
22
|
|
|
23
23
|
NOTES
|
|
24
24
|
-----
|
|
25
25
|
Source data are from Prof. Aswath Damodaran, Stern School of Business, NYU; available online
|
|
26
|
-
at https://pages.stern.nyu.edu/~adamodar/pc/datasets/margin.xls
|
|
26
|
+
at https://pages.stern.nyu.edu/~adamodar/pc/datasets/margin.xls
|
|
27
|
+
and https://pages.stern.nyu.edu/~adamodar/pc/archives/margin*.xls.
|
|
27
28
|
|
|
29
|
+
Gross margins are reported in 2017 data and later.
|
|
28
30
|
|
|
29
31
|
Use as, for example:
|
|
30
32
|
|
|
31
33
|
.. code-block:: python
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
import mergeron.data as mdat
|
|
34
36
|
|
|
35
|
-
shutil.copy2(
|
|
37
|
+
shutil.copy2(mdat.DAMODARAN_MARGIN_DATA, Path.home() / f"{DAMODARAN_MARGIN_DATA.name}")
|
|
36
38
|
"""
|
|
37
39
|
|
|
38
40
|
FTC_MERGER_INVESTIGATIONS_DATA = data_resources / "ftc_merger_investigations_data.zip"
|
|
39
41
|
"""
|
|
40
|
-
FTC merger
|
|
42
|
+
FTC merger investigations data published in 2004, 2007, 2008, and 2013
|
|
41
43
|
|
|
42
44
|
NOTES
|
|
43
45
|
-----
|
|
44
|
-
Raw data tables published by the FTC are loaded into a nested
|
|
46
|
+
Raw data tables published by the FTC are loaded into a nested dictionary, organized by
|
|
45
47
|
data period, table type, and table number. Each table is stored as a numerical array
|
|
46
|
-
(:mod:`numpy`
|
|
48
|
+
(:mod:`numpy` array), with additional attributes for the industry group and additional
|
|
47
49
|
evidence noted in the source data.
|
|
48
50
|
|
|
49
|
-
Data for
|
|
51
|
+
Data for additional data periods (time spans) not reported in the source data,
|
|
50
52
|
e.g., 2004-2011, are constructed by subtracting counts in the base data from counts
|
|
51
53
|
in the cumulative data, by table, for "enforced" mergers and "closed" mergers, when
|
|
52
54
|
the cumulative data for the longer period are consistent with the base data for
|
|
Binary file
|
mergeron/gen/__init__.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Defines constants, specifications (classes with attributes defining varous parameters) and
|
|
3
|
-
containers for industry data generation and testing.
|
|
4
|
-
|
|
5
|
-
"""
|
|
1
|
+
"""Defines constants, specifications and containers for industry data generation and testing."""
|
|
6
2
|
|
|
7
3
|
from __future__ import annotations
|
|
8
4
|
|
|
@@ -45,6 +41,8 @@ DEFAULT_BETA_BND_DIST_PARMS = np.array([0.5, 1.0, 0.0, 1.0], float)
|
|
|
45
41
|
|
|
46
42
|
@frozen
|
|
47
43
|
class SeedSequenceData:
|
|
44
|
+
"""Seed sequence values for shares, margins, and, optionally, firm-counts and prices."""
|
|
45
|
+
|
|
48
46
|
share: SeedSequence = field(eq=attrgetter("state"))
|
|
49
47
|
pcm: SeedSequence = field(eq=attrgetter("state"))
|
|
50
48
|
fcounts: SeedSequence | None = field(eq=lambda x: x if x is None else x.state)
|
|
@@ -78,7 +76,7 @@ class SHRDistribution(str, Enameled):
|
|
|
78
76
|
"""Shape parameter for all merging-firm-shares is unity (1)"""
|
|
79
77
|
|
|
80
78
|
DIR_FLAT_CONSTR = "Flat Dirichlet - Constrained"
|
|
81
|
-
"""Impose minimum
|
|
79
|
+
"""Impose minimum probability weight on each firm-count
|
|
82
80
|
|
|
83
81
|
Only firm-counts with probability weight of 3% or more
|
|
84
82
|
are included for data generation.
|
|
@@ -149,9 +147,9 @@ def _shr_dp_conv(_v: Sequence[float] | ArrayFloat | None, _i: ShareSpec) -> Arra
|
|
|
149
147
|
|
|
150
148
|
@frozen
|
|
151
149
|
class ShareSpec:
|
|
152
|
-
"""Market share specification
|
|
150
|
+
"""Market share specification.
|
|
153
151
|
|
|
154
|
-
A
|
|
152
|
+
A salient feature of market-share specification in this package is that
|
|
155
153
|
the draws represent markets with multiple different firm-counts.
|
|
156
154
|
Firm-counts are unspecified if the share distribution is
|
|
157
155
|
:attr:`mergeron.SHRDistribution.UNI`, for Dirichlet-distributed market-shares,
|
|
@@ -165,7 +163,6 @@ class ShareSpec:
|
|
|
165
163
|
In other words, recapture ratios cannot be estimated using
|
|
166
164
|
outside-good choice probabilities if the distribution of markets over firm-counts
|
|
167
165
|
is unspecified.
|
|
168
|
-
|
|
169
166
|
"""
|
|
170
167
|
|
|
171
168
|
dist_type: SHRDistribution = field(kw_only=False)
|
|
@@ -181,10 +178,9 @@ class ShareSpec:
|
|
|
181
178
|
Defaults to :attr:`DEFAULT_FCOUNT_WTS`, which specifies pre-merger
|
|
182
179
|
firm-counts of 2 to 7 with weights in descending order from 6 to 1.
|
|
183
180
|
|
|
184
|
-
ALERT: Firm-count weights are irrelevant when the
|
|
181
|
+
ALERT: Firm-count weights are irrelevant when the merging firms' shares are specified
|
|
185
182
|
to have uniform distribution; therefore this attribute is forced to None if
|
|
186
183
|
:attr:`.dist_type` == :attr:`.SHRDistribution.UNI`.
|
|
187
|
-
|
|
188
184
|
"""
|
|
189
185
|
|
|
190
186
|
@firm_counts_weights.default
|
|
@@ -210,7 +206,6 @@ class ShareSpec:
|
|
|
210
206
|
for Dirichlet-type distributions, a vector of shape parameters of length
|
|
211
207
|
equal to 1 plus the length of firm-count weights below; defaults depend on
|
|
212
208
|
type of Dirichlet-distribution specified.
|
|
213
|
-
|
|
214
209
|
"""
|
|
215
210
|
|
|
216
211
|
@dist_parms.default
|
|
@@ -227,7 +222,7 @@ class ShareSpec:
|
|
|
227
222
|
and 0 < len(_v) < (1 + len(_i.firm_counts_weights))
|
|
228
223
|
):
|
|
229
224
|
raise ValueError(
|
|
230
|
-
"If specified, the number of distribution parameters must
|
|
225
|
+
"If specified, the number of distribution parameters must equal or "
|
|
231
226
|
"exceed the maximum firm-count premerger, which is "
|
|
232
227
|
"1 plus the length of the vector specifying firm-count weights."
|
|
233
228
|
)
|
|
@@ -252,7 +247,7 @@ class ShareSpec:
|
|
|
252
247
|
outside good choice probabilities (:attr:`mergeron.RECForm.OUTIN`).
|
|
253
248
|
|
|
254
249
|
The recapture ratio is usually calibrated to the numbers-equivalent of the
|
|
255
|
-
HHI threshold for the
|
|
250
|
+
HHI threshold for the presumption of harm from unilateral competitive effects
|
|
256
251
|
in published merger guidelines. Accordingly, the recapture ratio rounded to
|
|
257
252
|
the nearest 5% is:
|
|
258
253
|
|
|
@@ -267,7 +262,6 @@ class ShareSpec:
|
|
|
267
262
|
|
|
268
263
|
ALERT: If diversion ratios are estimated by specifying a choice probability for the
|
|
269
264
|
outside good, the recapture ratio is set to None, overriding any user-specified value.
|
|
270
|
-
|
|
271
265
|
"""
|
|
272
266
|
|
|
273
267
|
@recapture_ratio.default
|
|
@@ -281,7 +275,7 @@ class ShareSpec:
|
|
|
281
275
|
elif _v is None and _i.recapture_form != RECForm.OUTIN:
|
|
282
276
|
raise ValueError(
|
|
283
277
|
f"Recapture specification, {_i.recapture_form!r} requires that "
|
|
284
|
-
"the market sample specification
|
|
278
|
+
"the market sample specification includes a recapture ratio in the "
|
|
285
279
|
"interval [0, 1)."
|
|
286
280
|
)
|
|
287
281
|
|
|
@@ -300,10 +294,10 @@ class PCMDistribution(str, Enameled):
|
|
|
300
294
|
@this_yaml.register_class
|
|
301
295
|
@enum.unique
|
|
302
296
|
class PCMRestriction(str, Enameled):
|
|
303
|
-
"""Firm 2 margins
|
|
297
|
+
"""Restriction on generated Firm 2 margins."""
|
|
304
298
|
|
|
305
|
-
IID = "
|
|
306
|
-
MNL = "MNL
|
|
299
|
+
IID = "independent and identically distributed (IID)"
|
|
300
|
+
MNL = "Nash-Bertrand equilibrium with multinomial logit (MNL) demand"
|
|
307
301
|
SYM = "symmetric"
|
|
308
302
|
|
|
309
303
|
|
|
@@ -320,7 +314,7 @@ def _pcm_dp_conv(_v: ArrayFloat | Sequence[float] | None, _i: PCMSpec) -> ArrayF
|
|
|
320
314
|
return DEFAULT_DIST_PARMS
|
|
321
315
|
elif _i.dist_type == PCMDistribution.EMPR and not isinstance(_v, np.ndarray):
|
|
322
316
|
raise ValueError(
|
|
323
|
-
"Invalid specification; use ..core.
|
|
317
|
+
"Invalid specification; use ..core.empirical_margin_distribution.margin_data_builder()[0]."
|
|
324
318
|
)
|
|
325
319
|
elif isinstance(_v, Sequence | np.ndarray):
|
|
326
320
|
return np.asarray(_v, float)
|
|
@@ -333,7 +327,7 @@ def _pcm_dp_conv(_v: ArrayFloat | Sequence[float] | None, _i: PCMSpec) -> ArrayF
|
|
|
333
327
|
|
|
334
328
|
@frozen
|
|
335
329
|
class PCMSpec:
|
|
336
|
-
"""Price-cost margin (PCM) specification
|
|
330
|
+
"""Price-cost margin (PCM) specification.
|
|
337
331
|
|
|
338
332
|
If price-cost margins are specified as having Beta distribution,
|
|
339
333
|
`dist_parms` is specified as a pair of positive, non-zero shape parameters of
|
|
@@ -343,8 +337,6 @@ class PCMSpec:
|
|
|
343
337
|
Bounded-Beta distribution, `dist_parms` is specified as
|
|
344
338
|
the tuple, (`mean`, `std deviation`, `min`, `max`), where `min` and `max`
|
|
345
339
|
are lower- and upper-bounds respectively within the interval :math:`[0, 1]`.
|
|
346
|
-
|
|
347
|
-
|
|
348
340
|
"""
|
|
349
341
|
|
|
350
342
|
dist_type: PCMDistribution = field()
|
|
@@ -365,7 +357,6 @@ class PCMSpec:
|
|
|
365
357
|
for Beta distribution, shape parameters, defaults to `(1, 1)`;
|
|
366
358
|
for Bounded-Beta distribution, vector of (min, max, mean, std. deviation), non-optional;
|
|
367
359
|
for empirical distribution based on Damodaran margin data, optional, ignored
|
|
368
|
-
|
|
369
360
|
"""
|
|
370
361
|
|
|
371
362
|
@dist_parms.default
|
|
@@ -399,7 +390,7 @@ class PCMSpec:
|
|
|
399
390
|
|
|
400
391
|
elif _i.dist_type == PCMDistribution.EMPR and not isinstance(_v, np.ndarray):
|
|
401
392
|
raise ValueError(
|
|
402
|
-
"Empirical distribution requires
|
|
393
|
+
"Empirical distribution requires deserialized margin data from Prof. Damodaran, NYU"
|
|
403
394
|
)
|
|
404
395
|
|
|
405
396
|
pcm_restriction: PCMRestriction = field(kw_only=True, default=PCMRestriction.IID)
|
|
@@ -409,9 +400,9 @@ class PCMSpec:
|
|
|
409
400
|
def __prv(_i: PCMSpec, _a: Attribute[PCMRestriction], _v: PCMRestriction) -> None:
|
|
410
401
|
if _v == PCMRestriction.MNL and _i.dist_type == PCMDistribution.EMPR:
|
|
411
402
|
print(
|
|
412
|
-
"NOTE:
|
|
413
|
-
"
|
|
414
|
-
"
|
|
403
|
+
"NOTE: For consistency of generated Firm 2 margins with source data,",
|
|
404
|
+
"respecify pcm_spec.pcm_restriction = PCMRestriction.IID.",
|
|
405
|
+
sep="\n",
|
|
415
406
|
)
|
|
416
407
|
|
|
417
408
|
|
|
@@ -458,7 +449,7 @@ class SSZConstant(float, Enameled):
|
|
|
458
449
|
|
|
459
450
|
@frozen
|
|
460
451
|
class MarketSampleData:
|
|
461
|
-
"""Container for generated
|
|
452
|
+
"""Container for generated market sample dataset."""
|
|
462
453
|
|
|
463
454
|
frmshr_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
464
455
|
"""Merging-firm shares (with two merging firms)"""
|
|
@@ -509,7 +500,7 @@ class MarketSampleData:
|
|
|
509
500
|
return retval
|
|
510
501
|
|
|
511
502
|
hhi_post: ArrayDouble = field(eq=cmp_using(np.array_equal))
|
|
512
|
-
"""Post-merger
|
|
503
|
+
"""Post-merger contribution to Herfindahl-Hirschman Index (HHI)"""
|
|
513
504
|
|
|
514
505
|
@hhi_post.default
|
|
515
506
|
def __hpd(_i: MarketSampleData) -> ArrayDouble:
|
|
@@ -538,7 +529,7 @@ class MarketSampleData:
|
|
|
538
529
|
|
|
539
530
|
|
|
540
531
|
@frozen
|
|
541
|
-
class
|
|
532
|
+
class ShareSampleData:
|
|
542
533
|
"""Container for generated market shares.
|
|
543
534
|
|
|
544
535
|
Includes related measures of market structure
|
|
@@ -559,7 +550,7 @@ class ShareDataSample:
|
|
|
559
550
|
|
|
560
551
|
|
|
561
552
|
@frozen
|
|
562
|
-
class
|
|
553
|
+
class PriceSampleData:
|
|
563
554
|
"""Container for generated price array, and related."""
|
|
564
555
|
|
|
565
556
|
price_array: ArrayDouble
|
|
@@ -570,7 +561,7 @@ class PriceDataSample:
|
|
|
570
561
|
|
|
571
562
|
|
|
572
563
|
@frozen
|
|
573
|
-
class
|
|
564
|
+
class MarginSampleData:
|
|
574
565
|
"""Container for generated margin array and related MNL test array."""
|
|
575
566
|
|
|
576
567
|
pcm_array: ArrayDouble
|
|
@@ -581,7 +572,7 @@ class MarginDataSample:
|
|
|
581
572
|
|
|
582
573
|
Applying restrictions from Bertrand-Nash oligopoly
|
|
583
574
|
with MNL demand results in draws of Firm 2 PCM falling
|
|
584
|
-
outside the
|
|
575
|
+
outside the feasible interval,:math:`[0, 1]`, depending
|
|
585
576
|
on the configuration of merging firm shares. Such draws
|
|
586
577
|
are are flagged as infeasible (False)in :code:`mnl_test_array` while
|
|
587
578
|
draws with PCM values within the feasible range are
|
|
@@ -593,9 +584,11 @@ class MarginDataSample:
|
|
|
593
584
|
@this_yaml.register_class
|
|
594
585
|
@enum.unique
|
|
595
586
|
class INVResolution(str, Enameled):
|
|
587
|
+
"""Report investigations resulting in clearance; enforcement; or both, respectively."""
|
|
588
|
+
|
|
596
589
|
CLRN = "clearance"
|
|
597
590
|
ENFT = "enforcement"
|
|
598
|
-
BOTH = "
|
|
591
|
+
BOTH = "clearance and enforcement, respectively"
|
|
599
592
|
|
|
600
593
|
|
|
601
594
|
@frozen
|
|
@@ -639,24 +632,22 @@ class UPPTestRegime:
|
|
|
639
632
|
|
|
640
633
|
@frozen
|
|
641
634
|
class UPPTestsCounts:
|
|
642
|
-
"""Counts of markets resolved as specified
|
|
635
|
+
"""Counts of markets resolved as specified.
|
|
643
636
|
|
|
644
637
|
Resolution may be either :attr:`INVResolution.ENFT`,
|
|
645
638
|
:attr:`INVResolution.CLRN`, or :attr:`INVResolution.BOTH`.
|
|
646
|
-
In the case of :attr:`INVResolution.BOTH`, two
|
|
639
|
+
In the case of :attr:`INVResolution.BOTH`, two columns of counts
|
|
647
640
|
are returned: one for each resolution.
|
|
648
|
-
|
|
649
641
|
"""
|
|
650
642
|
|
|
651
643
|
by_firm_count: ArrayBIGINT = field(eq=cmp_using(eq=np.array_equal))
|
|
652
644
|
by_delta: ArrayBIGINT = field(eq=cmp_using(eq=np.array_equal))
|
|
653
645
|
by_conczone: ArrayBIGINT = field(eq=cmp_using(eq=np.array_equal))
|
|
654
|
-
"""Zones are "
|
|
655
|
-
with
|
|
646
|
+
"""Zones are "unconcentrated", "moderately concentrated", and "highly concentrated",
|
|
647
|
+
with further detail by HHI and ΔHHI for mergers in the "unconcentrated" and
|
|
656
648
|
"moderately concentrated" zones. See
|
|
657
649
|
:attr:`mergeron.gen.enforcement_stats.HMG_PRESUMPTION_ZONE_MAP` and
|
|
658
650
|
:attr:`mergeron.gen.enforcement_stats.ZONE_VALS` for more detail.
|
|
659
|
-
|
|
660
651
|
"""
|
|
661
652
|
|
|
662
653
|
|
mergeron/gen/data_generation.py
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Methods to generate data for analyzing merger enforcement policy.
|
|
3
|
-
|
|
4
|
-
"""
|
|
1
|
+
"""Methods to generate data for analyzing merger enforcement policy."""
|
|
5
2
|
|
|
6
3
|
from __future__ import annotations
|
|
7
4
|
|
|
@@ -52,13 +49,13 @@ H5_CHUNK_SIZE = 10**6
|
|
|
52
49
|
|
|
53
50
|
|
|
54
51
|
class SamplingFunctionKWArgs(TypedDict, total=False):
|
|
55
|
-
"Keyword arguments of sampling methods defined below"
|
|
52
|
+
"""Keyword arguments of sampling methods defined below."""
|
|
56
53
|
|
|
57
54
|
sample_size: int
|
|
58
55
|
"""number of draws to generate"""
|
|
59
56
|
|
|
60
57
|
seed_data: SeedSequenceData | None
|
|
61
|
-
"""seed data to ensure
|
|
58
|
+
"""seed data to ensure independent and replicable draws"""
|
|
62
59
|
|
|
63
60
|
nthreads: int
|
|
64
61
|
"""number of parallel threads to use"""
|
|
@@ -116,7 +113,7 @@ class MarketSample:
|
|
|
116
113
|
hsr_filing_test_type: SSZConstant = field(
|
|
117
114
|
default=SSZConstant.ONE, validator=validators.instance_of(SSZConstant)
|
|
118
115
|
)
|
|
119
|
-
"""Method for modeling HSR filing
|
|
116
|
+
"""Method for modeling HSR filing thresholds, see :class:`SSZConstant`"""
|
|
120
117
|
|
|
121
118
|
sample_size: int = field(default=10**6, validator=validators.instance_of(int))
|
|
122
119
|
"""number of draws to simulate"""
|
|
@@ -166,7 +163,7 @@ class MarketSample:
|
|
|
166
163
|
"""
|
|
167
164
|
Generate share, diversion ratio, price, and margin data for MarketSpec.
|
|
168
165
|
|
|
169
|
-
see :attr:`SamplingFunctionKWArgs` for description of
|
|
166
|
+
see :attr:`SamplingFunctionKWArgs` for description of keyword parameters
|
|
170
167
|
|
|
171
168
|
Returns
|
|
172
169
|
-------
|
|
@@ -174,7 +171,6 @@ class MarketSample:
|
|
|
174
171
|
in the sample
|
|
175
172
|
|
|
176
173
|
"""
|
|
177
|
-
|
|
178
174
|
# Scale up sample size to offset discards based on specified criteria
|
|
179
175
|
shr_sample_size = sample_size * self.hsr_filing_test_type
|
|
180
176
|
shr_sample_size *= (
|
|
@@ -253,14 +249,13 @@ class MarketSample:
|
|
|
253
249
|
)
|
|
254
250
|
|
|
255
251
|
def generate_sample(self, /) -> None:
|
|
256
|
-
"""Populate :attr:`data` with generated data
|
|
252
|
+
"""Populate :attr:`data` with generated data.
|
|
257
253
|
|
|
258
254
|
Returns
|
|
259
255
|
-------
|
|
260
256
|
None
|
|
261
257
|
|
|
262
258
|
"""
|
|
263
|
-
|
|
264
259
|
self.dataset = self._gen_market_sample(
|
|
265
260
|
seed_data=self.seed_data,
|
|
266
261
|
sample_size=self.sample_size,
|
|
@@ -277,11 +272,10 @@ class MarketSample:
|
|
|
277
272
|
sample_size: int = 10**6,
|
|
278
273
|
nthreads: int = NTHREADS,
|
|
279
274
|
) -> UPPTestsCounts:
|
|
280
|
-
"""Generate market data and
|
|
275
|
+
"""Generate market data and compute UPP test counts on same.
|
|
281
276
|
|
|
282
277
|
Parameters
|
|
283
278
|
----------
|
|
284
|
-
|
|
285
279
|
_upp_test_parms
|
|
286
280
|
Guidelines thresholds for testing UPP and related statistics
|
|
287
281
|
|
|
@@ -302,10 +296,9 @@ class MarketSample:
|
|
|
302
296
|
|
|
303
297
|
Returns
|
|
304
298
|
-------
|
|
305
|
-
UPPTestCounts
|
|
299
|
+
UPPTestCounts object with of test counts by firm count, ΔHHI and concentration zone
|
|
306
300
|
|
|
307
301
|
"""
|
|
308
|
-
|
|
309
302
|
market_data_sample = self._gen_market_sample(
|
|
310
303
|
sample_size=sample_size, seed_data=seed_data, nthreads=nthreads
|
|
311
304
|
)
|
|
@@ -319,7 +312,7 @@ class MarketSample:
|
|
|
319
312
|
def __sim_enf_cnts_ll(
|
|
320
313
|
self, _enf_parm_vec: gbl.HMGThresholds, _sim_test_regime: UPPTestRegime, /
|
|
321
314
|
) -> UPPTestsCounts:
|
|
322
|
-
"""
|
|
315
|
+
"""Parallelize data-generation and testing.
|
|
323
316
|
|
|
324
317
|
The parameters `_sim_enf_cnts_kwargs` are passed unaltered to
|
|
325
318
|
the parent function, `sim_enf_cnts()`, except that, if provided,
|
|
@@ -331,7 +324,6 @@ class MarketSample:
|
|
|
331
324
|
|
|
332
325
|
Parameters
|
|
333
326
|
----------
|
|
334
|
-
|
|
335
327
|
_enf_parm_vec
|
|
336
328
|
Guidelines thresholds to test against
|
|
337
329
|
|
|
@@ -346,7 +338,7 @@ class MarketSample:
|
|
|
346
338
|
"""
|
|
347
339
|
sample_sz = self.sample_size
|
|
348
340
|
subsample_sz = H5_CHUNK_SIZE
|
|
349
|
-
iter_count = (sample_sz / subsample_sz).__ceil__()
|
|
341
|
+
iter_count = (sample_sz / subsample_sz).__ceil__()
|
|
350
342
|
thread_count = self.nthreads or cpu_count()
|
|
351
343
|
|
|
352
344
|
if (
|
|
@@ -382,7 +374,8 @@ class MarketSample:
|
|
|
382
374
|
"nthreads": thread_count,
|
|
383
375
|
})
|
|
384
376
|
|
|
385
|
-
|
|
377
|
+
_threads = min(thread_count, iter_count)
|
|
378
|
+
res_list = Parallel(n_jobs=_threads, prefer="threads")(
|
|
386
379
|
delayed(self.__sim_enf_cnts)(
|
|
387
380
|
_enf_parm_vec,
|
|
388
381
|
_sim_test_regime,
|
|
@@ -434,7 +427,6 @@ class MarketSample:
|
|
|
434
427
|
None
|
|
435
428
|
|
|
436
429
|
"""
|
|
437
|
-
|
|
438
430
|
if self.dataset is None:
|
|
439
431
|
self.enf_counts = self.__sim_enf_cnts_ll(_enf_parm_vec, _upp_test_regime)
|
|
440
432
|
else:
|
|
@@ -445,6 +437,7 @@ class MarketSample:
|
|
|
445
437
|
def to_archive(
|
|
446
438
|
self, zip_: zipfile.ZipFile, _subdir: str = "", /, *, save_dataset: bool = False
|
|
447
439
|
) -> None:
|
|
440
|
+
"""Serialize market sample to Zip archive."""
|
|
448
441
|
zpath = zipfile.Path(zip_, at=_subdir)
|
|
449
442
|
name_root = f"{_PKG_NAME}_market_sample"
|
|
450
443
|
|
|
@@ -455,7 +448,7 @@ class MarketSample:
|
|
|
455
448
|
if all((_ndt := self.dataset is None, _net := self.enf_counts is None)):
|
|
456
449
|
raise ValueError(
|
|
457
450
|
"No dataset and/or enforcement counts available for saving. "
|
|
458
|
-
"Generate some data or set save_dataset to False to
|
|
451
|
+
"Generate some data or set save_dataset to False to proceed."
|
|
459
452
|
)
|
|
460
453
|
|
|
461
454
|
if not _ndt:
|
|
@@ -469,6 +462,7 @@ class MarketSample:
|
|
|
469
462
|
def from_archive(
|
|
470
463
|
zip_: zipfile.ZipFile, _subdir: str = "", /, *, restore_dataset: bool = False
|
|
471
464
|
) -> MarketSample:
|
|
465
|
+
"""Deserialize market sample from Zip archive."""
|
|
472
466
|
zpath = zipfile.Path(zip_, at=_subdir)
|
|
473
467
|
name_root = f"{_PKG_NAME}_market_sample"
|
|
474
468
|
|
|
@@ -486,11 +480,11 @@ class MarketSample:
|
|
|
486
480
|
|
|
487
481
|
if _dt:
|
|
488
482
|
with _dp.open("rb") as _hfh:
|
|
489
|
-
object.__setattr__(
|
|
483
|
+
object.__setattr__(
|
|
490
484
|
market_sample_, "dataset", MarketSampleData.from_h5f(_hfh)
|
|
491
485
|
)
|
|
492
486
|
if _et:
|
|
493
|
-
object.__setattr__(
|
|
487
|
+
object.__setattr__(
|
|
494
488
|
market_sample_, "enf_counts", this_yaml.load(_ep.read_text())
|
|
495
489
|
)
|
|
496
490
|
return market_sample_
|
|
@@ -499,6 +493,7 @@ class MarketSample:
|
|
|
499
493
|
def to_yaml(
|
|
500
494
|
cls, _r: yaml.representer.RoundTripRepresenter, _d: MarketSample
|
|
501
495
|
) -> yaml.MappingNode:
|
|
496
|
+
"""Serialize market sample to YAML representation."""
|
|
502
497
|
retval: yaml.MappingNode = _r.represent_mapping(
|
|
503
498
|
f"!{cls.__name__}",
|
|
504
499
|
{
|
|
@@ -513,4 +508,5 @@ class MarketSample:
|
|
|
513
508
|
def from_yaml(
|
|
514
509
|
cls, _c: yaml.constructor.RoundTripConstructor, _n: yaml.MappingNode
|
|
515
510
|
) -> MarketSample:
|
|
511
|
+
"""Deserialize market sample from YAML representation."""
|
|
516
512
|
return cls(**yaml_rt_mapper(_c, _n))
|