mergeron 2025.739290.6__py3-none-any.whl → 2025.739290.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mergeron might be problematic. Click here for more details.
- mergeron/__init__.py +1 -1
- mergeron/core/__init__.py +30 -32
- mergeron/core/empirical_margin_distribution.py +6 -8
- mergeron/core/ftc_merger_investigations_data.py +9 -5
- mergeron/core/guidelines_boundaries.py +11 -11
- mergeron/core/guidelines_boundary_functions.py +115 -115
- mergeron/core/guidelines_boundary_functions_extra.py +208 -1
- mergeron/data/__init__.py +4 -7
- mergeron/data/ftc_merger_investigations_data.zip +0 -0
- mergeron/gen/__init__.py +29 -35
- mergeron/gen/data_generation.py +3 -14
- mergeron/gen/data_generation_functions.py +1 -1
- mergeron/gen/enforcement_stats.py +22 -11
- mergeron/gen/upp_tests.py +50 -144
- mergeron-2025.739290.9.dist-info/METADATA +178 -0
- mergeron-2025.739290.9.dist-info/RECORD +22 -0
- mergeron-2025.739290.6.dist-info/METADATA +0 -115
- mergeron-2025.739290.6.dist-info/RECORD +0 -22
- {mergeron-2025.739290.6.dist-info → mergeron-2025.739290.9.dist-info}/WHEEL +0 -0
|
@@ -17,6 +17,7 @@ from scipy.spatial.distance import minkowski as distance_function # type: ignor
|
|
|
17
17
|
from sympy import lambdify, simplify, solve, symbols # type: ignore
|
|
18
18
|
|
|
19
19
|
from .. import DEFAULT_REC_RATIO, VERSION, ArrayDouble # noqa: TID252
|
|
20
|
+
from . import GuidelinesBoundary, MPFloat
|
|
20
21
|
from . import guidelines_boundary_functions as gbf
|
|
21
22
|
|
|
22
23
|
__version__ = VERSION
|
|
@@ -505,7 +506,7 @@ def shrratio_boundary_xact_avg_mp( # noqa: PLR0914
|
|
|
505
506
|
)
|
|
506
507
|
)
|
|
507
508
|
|
|
508
|
-
bdry_inner = np.
|
|
509
|
+
bdry_inner = np.stack((_s_1, s_2), axis=1)
|
|
509
510
|
bdry_end = np.array([(mpf("0.0"), s_intcpt)])
|
|
510
511
|
|
|
511
512
|
bdry = np.vstack((
|
|
@@ -529,3 +530,209 @@ def shrratio_boundary_xact_avg_mp( # noqa: PLR0914
|
|
|
529
530
|
) - mp.power(_s_mid, 2)
|
|
530
531
|
|
|
531
532
|
return gbf.GuidelinesBoundary(bdry, float(mp.nstr(bdry_area_simpson, dps)))
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
# shrratio_boundary_wtd_avg_autoroot
|
|
536
|
+
# this function is about half as fast as the manual one! ... and a touch less precise
|
|
537
|
+
def _shrratio_boundary_wtd_avg_autoroot( # noqa: PLR0914
|
|
538
|
+
_delta_star: float = 0.075,
|
|
539
|
+
_r_val: float = DEFAULT_REC_RATIO,
|
|
540
|
+
/,
|
|
541
|
+
*,
|
|
542
|
+
agg_method: Literal[
|
|
543
|
+
"arithmetic mean", "geometric mean", "distance"
|
|
544
|
+
] = "arithmetic mean",
|
|
545
|
+
weighting: Literal["own-share", "cross-product-share", None] = "own-share",
|
|
546
|
+
recapture_form: Literal["inside-out", "proportional"] = "inside-out",
|
|
547
|
+
dps: int = 5,
|
|
548
|
+
) -> GuidelinesBoundary:
|
|
549
|
+
"""
|
|
550
|
+
Share combinations on the share-weighted average diversion ratio boundary.
|
|
551
|
+
|
|
552
|
+
Parameters
|
|
553
|
+
----------
|
|
554
|
+
_delta_star
|
|
555
|
+
Share ratio (:math:`\\overline{d} / \\overline{r}`)
|
|
556
|
+
_r_val
|
|
557
|
+
recapture ratio
|
|
558
|
+
agg_method
|
|
559
|
+
Whether "arithmetic mean", "geometric mean", or "distance".
|
|
560
|
+
weighting
|
|
561
|
+
Whether "own-share" or "cross-product-share" (or None for simple, unweighted average).
|
|
562
|
+
recapture_form
|
|
563
|
+
Whether recapture-ratio is MNL-consistent ("inside-out") or has fixed
|
|
564
|
+
value for both merging firms ("proportional").
|
|
565
|
+
dps
|
|
566
|
+
Number of decimal places for rounding returned shares and area.
|
|
567
|
+
|
|
568
|
+
Returns
|
|
569
|
+
-------
|
|
570
|
+
Array of share-pairs, area under boundary.
|
|
571
|
+
|
|
572
|
+
Notes
|
|
573
|
+
-----
|
|
574
|
+
An analytical expression for the share-weighted arithmetic mean boundary
|
|
575
|
+
is derived and plotted from y-intercept to the ray of symmetry as follows::
|
|
576
|
+
|
|
577
|
+
from sympy import plot as symplot, solve, symbols
|
|
578
|
+
s_1, s_2 = symbols("s_1 s_2", positive=True)
|
|
579
|
+
|
|
580
|
+
g_val, r_val, m_val = 0.06, 0.80, 0.30
|
|
581
|
+
delta_star = g_val / (r_val * m_val)
|
|
582
|
+
|
|
583
|
+
# recapture_form == "inside-out"
|
|
584
|
+
oswag = solve(
|
|
585
|
+
s_1 * s_2 / (1 - s_1)
|
|
586
|
+
+ s_2 * s_1 / (1 - (r_val * s_2 + (1 - r_val) * s_1))
|
|
587
|
+
- (s_1 + s_2) * delta_star,
|
|
588
|
+
s_2
|
|
589
|
+
)[0]
|
|
590
|
+
symplot(
|
|
591
|
+
oswag,
|
|
592
|
+
(s_1, 0., d_hat / (1 + d_hat)),
|
|
593
|
+
ylabel=s_2
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
cpswag = solve(
|
|
597
|
+
s_2 * s_2 / (1 - s_1)
|
|
598
|
+
+ s_1 * s_1 / (1 - (r_val * s_2 + (1 - r_val) * s_1))
|
|
599
|
+
- (s_1 + s_2) * delta_star,
|
|
600
|
+
s_2
|
|
601
|
+
)[1]
|
|
602
|
+
symplot(
|
|
603
|
+
cpwag,
|
|
604
|
+
(s_1, 0.0, d_hat / (1 + d_hat)), ylabel=s_2
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
# recapture_form == "proportional"
|
|
608
|
+
oswag = solve(
|
|
609
|
+
s_1 * s_2 / (1 - s_1)
|
|
610
|
+
+ s_2 * s_1 / (1 - s_2)
|
|
611
|
+
- (s_1 + s_2) * delta_star,
|
|
612
|
+
s_2
|
|
613
|
+
)[0]
|
|
614
|
+
symplot(
|
|
615
|
+
oswag,
|
|
616
|
+
(s_1, 0., d_hat / (1 + d_hat)),
|
|
617
|
+
ylabel=s_2
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
cpswag = solve(
|
|
621
|
+
s_2 * s_2 / (1 - s_1)
|
|
622
|
+
+ s_1 * s_1 / (1 - s_2)
|
|
623
|
+
- (s_1 + s_2) * delta_star,
|
|
624
|
+
s_2
|
|
625
|
+
)[1]
|
|
626
|
+
symplot(
|
|
627
|
+
cpswag,
|
|
628
|
+
(s_1, 0.0, d_hat / (1 + d_hat)),
|
|
629
|
+
ylabel=s_2
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
"""
|
|
634
|
+
|
|
635
|
+
_delta_star, _r_val = (mpf(f"{_v}") for _v in (_delta_star, _r_val))
|
|
636
|
+
_s_mid = mp.fdiv(_delta_star, 1 + _delta_star)
|
|
637
|
+
|
|
638
|
+
# initial conditions
|
|
639
|
+
bdry = [(_s_mid, _s_mid)]
|
|
640
|
+
s_1_pre, s_2_pre = _s_mid, _s_mid
|
|
641
|
+
s_2_oddval, s_2_oddsum, s_2_evnsum = True, 0.0, 0.0
|
|
642
|
+
|
|
643
|
+
# parameters for iteration
|
|
644
|
+
_step_size = mp.power(10, -dps)
|
|
645
|
+
theta_ = _step_size * (10 if weighting == "cross-product-share" else 1)
|
|
646
|
+
for s_1 in mp.arange(_s_mid - _step_size, 0, -_step_size):
|
|
647
|
+
|
|
648
|
+
def delta_test(x: MPFloat) -> MPFloat:
|
|
649
|
+
_de_1 = x / (1 - s_1)
|
|
650
|
+
_de_2 = (
|
|
651
|
+
s_1 / (1 - gbf.lerp(s_1, x, _r_val))
|
|
652
|
+
if recapture_form == "inside-out"
|
|
653
|
+
else s_1 / (1 - x)
|
|
654
|
+
)
|
|
655
|
+
_w = (
|
|
656
|
+
mp.fdiv(s_1 if weighting == "cross-product-share" else x, s_1 + x)
|
|
657
|
+
if weighting
|
|
658
|
+
else 0.5
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
match agg_method:
|
|
662
|
+
case "geometric mean":
|
|
663
|
+
delta_test = mp.expm1(
|
|
664
|
+
gbf.lerp(mp.log1p(_de_1), mp.log1p(_de_2), _w)
|
|
665
|
+
)
|
|
666
|
+
case "distance":
|
|
667
|
+
delta_test = mp.sqrt(gbf.lerp(_de_1**2, _de_2**2, _w))
|
|
668
|
+
case _:
|
|
669
|
+
delta_test = gbf.lerp(_de_1, _de_2, _w)
|
|
670
|
+
|
|
671
|
+
return _delta_star - delta_test
|
|
672
|
+
|
|
673
|
+
try:
|
|
674
|
+
s_2 = mp.findroot(
|
|
675
|
+
delta_test,
|
|
676
|
+
x0=(s_2_pre * (1 - theta_), s_2_pre * (1 + theta_)),
|
|
677
|
+
tol=mp.sqrt(_step_size),
|
|
678
|
+
solver="ridder",
|
|
679
|
+
)
|
|
680
|
+
except (mp.ComplexResult, ValueError, ZeroDivisionError) as _e:
|
|
681
|
+
print(s_1, s_2_pre)
|
|
682
|
+
raise _e
|
|
683
|
+
|
|
684
|
+
# Build-up boundary points
|
|
685
|
+
bdry.append((s_1, s_2))
|
|
686
|
+
|
|
687
|
+
# Build up area terms
|
|
688
|
+
s_2_oddsum += s_2 if s_2_oddval else 0
|
|
689
|
+
s_2_evnsum += s_2 if not s_2_oddval else 0
|
|
690
|
+
s_2_oddval = not s_2_oddval
|
|
691
|
+
|
|
692
|
+
# Hold share points
|
|
693
|
+
s_2_pre = s_2
|
|
694
|
+
s_1_pre = s_1
|
|
695
|
+
|
|
696
|
+
if (s_1_pre + s_2_pre) > mpf("0.99875"):
|
|
697
|
+
# Loss of accuracy at 3-9s and up
|
|
698
|
+
break
|
|
699
|
+
|
|
700
|
+
if s_2_oddval:
|
|
701
|
+
s_2_evnsum -= s_2_pre
|
|
702
|
+
else:
|
|
703
|
+
s_2_oddsum -= s_1_pre
|
|
704
|
+
|
|
705
|
+
_s_intcpt = gbf._shrratio_boundary_intcpt(
|
|
706
|
+
s_2_pre,
|
|
707
|
+
_delta_star,
|
|
708
|
+
_r_val,
|
|
709
|
+
recapture_form=recapture_form,
|
|
710
|
+
agg_method=agg_method,
|
|
711
|
+
weighting=weighting,
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
if weighting == "own-share":
|
|
715
|
+
gbd_prtlarea = (
|
|
716
|
+
_step_size * (4 * s_2_oddsum + 2 * s_2_evnsum + _s_mid + s_2_pre) / 3
|
|
717
|
+
)
|
|
718
|
+
# Area under boundary
|
|
719
|
+
bdry_area_total = float(
|
|
720
|
+
2 * (s_1_pre + gbd_prtlarea)
|
|
721
|
+
- (mp.power(_s_mid, "2") + mp.power(s_1_pre, "2"))
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
else:
|
|
725
|
+
gbd_prtlarea = (
|
|
726
|
+
_step_size * (4 * s_2_oddsum + 2 * s_2_evnsum + _s_mid + _s_intcpt) / 3
|
|
727
|
+
)
|
|
728
|
+
# Area under boundary
|
|
729
|
+
bdry_area_total = float(2 * gbd_prtlarea - mp.power(_s_mid, "2"))
|
|
730
|
+
|
|
731
|
+
bdry.append((mpf("0.0"), _s_intcpt))
|
|
732
|
+
bdry_array = np.array(bdry, float)
|
|
733
|
+
|
|
734
|
+
# Points defining boundary to point-of-symmetry
|
|
735
|
+
return GuidelinesBoundary(
|
|
736
|
+
np.vstack((bdry_array[::-1], bdry_array[1:, ::-1]), dtype=float),
|
|
737
|
+
round(float(bdry_area_total), dps),
|
|
738
|
+
)
|
mergeron/data/__init__.py
CHANGED
|
@@ -12,10 +12,9 @@ from .. import _PKG_NAME, VERSION # noqa: TID252
|
|
|
12
12
|
|
|
13
13
|
__version__ = VERSION
|
|
14
14
|
|
|
15
|
+
data_resources = resources.files(f"{_PKG_NAME}.data")
|
|
15
16
|
|
|
16
|
-
DAMODARAN_MARGIN_WORKBOOK =
|
|
17
|
-
"damodaran_margin_data.xls"
|
|
18
|
-
)
|
|
17
|
+
DAMODARAN_MARGIN_WORKBOOK = data_resources / "damodaran_margin_data.xls"
|
|
19
18
|
"""
|
|
20
19
|
Python object pointing to included copy of Prof. Damodaran's margin data
|
|
21
20
|
|
|
@@ -36,9 +35,7 @@ Use as, for example:
|
|
|
36
35
|
shutil.copy2(DAMODARAN_MARGIN_WORKBOOK, Path.home() / f"{DAMODARAN_MARGIN_WORKBOOK.name}")
|
|
37
36
|
"""
|
|
38
37
|
|
|
39
|
-
FTC_MERGER_INVESTIGATIONS_DATA =
|
|
40
|
-
"ftc_merger_investigations_data.zip"
|
|
41
|
-
)
|
|
38
|
+
FTC_MERGER_INVESTIGATIONS_DATA = data_resources / "ftc_merger_investigations_data.zip"
|
|
42
39
|
"""
|
|
43
40
|
FTC merger investigtions data published in 2004, 2007, 2008, and 2013
|
|
44
41
|
|
|
@@ -46,7 +43,7 @@ NOTES
|
|
|
46
43
|
-----
|
|
47
44
|
Raw data tables published by the FTC are loaded into a nested distionary, organized by
|
|
48
45
|
data period, table type, and table number. Each table is stored as a numerical array
|
|
49
|
-
(:
|
|
46
|
+
(:mod:`numpy` arrray), with additonal attrubutes for the industry group and additonal
|
|
50
47
|
evidence noted in the source data.
|
|
51
48
|
|
|
52
49
|
Data for additonal data periods (time spans) not reported in the source data,
|
|
Binary file
|
mergeron/gen/__init__.py
CHANGED
|
@@ -13,7 +13,7 @@ from operator import attrgetter
|
|
|
13
13
|
|
|
14
14
|
import h5py # type: ignore
|
|
15
15
|
import numpy as np
|
|
16
|
-
from attrs import Attribute, Converter, cmp_using, field, frozen
|
|
16
|
+
from attrs import Attribute, Converter, cmp_using, field, frozen
|
|
17
17
|
from numpy.random import SeedSequence
|
|
18
18
|
|
|
19
19
|
from .. import ( # noqa: TID252
|
|
@@ -588,45 +588,39 @@ class INVResolution(str, Enameled):
|
|
|
588
588
|
class UPPTestRegime:
|
|
589
589
|
"""Configuration for UPP tests."""
|
|
590
590
|
|
|
591
|
-
resolution: INVResolution = field(
|
|
592
|
-
|
|
593
|
-
default=INVResolution.ENFT,
|
|
594
|
-
validator=validators.in_([INVResolution.CLRN, INVResolution.ENFT]),
|
|
595
|
-
)
|
|
596
|
-
"""Whether to test clearance, enforcement, or both."""
|
|
597
|
-
|
|
598
|
-
guppi_aggregator: UPPAggrSelector = field(
|
|
599
|
-
kw_only=False, default=UPPAggrSelector.MIN
|
|
600
|
-
)
|
|
601
|
-
"""Aggregator for GUPPI test."""
|
|
602
|
-
|
|
603
|
-
divr_aggregator: UPPAggrSelector = field(kw_only=False, default=UPPAggrSelector.MIN)
|
|
604
|
-
"""Aggregator for diversion ratio test."""
|
|
605
|
-
|
|
591
|
+
resolution: INVResolution = field(kw_only=False, default=INVResolution.ENFT)
|
|
592
|
+
"""Whether to test clearance, enforcement."""
|
|
606
593
|
|
|
607
|
-
@
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
594
|
+
@resolution.validator
|
|
595
|
+
def _resvdtr(
|
|
596
|
+
_i: UPPTestRegime, _a: Attribute[INVResolution], _v: INVResolution
|
|
597
|
+
) -> None:
|
|
598
|
+
if _v == INVResolution.BOTH:
|
|
599
|
+
raise ValueError(
|
|
600
|
+
"GUPPI test cannot be performed with both resolutions; only useful for reporting"
|
|
601
|
+
)
|
|
602
|
+
elif _v not in {INVResolution.CLRN, INVResolution.ENFT}:
|
|
603
|
+
raise ValueError(
|
|
604
|
+
f"Must be one of, {INVResolution.CLRN!r} or {INVResolution.ENFT!r}"
|
|
605
|
+
)
|
|
616
606
|
|
|
617
|
-
|
|
618
|
-
"""
|
|
607
|
+
guppi_aggregator: UPPAggrSelector = field(kw_only=False)
|
|
608
|
+
"""Aggregator for GUPPI test."""
|
|
619
609
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
610
|
+
@guppi_aggregator.default
|
|
611
|
+
def __gad(_i: UPPTestRegime) -> UPPAggrSelector:
|
|
612
|
+
return (
|
|
613
|
+
UPPAggrSelector.MIN
|
|
614
|
+
if _i.resolution == INVResolution.ENFT
|
|
615
|
+
else UPPAggrSelector.MAX
|
|
616
|
+
)
|
|
624
617
|
|
|
625
|
-
|
|
626
|
-
"""
|
|
618
|
+
divr_aggregator: UPPAggrSelector = field(kw_only=False)
|
|
619
|
+
"""Aggregator for diversion ratio test."""
|
|
627
620
|
|
|
628
|
-
|
|
629
|
-
|
|
621
|
+
@divr_aggregator.default
|
|
622
|
+
def __dad(_i: UPPTestRegime) -> UPPAggrSelector:
|
|
623
|
+
return _i.guppi_aggregator
|
|
630
624
|
|
|
631
625
|
|
|
632
626
|
@frozen
|
mergeron/gen/data_generation.py
CHANGED
|
@@ -27,6 +27,7 @@ from ..core import guidelines_boundaries as gbl # noqa: TID252
|
|
|
27
27
|
from ..core.guidelines_boundaries import HMGThresholds # noqa: TID252
|
|
28
28
|
from . import (
|
|
29
29
|
FM2Constraint,
|
|
30
|
+
INVResolution, # noqa: F401
|
|
30
31
|
MarketSampleData,
|
|
31
32
|
PCMDistribution,
|
|
32
33
|
PCMSpec,
|
|
@@ -396,7 +397,7 @@ class MarketSample:
|
|
|
396
397
|
for _k in ("by_firm_count", "by_delta", "by_conczone")
|
|
397
398
|
])
|
|
398
399
|
upp_test_results = UPPTestsCounts(*[
|
|
399
|
-
np.
|
|
400
|
+
np.hstack((
|
|
400
401
|
(_gv := getattr(res_list_stacks, _g.name))[0, :, :_h],
|
|
401
402
|
np.einsum("ijk->jk", _gv[:, :, _h:], dtype=np.int64),
|
|
402
403
|
))
|
|
@@ -453,15 +454,6 @@ class MarketSample:
|
|
|
453
454
|
)
|
|
454
455
|
|
|
455
456
|
if not _ndt:
|
|
456
|
-
# byte_stream = io.BytesIO()
|
|
457
|
-
# with h5py.File(byte_stream, "w") as h5f:
|
|
458
|
-
# for _a in self.dataset.__attrs_attrs__:
|
|
459
|
-
# if all((
|
|
460
|
-
# (_arr := getattr(self.dataset, _a.name)).any(),
|
|
461
|
-
# not np.isnan(_arr).all(),
|
|
462
|
-
# )):
|
|
463
|
-
# h5f.create_dataset(_a.name, data=_arr, fletcher32=True)
|
|
464
|
-
|
|
465
457
|
with (zpath / f"{name_root}_dataset.h5").open("wb") as _hfh:
|
|
466
458
|
_hfh.write(self.dataset.to_h5bin())
|
|
467
459
|
|
|
@@ -490,10 +482,7 @@ class MarketSample:
|
|
|
490
482
|
if _dt:
|
|
491
483
|
with _dp.open("rb") as _hfh:
|
|
492
484
|
object.__setattr__( # noqa: PLC2801
|
|
493
|
-
market_sample_,
|
|
494
|
-
"dataset",
|
|
495
|
-
# MarketSampleData(**{_a: h5f[_a][:] for _a in h5f}),
|
|
496
|
-
MarketSampleData.from_h5f(_hfh),
|
|
485
|
+
market_sample_, "dataset", MarketSampleData.from_h5f(_hfh)
|
|
497
486
|
)
|
|
498
487
|
if _et:
|
|
499
488
|
object.__setattr__( # noqa: PLC2801
|
|
@@ -722,7 +722,7 @@ def _gen_margin_data(
|
|
|
722
722
|
del beta_min, beta_max
|
|
723
723
|
|
|
724
724
|
if dist_firm2_pcm == FM2Constraint.SYM:
|
|
725
|
-
pcm_array = np.
|
|
725
|
+
pcm_array = np.hstack((pcm_array,) * _frmshr_array.shape[1])
|
|
726
726
|
if dist_firm2_pcm == FM2Constraint.MNL:
|
|
727
727
|
# Impose FOCs from profit-maximization with MNL demand
|
|
728
728
|
if dist_type_pcm == PCMDistribution.EMPR:
|
|
@@ -7,7 +7,7 @@ import enum
|
|
|
7
7
|
from collections.abc import Mapping
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
|
-
from scipy.interpolate import
|
|
10
|
+
from scipy.interpolate import make_interp_spline # type: ignore
|
|
11
11
|
|
|
12
12
|
from .. import VERSION, ArrayBIGINT, Enameled, this_yaml # noqa: TID252
|
|
13
13
|
from ..core import ftc_merger_investigations_data as fid # noqa: TID252
|
|
@@ -77,7 +77,7 @@ HHI_DELTA_KNOTS = np.array(
|
|
|
77
77
|
)
|
|
78
78
|
HHI_POST_ZONE_KNOTS = np.array([0, 1800, 2400, 10001], dtype=np.int64)
|
|
79
79
|
hhi_delta_ranger, hhi_zone_post_ranger = (
|
|
80
|
-
|
|
80
|
+
make_interp_spline(_f / 1e4, _f, k=0)
|
|
81
81
|
for _f in (HHI_DELTA_KNOTS, HHI_POST_ZONE_KNOTS)
|
|
82
82
|
)
|
|
83
83
|
|
|
@@ -193,7 +193,7 @@ def enf_cnts_obs_byfirmcount(
|
|
|
193
193
|
case INVResolution.BOTH:
|
|
194
194
|
stats_kept_indxs = [-1, -3, -2]
|
|
195
195
|
|
|
196
|
-
return np.
|
|
196
|
+
return np.hstack([cnts_array[:, :ndim_in], cnts_array[:, stats_kept_indxs]])
|
|
197
197
|
|
|
198
198
|
|
|
199
199
|
def enf_cnts_obs_byhhianddelta(
|
|
@@ -226,7 +226,7 @@ def enf_cnts_obs_byhhianddelta(
|
|
|
226
226
|
case INVResolution.BOTH:
|
|
227
227
|
stats_kept_indxs = [-1, -3, -2]
|
|
228
228
|
|
|
229
|
-
return np.
|
|
229
|
+
return np.hstack([cnts_array[:, :ndim_in], cnts_array[:, stats_kept_indxs]])
|
|
230
230
|
|
|
231
231
|
|
|
232
232
|
def table_no_lku(
|
|
@@ -256,11 +256,16 @@ def table_no_lku(
|
|
|
256
256
|
|
|
257
257
|
|
|
258
258
|
def enf_cnts_byfirmcount(_cnts_array: ArrayBIGINT, /) -> ArrayBIGINT:
|
|
259
|
+
if not _cnts_array[:, 0].any():
|
|
260
|
+
return np.array([], int)
|
|
261
|
+
|
|
259
262
|
ndim_in = 1
|
|
260
263
|
return np.vstack([
|
|
261
264
|
np.concatenate([
|
|
262
265
|
(_i,),
|
|
263
|
-
np.einsum(
|
|
266
|
+
np.einsum(
|
|
267
|
+
"ij->j", _cnts_array[_cnts_array[:, 0] == _i][:, ndim_in:], dtype=int
|
|
268
|
+
),
|
|
264
269
|
])
|
|
265
270
|
for _i in np.unique(_cnts_array[:, 0])
|
|
266
271
|
])
|
|
@@ -271,14 +276,16 @@ def enf_cnts_bydelta(_cnts_array: ArrayBIGINT, /) -> ArrayBIGINT:
|
|
|
271
276
|
return np.vstack([
|
|
272
277
|
np.concatenate([
|
|
273
278
|
(_k,),
|
|
274
|
-
np.einsum(
|
|
279
|
+
np.einsum(
|
|
280
|
+
"ij->j", _cnts_array[_cnts_array[:, 1] == _k][:, ndim_in:], dtype=int
|
|
281
|
+
),
|
|
275
282
|
])
|
|
276
283
|
for _k in HHI_DELTA_KNOTS[:-1]
|
|
277
284
|
])
|
|
278
285
|
|
|
279
286
|
|
|
280
287
|
def enf_cnts_byconczone(_cnts_array: ArrayBIGINT, /) -> ArrayBIGINT:
|
|
281
|
-
if not _cnts_array.any():
|
|
288
|
+
if not _cnts_array[:, 0].any() or np.isnan(_cnts_array[:, 0]).all():
|
|
282
289
|
return np.array([], int)
|
|
283
290
|
# Step 1: Tag and agg. from HHI-post and Delta to zone triple
|
|
284
291
|
# NOTE: Although you could just map and not (partially) aggregate in this step,
|
|
@@ -315,7 +322,9 @@ def enf_cnts_byconczone(_cnts_array: ArrayBIGINT, /) -> ArrayBIGINT:
|
|
|
315
322
|
np.array(
|
|
316
323
|
(
|
|
317
324
|
*zone_val,
|
|
318
|
-
*np.einsum(
|
|
325
|
+
*np.einsum(
|
|
326
|
+
"ij->j", _cnts_array[:, _ndim_in:][conc_test], dtype=int
|
|
327
|
+
),
|
|
319
328
|
),
|
|
320
329
|
dtype=int,
|
|
321
330
|
),
|
|
@@ -326,10 +335,10 @@ def enf_cnts_byconczone(_cnts_array: ArrayBIGINT, /) -> ArrayBIGINT:
|
|
|
326
335
|
# Logical-and of multiple vectors:
|
|
327
336
|
hhi_zone_test = (
|
|
328
337
|
1
|
|
329
|
-
* np.
|
|
338
|
+
* np.stack([
|
|
330
339
|
cnts_byhhipostanddelta[:, _idx] == _val
|
|
331
340
|
for _idx, _val in enumerate(zone_val)
|
|
332
|
-
])
|
|
341
|
+
], axis=1)
|
|
333
342
|
).prod(axis=1) == 1
|
|
334
343
|
|
|
335
344
|
cnts_byconczone = np.vstack((
|
|
@@ -338,7 +347,9 @@ def enf_cnts_byconczone(_cnts_array: ArrayBIGINT, /) -> ArrayBIGINT:
|
|
|
338
347
|
(
|
|
339
348
|
zone_val,
|
|
340
349
|
np.einsum(
|
|
341
|
-
"ij->j",
|
|
350
|
+
"ij->j",
|
|
351
|
+
cnts_byhhipostanddelta[hhi_zone_test][:, _nkeys:],
|
|
352
|
+
dtype=int,
|
|
342
353
|
),
|
|
343
354
|
),
|
|
344
355
|
dtype=int,
|