Policy Scenarios¶
We simulate six policy scenarios:
Baseline (Profit Maximization): Stadium chooses optimal prices
Current Observed Prices: 12.50 beer
**Price Ceiling (7
**Price Ceiling (8
**Price Floor (15
Beer Ban: Zero beer sales
Social Optimum: Maximize social welfare including externalities
import sys
sys.path.insert(0, '../')
from src.model import StadiumEconomicModel
from src.simulation import BeerPriceControlSimulator
import pandas as pd
import numpy as np
import plotly.graph_objects as go
import plotly.express as px
# Initialize model with calibrated parameters
model = StadiumEconomicModel(
capacity=46537,
base_ticket_price=80.0,
base_beer_price=12.5,
ticket_elasticity=-0.625,
beer_elasticity=-0.965,
beer_cost=5.0,
beer_excise_tax=0.074,
beer_sales_tax_rate=0.08875,
experience_degradation_cost=250.0
)
simulator = BeerPriceControlSimulator(model)
print("✓ Model initialized with calibrated parameters")Run All Scenarios¶
# Run standard scenarios
results = simulator.run_all_scenarios(
price_ceiling=8.0,
price_floor=15.0,
crime_cost_per_beer=2.5,
health_cost_per_beer=1.5
)
# Add $7 price ceiling scenario
ceiling_7 = simulator.run_scenario(
"Price Ceiling ($7)",
beer_price_max=7.0,
crime_cost_per_beer=2.5,
health_cost_per_beer=1.5
)
# Combine results
results = pd.concat([results, pd.DataFrame([ceiling_7])], ignore_index=True)
# Display results
display_cols = [
'scenario', 'consumer_beer_price', 'stadium_beer_price',
'attendance', 'total_beers', 'profit', 'social_welfare',
'total_tax_revenue', 'externality_cost'
]
results_display = results[display_cols].copy()
results_display = results_display.round(2)
results_display$7 Price Ceiling: Detailed Analysis¶
The 12.85).
# Extract $7 ceiling and baseline for comparison
baseline = results[results['scenario'] == 'Baseline (Profit Max)'].iloc[0]
ceiling_7_result = results[results['scenario'] == 'Price Ceiling ($7)'].iloc[0]
current = results[results['scenario'] == 'Current Observed Prices'].iloc[0]
print("=" * 80)
print("$7 PRICE CEILING ANALYSIS")
print("=" * 80)
print()
print("PRICES")
print(f" Consumer pays: ${ceiling_7_result['consumer_beer_price']:.2f} (was ${current['consumer_beer_price']:.2f})")
print(f" Stadium receives: ${ceiling_7_result['stadium_beer_price']:.2f} (was ${current['stadium_beer_price']:.2f})")
print(f" Change for stadium: ${ceiling_7_result['stadium_beer_price'] - current['stadium_beer_price']:.2f}/beer")
print()
print("CONSUMPTION")
print(f" Attendance: {ceiling_7_result['attendance']:,.0f} (was {current['attendance']:,.0f})")
print(f" Total beers: {ceiling_7_result['total_beers']:,.0f} (was {current['total_beers']:,.0f})")
print(f" Change: {ceiling_7_result['total_beers'] - current['total_beers']:+,.0f} beers ({(ceiling_7_result['total_beers']/current['total_beers'] - 1)*100:+.1f}%)")
print()
print("STADIUM FINANCIALS")
print(f" Profit: ${ceiling_7_result['profit']:,.0f} (was ${current['profit']:,.0f})")
print(f" Change: ${ceiling_7_result['profit'] - current['profit']:+,.0f} ({(ceiling_7_result['profit']/current['profit'] - 1)*100:+.1f}%)")
print(f" Per game: ${ceiling_7_result['profit']:,.0f}")
print(f" Per season (81): ${ceiling_7_result['profit'] * 81:,.0f}")
print(f" Annual loss: ${(current['profit'] - ceiling_7_result['profit']) * 81:,.0f}")
print()
print("TAX REVENUE")
print(f" Per game: ${ceiling_7_result['total_tax_revenue']:,.0f} (was ${current['total_tax_revenue']:,.0f})")
print(f" Change: ${ceiling_7_result['total_tax_revenue'] - current['total_tax_revenue']:+,.0f}")
print(f" Annual: ${ceiling_7_result['total_tax_revenue'] * 81:,.0f}")
print()
print("SOCIAL WELFARE")
print(f" Consumer surplus: ${ceiling_7_result['consumer_surplus']:,.0f} (was ${current['consumer_surplus']:,.0f})")
print(f" Producer surplus: ${ceiling_7_result['producer_surplus']:,.0f} (was ${current['producer_surplus']:,.0f})")
print(f" Externality cost: ${ceiling_7_result['externality_cost']:,.0f} (was ${current['externality_cost']:,.0f})")
print(f" Social welfare: ${ceiling_7_result['social_welfare']:,.0f} (was ${current['social_welfare']:,.0f})")
print(f" Change: ${ceiling_7_result['social_welfare'] - current['social_welfare']:+,.0f}")
print()
print("=" * 80)Interpretation¶
The $7 price ceiling:
Effects on Consumption:
Increases beer consumption (lower price → higher quantity)
May increase attendance slightly (beer-ticket complementarity)
Effects on Stadium:
Reduces profit significantly (lower margin despite higher volume)
Reduces per-beer revenue from 6.35 (after taxes)
Annual revenue loss: ~$1.8M
Effects on Consumers:
Increases consumer surplus (lower prices)
More affordable beer access
May attract more price-sensitive fans
Effects on Society:
Higher externality costs (more consumption → more crime/health impacts)
Lower tax revenue (lower tax base)
Net social welfare effect depends on consumer surplus gain vs externality increase
Visual Comparison Across Scenarios¶
# Create comparison visualizations
fig = go.Figure()
# Profit comparison
fig.add_trace(go.Bar(
name='Stadium Profit',
x=results['scenario'],
y=results['profit'],
marker_color='#003087'
))
fig.update_layout(
title='Stadium Profit by Scenario',
xaxis_title='Policy Scenario',
yaxis_title='Profit per Game ($)',
height=500,
showlegend=False
)
fig.show()# Social welfare comparison
fig2 = go.Figure()
fig2.add_trace(go.Bar(
x=results['scenario'],
y=results['consumer_surplus'],
name='Consumer Surplus',
marker_color='lightblue'
))
fig2.add_trace(go.Bar(
x=results['scenario'],
y=results['producer_surplus'],
name='Producer Surplus',
marker_color='lightgreen'
))
fig2.add_trace(go.Bar(
x=results['scenario'],
y=-results['externality_cost'],
name='Externality Cost (negative)',
marker_color='salmon'
))
fig2.update_layout(
title='Welfare Components by Scenario',
xaxis_title='Policy Scenario',
yaxis_title='Value ($)',
barmode='stack',
height=600
)
fig2.show()# Beer consumption comparison
fig3 = go.Figure()
fig3.add_trace(go.Bar(
x=results['scenario'],
y=results['total_beers'],
marker_color='#E4002B',
text=results['total_beers'].round(0),
textposition='outside'
))
fig3.update_layout(
title='Total Beer Consumption by Scenario',
xaxis_title='Policy Scenario',
yaxis_title='Total Beers Sold',
height=500,
showlegend=False
)
fig3.show()Comparative Statics¶
Changes relative to current observed prices ($12.50 beer):
# Calculate changes from baseline
changes = simulator.calculate_comparative_statics(results, baseline_scenario='Current Observed Prices')
# Display key changes
change_cols = [
'scenario',
'profit_change',
'total_beers_change',
'social_welfare_change',
'externality_cost_change'
]
changes_display = changes[change_cols].copy()
changes_display = changes_display.round(0)
changes_display8 Price Ceiling Comparison¶
Comparing two different price ceiling levels:
ceiling_comparison = results[results['scenario'].str.contains('Price Ceiling')].copy()
print("Price Ceiling Comparison:")
print()
for _, row in ceiling_comparison.iterrows():
print(f"{row['scenario']}:")
print(f" Consumer price: ${row['consumer_beer_price']:.2f}")
print(f" Stadium receives: ${row['stadium_beer_price']:.2f}")
print(f" Total beers: {row['total_beers']:,.0f}")
print(f" Stadium profit: ${row['profit']:,.0f}")
print(f" Social welfare: ${row['social_welfare']:,.0f}")
print(f" Externality cost: ${row['externality_cost']:,.0f}")
print()
# Calculate marginal effect of lowering ceiling from $8 to $7
ceiling_8 = results[results['scenario'] == 'Price Ceiling ($8.0)'].iloc[0]
ceiling_7_data = results[results['scenario'] == 'Price Ceiling ($7)'].iloc[0]
print("Marginal Effect of Lowering Ceiling from $8 to $7:")
print(f" Beer consumption: {ceiling_7_data['total_beers'] - ceiling_8['total_beers']:+,.0f} beers")
print(f" Stadium profit: ${ceiling_7_data['profit'] - ceiling_8['profit']:+,.0f}")
print(f" Externality cost: ${ceiling_7_data['externality_cost'] - ceiling_8['externality_cost']:+,.0f}")
print(f" Social welfare: ${ceiling_7_data['social_welfare'] - ceiling_8['social_welfare']:+,.0f}")Selection Effects Analysis¶
A key feature of the heterogeneous consumer model is that price policies change who attends, not just how many. The $7 ceiling shifts crowd composition because:
Ticket price rises (+$7.22), reducing attendance overall
Non-drinkers only see the ticket increase → attendance falls -11.5%
Drinkers get value from cheaper beer, offsetting ticket increase → attendance falls only -6.3%
Net effect: crowd composition shifts from 40% → 41.4% drinkers (+1.4pp)
This selection effect means the marginal attendee lost is more likely to be a non-drinker than a drinker.
# Decomposition: Intensive vs Extensive Margin
# This is the KEY contribution of the heterogeneous model
# Get baseline and ceiling scenarios
baseline_beers = current['total_beers']
ceiling_beers = ceiling_7_result['total_beers']
total_change = ceiling_beers - baseline_beers
baseline_attendance = current['attendance']
ceiling_attendance = ceiling_7_result['attendance']
baseline_beers_per_fan = current['beers_per_fan']
ceiling_beers_per_fan = ceiling_7_result['beers_per_fan']
# Decomposition (Shapley-style average of two orderings)
# Order 1: Change attendance first, then beers/fan
intensive_1 = ceiling_attendance * (ceiling_beers_per_fan - baseline_beers_per_fan)
extensive_1 = (ceiling_attendance - baseline_attendance) * baseline_beers_per_fan
# Order 2: Change beers/fan first, then attendance
intensive_2 = baseline_attendance * (ceiling_beers_per_fan - baseline_beers_per_fan)
extensive_2 = (ceiling_attendance - baseline_attendance) * ceiling_beers_per_fan
# Shapley values (average)
intensive_margin = (intensive_1 + intensive_2) / 2
extensive_margin = (extensive_1 + extensive_2) / 2
print("=" * 70)
print("CONSUMPTION DECOMPOSITION: INTENSIVE VS EXTENSIVE MARGIN")
print("=" * 70)
print()
print(f"Total consumption change: {total_change:+,.0f} beers ({total_change/baseline_beers*100:+.1f}%)")
print()
print("Using Shapley decomposition:")
print(f" Intensive margin (116%): {intensive_margin:+,.0f} beers")
print(f" (Each fan drinks more at $7 vs $12.50)")
print()
print(f" Extensive margin (-16%): {extensive_margin:+,.0f} beers")
print(f" (Attendance falls due to higher ticket prices)")
print()
print("SELECTION EFFECT - Attendance by Consumer Type:")
print(f" Non-drinkers: -11.5% (only see ticket increase)")
print(f" Drinkers: -6.3% (ticket increase offset by cheaper beer value)")
print(f" Composition: 40.0% → 41.4% drinkers (+1.4pp)")
print()
print("The intensive margin dominates because per-fan consumption more than")
print("doubles, while attendance falls only modestly. The negative extensive")
print("margin means consumption would be even higher if attendance stayed constant.")
print()
print("=" * 70)# Visualization: Beers per fan across scenarios (selection effect proxy)
fig_selection = go.Figure()
scenarios_ordered = ['Beer Ban', 'Price Floor ($15.0)', 'Baseline (Profit Max)',
'Current Observed Prices', 'Price Ceiling ($8.0)', 'Price Ceiling ($7)']
# Filter and order results
plot_data = results[results['scenario'].isin(scenarios_ordered)].copy()
plot_data['order'] = plot_data['scenario'].map({s: i for i, s in enumerate(scenarios_ordered)})
plot_data = plot_data.sort_values('order')
fig_selection.add_trace(go.Bar(
x=plot_data['scenario'],
y=plot_data['beers_per_fan'],
marker_color=['#2ca02c' if 'Ban' in s or 'Floor' in s
else '#003087' if 'Baseline' in s or 'Current' in s
else '#E4002B' for s in plot_data['scenario']],
text=[f"{x:.2f}" for x in plot_data['beers_per_fan']],
textposition='outside'
))
fig_selection.add_hline(y=1.0, line_dash="dash", line_color="gray",
annotation_text="Baseline: 1.0 beers/fan")
fig_selection.update_layout(
title='Selection Effect: Beers per Fan by Policy Scenario<br><sup>Lower ceilings → higher per-fan consumption (intensive + composition effects)</sup>',
xaxis_title='Policy Scenario',
yaxis_title='Beers per Fan',
height=500,
showlegend=False
)
fig_selection.show()Summary Statistics¶
summary = simulator.summary_statistics(results)
print("Key Scenarios:")
print(f" Profit-maximizing: {summary['profit_maximizing_scenario']}")
print(f" Welfare-maximizing: {summary['welfare_maximizing_scenario']}")
print(f" Lowest externality: {summary['lowest_externality_scenario']}")
print()
print("Attendance Range:")
print(f" Mean: {summary['mean_attendance']:,.0f}")
print(f" Std: {summary['std_attendance']:,.0f}")
print(f" Min: {results['attendance'].min():,.0f}")
print(f" Max: {results['attendance'].max():,.0f}")
print()
print("Beer Consumption Range:")
print(f" Mean: {summary['mean_total_beers']:,.0f}")
print(f" Std: {summary['std_total_beers']:,.0f}")
print(f" Min: {results['total_beers'].min():,.0f}")
print(f" Max: {results['total_beers'].max():,.0f}")Policy Implications¶
Price Ceiling ($7)¶
Winners:
Consumers (lower beer prices, higher surplus)
Price-sensitive fans (increased access to beer)
Losers:
Stadium (lower profit from compressed margins)
Society (higher externality costs from increased consumption)
Net Effect: Depends on weight given to consumer surplus vs stadium profit vs externalities.
Key Insight: Selection Effects Matter¶
The heterogeneous consumer model reveals that price ceilings don’t just change how much people consume—they change who attends. This selection effect is absent from representative agent models and represents a novel contribution to the sports economics literature.
Limitations¶
This is a simulation study using calibrated parameters, not an empirical analysis with estimated coefficients. Key uncertain parameters include:
Cross-price elasticity (assumed 0.1, tested over [0, 0.3])
Drinker share of population (assumed 40%)
External cost estimates ($4/beer)
Qualitative conclusions (tickets rise, consumption increases) are robust across parameter ranges. Exact magnitudes should be interpreted with appropriate uncertainty.