diff --git a/Snakefile b/Snakefile index 2718015..9c3c86d 100644 --- a/Snakefile +++ b/Snakefile @@ -157,6 +157,24 @@ rule manim_shapley_value: out/manim_figures/videos/shapley_value_demo/{wildcards.height}p{wildcards.fps}/sections" +rule manim_comparative_equilibrium_entry: + input: + script = "src/manim_figures/comparative_equilibrium_entry.py" + output: + videos = expand( + "out/manim_figures/videos/comparative_equilibrium_entry/{height}p{fps}/sections/{section}.mp4", + section = find_manim_sections("src/manim_figures/comparative_equilibrium_entry.py"), + allow_missing=True + ) + params: + width = lambda wildcards: wildcards.height, + shell: + "manim render -qh {input.script} --save_sections --media_dir out/manim_figures \ + -r {params.width},{wildcards.height} --fps {wildcards.fps} && \ + python src/util/makeutils.py rename-manim-sections \ + out/manim_figures/videos/comparative_equilibrium_entry/{wildcards.height}p{wildcards.fps}/sections" + + rule figure_two_sided: output: fig = "out/figures/two_sided_lambda2-{lambda_2}.{ext}" @@ -171,6 +189,15 @@ rule figure_weighting_functions: "src/figures/weighting_functions.py" +rule figure_equilibrium_presentation: + input: + csv = "out/figures/equilibrium_{value_function}_{bargaining}_scale-{n_c}_lambda-{lambda_P}.csv" + output: + fig = "out/figures/equilibrium_{var}_{add_bargaining}-bargaining_{value_function}_{bargaining}_scale-{n_c}_lambda-{lambda_P}.{ext}" + script: + "src/figures/equilibrium_presentation.py" + + rule figure_equilibrium: input: script = "src/figures/equilibrium_symbolic.py" diff --git a/src/figures/equilibrium_presentation.py b/src/figures/equilibrium_presentation.py new file mode 100644 index 0000000..adcb363 --- /dev/null +++ b/src/figures/equilibrium_presentation.py @@ -0,0 +1,108 @@ +import matplotlib.pyplot as plt +import pandas as pd +from matplotlib.axes import Axes +from matplotlib.figure import Figure + +plt.rcParams.update({"text.usetex": True}) + + +VARS = { + "entry-fee": { + "benchmark": "K_F_opt", + "bargaining": "K_F_implied", + "pure_retail": None, + "y_label": "$K_F$", + }, + "platform-profit": { + "benchmark": "pi_P_bench", + "bargaining": "pi_P", + "pure_retail": "pi_P_noF", + "y_label": "$pi_P$", + }, + "fringe-number": { + "benchmark": "N_F_bench", + "bargaining": "N_F", + "pure_retail": None, + "y_label": "$N_F$", + }, + "aggregate": { + "benchmark": "A_bench", + "bargaining": "A", + "pure_retail": "A_noF", + "y_label": "$A$", + }, + "consumer-surplus": { + "benchmark": "CS_bench", + "bargaining": "CS", + "pure_retail": "CS_noF", + "y_label": "$CS$", + }, +} + + +def plot_equilibrium_outcomes( + df: pd.DataFrame, + benchmark_var: str, + bargaining_var: str | None = None, + pure_retail_var: str | None = None, + y_label: str = "", + hybrid_indicator_benchmark: str | None = None, + hybrid_indicator_bargaining: str | None = None, +) -> tuple[Figure, Axes]: + fig, ax = plt.subplots() + + ax.plot(df["N_P"], df[benchmark_var], label="Benchmark") + + if bargaining_var is not None: + ax.plot(df["N_P"], df[bargaining_var], label="Bargaining") + + if pure_retail_var is not None: + ax.plot( + df["N_P"], + df[pure_retail_var], + label="Pure retail", + color="black", + linestyle=":", + ) + + if hybrid_indicator_benchmark is not None: + shade_end = df.loc[df[hybrid_indicator_benchmark] != 0, "N_P"].max() + ax.axvspan(0, shade_end, color="gray", alpha=0.2) + + if hybrid_indicator_bargaining is not None: + shade_end = df.loc[df[hybrid_indicator_bargaining] != 0, "N_P"].max() + ax.axvspan(0, shade_end, color="gray", alpha=0.2) + + ax.set_xlabel("$N_P$") + ax.set_ylabel(y_label) + + ax.set_xlim(0, df["N_P"].max()) + ax.set_xticks([0, df["N_P"].max()]) + + ax.spines[["right", "top"]].set_visible(False) + ax.legend() + + return fig, ax + + +if __name__ == "__main__": + input_data = snakemake.input.csv # type: ignore # noqa: F821 + output_figure = snakemake.output[0] # type: ignore # noqa: F821 + var = snakemake.wildcards.var # type: ignore # noqa: F821 + plot_bargaining = snakemake.wildcards.add_bargaining == "with" # type: ignore # noqa: F821 + # TODO: handle vars + + df = pd.read_csv(input_data) + fig, _ = plot_equilibrium_outcomes( + df=df, + benchmark_var=VARS[var]["benchmark"], + bargaining_var=VARS[var]["bargaining"] if plot_bargaining else None, + pure_retail_var=VARS[var]["pure_retail"] if plot_bargaining else None, + y_label=VARS[var]["y_label"], + hybrid_indicator_benchmark="hybrid_bench", + hybrid_indicator_bargaining="hybrid" if plot_bargaining else None, + ) + + fig.set_size_inches(3, 2.5) + fig.tight_layout() + fig.savefig(output_figure, bbox_inches="tight", dpi=300) diff --git a/src/manim_figures/comparative_equilibrium_entry.py b/src/manim_figures/comparative_equilibrium_entry.py new file mode 100644 index 0000000..eae2189 --- /dev/null +++ b/src/manim_figures/comparative_equilibrium_entry.py @@ -0,0 +1,131 @@ +from manim import ( + BLACK, + BLUE_D, + DOWN, + RED_D, + UP, + WHITE, + Axes, + Brace, + BraceBetweenPoints, + Create, + Line, + MathTex, + ParametricFunction, + Scene, + Tex, + Text, + Transform, + Write, +) +from numpy import log +from scipy.optimize import fsolve + + +def pi_F(N_P, N_F): + numerator = N_F * (N_F + N_P) - (N_F + N_P + 1) * ( + N_F + log(N_P + 1) - log(N_F + N_P + 1) + ) + denominator = N_F * (N_F + N_P + 1) + return numerator / denominator + + +class Baseline(Scene): + def construct(self): + self.next_section("draw_graph") + + self.camera.background_color = WHITE # type: ignore + Text.set_default(color=BLACK) + Line.set_default(color=BLACK) + Tex.set_default(color=BLACK) + MathTex.set_default(color=BLACK) + Brace.set_default(color=BLACK) + ParametricFunction.set_default(color=BLACK) + + # Create plane + ax = Axes( + x_range=[0.005, 30, 0.1], + y_range=[0, 0.25, 0.01], + x_length=12, + y_length=8, + x_axis_config={"include_ticks": False}, + y_axis_config={"include_ticks": False}, + ) + x_label = ax.get_x_axis_label(r"N_F") + y_label = ax.get_y_axis_label(r"\pi_F") + + N_P_0 = 0 + N_P_1 = 0.5 + I_F = 0.005 + + # Intersections + N_F_opt_0 = fsolve(lambda N_F: pi_F(N_P_0, N_F) - I_F * N_F, 20)[0] + N_F_opt_0_val = pi_F(N_P_0, N_F_opt_0) + N_F_opt_point_0 = ax.c2p(N_F_opt_0, N_F_opt_0_val) + N_F_opt_1 = fsolve(lambda N_F: pi_F(N_P_1, N_F) - I_F * N_F, 20)[0] + N_F_opt_1_val = pi_F(N_P_1, N_F_opt_1) + N_F_opt_point_1 = ax.c2p(N_F_opt_1, N_F_opt_1_val) + + # Destinations + pi_F_orig = ax.plot(lambda x: pi_F(N_P_0, x), color=BLUE_D) + pi_F_alt = ax.plot(lambda x: pi_F(N_P_1, x), color=RED_D) + pi_F_orig_label = ax.get_graph_label( + pi_F_orig, + r"\pi_F(N_P, N_F)", + direction=UP, # type: ignore + ) + pi_F_alt_label = ax.get_graph_label( + pi_F_alt, + r"\pi_F(N_P', N_F)", + direction=DOWN, # type: ignore + ) + + N_F_opt_0_bar = ax.get_vertical_line(N_F_opt_point_0, color=BLACK) + N_F_opt_1_bar = ax.get_vertical_line(N_F_opt_point_1, color=BLACK) + # N_F_opt_0_label = MathTex(r"N_F^*(N_P)", color=BLUE_D) + # N_F_opt_0_label.next_to(N_F_opt_0_bar, DOWN) + # N_F_opt_1_label = MathTex(r"N_F^*(N_P')", color=RED_D) + # N_F_opt_1_label.next_to(N_F_opt_1_bar, DOWN) + + investment_cost = ax.plot(lambda x: I_F * x) + investment_cost_label = ax.get_graph_label( + investment_cost, + r"I_F N_F", + direction=UP, # type: ignore + ) + brace_loss = BraceBetweenPoints( + N_F_opt_1_bar.get_bottom(), # type: ignore + N_F_opt_0_bar.get_bottom(), # type: ignore + ) + brace_label = MathTex(r"> N_P' - N_P", color=BLACK) + brace_label.next_to(brace_loss, DOWN) + + # Moving objects + pi_F_plot = pi_F_orig.copy() + pi_F_label = pi_F_orig_label.copy() + N_F_opt_bar = N_F_opt_0_bar.copy() + # N_F_opt_label = N_F_opt_0_label.copy() + + # First phase: Draw pi_F and investment cost + self.play(Create(ax), Write(x_label), Write(y_label)) + self.play(Create(pi_F_plot), Create(pi_F_label)) + self.play(Create(investment_cost), Create(investment_cost_label)) + + # Second phase: mark equilibrium + self.next_section("mark_equilibrium") + self.play(Create(N_F_opt_bar)) + + # Third phase: move to alternative equilibrium + self.next_section("alternate_equilibrium") + self.add(pi_F_orig, pi_F_orig_label, N_F_opt_0_bar) + self.play(Transform(pi_F_plot, pi_F_alt), Transform(pi_F_label, pi_F_alt_label)) + self.wait(0.5) + self.play( + Transform(N_F_opt_bar, N_F_opt_1_bar), + # Transform(N_F_opt_label, N_F_opt_1_label) + ) + + # Fourth phase: show loss + self.next_section("show_loss") + self.play(Create(brace_loss), Write(brace_label)) + self.wait(0.1) diff --git a/src/presentation/defense.qmd b/src/presentation/defense.qmd index 72f499b..44e120a 100644 --- a/src/presentation/defense.qmd +++ b/src/presentation/defense.qmd @@ -380,7 +380,7 @@ $$ ::: {.column width="58%"} ::: {.highlight-block .fragment fragment-index=2 .smaller} -**Theorem 1.1** +**Theorem 1.1.** - Let $f$ be continuous on $[0, 1]$. - Let $X_n \coloneqq \frac{|\mathrm{prec}_P|}{n} \xrightarrow[]{d} X$ @@ -482,7 +482,7 @@ The **big player** gets a **larger slice** when ::: {.column width="58%"} ::: {.highlight-block .fragment .smaller} -**Theorem 1.2** +**Theorem 1.2.** - Let $X^j_n \coloneqq \frac{|\mathrm{prec}_{P_j}|}{n} \xrightarrow[]{d} X^j$ - with cdf $G(t) \, \forall j$ @@ -573,11 +573,10 @@ $$ :::: ::: {.highlight-block .fragment .smaller} -**Theorem 1.4** +**Theorem. 1.4.** Assume that -- Assume that - - $f$ is continuous on $[0, 1]^L$ - - $X_n \coloneqq \left( \frac{n_{A_1}(\mathrm{prec})}{n}, \dots, \frac{n_{A_L}(\mathrm{prec})}{n} \right) \xrightarrow[]{d} X$ with cdf $G(t_1, \dots, t_L)$ +- $f$ is continuous on $[0, 1]^L$ +- $X_n \coloneqq \left( \frac{n_{A_1}(\mathrm{prec})}{n}, \dots, \frac{n_{A_L}(\mathrm{prec})}{n} \right) \xrightarrow[]{d} X$ with cdf $G(t_1, \dots, t_L)$ $$ \implies \varphi_P^\infty = \int_0^1 \dots \int_0^1 f(t_1, \dots, t_L) \mathrm{d}G(t_1, \dots t_L) @@ -611,7 +610,7 @@ $$ ::: {.column width="58%" .fragment .smaller} ::: {.highlight-block} -**Proposition 1.7** +**Proposition 1.7.** Let $f$ be continuous on $[0, 1]^L$. Then, @@ -696,7 +695,7 @@ General idea: @sec-heterogeneity-general ::: {.column width="58%" .fragment .smaller} ::: {.highlight-block} -**Proposition 1.8** +**Proposition 1.8.** Let $f$ be continuous on $[0, 1]^L$. Then, @@ -746,7 +745,7 @@ Weighted value with **$\lambda_1 < \lambda_2$** :::: -## Conclusion +## Summary ::: {.fragment} - Provide a **tool-kit** for modeling **bargaining** in settings with one (few) **central player**(s) and many small ones @@ -764,12 +763,344 @@ Weighted value with **$\lambda_1 < \lambda_2$** ::: - # Chapter 2 – Application Hybrid platforms and bargaining power +## Motivation + +::: {.fragment} +- Hybrid platforms are + - Getting more and more common + - Seemingly obvious concerns and high-profile competition policy cases + - Despite this, relatively little reserach +::: +::: {.fragment} +- Bargaining between participants is not well understood in the platform setting + - Authors generally assume take-it-or-leave-it offers +::: + +::: {.highlight-block .fragment} +Examine the bargaining power implications of hybrid platforms and its welfare consequences +::: + + +## Main ideas + +::: {.fragment} +- **Shapley-value** based bargaining rule + - Analytically more **tractable** + - Has **bargaining microfoundations** [e.g. @hart1996bargaining; @stole1996intra] + - Has precedents in the IO literature [e.g. @hart1990property; @montez2007downstream] +::: +::: {.fragment} +- **Abstract away all frictions** + - Focus on the **bargaining channel** + - Offers a good benchmark model +::: +::: {.fragment} +- Consider a **continuum of small players** case for tractability + - Good approximation of the finite player case even for not too many players +::: + + +## Related literature + +::: {.fragment} + - Multi-sided markets + - Platform markets: @rochet2003platform, @hagiu2004optimal, @armstrong2006competition + - Hybrid platforms: **@anderson2021hybrid**, @gutierrez2021welfare, @hagiu2022should +::: +::: {.fragment} + - Other similarly structured markets + - Private labels in retail: @steiner2004nature + - Vertical integration: @hart1990vertical, @de2005vertical, @montez2007downstream +::: +::: {.fragment} + - Cooperative games in IO + - @hart1990property, @levy1997individual, **@montez2007downstream**, **@anderson2021hybrid** +::: + + +## Model – Players + +::: {.fragment} + - One **platform**: $P$ + - Without the platform, no value can be created + - Might have its own products (hybrid mode) +::: + +::: {.fragment} + - A continuum of potential **fringe entrants**: $F_i, i \in \mathbb{R}_0^+$ + - Infinitesimally small + - Have one product each + - Can only sell through the platform +::: + + +## Model – Timing and overview + +:::: {.columns} + +::: {.column width="50%" .fragment} +**Benchmark model** + +```{dot} +//| fig-width: 100% +digraph G { + node [shape=box, fontsize=18, style=rounded, margin=0.2, penwidth=2]; + edge [penwidth=2]; + + entry_fee [label=<T1: Platform unilaterally
sets the entry fee>]; + entry_decision [label=<T2: Potential entrants decide to
invest in a product and enter>]; + sales [label=<T3: Platform and fringe
set product prices>]; + final [label=<T4: Consumers make
consumption decisions>]; + + entry_fee -> entry_decision; + entry_decision -> sales; + sales -> final; +} +``` +::: + +::: {.column width="50%" .fragment} +**Bargaining model** + +```{dot} +//| fig-width: 100% +digraph G { + node [shape=box, fontsize=18, style=rounded, margin=0.2, penwidth=2]; + edge [penwidth=2]; + + entry_decision [label=<T1: Potential entrants decide to
invest in a product>, color="#5692e4"]; + entry_fee [label=<T2: Entry fees are negotiated
between platform and fringe entrants>, color="#5692e4"]; + sales [label=<T3: Platform and fringe
set product prices>]; + final [label=<T4: Consumers make
consumption decisions>]; + + entry_decision -> entry_fee; + entry_fee -> sales; + sales -> final; +} +``` +::: + +:::: + + +## Demand {#sec-demand} + +Logit-like demand for each product $T_i$ + +::: {.fragment .smaller} +- $T \in \{P, F\}$ +- $v$: value of the product +- $p$: price + +$$ +x_{Ti} = \frac{\exp\left( \frac{v_T - p_{T_i}}{\mu} \right)}{A} +$$ +::: +::: {.fragment .smaller} +where +$$ +A = \int_0^{N_F} \exp\left( \frac{v_F - p_{Fi}}{\mu} \right) \mathrm{d}i + \int_0^{N_P} \exp\left( \frac{v_P - p_{Pi}}{\mu} \right) \mathrm{d}i + 1 +$$ +::: + +::: {.crossref} +Utility function: @sec-utility-function +::: + + +## Production +::: {.fragment} +- Assume that the platform prices its products as if they were made by separate, competitive sellers + - Possible interpretation: profit-maximizing subsidiaries + - More importantly: "best case scenario" +::: +::: {.fragment .highlight-block .smaller} +**Proposition 2.3.** + +The optimal price is an additive markup over marginal costs: + +- $p^*_{Ti} = c_T + \mu$ +- $\pi^{v*}_{T_i} = \mu \frac{\exp \left( \frac{v_T - c_T - \mu}{\mu} \right)}{A} \coloneqq \frac{V_T}{A}$ +::: + + +## Free enetry + +::: {.fragment} +- Potential entrants decide to enter if they can cover the + - Investment cost $I_F$ + - Platform entry fee $K_F$ +::: + +::: {.fragment .highlight-block .smaller} +**Proposition 2.4.** + +If $I_F$ and $K_F$ are low enough, the equilibrium size of the aggregate is +$$ +A = \mu \frac{V_F}{K_F + I_F} +$$ +::: + +::: {.fragment} +- Does not directly depend on the platform's product variety +::: + + +## Benchmark model + +::: {.fragment .highlight-block .smaller} +**Theorem. 2.1** +The optimal entry fee is given by + +$$K_F^{opt} = \sqrt{\mu I_F V_F} - I_F$$ +::: + +::: {.fragment .highlight-block .smaller} +**Theorem. 2.2** +In the benchmark model under hybrid regime, + +- The equilibrium number of fringe firms is decreasing in the platform's product variety: $\frac{\mathrm{d} N_F}{\mathrm{d} N_P} = -\frac{V_P}{V_F} < 0$. +- The equilibrium size of the aggregate and consumer surplus are independent of the platform's product variety: $\frac{\mathrm{d} A}{\mathrm{d} N_P} = \frac{\mathrm{d} CS}{\mathrm{d} N_P} = 0$. +::: + + +## Benchmark model + +::: {layout-ncol=2} +![Entry fee](/out/figures/equilibrium_entry-fee_without-bargaining_profit_onesided_scale-1_lambda-1.svg){width=95% height=100%} + +![Number of fringe entrants](/out/figures/equilibrium_fringe-number_without-bargaining_profit_onesided_scale-1_lambda-1.svg){width=95% height=100%} +::: + + +## Bargaining model – profit division + +::: {.fragment} +- Total profits (≈ characteristic function): + +$$ +\Pi(N_P, N_F) = \mu \frac{N_F V_F + N_P V_P}{N_F V_F + N_P V_P + 1} +$$ +::: + +::: {.fragment} +- Everyone gets their **Shapley-value**: + +$$ +\pi^t_P = \int_0^1 \Pi(N_P, sN_F) \mathrm{d}s +$$ +::: + + +## Bargaining model – fringe entry + +::::: {.columns} + +:::: {.column width="40%"} + +::: {.fragment fragment-index=1} +- The fringe profits are hump-shaped +::: +::: {.fragment fragment-index=3} +- Platform product variety reduces fringe entry +::: +::: {.fragment fragment-index=4} +- In the bargaining case, more than proportionally +::: + +:::: + +:::: {.column width="60%"} + +::: {.r-stack} + +::: {.fragment fragment-index=1} +