import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
# Set seaborn theme for clean visualization styling
sns.set_theme(style="whitegrid")
plt.rcParams['font.sans-serif'] = 'DejaVu Sans'
# ==============================================================================
# 1. GENERATE OR LOAD DATA
# ==============================================================================
# Assuming 'footwear_durability_analysis.csv' exists, or generate in-memory:
try:
df = pd.read_csv("footwear_durability_analysis.csv")
except FileNotFoundError:
print("CSV not found locally; generating synthetic dataset...")
np.random.seed(42)
n_samples = 1000
construction_types = np.random.choice(['Cemented', 'Goodyear Welted'], size=n_samples, p=[0.5, 0.5])
records = []
for i in range(n_samples):
c_type = construction_types[i]
if c_type == 'Cemented':
initial_cost = round(np.random.uniform(65, 145), 2)
total_wears = int(max(60, min(np.random.normal(160, 35), 280)))
resole_count, avg_resole_cost, resale_value = 0, 0.0, 0.0
annual_care = round(np.random.uniform(2, 8), 2)
years = max(0.5, round(total_wears / np.random.uniform(90, 130), 1))
else:
initial_cost = round(np.random.normal(320, 80), 2)
resole_count = np.random.choice([1, 2, 3], p=[0.4, 0.4, 0.2])
wears_per_sole = np.random.normal(500, 70)
total_wears = int((resole_count + 1) * wears_per_sole)
avg_resole_cost = round(np.random.uniform(110, 150), 2)
annual_care = round(np.random.uniform(12, 25), 2)
years = round(total_wears / np.random.uniform(90, 130), 1)
resale_value = round(initial_cost * np.random.uniform(0.18, 0.30), 2)
total_lifetime = round(initial_cost + (resole_count * avg_resole_cost) + (years * annual_care) - resale_value, 2)
records.append({
'construction_type': c_type,
'total_wears': total_wears,
'cost_per_wear_usd': round(total_lifetime / total_wears, 3)
})
df = pd.DataFrame(records)
# ==============================================================================
# 2. CREATE VISUALIZATIONS (2-PANEL FIGURE)
# ==============================================================================
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
# --- PANEL 1: Cost-Per-Wear Distribution ---
sns.histplot(
data=df,
x='cost_per_wear_usd',
hue='construction_type',
kde=True,
ax=axes[0],
palette={'Cemented': '#E63946', 'Goodyear Welted': '#1D3557'},
bins=30,
alpha=0.6
)
# Add median benchmark lines
cemented_median = df[df['construction_type']=='Cemented']['cost_per_wear_usd'].median()
gyw_median = df[df['construction_type']=='Goodyear Welted']['cost_per_wear_usd'].median()
axes[0].axvline(cemented_median, color='#E63946', linestyle='--', linewidth=2, label=f'Cemented Median (${cemented_median:.2f})')
axes[0].axvline(gyw_median, color='#1D3557', linestyle='--', linewidth=2, label=f'GYW Median (${gyw_median:.2f})')
axes[0].set_title('Cost-Per-Wear Distribution: GYW vs. Cemented', fontsize=14, fontweight='bold', pad=12)
axes[0].set_xlabel('Cost Per Wear ($)', fontsize=12)
axes[0].set_ylabel('Frequency (Shoe Count)', fontsize=12)
axes[0].legend(title='Construction Type', frameon=True)
# --- PANEL 2: Break-Even Trajectory Model ---
wears_range = np.arange(1, 1400)
# Cumulative cost logic: Repurchasing $105 cemented shoes every 160 wears
cemented_cumulative = (np.floor((wears_range - 1) / 160) + 1) * 105.0
# Entry GYW ($215 upfront + $110 resole every 450 wears)
gyw_entry_cumulative = 215.0 + (np.floor(wears_range / 450) * 110.0)
# Mid-Tier GYW ($375 upfront + $140 resole every 550 wears)
gyw_mid_cumulative = 375.0 + (np.floor(wears_range / 550) * 140.0)
axes[1].plot(wears_range, cemented_cumulative, label='Disposable Cemented ($105 repurchased / 160 wears)', color='#E63946', linewidth=2.5)
axes[1].plot(wears_range, gyw_entry_cumulative, label='Entry GYW ($215 upfront + $110 resole)', color='#457B9D', linewidth=2.5)
axes[1].plot(wears_range, gyw_mid_cumulative, label='Mid-Tier GYW ($375 upfront + $140 resole)', color='#1D3557', linewidth=2.5)
# Annotate Break-Even Points
axes[1].scatter([320], [325], color='#457B9D', s=90, zorder=5)
axes[1].annotate('Entry GYW Break-Even (~320 wears)', xy=(320, 325), xytext=(350, 240),
arrowprops=dict(facecolor='#457B9D', shrink=0.05, width=1.5, headwidth=6), fontweight='bold', color='#457B9D')
axes[1].scatter([480], [375], color='#1D3557', s=90, zorder=5)
axes[1].annotate('Mid-Tier GYW Break-Even (~480 wears)', xy=(480, 375), xytext=(520, 480),
arrowprops=dict(facecolor='#1D3557', shrink=0.05, width=1.5, headwidth=6), fontweight='bold', color='#1D3557')
axes[1].set_title('Cumulative Spend Trajectory & Break-Even Points', fontsize=14, fontweight='bold', pad=12)
axes[1].set_xlabel('Cumulative Wear Days', fontsize=12)
axes[1].set_ylabel('Total Cumulative Spend ($)', fontsize=12)
axes[1].set_xlim(0, 1400)
axes[1].set_ylim(0, 1100)
axes[1].legend(loc='upper left', frameon=True)
plt.tight_layout()
plt.show()