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 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.739104.1"
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 RECTypes(enum.StrEnum):
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 VERSION, ArrayDouble, RECTypes, UPPAggrSelector # noqa: TID252
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 Guidlines, the agencies do not define a
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 symmettry in numbers-equivalent of post-merger HHI
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[RECTypes],
227
- _value: RECTypes,
232
+ _attribute: Attribute[RECForm],
233
+ _value: RECForm,
228
234
  /,
229
235
  ) -> None:
230
- if _value == RECTypes.OUTIN and _instance.recapture_rate:
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.RECTypes.INOUT` here, and "
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=0.85, validator=validators.instance_of(float)
271
+ kw_only=False, default=DEFAULT_REC_RATE, validator=validators.instance_of(float)
266
272
  )
267
273
 
268
- recapture_form: RECTypes | None = field(
274
+ recapture_form: RECForm | None = field(
269
275
  kw_only=True,
270
- default=RECTypes.INOUT,
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.RECTypes.INOUT`, the recapture rate for
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.RECTypes.FIXED`. (To specify that recapture rates be
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.RECTypes.OUTIN`.)
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, /, *, m_star: float = 1.00, r_bar: float = 0.8
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, /, *, m_star: float = 1.00, r_bar: float = 0.8
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 = 0.85,
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 = 0.85,
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 = 0.85,
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 = 0.85, /, *, prec: int = 10
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 = 0.85,
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 = 0.85,
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
- RECTypes,
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 SHRDistributions(enum.StrEnum):
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.SHRDistributions.UNI`, for Dirichlet-distributed market-shares,
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.SHRDistributions.UNI`,
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.RECTypes.OUTIN`.
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.SHRDistributions`).
116
+ (see :class:`mergeron.gen.SHRDistribution`).
116
117
 
