mergeron 2024.738953.0__py3-none-any.whl → 2024.738953.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.

@@ -1,135 +0,0 @@
1
- """
2
-
3
- Estimation of intrinsic clearance rates for ΔHHI safeharbor and GUPPI safeharbor
4
- Demonstrates MC integration with closed-form integrand and indicator-function
5
- integrand (Tested with Python 3.8 and 3.9; NumPy module is required.)
6
-
7
- S. Murthy Kambhampaty, © 2019. This work is licensed under a CC BY-NC-SA 4.0 License
8
- https://creativecommons.org/licenses/by-nc-sa/4.0/
9
-
10
- """
11
-
12
- from datetime import datetime
13
-
14
- import numpy as np
15
- from numpy.random import PCG64DXSM, Generator
16
- from numpy.typing import NDArray
17
-
18
- rng = Generator(PCG64DXSM())
19
-
20
- dh_bar = 0.01
21
- g_bar = 0.06
22
- r_bar = 0.80
23
- d_bar = 0.2 # 0.2 # r_bar # g_bar # 0.8 * 0.125 / (1 - 0.125)
24
- sample_sz = 10**8
25
-
26
-
27
- def icr_gsh_sym(_ssz: int = sample_sz) -> NDArray[np.float64]:
28
- """With symmetric shares, margins, and prices; closed-form integrand."""
29
- _m_lim = g_bar / d_bar
30
- _m_star = _m_lim + (1.0 - _m_lim) * rng.uniform(size=(_ssz, 1))
31
- _d_star = g_bar / (r_bar * _m_star)
32
- _divr_limit_prob = 2 * g_bar / (r_bar + d_bar)
33
- _guppi_bound_prob = 2 * (1 - _m_lim) * (_d_star / (1 + _d_star)).mean()
34
- return np.array([_ssz, _divr_limit_prob + _guppi_bound_prob, np.nan, np.nan])
35
-
36
-
37
- def icr_gsh_asymmshr(_ssz: int = sample_sz) -> NDArray[np.float64]:
38
- """With symmetric margins and prices, unequal shares; closed-form integrand."""
39
- _m_lim = g_bar / d_bar
40
- _m_star = _m_lim + (1.0 - _m_lim) * rng.random(size=(_ssz, 1))
41
-
42
- _d_star = g_bar / (r_bar * _m_star)
43
- _divr_limit_prob = 2 * (g_bar / r_bar) * d_bar / (r_bar + d_bar)
44
- _guppi_bound_prob = 2 * (1 - _m_lim) * (_d_star**2 / (1 + _d_star)).mean()
45
- return np.array([_ssz, _divr_limit_prob + _guppi_bound_prob, np.nan, np.nan])
46
-
47
-
48
- def gen_qtyshr(_ssz: int = sample_sz, *, sym_flag: bool = False) -> NDArray[np.float64]:
49
- """Unequal shares and margins, and symmetric prices; indicator-function integrand."""
50
- if sym_flag:
51
- # for symmetric shares
52
- _mktshr_array = 0.5 * rng.uniform(size=(_ssz, 1))
53
- _mktshr_array = _mktshr_array[:, [0, 0]]
54
- else:
55
- _mktshr_array = np.sort(rng.random(size=(_ssz, 2)))
56
- _mktshr_array = np.column_stack((_mktshr_array[:, [0]], np.diff(_mktshr_array)))
57
- return _mktshr_array
58
-
59
-
60
- def icr_gsh_asymmshrmgn(_ssz: int = sample_sz) -> NDArray[np.float64]:
61
- """With symmetric prices, unequal shares and margins; indicator-function integrand."""
62
-
63
- _shr_sym_flag = False
64
- _mktshr_array = gen_qtyshr(_ssz, sym_flag=_shr_sym_flag)
65
- _hhi_delta = np.einsum("ij,ij->i", _mktshr_array, _mktshr_array[:, ::-1])
66
- _divr_array = r_bar * _mktshr_array[:, ::-1] / (1 - _mktshr_array)
67
- _delta_test = _hhi_delta < dh_bar
68
- del _hhi_delta
69
-
70
- _pcm_array = rng.uniform(size=_divr_array.shape)
71
- _pcm_sym_flag = False
72
- if _pcm_sym_flag:
73
- _pcm_array = _pcm_array[:, [0, 0]]
74
-
75
- _pr_sym_flag = True
76
- _pr_corr_sign = None
77
- if _pr_corr_sign not in (_pcvs := (None, "positive", "negative")):
78
- raise ValueError(f"Price correlation must be one of {_pcvs!r}")
79
- if _pr_sym_flag:
80
- _pr_ratio_array = np.ones(_pcm_array.shape)
81
- else:
82
- _pr_max_ratio = 5
83
- if _pr_corr_sign == "positive":
84
- _pr_array = 1 + np.floor(_pr_max_ratio * _mktshr_array)
85
- elif _pr_corr_sign == "negative":
86
- _pr_array = _pr_max_ratio - np.floor(_pr_max_ratio * _mktshr_array)
87
- else:
88
- _pr_array = rng.choice(
89
- 1.00 + np.arange(_pr_max_ratio), size=_pcm_array.shape
90
- )
91
- _pr_ratio_array = np.divide(_pr_array, _pr_array[:, ::-1])
92
- del _pr_max_ratio, _pr_array
93
-
94
- _guppi_array = np.einsum(
95
- "ij,ij,ij->ij", _divr_array, _pcm_array[:, ::-1], _pr_ratio_array[:, ::-1]
96
- )
97
- _gbd_test = _guppi_array.max(axis=1) < g_bar
98
- _divr_test = _divr_array.max(axis=1) < d_bar
99
- _pcm_min_test = _pcm_array.min(axis=1) >= (g_bar / d_bar)
100
- _divr_limit_test = _gbd_test & _divr_test & np.logical_not(_pcm_min_test)
101
- _gbd_not_in_deltah = np.logical_not(_delta_test) & _gbd_test & _divr_test
102
-
103
- _scount = len(_gbd_test)
104
- _gbd_prob = len(_gbd_test[_gbd_test & _divr_test]) / _scount
105
- _deltah_prob = len(_gbd_test[_delta_test]) / _scount
106
- _cum_clr_prob = len(_gbd_test[_delta_test | _gbd_not_in_deltah]) / _scount
107
-
108
- del _guppi_array, _divr_array, _pcm_array
109
- del _gbd_test, _divr_test, _pcm_min_test, _divr_limit_test
110
-
111
- return np.array([_scount, _gbd_prob, _deltah_prob, _cum_clr_prob])
112
-
113
-
114
- if __name__ == "__main__":
115
- for run_func in (icr_gsh_sym, icr_gsh_asymmshr, icr_gsh_asymmshrmgn):
116
- resv = np.array([0.0, 0.0, 0.0, 0.0])
117
- icount = 1000
118
- stime = datetime.now()
119
- for _ in range(icount):
120
- tmpv = run_func(sample_sz // icount)
121
- resv += tmpv[0] * tmpv
122
- resv = np.round(resv / (icount * np.sqrt(resv[0] / icount)), 4)
123
-
124
- # In the printed output,
125
- # first column reports the intrinsic clearance rate for
126
- # the GUPPI safeharbor Where reported,
127
- # second column reports the intrinsic clearance rate for the ΔHHI safeharbor
128
- # third column reports the intrinsic clearance rate for cumulative application
129
- # of the ΔHHI safeharbor and the GUPPI safeharbor
130
- print(
131
- np.sum(resv[1]),
132
- resv[-2],
133
- resv[-1],
134
- f"; duration {(datetime.now() - stime).total_seconds()} secs.",
135
- )
@@ -1,100 +0,0 @@
1
- """
2
- Plot the empirical distribution of margin data downloaded from
3
- Prof. Damodaran's website at NYU.
4
-
5
- """
6
-
7
- import warnings
8
- from pathlib import Path
9
-
10
- import numpy as np
11
- from matplotlib.ticker import StrMethodFormatter
12
- from numpy.typing import NDArray
13
- from scipy import stats # type: ignore
14
-
15
- import mergeron.core.damodaran_margin_data as dmgn
16
- from mergeron import DATA_DIR
17
- from mergeron.core.guidelines_boundaries import boundary_plot
18
-
19
- PROG_PATH = Path(__file__)
20
-
21
-
22
- def _get_margin_data() -> (
23
- tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]
24
- ):
25
- return dmgn.mgn_data_builder()
26
-
27
-
28
- if __name__ == "__main__":
29
- mgn_data_obs, mgn_data_wts, mgn_data_stats = _get_margin_data()
30
- print(repr(mgn_data_obs))
31
- print(repr(mgn_data_stats))
32
-
33
- plt, mgn_fig, mgn_ax, set_axis_def = boundary_plot(mktshares_plot_flag=False)
34
- mgn_fig.set_figheight(6.5)
35
- mgn_fig.set_figwidth(9.0)
36
-
37
- bin_count = 25
38
- _, mgn_bins, _ = mgn_ax.hist(
39
- x=mgn_data_obs,
40
- weights=mgn_data_wts,
41
- bins=bin_count,
42
- alpha=0.4,
43
- density=True,
44
- label="Downloaded data",
45
- color="#004488", # Paul Tol's High Contrast Blue
46
- )
47
- mgn_ax_yticklabels = mgn_ax.get_yticklabels()
48
- with warnings.catch_warnings():
49
- warnings.filterwarnings("ignore", category=UserWarning)
50
- # Don't warn regarding the below; ticklabels have been fixed before this point
51
- mgn_ax.set_yticklabels([
52
- f"{float(_g.get_text()) * np.diff(mgn_bins)[-1]:.0%}"
53
- for _g in mgn_ax_yticklabels
54
- ])
55
-
56
- # Add KDE plot
57
- # https://stackoverflow.com/questions/33323432
58
- mgn_kde = stats.gaussian_kde(mgn_data_obs, weights=mgn_data_wts)
59
- print(
60
- f"Approximately {mgn_kde.integrate_box_1d(0, 1):.2%} of the estimated mass",
61
- "of the empirical distribution under the Gaussian KDE",
62
- "estimated using selected margin data falls in the interval [0, 1].",
63
- )
64
- mgn_xx = np.linspace(0, bin_count, 10**5)
65
- mgn_ax.plot(
66
- mgn_xx,
67
- mgn_kde(mgn_xx),
68
- color="#004488",
69
- rasterized=True,
70
- label="Estimated Density",
71
- )
72
-
73
- sample_size = 10**6
74
- mgn_ax.hist(
75
- x=dmgn.resample_mgn_data(sample_size),
76
- color="#DDAA33", # Paul Tol's High Contrast Yellow
77
- alpha=0.6,
78
- bins=bin_count,
79
- density=True,
80
- label="Generated data",
81
- )
82
-
83
- mgn_ax.legend(
84
- loc="best",
85
- fancybox=False,
86
- shadow=False,
87
- frameon=True,
88
- facecolor="white",
89
- edgecolor="white",
90
- framealpha=1,
91
- fontsize="small",
92
- )
93
-
94
- mgn_ax.set_xlim(0.0, 1.0)
95
- mgn_ax.xaxis.set_major_formatter(StrMethodFormatter("{x:>3.0%}"))
96
- mgn_ax.set_xlabel("Price Cost Margin", fontsize=10)
97
- mgn_ax.set_ylabel("Relative Frequency", fontsize=10)
98
-
99
- mgn_fig.tight_layout()
100
- plt.savefig(DATA_DIR / PROG_PATH.with_suffix(".pdf").name)
@@ -1,298 +0,0 @@
1
- """
2
- Defining the merging firm with the larger share as buyer under a
3
- GUPPI safeharbor bound of 6%, and the merging firm with the
4
- *smaller* share as the buyer under a GUPPI safeharbor bound of 5%,
5
- and incrementing shares and margins by 5%, plot mergers that
6
- clear the safeharbor threshold, color-coded by margin of the firm
7
- with the larger GUPPI estimate.
8
-
9
- """
10
-
11
- from __future__ import annotations
12
-
13
- import gc
14
- from dataclasses import fields
15
- from pathlib import Path
16
- from typing import Final
17
-
18
- import numpy as np
19
- from matplotlib import cm, colors
20
- from matplotlib.ticker import StrMethodFormatter
21
- from numpy.typing import NDArray
22
-
23
- import mergeron.core.guidelines_boundaries as gbl
24
- import mergeron.gen.data_generation as dgl
25
- import mergeron.gen.upp_tests as utl
26
- from mergeron import DATA_DIR, RECConstants, UPPAggrSelector
27
- from mergeron.core.pseudorandom_numbers import DIST_PARMS_DEFAULT
28
- from mergeron.gen import (
29
- INVResolution,
30
- MarketDataSample,
31
- MarketSampleSpec,
32
- ShareSpec,
33
- SHRConstants,
34
- UPPTestRegime,
35
- )
36
-
37
- PROG_PATH = Path(__file__)
38
-
39
-
40
- def gen_plot_data(
41
- _market_data: MarketDataSample,
42
- _std_vec: gbl.HMGThresholds,
43
- _pcm_firm2_star: float,
44
- _test_regime: UPPTestRegime,
45
- /,
46
- *,
47
- save_data_to_file: utl.SaveData = False,
48
- ) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
49
- if save_data_to_file:
50
- _, _h5_file, _h5_hier = save_data_to_file
51
- _h5_hier = _h5_file.create_group(
52
- _h5_hier,
53
- "plotData_mstar{}PCT".format(
54
- f"{_pcm_firm2_star * 100:03.1f}".replace(".", "dot")
55
- ),
56
- title=f"Firm 2 margin = {_pcm_firm2_star * 100:03.1f}%",
57
- )
58
- save_data_to_file = (True, _h5_file, _h5_hier)
59
-
60
- _pcm_array = np.column_stack((
61
- _m1 := _market_data.pcm_array[:, [0]],
62
- _pcm_firm2_star * np.ones_like(_m1),
63
- ))
64
- del _m1
65
-
66
- _upp_test_raw = utl.gen_upp_arrays(
67
- _std_vec,
68
- MarketDataSample(*[ # type: ignore
69
- _pcm_array if _f.name == "pcm_array" else getattr(_market_data, _f.name)
70
- for _f in fields(_market_data)
71
- ]),
72
- _test_regime,
73
- )
74
-
75
- _gbd_test_rows = np.where(_upp_test_raw.guppi_test_simple)[0]
76
- del _upp_test_raw
77
-
78
- _qtyshr_firm1_inv, _qtyshr_firm2_inv = (
79
- _market_data.frmshr_array[_gbd_test_rows][:, [0]],
80
- _market_data.frmshr_array[_gbd_test_rows][:, [1]],
81
- )
82
- _pcm_firm1_inv, _pcm_firm2_inv = (
83
- _pcm_array[_gbd_test_rows][:, [0]],
84
- _pcm_array[_gbd_test_rows][:, [1]],
85
- )
86
- del _gbd_test_rows
87
-
88
- _pcm_plotter = _pcm_firm1_inv
89
-
90
- if save_data_to_file:
91
- print("Save data to tables")
92
- for _array_name in (
93
- "qtyshr_firm1_inv",
94
- "qtyshr_firm2_inv",
95
- "pcm_firm1_inv",
96
- "pcm_firm2_inv",
97
- ):
98
- _array_obj: NDArray[any] = locals().get(f"_{_array_name}") # type: ignore
99
- if np.any(_array_obj):
100
- utl.save_array_to_hdf5(
101
- _array_obj, _array_name, save_data_to_file[-1], save_data_to_file[1]
102
- )
103
-
104
- _pcm_sorter = np.argsort(_pcm_plotter, axis=0)
105
- if test_regime.resolution != INVResolution.CLRN:
106
- _pcm_sorter = _pcm_sorter[::-1, :]
107
- _qtyshr_firm1_plotter = _qtyshr_firm1_inv[_pcm_sorter]
108
- _qtyshr_firm2_plotter = _qtyshr_firm2_inv[_pcm_sorter]
109
- _pcm_plotter = _pcm_plotter[_pcm_sorter]
110
-
111
- del (
112
- _qtyshr_firm1_inv,
113
- _qtyshr_firm2_inv,
114
- _pcm_firm1_inv,
115
- _pcm_firm2_inv,
116
- _pcm_sorter,
117
- )
118
-
119
- return _qtyshr_firm1_plotter, _qtyshr_firm2_plotter, _pcm_plotter
120
-
121
-
122
- # Generate market data
123
- def _main(
124
- _hmg_pub_year: gbl.HMGPubYear,
125
- _market_sample_spec: MarketSampleSpec,
126
- _test_regime: UPPTestRegime,
127
- save_data_to_file: utl.SaveData,
128
- ) -> None:
129
- guidelins_std_vec = getattr(
130
- gbl.GuidelinesThresholds(_hmg_pub_year),
131
- "safeharbor" if test_regime.resolution == INVResolution.ENFT else "presumption",
132
- )
133
-
134
- _r_bar, _g_bar, _divr_bar = (
135
- getattr(guidelins_std_vec, _f) for _f in ("rec", "guppi", "divr")
136
- )
137
-
138
- market_data = dgl.gen_market_sample(_market_sample_spec, seed_seq_list=None)
139
-
140
- # Set up a plot grid to fill in the various scatterplots
141
- print(
142
- "Construct panel of scatter plots of cleared mergers by Firm 2 margin",
143
- "with each plot color-coded by Firm 1 margin",
144
- sep=", ",
145
- )
146
- _fig_norm = colors.Normalize(0.0, 1.0)
147
- _cmap_kwargs = {"cmap": "cividis", "norm": _fig_norm}
148
- _plt, _, _, _set_axis_def = gbl.boundary_plot()
149
-
150
- _fig_2dsg = _plt.figure(figsize=(8.5, 9.5), dpi=600)
151
-
152
- _fig_grid = _fig_2dsg.add_gridspec(
153
- nrows=1, ncols=2, figure=_fig_2dsg, width_ratios=[6, 0.125], wspace=0.0
154
- )
155
- _fig_grid_gbd = _fig_grid[0, 0].subgridspec(
156
- nrows=3, ncols=1, wspace=0, hspace=0.125
157
- )
158
-
159
- for _ax_row, _pcm_firm2_star in enumerate((
160
- 1.00,
161
- _g_bar / _divr_bar,
162
- _g_bar / _r_bar,
163
- )):
164
- _ax_now = _fig_2dsg.add_subplot(_fig_grid_gbd[_ax_row, 0])
165
- _ax_now = _set_axis_def(_ax_now, mktshares_plot_flag=True)
166
- _ax_now.set_xlabel(None)
167
- _ax_now.set_ylabel(None)
168
- _plt.setp(_ax_now.get_xticklabels()[1::2], visible=False)
169
- _plt.setp(_ax_now.get_yticklabels()[1::2], visible=False)
170
-
171
- _ax_now.text(
172
- 0.81,
173
- 0.72,
174
- "\n".join((
175
- R"$m_2 = m^* = {0:.{1}f}\%$".format(
176
- (_pcmv := _pcm_firm2_star * 100), 1 * (_pcmv % 1 > 0)
177
- ),
178
- R"$m_1 \neq m_2$",
179
- )),
180
- rotation=0,
181
- ha="right",
182
- va="top",
183
- fontsize=10,
184
- zorder=5,
185
- )
186
- if _ax_row == 0:
187
- # Set y-axis label
188
- _ax_now.yaxis.set_label_coords(-0.20, 1.0)
189
- _ax_now.set_ylabel(
190
- "Firm 2 Market Share, $s_2$",
191
- rotation=90,
192
- ha="right",
193
- va="top",
194
- fontsize=10,
195
- )
196
- elif _ax_row == 2:
197
- _ax_now.xaxis.set_label_coords(1.0, -0.15)
198
- _ax_now.set_xlabel(
199
- "\n".join(["Firm 1 Market Share, $s_1$"]), ha="right", fontsize=10
200
- )
201
-
202
- _qtyshr_firm1_plotter, _qtyshr_firm2_plotter, _pcm_plotter = gen_plot_data(
203
- market_data,
204
- guidelins_std_vec,
205
- _pcm_firm2_star,
206
- _test_regime,
207
- save_data_to_file=save_data_to_file,
208
- )
209
-
210
- _ax_now.scatter(
211
- _qtyshr_firm1_plotter,
212
- _qtyshr_firm2_plotter,
213
- marker=".",
214
- s=(0.1 * 72.0 / _fig_2dsg.dpi) ** 2,
215
- c=_pcm_plotter,
216
- **_cmap_kwargs,
217
- rasterized=True,
218
- )
219
- _ax_now.set_aspect(1.0)
220
-
221
- gc.collect()
222
-
223
- # Colorbar
224
- _ax_cm = _fig_2dsg.add_subplot(_fig_grid[-1, -1], frameon=False)
225
- _ax_cm.axis("off")
226
- _cm_plot = _fig_2dsg.colorbar(
227
- cm.ScalarMappable(**_cmap_kwargs), # type: ignore
228
- use_gridspec=True,
229
- ax=_ax_cm,
230
- orientation="vertical",
231
- fraction=3.0,
232
- ticks=np.arange(0, 1.2, 0.2),
233
- format=StrMethodFormatter("{x:>3.0%}"),
234
- )
235
- _cm_plot.set_label(label="Firm 1 Price-Cost Margin, $m_1$", fontsize=10)
236
- _cm_plot.ax.tick_params(length=5, width=0.5, labelsize=6)
237
- _plt.setp(
238
- _cm_plot.ax.yaxis.get_majorticklabels(), horizontalalignment="left", fontsize=6
239
- )
240
-
241
- _cm_plot.outline.set_visible(False)
242
-
243
- _base_name = DATA_DIR / "{}_{}Rate_{}gbar{}PCT_{}Recapture".format(
244
- PROG_PATH.stem,
245
- f"{test_regime.resolution}".capitalize(),
246
- _hmg_pub_year,
247
- f"{_g_bar * 100:02.0f}",
248
- market_sample_spec.share_spec.recapture_spec,
249
- )
250
- _my_fig_2dsg_savepath = DATA_DIR / f"{_base_name}_2DScatterGrid.pdf"
251
- print(f"Save 2D plot to, {f'"{_my_fig_2dsg_savepath}"'}")
252
- _fig_2dsg.savefig(_my_fig_2dsg_savepath, dpi=600)
253
-
254
-
255
- if __name__ == "__main__":
256
- # Get Guidelines parameter values
257
- hmg_pub_year: Final = 2023
258
-
259
- test_regime: UPPTestRegime = UPPTestRegime(
260
- INVResolution.ENFT, UPPAggrSelector.MIN, UPPAggrSelector.MIN
261
- )
262
-
263
- r_bar = getattr(
264
- gbl.GuidelinesThresholds(hmg_pub_year),
265
- "presumption" if test_regime.resolution == INVResolution.ENFT else "safeharbor",
266
- ).rec
267
-
268
- sample_sz = 10**7
269
-
270
- market_sample_spec = MarketSampleSpec(
271
- sample_sz,
272
- r_bar,
273
- share_spec=ShareSpec(
274
- RECConstants.INOUT, SHRConstants.UNI, DIST_PARMS_DEFAULT, None
275
- ),
276
- )
277
-
278
- save_data_to_file_flag = True
279
- if save_data_to_file_flag:
280
- h5_path = DATA_DIR / PROG_PATH.with_suffix(".h5").name
281
- (_, h5_file, h5_group), h5_subgroup_name = utl.initialize_hd5( # type: ignore
282
- h5_path, hmg_pub_year, test_regime
283
- )
284
-
285
- h5_subgroup = h5_file.create_group(
286
- h5_group,
287
- h5_subgroup_name,
288
- title=f"Market sample specifications: {market_sample_spec}",
289
- )
290
- save_data_to_file: utl.SaveData = (True, h5_file, h5_subgroup)
291
- else:
292
- save_data_to_file = False
293
-
294
- _main(hmg_pub_year, market_sample_spec, test_regime, save_data_to_file)
295
-
296
- if save_data_to_file_flag:
297
- save_data_to_file[1].flush() # type: ignore
298
- save_data_to_file[1].close() # type: ignore