Policy Scenarios¶
We simulate several policy scenarios:
Baseline (Profit Maximization): Stadium chooses optimal prices
Current Observed Prices: 12.50 beer
Price Ceiling ($6): Half-price beer (main analysis)
**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 $6 price ceiling scenario (half price - main analysis)
ceiling_6 = simulator.run_scenario(
"Price Ceiling ($6)",
beer_price_max=6.0,
crime_cost_per_beer=2.5,
health_cost_per_beer=1.5
)
# Combine results
results = pd.concat([results, pd.DataFrame([ceiling_6])], 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$6 Price Ceiling: Detailed Analysis¶
The 12.50).
# Extract $6 ceiling and baseline for comparison
baseline = results[results['scenario'] == 'Baseline (Profit Max)'].iloc[0]
ceiling_6_result = results[results['scenario'] == 'Price Ceiling ($6)'].iloc[0]
current = results[results['scenario'] == 'Current Observed Prices'].iloc[0]
print("=" * 80)
print("$6 PRICE CEILING ANALYSIS (HALF PRICE)")
print("=" * 80)
print()
print("PRICES")
print(f" Consumer pays: ${ceiling_6_result['consumer_beer_price']:.2f} (was ${current['consumer_beer_price']:.2f})")
print(f" Stadium receives: ${ceiling_6_result['stadium_beer_price']:.2f} (was ${current['stadium_beer_price']:.2f})")
print(f" Change for stadium: ${ceiling_6_result['stadium_beer_price'] - current['stadium_beer_price']:.2f}/beer")
print()
print("CONSUMPTION")
print(f" Attendance: {ceiling_6_result['attendance']:,.0f} (was {current['attendance']:,.0f})")
print(f" Total beers: {ceiling_6_result['total_beers']:,.0f} (was {current['total_beers']:,.0f})")
print(f" Change: {ceiling_6_result['total_beers'] - current['total_beers']:+,.0f} beers ({(ceiling_6_result['total_beers']/current['total_beers'] - 1)*100:+.1f}%)")
print()
print("STADIUM FINANCIALS")
print(f" Profit: ${ceiling_6_result['profit']:,.0f} (was ${current['profit']:,.0f})")
print(f" Change: ${ceiling_6_result['profit'] - current['profit']:+,.0f} ({(ceiling_6_result['profit']/current['profit'] - 1)*100:+.1f}%)")
print(f" Per game: ${ceiling_6_result['profit']:,.0f}")
print(f" Per season (81): ${ceiling_6_result['profit'] * 81:,.0f}")
print(f" Annual loss: ${(current['profit'] - ceiling_6_result['profit']) * 81:,.0f}")
print()
print("TAX REVENUE")
print(f" Per game: ${ceiling_6_result['total_tax_revenue']:,.0f} (was ${current['total_tax_revenue']:,.0f})")
print(f" Change: ${ceiling_6_result['total_tax_revenue'] - current['total_tax_revenue']:+,.0f}")
print(f" Annual: ${ceiling_6_result['total_tax_revenue'] * 81:,.0f}")
print()
print("SOCIAL WELFARE")
print(f" Consumer surplus: ${ceiling_6_result['consumer_surplus']:,.0f} (was ${current['consumer_surplus']:,.0f})")
print(f" Producer surplus: ${ceiling_6_result['producer_surplus']:,.0f} (was ${current['producer_surplus']:,.0f})")
print(f" Externality cost: ${ceiling_6_result['externality_cost']:,.0f} (was ${current['externality_cost']:,.0f})")
print(f" Social welfare: ${ceiling_6_result['social_welfare']:,.0f} (was ${current['social_welfare']:,.0f})")
print(f" Change: ${ceiling_6_result['social_welfare'] - current['social_welfare']:+,.0f}")
print()
print("=" * 80)Interpretation¶
The $6 price ceiling (half price):
Effects on Consumption:
Increases beer consumption dramatically (per-fan consumption triples)
Attendance falls due to higher ticket prices
Effects on Stadium:
Reduces profit significantly (lower margin despite higher volume)
Stadium responds by raising ticket prices ~21%
Annual revenue loss substantial
Effects on Consumers:
Mixed effects: Lower beer prices but higher ticket prices
Drinkers gain more than non-drinkers lose
Effects on Society:
Higher externality costs (more consumption → more crime/health impacts)
Selection effect shifts crowd composition toward drinkers
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 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 $6
ceiling_8 = results[results['scenario'] == 'Price Ceiling ($8.0)'].iloc[0]
ceiling_6_data = results[results['scenario'] == 'Price Ceiling ($6)'].iloc[0]
print("Marginal Effect of Lowering Ceiling from $8 to $6:")
print(f" Beer consumption: {ceiling_6_data['total_beers'] - ceiling_8['total_beers']:+,.0f} beers")
print(f" Stadium profit: ${ceiling_6_data['profit'] - ceiling_8['profit']:+,.0f}")
print(f" Externality cost: ${ceiling_6_data['externality_cost'] - ceiling_8['externality_cost']:+,.0f}")
print(f" Social welfare: ${ceiling_6_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 $6 ceiling shifts crowd composition because:
Ticket price rises (+21%), reducing attendance overall
Non-drinkers only see the ticket increase → attendance falls more
Drinkers get value from cheaper beer, offsetting ticket increase → attendance falls less
Net effect: crowd composition shifts toward drinkers
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_6_result['total_beers']
total_change = ceiling_beers - baseline_beers
baseline_attendance = current['attendance']
ceiling_attendance = ceiling_6_result['attendance']
baseline_beers_per_fan = current['beers_per_fan']
ceiling_beers_per_fan = ceiling_6_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 (127%): {intensive_margin:+,.0f} beers")
print(f" (Each fan drinks more at $6 vs $12.50)")
print()
print(f" Extensive margin (-27%): {extensive_margin:+,.0f} beers")
print(f" (Attendance falls due to higher ticket prices)")
print()
print("SELECTION EFFECT:")
print(f" Non-drinkers: attendance falls more (only see ticket increase)")
print(f" Drinkers: attendance falls less (ticket offset by cheaper beer)")
print(f" Result: crowd composition shifts toward drinkers")
print()
print("The intensive margin dominates because per-fan consumption triples,")
print("while attendance falls ~20%. The negative extensive margin means")
print("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 ($6)']
# 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 ($6 = Half Price)¶
Winners:
Drinkers (lower beer prices outweigh higher ticket prices)
Price-sensitive beer consumers
Losers:
Stadium (lower profit from compressed margins)
Non-drinkers (only see ticket increase, no beer benefit)
Society (higher externality costs from increased consumption)
Net Effect: Depends on weight given to different groups. Selection effects shift benefits toward drinkers.
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.