117
118
  """
118
119
 
119
- dist_type: SHRDistributions
120
- """See :class:`SHRDistributions`"""
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 == SHRDistributions.UNI:
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 {SHRDistributions.DIR_FLAT}, which gives "
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: RECTypes = field(default=RECTypes.INOUT)
157
- """See :class:`mergeron.RECTypes`"""
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[RECTypes], _v: RECTypes) -> None:
161
- if _v == RECTypes.OUTIN and _i.dist_type == SHRDistributions.UNI:
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=0.8)
168
- """A value between 0 and 1, typically 0.8.
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.RECTypes.OUTIN`).
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 != RECTypes.OUTIN:
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 PCMDistributions(enum.StrEnum):
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 FM2Constants(enum.StrEnum):
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: PCMDistributions = field(kw_only=False, default=PCMDistributions.UNI)
238
- """See :class:`PCMDistributions`"""
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 == PCMDistributions.BETA and len(_v) != len(("a", "b"))
264
+ _i.dist_type == PCMDistribution.BETA and len(_v) != len(("a", "b"))
264
265
  ) or (
265
- _i.dist_type == PCMDistributions.BETA_BND
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 == PCMDistributions.EMPR and _v is not None:
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: FM2Constants = field(kw_only=False, default=FM2Constants.IID)
280
- """See :class:`FM2Constants`"""
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 SSZConstants(float, enum.ReprEnum):
287
+ class SSZConstant(float, enum.ReprEnum):
285
288
  """
286
289
  Scale factors to offset sample size reduction.
287
290
 
@@ -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, RECTypes # noqa: TID252 # noqa
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
- FM2Constants,
20
+ FM2Constraint,
21
21
  MarketDataSample,
22
- PCMDistributions,
22
+ PCMDistribution,
23
23
  PCMSpec,
24
24
  PriceSpec,
25
25
  ShareSpec,
26
- SHRDistributions,
27
- SSZConstants,
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 function, :func:`MarketSample.sim_enf_cnts`"
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(SHRDistributions.UNI, None, None, RECTypes.INOUT, 0.8),
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(PCMDistributions.UNI, None, FM2Constants.IID)
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 == RECTypes.FIXED
90
- and _v.firm2_pcm_constraint == FM2Constants.MNL
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'"{FM2Constants.IID}" or "{FM2Constants.SYM}".'
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: SSZConstants = field(
105
+ hsr_filing_test_type: SSZConstant = field(
104
106
  kw_only=True,
105
- default=SSZConstants.ONE,
106
- validator=validators.instance_of(SSZConstants),
107
+ default=SSZConstant.ONE,
108
+ validator=validators.instance_of(SSZConstant),
107
109
  )
108
- """Method for modeling HSR filing threholds, see :class:`SSZConstants`"""
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 == FM2Constants.MNL:
153
- _shr_sample_size *= SSZConstants.MNL_DEP
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 == FM2Constants.MNL:
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 = 10**6,
244
- seed_seq_list: list[SeedSequence] | None,
245
+ sample_size: int,
246
+ seed_seq_list: Sequence[SeedSequence],
245
247
  nthreads: int,
246
- save_data_to_file: SaveData = False,
247
- saved_array_name_suffix: str = "",
248
+ save_data_to_file: SaveData,
249
+ saved_array_name_suffix: str,
248
250
  ) -> None:
249
- """Generate market data"""
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 UPPTestCounts
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 != RECTypes.OUTIN
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: list[SeedSequence] | None,
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
- """Estimate enforcement counts
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
- Size of the market sample drawn
492
+ Number of draws to simulate
482
493
 
483
494
  seed_seq_list
484
- List of :code:`numpy.random.SeedSequence` objects
495
+ List of seed sequences, to assure independent samples in each thread
485
496
 
486
497
  nthreads
487
- Number of threads to use
498
+ Number of parallel processes to use
488
499
 
489
500
  save_data_to_file
490
- Save data to given HDF5 file, at specified group node
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 VERSION, ArrayBIGINT, ArrayDouble, RECTypes # noqa: TID252
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
- FM2Constants,
30
+ FM2Constraint,
25
31
  MarginDataSample,
26
- PCMDistributions,
32
+ PCMDistribution,
27
33
  PCMSpec,
28
34
  PriceDataSample,
29
35
  PriceSpec,
30
36
  SeedSequenceData,
31
37
  ShareDataSample,
32
38
  ShareSpec,
33
- SHRDistributions,
34
- SSZConstants,
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 == SHRDistributions.UNI:
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 0.8
104
- if _recapture_form == RECTypes.INOUT:
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: RECTypes = RECTypes.INOUT,
181
- _dist_type_dir: SHRDistributions = SHRDistributions.DIR_FLAT,
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 == SHRDistributions.DIR_ASYM:
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 == SHRDistributions.DIR_COND:
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: RECTypes = RECTypes.INOUT,
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 RECTypes.FIXED, otherwise MNL-consistent. If
342
- RECTypes.OUTIN; the number of columns in the output share array
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 == RECTypes.OUTIN:
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 == RECTypes.OUTIN:
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: RECTypes,
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.RECTypes.OUTIN`, then the
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 == RECTypes.FIXED:
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: SSZConstants,
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.SSZConstants`.
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=FM2Constants.IID)
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 == FM2Constants.MNL:
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 SSZConstants.HSR_TEN:
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 SSZConstants.HSR_NTH:
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
- ) | (_frmshr_array.min(axis=1) >= _test_rev_ratio_inv)
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 == FM2Constants.SYM
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 == PCMDistributions.EMPR:
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 == PCMDistributions.UNI else "Beta"
684
- if _dist_type_pcm == PCMDistributions.BETA:
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 == PCMDistributions.BETA_BND: # Bounded beta
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 == PCMDistributions.UNI
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 == PCMDistributions.BETA_BND:
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 == FM2Constants.SYM:
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 == FM2Constants.MNL:
722
+ if _dist_firm2_pcm == FM2Constraint.MNL:
716
723
  # Impose FOCs from profit-maximization with MNL demand
717
- if _dist_type_pcm == PCMDistributions.EMPR:
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 = FM2Constants.IID."
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: SHRDistributions,
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 == SHRDistributions.UNI else 3
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 INDGRPConstants(enum.StrEnum):
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 EVIDENConstants(enum.StrEnum):
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: INDGRPConstants = INDGRPConstants.ALL,
240
- _table_evid_cond: EVIDENConstants = EVIDENConstants.UR,
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: INDGRPConstants,
297
- _table_evid_cond: EVIDENConstants,
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: INDGRPConstants = INDGRPConstants.ALL,
333
- _table_evid_cond: EVIDENConstants = EVIDENConstants.UR,
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: INDGRPConstants = INDGRPConstants.ALL,
369
- _table_evid_cond: EVIDENConstants = EVIDENConstants.UR,
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: INDGRPConstants = INDGRPConstants.ALL,
404
- _table_evid_cond: EVIDENConstants = EVIDENConstants.UR,
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
- RECTypes,
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
- _firm_counts_list = np.unique(_fcounts)
98
- if _firm_counts_list is not None and np.all(_firm_counts_list >= 0):
99
- # _max_firm_count = len(_firm_counts_list)
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 _firm_cnt in 1 + np.arange(1, _max_firm_count):
104
- _firm_count_test = _fcounts == _firm_cnt
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
- _firm_cnt,
110
- np.einsum("ij->", 1 * _firm_count_test),
108
+ _firmcount,
109
+ np.einsum("ij->", 1 * _firmcount_test),
111
110
  *[
112
111
  np.einsum(
113
112
  "ij->",
114
- 1 * (_firm_count_test & getattr(_upp_test_arrays, _f)),
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=MXow5RSpGz49ggTHygQKv5TIJd5iRpTKyQyT9sYGdm4,1455
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=Iu7MBg3WzaRUALOobNmzmv_g7l-Njk1k7Y-esLBZBbk,15541
7
- mergeron/core/guidelines_boundary_functions.py,sha256=gohb7Uj1AjJQtD5ew7bVZZjGhJEYCjNNAPB1o6TsA9M,29683
8
- mergeron/core/guidelines_boundary_functions_extra.py,sha256=t84dMsaMKnYUNuvvGrMCP6vI8MDn88PJOgGZlNe1Zts,11280
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=A-co8RQL-QTsgfErF2O6qC3zcYYR56F6m9PBjH_M1kk,16564
24
- mergeron/gen/data_generation.py,sha256=_sjFm426uIj0MkoMIWQyL8hq3P4XbBIKe0kMUy7aVps,16449
25
- mergeron/gen/data_generation_functions.py,sha256=Pglv9rzeb2mG1SKp01AtkILJdvggDQvDnK-u90019po,28505
26
- mergeron/gen/enforcement_stats.py,sha256=ANnviFnoSUAwZOM5cMG1WrZwbkjuUohplRn5fQmd8Bs,27410
27
- mergeron/gen/upp_tests.py,sha256=iDtfy-lc7uSnBK37f_AxDIz3mO7Gf7-G5tHbX3QRDI0,12647
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.739104.1.dist-info/METADATA,sha256=A4K7HcIzkIoT7KUmlNOqUic42V1psF1kxNgtIih5l68,10922
30
- mergeron-2024.739104.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
31
- mergeron-2024.739104.1.dist-info/RECORD,,
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
-