mergeron 2025.739439.11__py3-none-any.whl → 2025.739439.13__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/gen/__init__.py CHANGED
@@ -15,7 +15,7 @@ from attrs import Attribute, Converter, cmp_using, field, frozen
15
15
  from numpy.random import SeedSequence
16
16
 
17
17
  from .. import ( # noqa: TID252
18
- DEFAULT_REC_RATIO,
18
+ DEFAULT_REC,
19
19
  VERSION,
20
20
  ArrayBIGINT,
21
21
  ArrayBoolean,
@@ -89,9 +89,9 @@ class SHRDistribution(str, Enameled):
89
89
  """Share distribution for merging-firm shares has a higher peak share
90
90
 
91
91
  By default, shape parameter for merging-firm-share is 2.5, and
92
- 1.0 for all others. Defining, :attr:`.ShareSpec.dist_parms`
92
+ 1.0 for all others. Defining, :attr:`.MarketShareSpec.dist_parms`
93
93
  as a vector of shape parameters with length matching
94
- that of :attr:`.ShareSpec.dist_parms` allows flexible specification
94
+ that of :attr:`.MarketShareSpec.dist_parms` allows flexible specification
95
95
  of Dirichlet-distributed share-data generation.
96
96
  """
97
97
 
@@ -104,7 +104,7 @@ class SHRDistribution(str, Enameled):
104
104
 
105
105
 
106
106
  def _fc_wts_conv(
107
- _v: Sequence[float | int] | ArrayDouble | ArrayINT | None, _i: ShareSpec
107
+ _v: Sequence[float | int] | ArrayDouble | ArrayINT | None, _i: MarketShareSpec
108
108
  ) -> ArrayFloat | None:
109
109
  if _i.dist_type == SHRDistribution.UNI:
110
110
  return None
@@ -114,7 +114,9 @@ def _fc_wts_conv(
114
114
  return _tv if (_tv := np.asarray(_v, float)).sum() == 1 else _tv / _tv.sum()
115
115
 
116
116
 
117
- def _shr_dp_conv(_v: Sequence[float] | ArrayFloat | None, _i: ShareSpec) -> ArrayFloat:
117
+ def _shr_dp_conv(
118
+ _v: Sequence[float] | ArrayFloat | None, _i: MarketShareSpec
119
+ ) -> ArrayFloat:
118
120
  if _v is None or len(_v) == 0 or np.array_equal(_v, DEFAULT_DIST_PARMS):
119
121
  if _i.dist_type == SHRDistribution.UNI:
120
122
  return DEFAULT_DIST_PARMS
@@ -149,7 +151,7 @@ def _shr_dp_conv(_v: Sequence[float] | ArrayFloat | None, _i: ShareSpec) -> Arra
149
151
 
150
152
 
151
153
  @frozen
152
- class ShareSpec:
154
+ class MarketShareSpec:
153
155
  """Market share specification.
154
156
 
155
157
  A salient feature of market-share specification in this package is that
@@ -163,7 +165,7 @@ class ShareSpec:
163
165
  -----
164
166
  If :attr:`.dist_type` == :attr:`.SHRDistribution.UNI`, it is then infeasible that
165
167
  :attr:`.recapture_form` == :attr:`mergeron.RECForm.OUTIN`.
166
- In other words, recapture ratios cannot be estimated using
168
+ In other words, recapture rates cannot be estimated using
167
169
  outside-good choice probabilities if the distribution of markets over firm-counts
168
170
  is unspecified.
169
171
  """
@@ -187,11 +189,11 @@ class ShareSpec:
187
189
  """
188
190
 
189
191
  @firm_counts_weights.default
190
- def __fcwd(_i: ShareSpec) -> ArrayFloat | None:
192
+ def __fcwd(_i: MarketShareSpec) -> ArrayFloat | None:
191
193
  return _fc_wts_conv(None, _i)
192
194
 
193
195
  @firm_counts_weights.validator
194
- def __fcv(_i: ShareSpec, _a: Attribute[ArrayFloat], _v: ArrayFloat) -> None:
196
+ def __fcv(_i: MarketShareSpec, _a: Attribute[ArrayFloat], _v: ArrayFloat) -> None:
195
197
  if _i.dist_type != SHRDistribution.UNI and not len(_v):
196
198
  raise ValueError(
197
199
  f"Attribute, {'"firm_counts_weights"'} must be populated if the share distribution is "
@@ -212,13 +214,13 @@ class ShareSpec:
212
214
  """
213
215
 
214
216
  @dist_parms.default
215
- def __dpd(_i: ShareSpec) -> ArrayFloat:
217
+ def __dpd(_i: MarketShareSpec) -> ArrayFloat:
216
218
  # converters run after defaults, and we
217
219
  # avoid redundancy and confusion here
218
220
  return _shr_dp_conv(None, _i)
219
221
 
220
222
  @dist_parms.validator
221
- def __dpv(_i: ShareSpec, _a: Attribute[ArrayFloat], _v: ArrayFloat) -> None:
223
+ def __dpv(_i: MarketShareSpec, _a: Attribute[ArrayFloat], _v: ArrayFloat) -> None:
222
224
  if (
223
225
  _i.firm_counts_weights is not None
224
226
  and _v is not None
@@ -234,7 +236,7 @@ class ShareSpec:
234
236
  """See :class:`mergeron.RECForm`"""
235
237
 
236
238
  @recapture_form.validator
237
- def __rfv(_i: ShareSpec, _a: Attribute[RECForm], _v: RECForm) -> None:
239
+ def __rfv(_i: MarketShareSpec, _a: Attribute[RECForm], _v: RECForm) -> None:
238
240
  if _i.dist_type == SHRDistribution.UNI and _v == RECForm.OUTIN:
239
241
  raise ValueError(
240
242
  "Outside-good choice probabilities cannot be generated if the "
@@ -243,15 +245,15 @@ class ShareSpec:
243
245
  "firm-counts unspecified."
244
246
  )
245
247
 
246
- recapture_ratio: float | None = field(kw_only=True)
248
+ recapture_rate: float | None = field(kw_only=True)
247
249
  """A value between 0 and 1.
248
250
 
249
251
  :code:`None` if market share specification requires direct generation of
250
252
  outside good choice probabilities (:attr:`mergeron.RECForm.OUTIN`).
251
253
 
252
- The recapture ratio is usually calibrated to the numbers-equivalent of the
254
+ The recapture rate is usually calibrated to the numbers-equivalent of the
253
255
  HHI threshold for the presumption of harm from unilateral competitive effects
254
- in published merger guidelines. Accordingly, the recapture ratio rounded to
256
+ in published merger guidelines. Accordingly, the recapture rate rounded to
255
257
  the nearest 5% is:
256
258
 
257
259
  * 0.85, **7-to-6 merger from symmetry**; US Guidelines, 1992, 2023
@@ -264,21 +266,21 @@ class ShareSpec:
264
266
  presumptions no harm.)
265
267
 
266
268
  ALERT: If diversion ratios are estimated by specifying a choice probability for the
267
- outside good, the recapture ratio is set to None, overriding any user-specified value.
269
+ outside good, the recapture rate is set to None, overriding any user-specified value.
268
270
  """
269
271
 
270
- @recapture_ratio.default
271
- def __rrd(_i: ShareSpec) -> float | None:
272
- return None if _i.recapture_form == RECForm.OUTIN else DEFAULT_REC_RATIO
272
+ @recapture_rate.default
273
+ def __rrd(_i: MarketShareSpec) -> float | None:
274
+ return None if _i.recapture_form == RECForm.OUTIN else DEFAULT_REC
273
275
 
274
- @recapture_ratio.validator
275
- def __rrv(_i: ShareSpec, _a: Attribute[float], _v: float) -> None:
276
+ @recapture_rate.validator
277
+ def __rrv(_i: MarketShareSpec, _a: Attribute[float], _v: float) -> None:
276
278
  if _v and not (0 < _v <= 1):
277
- raise ValueError("Recapture ratio must lie in the interval, [0, 1).")
279
+ raise ValueError("Recapture rate must lie in the interval, [0, 1).")
278
280
  elif _v is None and _i.recapture_form != RECForm.OUTIN:
279
281
  raise ValueError(
280
282
  f"Recapture specification, {_i.recapture_form!r} requires that "
281
- "the market sample specification includes a recapture ratio in the "
283
+ "the market sample specification includes a recapture rate in the "
282
284
  "interval [0, 1)."
283
285
  )
284
286
 
@@ -455,7 +457,7 @@ class SSZConstant(float, Enameled):
455
457
 
456
458
 
457
459
  @frozen
458
- class MarketSampleData:
460
+ class MarketsData:
459
461
  """Container for generated market sample dataset."""
460
462
 
461
463
  frmshr_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
@@ -467,7 +469,7 @@ class MarketSampleData:
467
469
  price_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
468
470
  """Merging-firms' price-cost margins (PCM)"""
469
471
 
470
- divr_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
472
+ divratio_array: ArrayDouble = field(eq=cmp_using(np.array_equal))
471
473
  """Diversion ratio between the merging firms"""
472
474
 
473
475
  hhi_delta: ArrayDouble = field(eq=cmp_using(np.array_equal))
@@ -481,7 +483,7 @@ class MarketSampleData:
481
483
  """
482
484
 
483
485
  @aggregate_purchase_prob.default
484
- def __appd(_i: MarketSampleData) -> ArrayDouble:
486
+ def __appd(_i: MarketsData) -> ArrayDouble:
485
487
  retval: ArrayDouble = np.empty_like(_i.frmshr_array[:, :1], float)
486
488
  retval.fill(np.nan)
487
489
  return retval
@@ -490,7 +492,7 @@ class MarketSampleData:
490
492
  """Number of firms in market"""
491
493
 
492
494
  @fcounts.default
493
- def __fcd(_i: MarketSampleData) -> ArrayINT:
495
+ def __fcd(_i: MarketsData) -> ArrayINT:
494
496
  return np.zeros_like(_i.frmshr_array[:, :1], np.uint8)
495
497
 
496
498
  nth_firm_share: ArrayDouble = field(eq=cmp_using(np.array_equal))
@@ -501,7 +503,7 @@ class MarketSampleData:
501
503
  """
502
504
 
503
505
  @nth_firm_share.default
504
- def __nfsd(_i: MarketSampleData) -> ArrayDouble:
506
+ def __nfsd(_i: MarketsData) -> ArrayDouble:
505
507
  retval: ArrayDouble = np.empty_like(_i.frmshr_array[:, :1], float)
506
508
  retval.fill(np.nan)
507
509
  return retval
@@ -510,7 +512,7 @@ class MarketSampleData:
510
512
  """Post-merger contribution to Herfindahl-Hirschman Index (HHI)"""
511
513
 
512
514
  @hhi_post.default
513
- def __hpd(_i: MarketSampleData) -> ArrayDouble:
515
+ def __hpd(_i: MarketsData) -> ArrayDouble:
514
516
  retval: ArrayDouble = np.empty_like(_i.frmshr_array[:, :1], float)
515
517
  retval.fill(np.nan)
516
518
  return retval
@@ -530,7 +532,7 @@ class MarketSampleData:
530
532
  @classmethod
531
533
  def from_h5f(
532
534
  cls, _hfh: io.BufferedReader | zipfile.ZipExtFile | IO[bytes]
533
- ) -> MarketSampleData:
535
+ ) -> MarketsData:
534
536
  """Load market sample data from HDF5 file."""
535
537
  with h5py.File(_hfh, "r") as _h5f:
536
538
  _retval = cls(**{_a: _h5f[_a][:] for _a in _h5f})
@@ -538,7 +540,7 @@ class MarketSampleData:
538
540
 
539
541
 
540
542
  @frozen
541
- class ShareSampleData:
543
+ class MarketSharesData:
542
544
  """Container for generated market shares.
543
545
 
544
546
  Includes related measures of market structure
@@ -559,7 +561,7 @@ class ShareSampleData:
559
561
 
560
562
 
561
563
  @frozen
562
- class PriceSampleData:
564
+ class PricesData:
563
565
  """Container for generated price array, and related."""
564
566
 
565
567
  price_array: ArrayDouble
@@ -570,19 +572,19 @@ class PriceSampleData:
570
572
 
571
573
 
572
574
  @frozen
573
- class MarginSampleData:
575
+ class MarginsData:
574
576
  """Container for generated margin array and related MNL test array."""
575
577
 
576
578
  pcm_array: ArrayDouble
577
579
  """Merging-firms' PCMs"""
578
580
 
579
- mnl_test_array: ArrayBoolean
581
+ mnl_test: ArrayBoolean
580
582
  """Flags infeasible observations as False and rest as True
581
583
 
582
584
  Applying restrictions from Bertrand-Nash oligopoly with MNL demand results
583
585
  in some draws of Firm 2 PCM falling outside the feasible interval, :math:`[0, 1]`
584
586
  for certain combinations of merging firms shares as initially drawn. Such draws
585
- are flagged as infeasible (False) in :code:`mnl_test_array` while draws with
587
+ are flagged as infeasible (False) in :code:`mnl_test` while draws with
586
588
  feaseible PCM values flagged True. This array is used to exclude infeasible draws
587
589
  when imposing MNL demand in simulations.
588
590
  """
@@ -658,5 +660,5 @@ class UPPTestsCounts:
658
660
  """
659
661
 
660
662
 
661
- for _typ in (SeedSequenceData, ShareSpec, PCMSpec, UPPTestsCounts, UPPTestRegime):
663
+ for _typ in (SeedSequenceData, MarketShareSpec, PCMSpec, UPPTestsCounts, UPPTestRegime):
662
664
  yamelize_attrs(_typ)
@@ -24,22 +24,22 @@ from ..core import guidelines_boundaries as gbl # noqa: TID252
24
24
  from ..core.guidelines_boundaries import HMGThresholds # noqa: TID252
25
25
  from . import (
26
26
  INVResolution, # noqa: F401
27
- MarketSampleData,
27
+ MarketsData,
28
+ MarketShareSpec,
28
29
  PCMDistribution,
29
30
  PCMRestriction,
30
31
  PCMSpec,
31
32
  PriceSpec,
32
33
  SeedSequenceData,
33
- ShareSpec,
34
34
  SHRDistribution,
35
35
  SSZConstant,
36
36
  UPPTestRegime,
37
37
  UPPTestsCounts,
38
38
  )
39
39
  from .data_generation_functions import (
40
- gen_divr_array,
41
- gen_margin_price_data,
42
- gen_share_data,
40
+ diversion_ratios_builder,
41
+ market_share_sampler,
42
+ prices_sampler,
43
43
  )
44
44
  from .upp_tests import compute_upp_test_counts
45
45
 
@@ -82,11 +82,11 @@ def _seed_data_conv(_v: SeedSequenceData | None, _i: MarketSample) -> SeedSequen
82
82
  class MarketSample:
83
83
  """Parameter specification for market data generation."""
84
84
 
85
- share_spec: ShareSpec = field(
86
- default=ShareSpec(SHRDistribution.UNI),
87
- validator=validators.instance_of(ShareSpec),
85
+ share_spec: MarketShareSpec = field(
86
+ default=MarketShareSpec(SHRDistribution.UNI),
87
+ validator=validators.instance_of(MarketShareSpec),
88
88
  )
89
- """Market-share specification, see :class:`ShareSpec`"""
89
+ """Market-share specification, see :class:`MarketShareSpec`"""
90
90
 
91
91
  pcm_spec: PCMSpec = field(
92
92
  default=PCMSpec(PCMDistribution.UNI), validator=validators.instance_of(PCMSpec)
@@ -101,7 +101,7 @@ class MarketSample:
101
101
  ):
102
102
  raise ValueError(
103
103
  f'Specification of "PCMSpec.pcm_restriction", as {PCMRestriction.MNL!r} '
104
- f'requires that "ShareSpec.recapture_form" be {RECForm.INOUT!r} '
104
+ f'requires that "MarketShareSpec.recapture_form" be {RECForm.INOUT!r} '
105
105
  f"or {RECForm.OUTIN!r}, not {RECForm.FIXED!r} as presently specified"
106
106
  )
107
107
 
@@ -153,13 +153,13 @@ class MarketSample:
153
153
  nthreads: int = field(default=NTHREADS, validator=validators.instance_of(int))
154
154
  """number of parallel threads to use"""
155
155
 
156
- dataset: MarketSampleData | None = field(default=None, init=False)
156
+ dataset: MarketsData | None = field(default=None, init=False)
157
157
 
158
158
  enf_counts: UPPTestsCounts | None = field(default=None, init=False)
159
159
 
160
- def _gen_market_sample(
160
+ def _markets_sampler(
161
161
  self, /, *, sample_size: int, seed_data: SeedSequenceData, nthreads: int
162
- ) -> MarketSampleData:
162
+ ) -> MarketsData:
163
163
  """
164
164
  Generate share, diversion ratio, price, and margin data for MarketSpec.
165
165
 
@@ -181,7 +181,7 @@ class MarketSample:
181
181
  shr_sample_size = int(shr_sample_size)
182
182
 
183
183
  # Generate share data
184
- mktshr_data = gen_share_data(
184
+ mktshr_data = market_share_sampler(
185
185
  shr_sample_size,
186
186
  self.share_spec,
187
187
  seed_data.fcounts,
@@ -195,7 +195,7 @@ class MarketSample:
195
195
  del mktshr_data
196
196
 
197
197
  # Generate merging-firm price and PCM data
198
- margin_data, price_data = gen_margin_price_data(
198
+ margin_data, price_data = prices_sampler(
199
199
  mktshr_array_[:, :2],
200
200
  nth_firm_share_,
201
201
  aggregate_purchase_prob_,
@@ -210,7 +210,7 @@ class MarketSample:
210
210
  price_array_ = price_data.price_array
211
211
 
212
212
  if shr_sample_size > sample_size:
213
- mnl_test_rows = margin_data.mnl_test_array * price_data.hsr_filing_test
213
+ mnl_test_rows = margin_data.mnl_test * price_data.hsr_filing_test
214
214
 
215
215
  mktshr_array_ = mktshr_array_[mnl_test_rows][:sample_size]
216
216
  pcm_array_ = margin_data.pcm_array[mnl_test_rows][:sample_size]
@@ -224,18 +224,18 @@ class MarketSample:
224
224
  del mnl_test_rows
225
225
 
226
226
  # Calculate diversion ratios
227
- divr_array = gen_divr_array(
227
+ divratio_array = diversion_ratios_builder(
228
228
  self.share_spec.recapture_form,
229
- self.share_spec.recapture_ratio,
229
+ self.share_spec.recapture_rate,
230
230
  mktshr_array_[:, :2],
231
231
  aggregate_purchase_prob_,
232
232
  )
233
233
 
234
- return MarketSampleData(
234
+ return MarketsData(
235
235
  mktshr_array_[:, :2],
236
236
  pcm_array_,
237
237
  price_array_,
238
- divr_array,
238
+ divratio_array,
239
239
  np.einsum("ij,ij->i", mktshr_array_[:, :2], mktshr_array_[:, [1, 0]])[
240
240
  :, None
241
241
  ],
@@ -256,7 +256,7 @@ class MarketSample:
256
256
  None
257
257
 
258
258
  """
259
- self.dataset = self._gen_market_sample(
259
+ self.dataset = self._markets_sampler(
260
260
  seed_data=self.seed_data,
261
261
  sample_size=self.sample_size,
262
262
  nthreads=self.nthreads,
@@ -299,7 +299,7 @@ class MarketSample:
299
299
  UPPTestCounts object with of test counts by firm count, ΔHHI and concentration zone
300
300
 
301
301
  """
302
- market_data_sample = self._gen_market_sample(
302
+ market_data_sample = self._markets_sampler(
303
303
  sample_size=sample_size, seed_data=seed_data, nthreads=nthreads
304
304
  )
305
305
 
@@ -343,11 +343,11 @@ class MarketSample:
343
343
 
344
344
  if (
345
345
  self.share_spec.recapture_form != RECForm.OUTIN
346
- and self.share_spec.recapture_ratio != _enf_parm_vec.rec
346
+ and self.share_spec.recapture_rate != _enf_parm_vec.rec
347
347
  ):
348
348
  raise ValueError(
349
349
  "{} {} {}".format(
350
- f"Recapture ratio from market sample spec, {self.share_spec.recapture_ratio}",
350
+ f"Recapture rate from market sample spec, {self.share_spec.recapture_rate}",
351
351
  f"must match the value, {_enf_parm_vec.rec}",
352
352
  "the guidelines thresholds vector.",
353
353
  )
@@ -488,7 +488,7 @@ class MarketSample:
488
488
  if _dt:
489
489
  with _dp.open("rb") as _hfh:
490
490
  object.__setattr__(
491
- market_sample_, "dataset", MarketSampleData.from_h5f(_hfh)
491
+ market_sample_, "dataset", MarketsData.from_h5f(_hfh)
492
492
  )
493
493
  if _et:
494
494
  object.__setattr__(