mergeron 2024.739091.2__py3-none-any.whl → 2024.739097.1__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
@@ -2,16 +2,12 @@ from __future__ import annotations
2
2
 
3
3
  import enum
4
4
  from pathlib import Path
5
- from typing import Any
6
5
 
7
6
  import numpy as np
8
- import pendulum # type: ignore
9
- from icecream import argumentToString, ic, install # type: ignore
10
- from numpy.typing import NDArray
11
7
 
12
8
  _PKG_NAME: str = Path(__file__).parent.stem
13
9
 
14
- VERSION = "2024.739091.2"
10
+ VERSION = "2024.739097.1"
15
11
 
16
12
  __version__ = VERSION
17
13
 
@@ -28,19 +24,6 @@ if not DATA_DIR.is_dir():
28
24
  np.set_printoptions(precision=18)
29
25
 
30
26
 
31
- def _timestamper() -> str:
32
- return f"{pendulum.now().strftime("%F %T.%f")} |> "
33
-
34
-
35
- @argumentToString.register(np.ndarray) # type: ignore
36
- def _(_obj: NDArray[Any]) -> str:
37
- return f"ndarray, shape={_obj.shape}, dtype={_obj.dtype}"
38
-
39
-
40
- ic.configureOutput(prefix=_timestamper, includeContext=True)
41
- install()
42
-
43
-
44
27
  @enum.unique
45
28
  class RECConstants(enum.StrEnum):
46
29
  """Recapture rate - derivation methods."""
@@ -202,7 +202,7 @@ def mgn_data_resampler(
202
202
  Parameters
203
203
  ----------
204
204
  _sample_size
205
- Number of draws
205
+ Number of draws; if tuple, (number of draws, number of columns)
206
206
 
207
207
  seed_sequence
208
208
  SeedSequence for seeding random-number generator when results
@@ -22,7 +22,6 @@ import numpy as np
22
22
  import re2 as re # type: ignore
23
23
  import requests
24
24
  from bs4 import BeautifulSoup
25
- from icecream import ic # type: ignore
26
25
  from numpy.testing import assert_array_equal
27
26
  from numpy.typing import NDArray
28
27
 
@@ -345,7 +344,7 @@ def _construct_new_period_data(
345
344
  # _invdata_array_bld_enfcls < 0, _invdata_array_bld_enfcls, 0
346
345
  # )
347
346
  # if np.einsum('ij->', invdata_array_bld_tbc):
348
- # ic(
347
+ # print(
349
348
  # f"{_data_period}, {_table_no}, {_invdata_ind_group}:",
350
349
  # abs(np.einsum('ij->', invdata_array_bld_tbc))
351
350
  # )
@@ -551,7 +550,7 @@ def _parse_table_blocks(
551
550
  _invdata_evid_cond = "Unrestricted on additional evidence"
552
551
 
553
552
  else:
554
- # ic(_table_blocks)
553
+ # print(_table_blocks)
555
554
  _invdata_evid_cond = (
556
555
  _table_blocks[1][-3].strip()
557
556
  if _table_ser == 9
@@ -570,8 +569,8 @@ def _parse_table_blocks(
570
569
 
571
570
  _table_array = process_table_func(_table_blocks)
572
571
  if not isinstance(_table_array, np.ndarray) or _table_array.dtype != np.int64:
573
- ic(_table_num)
574
- ic(_table_blocks)
572
+ print(_table_num)
573
+ print(_table_blocks)
575
574
  raise ValueError
576
575
 
577
576
  _table_data = INVTableData(_invdata_ind_group, _invdata_evid_cond, _table_array)
@@ -11,6 +11,14 @@ This module is designed for producing formatted summary output. For
11
11
  writing bulk data to Excel, facilities provided in third-party packages
12
12
  such as `polars <https://pola.rs/>`_ likely provide better performance.
13
13
 
14
+ License
15
+ ========
16
+
17
+ Copyright 2017-2023 S. Murthy Kambhampaty
18
+ Licese: MIT
19
+ https://mit-license.org/
20
+
21
+
14
22
  """
15
23
 
16
24
  from __future__ import annotations
mergeron/gen/__init__.py CHANGED
@@ -21,7 +21,7 @@ __version__ = VERSION
21
21
 
22
22
  EMPTY_ARRAY_DEFAULT = np.zeros(2)
23
23
  FCOUNT_WTS_DEFAULT = np.divide(
24
- (_nr := np.arange(1, 6)[::-1]), _nr.sum(), dtype=np.float64
24
+ (_nr := np.arange(1, 7)[::-1]), _nr.sum(), dtype=np.float64
25
25
  )
26
26
 
27
27
 
@@ -36,7 +36,7 @@ class PriceConstants(tuple[bool, str | None], enum.ReprEnum):
36
36
  ZERO = (False, None)
37
37
  NEG = (False, "negative share-correlation")
38
38
  POS = (False, "positive share-correlation")
39
- # TODO: CSY = (False, "market-wide cost-symmetry")
39
+ CSY = (False, "market-wide cost-symmetry")
40
40
 
41
41
 
42
42
  @enum.unique
@@ -59,7 +59,11 @@ class SHRConstants(enum.StrEnum):
59
59
  DIR_ASYM = "Asymmetric Dirichlet"
60
60
  """Share distribution for merging-firm shares has a higher peak share
61
61
 
62
- Shape parameter for merging-firm-share is 2.5, and 1.0 for all others.
62
+ By default, shape parameter for merging-firm-share is 2.5, and
63
+ 1.0 for all others. Defining, :attr:`mergeron.ShareSpec.dist_parms`
64
+ as a vector of shape parameters with length matching
65
+ that of :attr:`mergeron.ShareSpec.dist_parms` allows flexible specification
66
+ of Dirichlet-distributed share-data generation.
63
67
  """
64
68
 
65
69
  DIR_COND = "Conditional Dirichlet"
@@ -74,13 +78,24 @@ class SHRConstants(enum.StrEnum):
74
78
  class ShareSpec:
75
79
  """Market share specification
76
80
 
81
+ A key feature of market-share specification in this package is that
82
+ the draws represent markets with multiple different firm-counts.
83
+ Firm-counts are unspecified if the share distribution is
84
+ :attr:`mergeron.SHRConstants.UNI`, for Dirichlet-distributed market-shares,
85
+ the default specification is that firm-counts vary between
86
+ 2 and 7 firms with each value equally likely.
87
+
77
88
  Notes
78
89
  -----
79
- If recapture is determined "outside-in", market shares cannot have
80
- Uniform distribution.
90
+ If :attr:`mergeron.gen.ShareSpec.dist_type`:code:` == `:attr:`mergeron.gen.SHRConstants.UNI`,
91
+ then it is infeasible that
92
+ :attr:`mergeron.gen.ShareSpec.recapture_form`:code:` == `:attr:`mergeron.RECConstants.OUTIN`.
93
+ In other words, if firm-counts are unspecified, the recapture rate cannot be
94
+ estimated using outside good choice probabilities.
81
95
 
82
- If sample with varying firm counts is required, market shares must
83
- be specified as having a supported Dirichlet distribution.
96
+ For a sample with explicit firm counts, market shares must
97
+ be specified as having a supported Dirichlet distribution
98
+ (see :class:`mergeron.gen.SHRConstants`).
84
99
 
85
100
  """
86
101
 
@@ -446,7 +461,8 @@ class UPPTestRegime:
446
461
  default=UPPAggrSelector.MIN, validator=validators.instance_of(UPPAggrSelector)
447
462
  )
448
463
  divr_aggregator: UPPAggrSelector | None = field(
449
- default=None, validator=validators.instance_of((UPPAggrSelector, type(None)))
464
+ default=UPPAggrSelector.MIN,
465
+ validator=validators.instance_of((UPPAggrSelector, type(None))),
450
466
  )
451
467
 
452
468
 
@@ -7,6 +7,7 @@ from __future__ import annotations
7
7
  from typing import Literal
8
8
 
9
9
  import numpy as np
10
+ from attrs import evolve
10
11
  from numpy.random import SeedSequence
11
12
  from numpy.typing import NDArray
12
13
 
@@ -21,11 +22,12 @@ from . import (
21
22
  FCOUNT_WTS_DEFAULT,
22
23
  FM2Constants,
23
24
  MarginDataSample,
24
- MarketSpec,
25
25
  PCMConstants,
26
+ PCMSpec,
26
27
  PriceConstants,
27
28
  PriceDataSample,
28
29
  ShareDataSample,
30
+ ShareSpec,
29
31
  SHRConstants,
30
32
  SSZConstants,
31
33
  )
@@ -35,7 +37,7 @@ __version__ = VERSION
35
37
 
36
38
  def _gen_share_data(
37
39
  _sample_size: int,
38
- _mkt_sample_spec: MarketSpec,
40
+ _share_spec: ShareSpec,
39
41
  _fcount_rng_seed_seq: SeedSequence | None,
40
42
  _mktshr_rng_seed_seq: SeedSequence,
41
43
  _nthreads: int = 16,
@@ -45,8 +47,8 @@ def _gen_share_data(
45
47
 
46
48
  Parameters
47
49
  ----------
48
- _mkt_sample_spec
49
- Class specifying parameters for share-, price-, and margin-data generation
50
+ _share_spec
51
+ Class specifying parameters for generating market share data
50
52
  _fcount_rng_seed_seq
51
53
  Seed sequence for assuring independent and, optionally, redundant streams
52
54
  _mktshr_rng_seed_seq
@@ -60,44 +62,43 @@ def _gen_share_data(
60
62
 
61
63
  """
62
64
 
63
- _recapture_form, _dist_type_mktshr, _dist_parms_mktshr, _firm_count_prob_wts_raw = (
64
- getattr(_mkt_sample_spec.share_spec, _f)
65
+ _recapture_form, _dist_type_mktshr, _dist_parms_mktshr, _firm_count_prob_wts = (
66
+ getattr(_share_spec, _f)
65
67
  for _f in ("recapture_form", "dist_type", "dist_parms", "firm_counts_weights")
66
68
  )
67
69
 
68
70
  _ssz = _sample_size
69
71
 
70
- match _dist_type_mktshr:
71
- case SHRConstants.UNI:
72
- _mkt_share_sample = _gen_market_shares_uniform(
73
- _ssz, _dist_parms_mktshr, _mktshr_rng_seed_seq, _nthreads
74
- )
72
+ if _dist_type_mktshr == SHRConstants.UNI:
73
+ _mkt_share_sample = _gen_market_shares_uniform(
74
+ _ssz, _dist_parms_mktshr, _mktshr_rng_seed_seq, _nthreads
75
+ )
75
76
 
76
- case _ if _dist_type_mktshr.name.startswith("DIR_"):
77
- _firm_count_prob_wts = (
78
- None
79
- if _firm_count_prob_wts_raw is None
80
- else np.array(_firm_count_prob_wts_raw, dtype=np.float64)
81
- )
82
- _mkt_share_sample = _gen_market_shares_dirichlet_multisample(
83
- _ssz,
84
- _recapture_form,
85
- _dist_type_mktshr,
86
- _dist_parms_mktshr,
87
- _firm_count_prob_wts,
88
- _fcount_rng_seed_seq,
89
- _mktshr_rng_seed_seq,
90
- _nthreads,
91
- )
77
+ elif _dist_type_mktshr.name.startswith("DIR_"):
78
+ _firm_count_prob_wts = (
79
+ None
80
+ if _firm_count_prob_wts is None
81
+ else np.array(_firm_count_prob_wts, dtype=np.float64)
82
+ )
83
+ _mkt_share_sample = _gen_market_shares_dirichlet_multisample(
84
+ _ssz,
85
+ _recapture_form,
86
+ _dist_type_mktshr,
87
+ _dist_parms_mktshr,
88
+ _firm_count_prob_wts,
89
+ _fcount_rng_seed_seq,
90
+ _mktshr_rng_seed_seq,
91
+ _nthreads,
92
+ )
92
93
 
93
- case _:
94
- raise ValueError(
95
- f'Unexpected type, "{_dist_type_mktshr}" for share distribution.'
96
- )
94
+ else:
95
+ raise ValueError(
96
+ f'Unexpected type, "{_dist_type_mktshr}" for share distribution.'
97
+ )
97
98
 
98
99
  # If recapture_form == "inside-out", recalculate _aggregate_purchase_prob
99
100
  _frmshr_array = _mkt_share_sample.mktshr_array[:, :2]
100
- _r_bar = _mkt_sample_spec.share_spec.recapture_rate or 0.85
101
+ _r_bar = _share_spec.recapture_rate or 0.85
101
102
  if _recapture_form == RECConstants.INOUT:
102
103
  _mkt_share_sample = ShareDataSample(
103
104
  _mkt_share_sample.mktshr_array,
@@ -392,15 +393,163 @@ def _gen_market_shares_dirichlet(
392
393
  )
393
394
 
394
395
 
396
+ def _gen_margin_price_data(
397
+ _frmshr_array: NDArray[np.float64],
398
+ _nth_firm_share: NDArray[np.float64],
399
+ _aggregate_purchase_prob: NDArray[np.float64],
400
+ _pcm_spec: PCMSpec,
401
+ _price_spec: PriceConstants,
402
+ _hsr_filing_test_type: SSZConstants,
403
+ _pcm_rng_seed_seq: SeedSequence,
404
+ _pr_rng_seed_seq: SeedSequence | None = None,
405
+ _nthreads: int = 16,
406
+ /,
407
+ ) -> tuple[MarginDataSample, PriceDataSample]:
408
+ _price_array, _price_ratio_array = (
409
+ np.ones_like(_frmshr_array, np.float64),
410
+ np.empty_like(_frmshr_array, np.float64),
411
+ )
412
+
413
+ _pr_max_ratio = 5.0
414
+ match _price_spec:
415
+ case PriceConstants.SYM:
416
+ _nth_firm_price = np.ones((len(_frmshr_array), 1))
417
+ case PriceConstants.POS:
418
+ _price_array, _nth_firm_price = (
419
+ np.ceil(_p * _pr_max_ratio) for _p in (_frmshr_array, _nth_firm_share)
420
+ )
421
+ case PriceConstants.NEG:
422
+ _price_array, _nth_firm_price = (
423
+ np.ceil((1 - _p) * _pr_max_ratio)
424
+ for _p in (_frmshr_array, _nth_firm_share)
425
+ )
426
+ case PriceConstants.ZERO:
427
+ _price_array_gen = prng(_pr_rng_seed_seq).choice(
428
+ 1 + np.arange(_pr_max_ratio), size=(len(_frmshr_array), 3)
429
+ )
430
+ _price_array = _price_array_gen[:, :2]
431
+ _nth_firm_price = _price_array_gen[:, [2]]
432
+ # del _price_array_gen
433
+ case PriceConstants.CSY:
434
+ # TODO:
435
+ # evolve FM2Constraint (save running MNL test twice); evolve copy of _mkt_sample_spec=1q
436
+ # generate the margin data
437
+ # generate price and margin data
438
+ _frmshr_array_plus = np.hstack((_frmshr_array, _nth_firm_share))
439
+ _pcm_spec_here = evolve(_pcm_spec, firm2_pcm_constraint=FM2Constants.IID)
440
+ _margin_data = _gen_margin_data(
441
+ _frmshr_array_plus,
442
+ np.ones_like(_frmshr_array_plus, np.float64),
443
+ _aggregate_purchase_prob,
444
+ _pcm_spec_here,
445
+ _pcm_rng_seed_seq,
446
+ _nthreads,
447
+ )
448
+
449
+ _pcm_array, _mnl_test_array = (
450
+ getattr(_margin_data, _f) for _f in ("pcm_array", "mnl_test_array")
451
+ )
452
+
453
+ _price_array_here = 1 / (1 - _pcm_array)
454
+ _price_array = _price_array_here[:, :2]
455
+ _nth_firm_price = _price_array_here[:, [-1]]
456
+ if _pcm_spec.firm2_pcm_constraint == FM2Constants.MNL:
457
+ # Generate i.i.d. PCMs then take PCM0 and construct PCM1
458
+ # Regenerate MNL test
459
+ _purchase_prob_array = _aggregate_purchase_prob * _frmshr_array
460
+ _pcm_array[:, 1] = np.divide(
461
+ (
462
+ _m1_nr := np.divide(
463
+ np.einsum(
464
+ "ij,ij,ij->ij",
465
+ _price_array[:, [0]],
466
+ _pcm_array[:, [0]],
467
+ 1 - _purchase_prob_array[:, [0]],
468
+ ),
469
+ 1 - _purchase_prob_array[:, [1]],
470
+ )
471
+ ),
472
+ 1 + _m1_nr,
473
+ )
474
+ _mnl_test_array = (_pcm_array[:, [1]] >= 0) & (_pcm_array[:, [1]] <= 1)
475
+ else:
476
+ # Generate i.i.d. PCMs
477
+ # Construct price_array = 1/ (1 - pcm_array)
478
+ # Rgenerate MNL test
479
+ pass
480
+
481
+ _margin_data = MarginDataSample(_pcm_array[:, :2], _mnl_test_array)
482
+ del _price_array_here
483
+ case _:
484
+ raise ValueError(
485
+ f"Specitication of price distribution"
486
+ f' "{_price_spec.value}" is invalid.'
487
+ )
488
+ if _price_spec != PriceConstants.CSY:
489
+ _margin_data = _gen_margin_data(
490
+ _frmshr_array,
491
+ _price_array,
492
+ _aggregate_purchase_prob,
493
+ _pcm_spec,
494
+ _pcm_rng_seed_seq,
495
+ _nthreads,
496
+ )
497
+
498
+ _price_array = _price_array.astype(np.float64)
499
+ _rev_array = _price_array * _frmshr_array
500
+ _nth_firm_rev = _nth_firm_price * _nth_firm_share
501
+
502
+ # Although `_test_rev_ratio_inv` is not fixed at 10%,
503
+ # the ratio has not changed since inception of the HSR filing test,
504
+ # so we treat it as a constant of merger enforcement policy.
505
+ _test_rev_ratio, _test_rev_ratio_inv = 10, 1 / 10
506
+
507
+ match _hsr_filing_test_type:
508
+ case SSZConstants.HSR_TEN:
509
+ # See, https://www.ftc.gov/enforcement/premerger-notification-program/
510
+ # -> Procedures For Submitting Post-Consummation Filings
511
+ # -> Key Elements to Determine Whether a Post Consummation Filing is Required
512
+ # under heading, "Historical Thresholds"
513
+ # Revenue ratio has been 10-to-1 since inception
514
+ # Thus, a simple form of the HSR filing test would impose a 10-to-1
515
+ # ratio restriction on the merging firms' revenues
516
+ _rev_ratio = (_rev_array.min(axis=1) / _rev_array.max(axis=1)).round(4)
517
+ _hsr_filing_test = _rev_ratio >= _test_rev_ratio_inv
518
+ # del _rev_array, _rev_ratio
519
+ case SSZConstants.HSR_NTH:
520
+ # To get around the 10-to-1 ratio restriction, specify that the nth firm test:
521
+ # if the smaller merging firm matches or exceeds the n-th firm in size, and
522
+ # the larger merging firm has at least 10 times the size of the nth firm,
523
+ # the size test is considered met.
524
+ # Alternatively, if the smaller merging firm has 10% or greater share,
525
+ # the value of transaction test is considered met.
526
+ _rev_ratio_to_nth = np.round(np.sort(_rev_array, axis=1) / _nth_firm_rev, 4)
527
+ _hsr_filing_test = (
528
+ np.einsum(
529
+ "ij->i",
530
+ 1 * (_rev_ratio_to_nth > [1, _test_rev_ratio]),
531
+ dtype=np.int64,
532
+ )
533
+ == _rev_ratio_to_nth.shape[1]
534
+ ) | (_frmshr_array.min(axis=1) >= _test_rev_ratio_inv)
535
+
536
+ # del _nth_firm_rev, _rev_ratio_to_nth
537
+ case _:
538
+ # Otherwise, all draws meet the filing test
539
+ _hsr_filing_test = np.ones(len(_frmshr_array), dtype=bool)
540
+
541
+ return _margin_data, PriceDataSample(_price_array, _hsr_filing_test)
542
+
543
+
544
+ # marked for deletion
395
545
  def _gen_price_data(
396
546
  _frmshr_array: NDArray[np.float64],
397
547
  _nth_firm_share: NDArray[np.float64],
398
- _mkt_sample_spec: MarketSpec,
548
+ _price_spec: PriceConstants,
549
+ _hsr_filing_test_type: SSZConstants,
399
550
  _seed_seq: SeedSequence | None = None,
400
551
  /,
401
552
  ) -> PriceDataSample:
402
- _hsr_filing_test_type = _mkt_sample_spec.hsr_filing_test_type
403
-
404
553
  _price_array, _price_ratio_array, _hsr_filing_test = (
405
554
  np.ones_like(_frmshr_array, np.float64),
406
555
  np.empty_like(_frmshr_array, np.float64),
@@ -408,7 +557,7 @@ def _gen_price_data(
408
557
  )
409
558
 
410
559
  _pr_max_ratio = 5.0
411
- match _mkt_sample_spec.price_spec:
560
+ match _price_spec:
412
561
  case PriceConstants.SYM:
413
562
  _nth_firm_price = np.ones((len(_frmshr_array), 1))
414
563
  case PriceConstants.POS:
@@ -427,10 +576,12 @@ def _gen_price_data(
427
576
  _price_array = _price_array_gen[:, :2]
428
577
  _nth_firm_price = _price_array_gen[:, [2]]
429
578
  # del _price_array_gen
579
+ case PriceConstants.CSY:
580
+ raise ValueError("Market-wide cost-symmetry is not yet implemented.")
430
581
  case _:
431
582
  raise ValueError(
432
- f"Condition regarding price symmetry"
433
- f' "{_mkt_sample_spec.price_spec.value}" is invalid.'
583
+ f"Specitication of price distribution"
584
+ f' "{_price_spec.value}" is invalid.'
434
585
  )
435
586
 
436
587
  _price_array = _price_array.astype(np.float64)
@@ -471,7 +622,6 @@ def _gen_price_data(
471
622
  == _rev_ratio_to_nth.shape[1]
472
623
  ) | (_frmshr_array.min(axis=1) >= _test_rev_ratio_inv)
473
624
 
474
- # del _nth_firm_rev, _rev_ratio_to_nth
475
625
  case _:
476
626
  # Otherwise, all draws meet the filing test
477
627
  _hsr_filing_test = np.ones(len(_frmshr_array), dtype=bool)
@@ -479,23 +629,26 @@ def _gen_price_data(
479
629
  return PriceDataSample(_price_array, _hsr_filing_test)
480
630
 
481
631
 
482
- def _gen_pcm_data(
632
+ def _gen_margin_data(
483
633
  _frmshr_array: NDArray[np.float64],
484
634
  _price_array: NDArray[np.float64],
485
635
  _aggregate_purchase_prob: NDArray[np.float64],
486
- _mkt_sample_spec: MarketSpec,
636
+ _pcm_spec: PCMSpec,
487
637
  _pcm_rng_seed_seq: SeedSequence,
488
638
  _nthreads: int = 16,
489
639
  /,
490
640
  ) -> MarginDataSample:
491
641
  _dist_type_pcm, _dist_firm2_pcm, _dist_parms_pcm = (
492
- getattr(_mkt_sample_spec.pcm_spec, _f)
642
+ getattr(_pcm_spec, _f)
493
643
  for _f in ("dist_type", "firm2_pcm_constraint", "dist_parms")
494
644
  )
495
645
 
496
646
  _dist_type: Literal["Beta", "Uniform"]
497
- _pcm_array = np.empty((len(_frmshr_array), 2), dtype=np.float64)
498
- _mnl_test_array = np.empty((len(_frmshr_array), 2), dtype=int)
647
+ _pcm_array = (
648
+ np.empty((len(_frmshr_array), 1), dtype=np.float64)
649
+ if _pcm_spec.firm2_pcm_constraint == FM2Constants.SYM
650
+ else np.empty_like(_frmshr_array, dtype=np.float64)
651
+ )
499
652
 
500
653
  _beta_min, _beta_max = [None] * 2 # placeholder
501
654
  if _dist_type_pcm == PCMConstants.EMPR:
@@ -534,25 +687,33 @@ def _gen_pcm_data(
534
687
  _pcm_array = (_beta_max - _beta_min) * _pcm_array + _beta_min
535
688
  del _beta_min, _beta_max
536
689
 
690
+ if _dist_firm2_pcm == FM2Constants.SYM:
691
+ _pcm_array = np.column_stack((_pcm_array,) * _frmshr_array.shape[1])
537
692
  if _dist_firm2_pcm == FM2Constants.MNL:
538
693
  # Impose FOCs from profit-maximization with MNL demand
539
- _purchprob_array = _aggregate_purchase_prob * _frmshr_array
694
+ if _dist_type_pcm == PCMConstants.EMPR:
695
+ print(
696
+ "NOTE: Estimated Firm 2 parameters will not be consistent with "
697
+ "the empirical distribution of margins in the source data. For "
698
+ "consistency, respecify pcm_spec.firm2_pcm_constraint = FM2Constants.IID."
699
+ )
700
+ _purchase_prob_array = _aggregate_purchase_prob * _frmshr_array
540
701
 
541
702
  _pcm_array[:, [1]] = np.divide(
542
703
  np.einsum(
543
704
  "ij,ij,ij->ij",
544
705
  _price_array[:, [0]],
545
706
  _pcm_array[:, [0]],
546
- 1 - _purchprob_array[:, [0]],
707
+ 1 - _purchase_prob_array[:, [0]],
708
+ ),
709
+ np.einsum(
710
+ "ij,ij->ij", _price_array[:, [1]], 1 - _purchase_prob_array[:, [1]]
547
711
  ),
548
- np.einsum("ij,ij->ij", _price_array[:, [1]], 1 - _purchprob_array[:, [1]]),
549
712
  )
550
713
 
551
714
  _mnl_test_array = _pcm_array[:, 1].__ge__(0) & _pcm_array[:, 1].__le__(1)
552
715
  else:
553
716
  _mnl_test_array = np.ones(len(_pcm_array), dtype=bool)
554
- if _dist_firm2_pcm == FM2Constants.SYM:
555
- _pcm_array[:, [1]] = _pcm_array[:, [0]]
556
717
 
557
718
  return MarginDataSample(_pcm_array, _mnl_test_array)
558
719
 
@@ -1,5 +1,6 @@
1
1
  """
2
- Methods to generate data for analyzing merger enforcement policy.
2
+ Methods to generate market data, including shares price, marginsm, and diversion ratios
3
+ for analyzing merger enforcement policy.
3
4
 
4
5
  """
5
6
 
@@ -21,7 +22,7 @@ from . import (
21
22
  SHRConstants,
22
23
  SSZConstants,
23
24
  )
24
- from ._data_generation_functions import _gen_pcm_data, _gen_price_data, _gen_share_data
25
+ from ._data_generation_functions import _gen_margin_price_data, _gen_share_data
25
26
 
26
27
  __version__ = VERSION
27
28
 
@@ -42,24 +43,13 @@ def gen_market_sample(
42
43
  nthreads: int = 16,
43
44
  ) -> MarketDataSample:
44
45
  """
45
- Generate share, diversion ratio, price, and margin data based on supplied parameters
46
+ Generate share, diversion ratio, price, and margin data for MarketSpec.
46
47
 
47
- Diversion ratios generated assuming share-proportionality, unless
48
- `recapture_form` = "proportional", in which case both firms' recapture rate
49
- is set to `r_bar`.
50
-
51
- The tuple of SeedSequences, if specified, is parsed in the following order
52
- for generating the relevant random variates:
53
- 1.) quantity shares
54
- 2.) price-cost margins
55
- 3.) firm-counts, from :code:`[2, 2 + len(firm_counts_weights)]`,
56
- weighted by :code:`firm_counts_weights`, where relevant
57
- 4.) prices, if :code:`price_spec == PriceConstants.ZERO`.
58
48
 
59
49
  Parameters
60
50
  ----------
61
51
  _mkt_sample_spec
62
- class specifying parameters for data generation
52
+ class specifying parameters for data generation, see :class:`mergeron.gen.MarketSpec`
63
53
  sample_size
64
54
  number of draws to generate
65
55
  seed_seq_list
@@ -100,7 +90,7 @@ def gen_market_sample(
100
90
  # Generate share data
101
91
  _mktshr_data = _gen_share_data(
102
92
  _shr_sample_size,
103
- _mkt_sample_spec,
93
+ _mkt_sample_spec.share_spec,
104
94
  _fcount_rng_seed_seq,
105
95
  _mktshr_rng_seed_seq,
106
96
  nthreads,
@@ -116,40 +106,28 @@ def gen_market_sample(
116
106
  )
117
107
  )
118
108
 
119
- # Generate merging-firm price data
120
- _price_data = _gen_price_data(
121
- _mktshr_array[:, :2], _nth_firm_share, _mkt_sample_spec, _pr_rng_seed_seq
109
+ # Generate merging-firm price and PCM data
110
+ _margin_data, _price_data = _gen_margin_price_data(
111
+ _mktshr_array[:, :2],
112
+ _nth_firm_share,
113
+ _aggregate_purchase_prob,
114
+ _mkt_sample_spec.pcm_spec,
115
+ _mkt_sample_spec.price_spec,
116
+ _mkt_sample_spec.hsr_filing_test_type,
117
+ _pcm_rng_seed_seq,
118
+ _pr_rng_seed_seq,
119
+ nthreads,
122
120
  )
123
121
 
124
122
  _price_array, _hsr_filing_test = (
125
123
  getattr(_price_data, _f) for _f in ("price_array", "hsr_filing_test")
126
124
  )
127
125
 
128
- if _hsr_filing_test_type != SSZConstants.ONE:
129
- _mktshr_array = _mktshr_array[_hsr_filing_test]
130
- _fcounts = _fcounts[_hsr_filing_test]
131
- _aggregate_purchase_prob = _aggregate_purchase_prob[_hsr_filing_test]
132
- _nth_firm_share = _nth_firm_share[_hsr_filing_test]
133
- _price_array = _price_array[_hsr_filing_test]
134
-
135
- # Calculate diversion ratios
136
- _divr_array = gen_divr_array(
137
- _recapture_form, _recapture_rate, _mktshr_array[:, :2], _aggregate_purchase_prob
138
- )
139
-
140
- # Generate margin data
141
- _pcm_data = _gen_pcm_data(
142
- _mktshr_array[:, :2],
143
- _price_array,
144
- _aggregate_purchase_prob,
145
- _mkt_sample_spec,
146
- _pcm_rng_seed_seq,
147
- nthreads,
148
- )
149
126
  _pcm_array, _mnl_test_rows = (
150
- getattr(_pcm_data, _f) for _f in ("pcm_array", "mnl_test_array")
127
+ getattr(_margin_data, _f) for _f in ("pcm_array", "mnl_test_array")
151
128
  )
152
129
 
130
+ _mnl_test_rows = _mnl_test_rows * _hsr_filing_test
153
131
  _s_size = sample_size # originally-specified sample size
154
132
  if _dist_firm2_pcm == FM2Constants.MNL:
155
133
  _mktshr_array = _mktshr_array[_mnl_test_rows][:_s_size]
@@ -158,7 +136,11 @@ def gen_market_sample(
158
136
  _fcounts = _fcounts[_mnl_test_rows][:_s_size]
159
137
  _aggregate_purchase_prob = _aggregate_purchase_prob[_mnl_test_rows][:_s_size]
160
138
  _nth_firm_share = _nth_firm_share[_mnl_test_rows][:_s_size]
161
- _divr_array = _divr_array[_mnl_test_rows][:_s_size]
139
+
140
+ # Calculate diversion ratios
141
+ _divr_array = gen_divr_array(
142
+ _recapture_form, _recapture_rate, _mktshr_array[:, :2], _aggregate_purchase_prob
143
+ )
162
144
 
163
145
  del _mnl_test_rows, _s_size
164
146
 
@@ -188,7 +170,33 @@ def parse_seed_seq_list(
188
170
  _price_spec: PriceConstants,
189
171
  /,
190
172
  ) -> SeedSequenceData:
191
- """Initialize RNG seed sequences to ensure independence of distinct random streams."""
173
+ """Initialize RNG seed sequences to ensure independence of distinct random streams.
174
+
175
+ The tuple of SeedSequences, is parsed in the following order
176
+ for generating the relevant random variates:
177
+ 1.) quantity shares
178
+ 2.) price-cost margins
179
+ 3.) firm-counts, if :code:`MarketSpec.share_spec.dist_type` is a Dirichlet distribution
180
+ 4.) prices, if :code:`MarketSpec.price_spec ==`:attr:`mergeron.gen.PriceConstants.ZERO`.
181
+
182
+
183
+
184
+ Parameters
185
+ ----------
186
+ _sseq_list
187
+ List of RNG seed sequences
188
+
189
+ _mktshr_dist_type
190
+ Market share distribution type
191
+
192
+ _price_spec
193
+ Price specification
194
+
195
+ Returns
196
+ -------
197
+ Seed sequence data
198
+
199
+ """
192
200
  _fcount_rng_seed_seq: SeedSequence | None = None
193
201
  _pr_rng_seed_seq: SeedSequence | None = None
194
202
 
@@ -23,22 +23,51 @@ class MarketSample(MarketSpec):
23
23
  self,
24
24
  /,
25
25
  *,
26
- sample_size: int = 10**6,
26
+ sample_size: int,
27
27
  seed_seq_list: list[SeedSequence] | None,
28
28
  nthreads: int,
29
- save_data_to_file: SaveData = False,
29
+ save_data_to_file: SaveData,
30
30
  ) -> None:
31
+ """Generate market data
32
+
33
+ Parameters
34
+ ----------
35
+ sample_size
36
+ Size of the market sample drawn
37
+
38
+ seed_seq_list
39
+ List of :code:`numpy.random.SeedSequence` objects
40
+
41
+ nthreads
42
+ Number of threads to use
43
+
44
+ save_data_to_file
45
+ Save data to given HDF5 file, at specified group node
46
+
47
+ Returns
48
+ -------
49
+ None
50
+
51
+ Notes
52
+ -----
53
+ See documentation for :class:`mergeron.gen.data_generation.gen_market_sample`
54
+ for more information, and :func:`mergeron.gen.data_generation.parse_seed_seq_list`
55
+ on the specification of :code:`seed_seq_list`.
56
+
57
+ """
31
58
  self.data = gen_market_sample(
32
59
  self,
33
60
  sample_size=sample_size,
34
61
  seed_seq_list=seed_seq_list,
35
62
  nthreads=nthreads,
36
63
  )
64
+
37
65
  _invalid_array_names = (
38
66
  ("fcounts", "choice_prob_outgd", "nth_firm_share", "hhi_post")
39
67
  if self.share_spec.dist_type == "Uniform"
40
68
  else ()
41
69
  )
70
+
42
71
  if save_data_to_file:
43
72
  save_data_to_hdf5(
44
73
  self.data,
@@ -57,19 +86,58 @@ class MarketSample(MarketSpec):
57
86
  nthreads: int,
58
87
  save_data_to_file: SaveData = False,
59
88
  ) -> None:
89
+ """Generate market data
90
+
91
+ Parameters
92
+ ----------
93
+ _enf_parm_vec
94
+ Threshold values for various Guidelines criteria
95
+
96
+ _upp_test_regime
97
+ Specifies whether to analyze enforcement, clearance, or both
98
+ and the GUPPI and diversion ratio aggregators employed, with
99
+ default being to analyze enforcement based on the maximum
100
+ merging-firm GUPPI and maximum diversion ratio between the
101
+ merging firms
102
+
103
+ sample_size
104
+ Size of the market sample drawn
105
+
106
+ seed_seq_list
107
+ List of :code:`numpy.random.SeedSequence` objects
108
+
109
+ nthreads
110
+ Number of threads to use
111
+
112
+ save_data_to_file
113
+ Save data to given HDF5 file, at specified group node
114
+
115
+ Returns
116
+ -------
117
+ None
118
+
119
+ Notes
120
+ -----
121
+ See documentation for :class:`mergeron.gen.MarketSpec` for details on specifying
122
+ how shares, margins, prices, and diversion ratios are generated, and whether to restrict
123
+ the sample to draws representing mergers that meet the HSR filing requirements. See
124
+ :class:`mergeron.gen.MarketDataSample` on the sample data generated; see,
125
+ :func:`mergeron.gen.data_generation.parse_seed_seq_list` on
126
+ the specification of :code:`seed_seq_list`.
127
+
128
+ """
129
+
60
130
  if getattr(self, "market_data_sample", None) is None:
61
131
  self.enf_counts = sim_enf_cnts_ll(
62
132
  self,
63
133
  _enf_parm_vec,
64
134
  _upp_test_regime,
65
- save_data_to_file=save_data_to_file,
66
135
  sample_size=sample_size,
67
136
  seed_seq_list=seed_seq_list,
68
137
  nthreads=nthreads,
138
+ save_data_to_file=save_data_to_file,
69
139
  )
70
140
  else:
71
- self.enf_counts = enf_cnts(
72
- self.data, _enf_parm_vec, _upp_test_regime
73
- )
74
- if save_data_to_file:
75
- save_data_to_hdf5(self.enf_counts, save_data_to_file=save_data_to_file)
141
+ self.enf_counts = enf_cnts(self.data, _enf_parm_vec, _upp_test_regime)
142
+ if save_data_to_file:
143
+ save_data_to_hdf5(self.enf_counts, save_data_to_file=save_data_to_file)
mergeron/gen/upp_tests.py CHANGED
@@ -11,7 +11,6 @@ from typing import Literal, TypeAlias, TypedDict
11
11
 
12
12
  import numpy as np
13
13
  import tables as ptb # type: ignore
14
- from icecream import ic # type: ignore
15
14
  from joblib import Parallel, cpu_count, delayed # type: ignore
16
15
  from numpy.random import SeedSequence
17
16
  from numpy.typing import NDArray
@@ -195,9 +194,7 @@ def sim_enf_cnts(
195
194
  save_data_to_file=save_data_to_file,
196
195
  )
197
196
 
198
- _upp_test_arrays = enf_cnts(
199
- _market_data_sample, _upp_test_parms, _sim_test_regime
200
- )
197
+ _upp_test_arrays = enf_cnts(_market_data_sample, _upp_test_parms, _sim_test_regime)
201
198
 
202
199
  save_data_to_hdf5(
203
200
  _upp_test_arrays,
@@ -279,7 +276,7 @@ def enf_cnts(
279
276
  try:
280
277
  _hhi_zone_post_ranged = esl.hhi_zone_post_ranger(_hhi_post)
281
278
  except ValueError as _err:
282
- ic(_hhi_post)
279
+ print(_hhi_post)
283
280
  raise _err
284
281
 
285
282
  _stats_byconczone_sim = -1 * np.ones(_stats_rowlen + 1, np.int64)
@@ -310,9 +307,7 @@ def enf_cnts(
310
307
  ]),
311
308
  ))
312
309
 
313
- _enf_cnts_sim_byconczone_array = esl.enf_cnts_byconczone(
314
- _stats_byconczone_sim[1:]
315
- )
310
+ _enf_cnts_sim_byconczone_array = esl.enf_cnts_byconczone(_stats_byconczone_sim[1:])
316
311
  del _stats_byconczone_sim
317
312
  del _hhi_delta, _hhi_post, _fcounts
318
313
 
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.1
2
+ Name: mergeron
3
+ Version: 2024.739097.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: lxml (>=5.0)
29
+ Requires-Dist: matplotlib (>=3.8)
30
+ Requires-Dist: mpmath (>=1.3)
31
+ Requires-Dist: msgpack (>=1.0)
32
+ Requires-Dist: msgpack-numpy (>=0.4)
33
+ Requires-Dist: numpy (>=1.26,<2.0)
34
+ Requires-Dist: openpyxl (>=3.1.2)
35
+ Requires-Dist: poetry-plugin-export (>=1.8.0,<2.0.0)
36
+ Requires-Dist: requests (>=2.31)
37
+ Requires-Dist: requests-toolbelt (>=1.0.0)
38
+ Requires-Dist: scipy (>=1.12)
39
+ Requires-Dist: sympy (>=1.12)
40
+ Requires-Dist: tables (>=3.8)
41
+ Requires-Dist: types-beautifulsoup4 (>=4.11.2)
42
+ Requires-Dist: types-requests (>=2.31.0)
43
+ Requires-Dist: xlrd (>=2.0.1,<3.0.0)
44
+ Requires-Dist: xlsxwriter (>=3.1)
45
+ Description-Content-Type: text/x-rst
46
+
47
+ mergeron: Merger Policy Analysis using Python
48
+ =============================================
49
+
50
+ 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 impled 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.
51
+
52
+ Intrinsic enforcement rates and intrinsice clearance rates are distinguished from *observed* clearance and enforcement rates in that the former are derived from analyzing theorectical predictions regarding firm conduct against enforcement thresholds, treating enforcement policy as exogenous to firm conduct. 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, ny definition, the observed clearance rate is 1 minus the observed enforement rate.
53
+
54
+ Introduction
55
+ ------------
56
+
57
+ 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 concentation and diversion-ratio boundaries, and functions for mapping GUPPI standards to concentration (ΔHHI) standards, and vice-versa.
58
+
59
+ 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 genarated 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.
60
+
61
+ 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.ShareContants`. 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.
62
+
63
+ Recapture rates can be specifed 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.ShareContants.UNI`.
64
+
65
+ 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-mazimization in) Bertrand-Nash oligopoly with MNL demand (see, :code:`mergeron.gen.PCMSpec`).
66
+
67
+ 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`).
68
+
69
+ 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.
70
+
71
+ 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 perfomance penalty is slight, but can be considerable if saving to SATA storage).
72
+
73
+ 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`.
74
+
75
+ Programs demonstrating the use of this package are included in the sub-package, :code:`mergeron.demo`.
76
+
77
+ 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:
78
+
79
+ .. code-block:: python
80
+
81
+ import mergeron.core.pseudorandom_numbers as prng
82
+
83
+ 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:
84
+
85
+ .. code-block:: python
86
+
87
+ import mergeron.core.market_sample as market_sample
88
+
89
+ help(market_sample.MarketSample)
90
+
91
+ ''Extras'' Subpackage
92
+ ---------------------
93
+
94
+ This module includes a small number of modules potentially useful to users, but which do not implement the principal functions of the package, and are hence considered ''extras'' or ''external'' modules. One of these modules is, in fact, repackaged here although published independently.
95
+
96
+ On of the external modules provides methods for estimating confidence intervals for proportions and for contrasts (differences) in proportions. This module improve is coded for conformance to the literature and to results from the corresponding modules in :code:`R`. Although written from scratch, the APIs implemented in the module included here are designed for consistency with the APIs in, :code:`statsmodels.stats.proportion` from the package, :code:`statsmodels` (https://pypi.org/project/statsmodels/). To access these directly:
97
+
98
+ .. code-block:: python
99
+
100
+ import mergeron.ext.proportions_tests as prci
101
+
102
+ Module :code:`mergeron.ext.xlsxw_helper` is useful for writing highly formatted output to spreadsheets with xlsx format. The class, :code:`mergeron.ext.xlsxw_helper.CFmt` and function, :code:`mergeron.ext.xlsxw_helper.array_to_sheet` are of particular interest, and can be accessed as :code:`xlh.CFmt` and :code:`xlh.array_to_sheet` with the following import:
103
+
104
+ .. code-block:: python
105
+
106
+ import mergeron.ext.xlsxw_helper as xlsxw_helper
107
+
108
+ A recent version of Paul Tol's python module, :code:`tol_colors.py`, which provides high-contrast color schemes for making displays with improved visibility for individuals with color-blindness, is redistributed within this package. Other than re-formatting and type annotation, the :code:`mergeron.ext.tol_colors` module is re-distributed as downloaded from, https://personal.sron.nl/~pault/data/tol_colors.py. The :code:`tol_colors.py` module is distributed under the Standard 3-clause BSD license. To access the :code:`mergeron.ext.tol_colors` module directly:
109
+
110
+ .. code-block:: python
111
+
112
+ import mergeron.ext.tol_colors as ptc
113
+
114
+ .. image:: https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json
115
+ :alt: Poetry
116
+ :target: https://python-poetry.org/
117
+
118
+ .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
119
+ :alt: Ruff
120
+ :target: https://github.com/astral-sh/ruff
121
+
122
+ .. image:: https://www.mypy-lang.org/static/mypy_badge.svg
123
+ :alt: Checked with mypy
124
+ :target: https://mypy-lang.org/
125
+
126
+ .. image:: https://img.shields.io/badge/License-MIT-yellow.svg
127
+ :alt: License: MIT
128
+ :target: https://opensource.org/licenses/MIT
129
+
130
+
@@ -1,12 +1,11 @@
1
1
  mergeron/License.txt,sha256=7iX-y0EyjkbVJKJLS4ZKzuuE1wd0lryfsD_IytLG8lQ,1246
2
- mergeron/__init__.py,sha256=dQe1ZhZ4GP5p0TMy4E_chUcvo-6TpWhft70eWLQCZBY,1647
2
+ mergeron/__init__.py,sha256=ugOcG1IgCGF05t7lYMTGPqAO-qXn9AsB73YoJ3M0l-8,1180
3
3
  mergeron/core/__init__.py,sha256=KtjBlZOl7jwBCAUhrTJB9PdrN39YLYytNiSUSM_gRmA,62
4
- mergeron/core/damodaran_margin_data.py,sha256=pjI1rSK_O1-3Oel5b9KXH6ctnInjX1Vii7fypt00gV8,8541
5
- mergeron/core/ftc_merger_investigations_data.py,sha256=ZaV2DO7UZabV8eX0Ubq_ToIor7tIRzcvYC54ADliuTk,27931
4
+ mergeron/core/damodaran_margin_data.py,sha256=mzCRsva47lnrLuTqzAXCKEIgqwvcUN6C3_qiqf_yDYI,8589
5
+ mergeron/core/ftc_merger_investigations_data.py,sha256=9U0GPD6bl_xAsbFclt32Vnm9-rz9vnIWuiIi6jK1sc8,27903
6
6
  mergeron/core/guidelines_boundaries.py,sha256=__OHme8aGtwOgRXKp56WdX7k4vssAVQ8Ub54XwpS7mg,15621
7
7
  mergeron/core/guidelines_boundary_functions.py,sha256=rXjncqTn7NPgI2KY9Wuv3WNrsjmv74hpH9-mUI56NgQ,29714
8
8
  mergeron/core/guidelines_boundary_functions_extra.py,sha256=TYq3M5onfAIAY-35Q_SaSVF0Upa9hCSKIQkY-KCGzwM,11393
9
- mergeron/core/proportions_tests.py,sha256=akq0Xhdgtst4RAT42_E5cBD_kATq_V4bQeBznmzRSLg,15267
10
9
  mergeron/core/pseudorandom_numbers.py,sha256=k3sDs_NJ2jXlkIWKQ6iiTB5n_QS0RoJ-sqzvFYkC7pY,9277
11
10
  mergeron/data/__init__.py,sha256=KtjBlZOl7jwBCAUhrTJB9PdrN39YLYytNiSUSM_gRmA,62
12
11
  mergeron/data/damodaran_margin_data.xls,sha256=Qggl1p5nkOMJI8YUXhkwXQRz-OhRSqBTzz57N0JQyYA,79360
@@ -22,15 +21,16 @@ mergeron/data/jinja2_LaTeX_templates/setup_tikz_tables.tex,sha256=1hw3RINDtBrh9Z
22
21
  mergeron/demo/__init__.py,sha256=KtjBlZOl7jwBCAUhrTJB9PdrN39YLYytNiSUSM_gRmA,62
23
22
  mergeron/demo/visualize_empirical_margin_distribution.py,sha256=v1xFJumBX2Ooye82kSSgly-_GpFVkYSDqBwM__rcmZY,2363
24
23
  mergeron/ext/__init__.py,sha256=KtjBlZOl7jwBCAUhrTJB9PdrN39YLYytNiSUSM_gRmA,62
24
+ mergeron/ext/proportions_tests.py,sha256=akq0Xhdgtst4RAT42_E5cBD_kATq_V4bQeBznmzRSLg,15267
25
25
  mergeron/ext/tol_colors.py,sha256=QBw8s-ZGpUpIIYOplHbLFZSXVoa6eDDJDtzSScP954E,22303
26
- mergeron/ext/xlsxw_helper.py,sha256=4wAhwQjWKSm65931sR_4yO6EuoU2RnWvtW5aCSQn5Iw,17951
27
- mergeron/gen/__init__.py,sha256=pAEirl5FAO9r_6f7BdvqU0LCH9lXMe1YR0SnrGMpCWI,16200
28
- mergeron/gen/_data_generation_functions.py,sha256=7fP4mSVaN36FBhPKSf1y_TbxfRUe-I7fgqdBt74oaCA,21029
29
- mergeron/gen/data_generation.py,sha256=gDvCZYJwGpQnokcygM7IRzHBpE5rYI2J5I8uu0_wQyE,8727
26
+ mergeron/ext/xlsxw_helper.py,sha256=Kqlt89kcnoiE2aCtP3xbM4fypy_JNIg9ebjjURmyJ38,18050
27
+ mergeron/gen/__init__.py,sha256=Ky1p93L-epw42g8HDIcfiENGT1iiXdolnsv12ZUWGM4,17169
28
+ mergeron/gen/_data_generation_functions.py,sha256=boNQkDtarx_BqYHWjlsIXSQHqOBvgBcI_R53UfT66qg,27763
29
+ mergeron/gen/data_generation.py,sha256=n_rdi6Zk5-2Q0K_xXdWI14sj9XZyrF57hnyqvBf5v6Y,8531
30
30
  mergeron/gen/enforcement_stats.py,sha256=Hr-w3LZ9SJD4A1RZS1KMhXwuKyGzPC7eeNQullWZRNU,24410
31
- mergeron/gen/market_sample.py,sha256=ekMA9db2AWvrA-GDbIieu270fovFX0JyynCo5FRAGzk,2270
32
- mergeron/gen/upp_tests.py,sha256=GQZcXU4vQPJRxdI2DVsY7yX6TPqhntlTH-DOAufWFmM,17197
31
+ mergeron/gen/market_sample.py,sha256=HkzRFTKBXYIs2HbAyVDUiUHo9nCtAciSn5sohR-34cM,4282
32
+ mergeron/gen/upp_tests.py,sha256=N-spIAYKPGJngtlckSjhQGcFBZv_sVZDvCHYzy0TxSc,17132
33
33
  mergeron/py.typed,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
34
- mergeron-2024.739091.2.dist-info/METADATA,sha256=wK0ioIu_QUYP5PLYeJgBKqM8A1wfIWJOG22beUjM8AI,8899
35
- mergeron-2024.739091.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
36
- mergeron-2024.739091.2.dist-info/RECORD,,
34
+ mergeron-2024.739097.1.dist-info/METADATA,sha256=wlcb2jcxwGDPnhsU_j3ZYylqREvAE9S-GjbOiFLKV0I,13261
35
+ mergeron-2024.739097.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
36
+ mergeron-2024.739097.1.dist-info/RECORD,,
@@ -1,110 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: mergeron
3
- Version: 2024.739091.2
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: icecream (>=2.1.0)
27
- Requires-Dist: jinja2 (>=3.1)
28
- Requires-Dist: joblib (>=1.3)
29
- Requires-Dist: lxml (>=5.0)
30
- Requires-Dist: matplotlib (>=3.8)
31
- Requires-Dist: mpmath (>=1.3)
32
- Requires-Dist: msgpack (>=1.0)
33
- Requires-Dist: msgpack-numpy (>=0.4)
34
- Requires-Dist: numpy (>=1.26,<2.0)
35
- Requires-Dist: openpyxl (>=3.1.2)
36
- Requires-Dist: pendulum (>=3.0.0)
37
- Requires-Dist: poetry-plugin-export (>=1.8.0,<2.0.0)
38
- Requires-Dist: requests (>=2.31)
39
- Requires-Dist: requests-toolbelt (>=1.0.0)
40
- Requires-Dist: scipy (>=1.12)
41
- Requires-Dist: sympy (>=1.12)
42
- Requires-Dist: tables (>=3.8)
43
- Requires-Dist: types-beautifulsoup4 (>=4.11.2)
44
- Requires-Dist: types-requests (>=2.31.0)
45
- Requires-Dist: xlrd (>=2.0.1,<3.0.0)
46
- Requires-Dist: xlsxwriter (>=3.1)
47
- Description-Content-Type: text/x-rst
48
-
49
- mergeron: Merger Policy Analysis using Python
50
- =============================================
51
-
52
- Download and analyze merger investigations data published by the U.S. Federal Trade Commission in various reports on extended merger investigations during 1996 to 2011. Model the sets of mergers conforming to various U.S. Horizontal Merger Guidelines standards. Analyze intrinsic clearance rates and intrinsic enforcement rates under Guidelines standards using generated data with specified distributions of market shares, price-cost margins, firm counts, and prices, optionally imposing restrictions impled by statutory filing thresholds and/or Bertrand-Nash oligopoly with MNL demand.
53
-
54
- Intrinsic clearance and enforcement rates are distinguished from *observed* clearance and enforcement rates in that the former do not reflect the effects of screening and deterrence as do the latter.
55
-
56
-
57
- Introduction
58
- ------------
59
-
60
- Classes for specifying concentration standards (:code:`mergeron.core.guidelines_boundaries.ConcentrationBoundary`) and diversion-ratio standards (:code:`mergeron.core.guidelines_boundaries.DiversionRatioBoundary`), with automatic generation of boundary (as an array of share-pairs) and area, are provided in :code:`mergeron.core.guidelines_boundaries`. This module also includes a function for generating plots of concentation and diversion-ratio boundaries, and functions for mapping GUPPI standards to concentration (ΔHHI) standards, and vice-versa.
61
-
62
- Methods for generating industry data under various distributions of shares, margins, and prices are included in, :code:`mergeron.gen.data_generation`. Shares are drawn with uniform distribution with :math:`s_1 + s_2 \leqslant 1` and an unspecified number of firms. Alternatively, shares may be drawn from the Dirichlet distribution. When drawing shares from the Dirichlet distribution, the user can specify a fixed number for firms or provide 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>`_). The user can specify recapture rates as, "proportional", "inside-out" --- i.e., consistent with merging-firms' in-market shares and a default recapture rate) --- or "outside-in" --- i.e., purchase probabilities are 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. Documentation on specifying the sampling strategy for market shares is at :code:`mergeron.gen.ShareSpec`. Price-cost-margins may be specified as symmetric, i.i.d., or subject to equilibrium conditions for (profit-mazimization in) Bertrand-Nash oligopoly with MNL demand (see, :code:`mergeron.gen.PCMSpec`). 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`). Two alternative approaches for modeling statutory filing requirements (HSR filing thresholds) are implemented (see, :code:`mergeron.gen.SSZConstants`). The full specification of a market sample is given in a :code:`mergeron.gen.market_sample.MarketSample` object. 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_invres_counts`, which adds an :code:`invres_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_invres_counts` on a :code:`mergeron.gen.market_sample.MarketSample` object without first invoking :code:`generate_sample`. Note, however, that this strategy discards the market sample in the interests of conserving memory and maintaining high performance.
63
-
64
- Methods for printing 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`.
65
-
66
- Programs demonstrating the analysis and reporting facilites provided by the sub-package, :code:`mergeron.demo`.
67
-
68
- This package exposes methods employed 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. To access these directly:
69
-
70
- .. code-block:: python
71
-
72
- import mergeron.core.pseudorandom_numbers as prng
73
-
74
- Also included are methods for estimating confidence intervals for proportions and for contrasts (differences) in proportions. (Although coded from scratch using the source literature, the APIs implemented in the module included here are designed for consistency with the APIs in, :code:`statsmodels.stats.proportion` from the package, :code:`statsmodels` (https://pypi.org/project/statsmodels/).) To access these directly:
75
-
76
- .. code-block:: python
77
-
78
- import mergeron.core.proportions_tests as prci
79
-
80
- A recent version of Paul Tol's python module, :code:`tol_colors.py` is redistributed within this package. Other than re-formatting and type annotation, the :code:`mergeron.ext.tol_colors` module is re-distributed as downloaded from, https://personal.sron.nl/~pault/data/tol_colors.py. The :code:`tol_colors.py` module is distributed under the Standard 3-clause BSD license. To access the :code:`mergeron.ext.tol_colors` module directly:
81
-
82
- .. code-block:: python
83
-
84
- import mergeron.ext.tol_colors as ptc
85
-
86
- 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:
87
-
88
- .. code-block:: python
89
-
90
- import mergeron.core.market_sample as market_sample
91
-
92
- help(market_sample.MarketSample)
93
-
94
-
95
- .. image:: https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json
96
- :alt: Poetry
97
- :target: https://python-poetry.org/
98
-
99
- .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
100
- :alt: Ruff
101
- :target: https://github.com/astral-sh/ruff
102
-
103
- .. image:: https://www.mypy-lang.org/static/mypy_badge.svg
104
- :alt: Checked with mypy
105
- :target: https://mypy-lang.org/
106
-
107
- .. image:: https://img.shields.io/badge/License-MIT-yellow.svg
108
- :alt: License: MIT
109
- :target: https://opensource.org/licenses/MIT
110
-
File without changes