lcavana2/NFL_Combine_Performance_Analytics
0
1import string2from pathlib import Path3 4import gradio as gr5import matplotlib.pyplot as plt6import numpy as np7import pandas as pd8import seaborn as sns9from scipy.spatial.distance import euclidean10from sklearn.linear_model import LinearRegression11from sklearn.model_selection import train_test_split12 13 14APP_DIR = Path(__file__).parent15DATA_DIRS = [APP_DIR, APP_DIR / "data"]16 17COMBINE_FILE = "NFL_Combine_Since_2000.csv"18WEEKLY_OFFENSE_FILE = "weekly_player_stats_offense.csv"19YEARLY_OFFENSE_FILE = "yearly_player_stats_offense.csv"20 21METRICS = ["40-yd Dash", "Vertical Jump", "Broad Jump", "Height", "Weight"]22CORE_EVENTS = ["40-yd Dash", "Vertical Jump", "Broad Jump"]23NFL_PERFORMANCE_METRICS = [24 "career_fantasy_points_standard",25 "career_fantasy_points_half_ppr",26 "career_fantasy_points_ppr",27 "best_single_season_ppr",28 "avg_ppr_per_season",29 "career_length_seasons",30]31METRIC_LABEL_MAP = {32 "career_fantasy_points_standard": "Career Fantasy Points (Std)",33 "career_fantasy_points_half_ppr": "Career Fantasy Points (Half PPR)",34 "career_fantasy_points_ppr": "Career Fantasy Points (PPR)",35 "best_single_season_ppr": "Best Single Season (PPR)",36 "avg_ppr_per_season": "Avg. PPR Per Season",37 "career_length_seasons": "Career Length (Seasons)",38}39 40 41def find_data_file(filename):42 for directory in DATA_DIRS:43 candidate = directory / filename44 if candidate.exists():45 return candidate46 return None47 48 49def clean_name(name):50 if isinstance(name, str):51 name = name.lower()52 name = name.translate(str.maketrans("", "", string.punctuation))53 return name.strip()54 return name55 56 57def load_and_prepare_data():58 required_files = {59 COMBINE_FILE: find_data_file(COMBINE_FILE),60 WEEKLY_OFFENSE_FILE: find_data_file(WEEKLY_OFFENSE_FILE),61 YEARLY_OFFENSE_FILE: find_data_file(YEARLY_OFFENSE_FILE),62 }63 missing_files = [filename for filename, path in required_files.items() if path is None]64 if missing_files:65 return None, missing_files66 67 df = pd.read_csv(required_files[COMBINE_FILE])68 weekly_offense = pd.read_csv(required_files[WEEKLY_OFFENSE_FILE])69 year_offense = pd.read_csv(required_files[YEARLY_OFFENSE_FILE])70 71 df["cleaned_player_name"] = df["Player"].apply(clean_name)72 year_offense["cleaned_player_name"] = year_offense["player_name"].apply(clean_name)73 weekly_offense["cleaned_player_name"] = weekly_offense["player_name"].apply(clean_name)74 75 df_percentiles = df.copy()76 for metric in METRICS:77 ascending_bool = metric != "40-yd Dash"78 df_percentiles[f"{metric}_percentile"] = (79 df_percentiles.groupby("Position")[metric].rank(pct=True, ascending=ascending_bool) * 10080 )81 82 percentile_cols = [f"{metric}_percentile" for metric in METRICS]83 df_percentiles["Overall_Combine_Score"] = df_percentiles[percentile_cols].mean(axis=1)84 85 df_rethought_score = df_percentiles.copy()86 core_percentile_cols = [f"{metric}_percentile" for metric in CORE_EVENTS]87 df_rethought_score["valid_core_events_count"] = df_rethought_score[core_percentile_cols].count(axis=1)88 df_rethought_score["Overall_Combine_Score_Rethought"] = np.nan89 eligible_players = df_rethought_score["valid_core_events_count"] > 090 df_rethought_score.loc[eligible_players, "Overall_Combine_Score_Rethought"] = df_rethought_score.loc[91 eligible_players, percentile_cols92 ].mean(axis=1)93 df_rethought_score = df_rethought_score.drop(columns=["valid_core_events_count"])94 95 first_nfl_season_df = year_offense.groupby("cleaned_player_name")["season"].min().reset_index()96 first_nfl_season_df = first_nfl_season_df.rename(columns={"season": "first_nfl_season"})97 df_merged_temp = pd.merge(df_rethought_score, first_nfl_season_df, on="cleaned_player_name", how="inner")98 df_filtered_by_season = df_merged_temp[df_merged_temp["Year"] == df_merged_temp["first_nfl_season"]]99 weekly_active_players = weekly_offense["cleaned_player_name"].unique()100 df_merged_data = df_filtered_by_season[101 df_filtered_by_season["cleaned_player_name"].isin(weekly_active_players)102 ].copy()103 104 career_stats_df = (105 year_offense.groupby("cleaned_player_name")106 .agg(107 career_fantasy_points_standard=("fantasy_points_standard", "sum"),108 career_fantasy_points_half_ppr=("fantasy_points_half_ppr", "sum"),109 career_fantasy_points_ppr=("fantasy_points_ppr", "sum"),110 best_single_season_ppr=("fantasy_points_ppr", "max"),111 avg_ppr_per_season=("fantasy_points_ppr", "mean"),112 career_length_seasons=("season", "nunique"),113 )114 .reset_index()115 )116 117 cols_to_merge = career_stats_df.columns.drop("cleaned_player_name")118 new_cols_to_add = [col for col in cols_to_merge if col not in df_merged_data.columns]119 if new_cols_to_add:120 career_stats_subset = career_stats_df[["cleaned_player_name"] + new_cols_to_add]121 df_merged_data = pd.merge(df_merged_data, career_stats_subset, on="cleaned_player_name", how="left")122 123 df_merged_data["best_single_season_ppr_percentile"] = (124 df_merged_data["best_single_season_ppr"].rank(pct=True, ascending=True) * 100125 )126 127 columns_for_search = [128 "Player",129 "cleaned_player_name",130 "Position",131 "Overall_Combine_Score_Rethought",132 "40-yd Dash_percentile",133 "Vertical Jump_percentile",134 "Broad Jump_percentile",135 "Height_percentile",136 "Weight_percentile",137 "best_single_season_ppr",138 "career_fantasy_points_ppr",139 "best_single_season_ppr_percentile",140 ]141 df_player_search = df_merged_data[columns_for_search].copy()142 df_player_search.drop_duplicates(subset=["cleaned_player_name"], keep="first", inplace=True)143 144 p_correlation_data = df_merged_data[["Overall_Combine_Score_Rethought"] + NFL_PERFORMANCE_METRICS].copy()145 overall_correlation_matrix = p_correlation_data.corr(method="pearson")146 combine_performance_correlations = overall_correlation_matrix.loc[147 ["Overall_Combine_Score_Rethought"], NFL_PERFORMANCE_METRICS148 ]149 150 position_correlations = {}151 for position, group_df in df_merged_data.groupby("Position"):152 if len(group_df) <= 1:153 continue154 group_correlation_data = group_df[["Overall_Combine_Score_Rethought"] + NFL_PERFORMANCE_METRICS].copy()155 group_correlation_data = group_correlation_data.dropna(156 subset=["Overall_Combine_Score_Rethought"] + NFL_PERFORMANCE_METRICS157 )158 if len(group_correlation_data) > 1:159 corr_matrix = group_correlation_data.corr(method="pearson")160 if "Overall_Combine_Score_Rethought" in corr_matrix.index:161 position_correlations[position] = corr_matrix.loc[162 "Overall_Combine_Score_Rethought", NFL_PERFORMANCE_METRICS163 ]164 165 target_variable = "best_single_season_ppr"166 features = ["Overall_Combine_Score_Rethought"]167 model_data = df_merged_data[features + [target_variable]].copy()168 model_data.dropna(subset=features + [target_variable], inplace=True)169 model = LinearRegression()170 if len(model_data) >= 2:171 x = model_data[features]172 y = model_data[target_variable]173 x_train, _, y_train, _ = train_test_split(x, y, test_size=0.2, random_state=42)174 model.fit(x_train, y_train)175 else:176 model = None177 178 min_players_per_position = 5179 min_avg_metrics_per_player = 3180 percentile_cols_in_merged = [181 col for col in df_merged_data.columns if "_percentile" in col and col != "best_single_season_ppr_percentile"182 ]183 numeric_percentile_cols = [184 col for col in percentile_cols_in_merged if pd.api.types.is_numeric_dtype(df_merged_data[col])185 ]186 187 if not numeric_percentile_cols:188 sufficient_data_positions = df_merged_data["Position"].dropna().unique().tolist()189 else:190 temp_df_merged_data_for_metrics = df_merged_data.copy()191 temp_df_merged_data_for_metrics["temp_valid_metrics_count"] = temp_df_merged_data_for_metrics[192 numeric_percentile_cols193 ].notna().sum(axis=1)194 position_stats = temp_df_merged_data_for_metrics.groupby("Position").agg(195 num_players=("Player", "count"),196 avg_valid_metrics=("temp_valid_metrics_count", "mean"),197 )198 sufficient_data_positions = position_stats[199 (position_stats["num_players"] >= min_players_per_position)200 & (position_stats["avg_valid_metrics"] >= min_avg_metrics_per_player)201 ].index.tolist()202 203 prediction_tab_exclusions = ["C", "FB", "OT", "OL", "P"]204 projection_tool_additions = ["C", "OT", "OL"]205 filtered_prediction_positions = [206 position for position in sufficient_data_positions if position not in prediction_tab_exclusions207 ]208 projection_tool_positions = sorted(209 set(filtered_prediction_positions + [pos for pos in sufficient_data_positions if pos in projection_tool_additions])210 )211 filtered_prediction_positions.sort()212 213 return {214 "df_rethought_score": df_rethought_score,215 "df_merged_data": df_merged_data,216 "df_player_search": df_player_search,217 "combine_performance_correlations": combine_performance_correlations,218 "position_correlations": position_correlations,219 "model": model,220 "filtered_prediction_positions": filtered_prediction_positions,221 "projection_tool_positions": projection_tool_positions,222 }, []223 224 225DATA, MISSING_FILES = load_and_prepare_data()226 227 228def display_overall_correlations():229 correlations = DATA["combine_performance_correlations"].loc[230 "Overall_Combine_Score_Rethought", NFL_PERFORMANCE_METRICS231 ]232 plot_df = correlations.reset_index()233 plot_df.columns = ["Metric", "Correlation"]234 plot_df["Metric"] = plot_df["Metric"].map(METRIC_LABEL_MAP)235 236 fig = plt.figure(figsize=(10, 6))237 fig.patch.set_facecolor("#293c59")238 ax = fig.gca()239 ax.set_facecolor("#293c59")240 241 sns.barplot(x="Metric", y="Correlation", data=plot_df, palette="viridis", hue="Metric", legend=False, ax=ax)242 243 ax.tick_params(colors="white", labelcolor="white")244 ax.xaxis.label.set_color("white")245 ax.yaxis.label.set_color("white")246 ax.title.set_color("white")247 plt.title("Overall Correlation: Combine Score vs. NFL Metrics", color="white")248 plt.xticks(rotation=45, ha="right", color="white")249 plt.yticks(color="white")250 plt.xlabel("NFL Performance Metric", color="white")251 plt.ylabel("Pearson Correlation Coefficient", color="white")252 plt.ylim(-1, 1)253 plt.axhline(0, color="white", linewidth=0.8)254 255 for index, row in plot_df.iterrows():256 offset = 0.02 if row["Correlation"] >= 0 else -0.05257 plt.text(index, row["Correlation"] + offset, f"{row['Correlation']:.2f}", color="white", ha="center")258 259 plt.tight_layout()260 markdown_table = (261 "### Precise Correlation Values:<span style='color: #3385c3;'> "262 "(Overall Combine Score vs. NFL Metrics)</span>\n\n"263 + DATA["combine_performance_correlations"].rename(columns=METRIC_LABEL_MAP).to_markdown(index=True)264 )265 return fig, markdown_table266 267 268def display_position_correlations(position):269 fig = plt.figure(figsize=(10, 6))270 fig.patch.set_facecolor("#293c59")271 ax = fig.gca()272 ax.set_facecolor("#293c59")273 274 position_correlations = DATA["position_correlations"]275 if position in position_correlations and not position_correlations[position].empty:276 plot_df = position_correlations[position].reset_index()277 plot_df.columns = ["Metric", "Correlation"]278 plot_df["Metric"] = plot_df["Metric"].map(METRIC_LABEL_MAP)279 280 sns.lineplot(x="Metric", y="Correlation", data=plot_df, marker="o", color="#3385c3", ax=ax)281 282 ax.tick_params(colors="white", labelcolor="white")283 ax.xaxis.label.set_color("white")284 ax.yaxis.label.set_color("white")285 ax.title.set_color("white")286 plt.title(f"Correlation for {position}", color="white")287 plt.xlabel("NFL Performance Metric", color="white")288 plt.ylabel("Pearson Correlation Coefficient", color="white")289 plt.xticks(rotation=45, ha="right", color="white")290 plt.yticks(color="white")291 plt.ylim(-1, 1)292 plt.axhline(0, color="white", linewidth=0.8)293 294 for index, row in plot_df.iterrows():295 plt.text(index, row["Correlation"] + 0.05, f"{row['Correlation']:.2f}", color="white", ha="center")296 297 plt.tight_layout()298 else:299 ax.text(0.5, 0.5, "Insufficient data", ha="center", va="center", color="white", transform=ax.transAxes)300 ax.axis("off")301 return fig302 303 304def display_home_page_content():305 introduction = """306 <h1 style='color: #00336b;'>Welcome to the NFL Combine Analytics Dashboard!</h1>307 <p style='color: #0f2144;'>This interactive dashboard allows you to explore the relationship between NFL Combine performance and a player's professional career success. Leverage our tools to:</p>308 <ul style='color: #0f2144;'>309 <li><strong style='color: #00336b;'>Player Search:</strong> Look up specific players, view their combine percentile scores, and key NFL career statistics.</li>310 <li><strong style='color: #00336b;'>Predict Player Success:</strong> Input hypothetical combine metrics for a player and predict their best single-season PPR score.</li>311 <li><strong style='color: #00336b;'>Player Projection Tool:</strong> Find real-world players most similar to a hypothetical prospect based on combine percentiles and project their potential.</li>312 <li><strong style='color: #00336b;'>Correlation Analysis:</strong> Dive deep into the correlations between combine metrics and NFL success, both overall and position-specific.</li>313 </ul>314 <h3 style='color: #00336b;'>Understanding the Overall Relationship</h3>315 <p style='color: #0f2144;'>Let's start by examining the overall correlation between our calculated 'Overall Combine Score' and various NFL career performance metrics.</p>316 """317 fig, table_md = display_overall_correlations()318 interpretation = """319 <h3 style='color: #00336b;'>Interpreting Overall Correlations</h3>320 <p style='color: #0f2144;'>The bar chart above and the table below illustrate the Pearson correlation coefficients between a player's 'Overall Combine Score' and several key NFL career performance metrics across all positions. A positive correlation indicates that higher combine scores tend to be associated with higher values in that NFL metric, while a negative correlation suggests the opposite. A correlation close to zero implies a very weak linear relationship.</p>321 <p style='color: #0f2144;'><strong>Key Observations:</strong></p>322 <ul style='color: #0f2144;'>323 <li>We generally observe a <strong style='color: #00336b;'>weak positive correlation</strong> between the 'Overall Combine Score' and NFL career success metrics, such as fantasy points and career length. This suggests that while combine performance might have some bearing on a player's NFL career, it is far from being the sole determinant.</li>324 <li>Metrics like <code>avg_ppr_per_season</code> and <code>best_single_season_ppr</code> show slightly stronger, though still weak, positive correlations compared to total career fantasy points or career length. This may indicate that combine performance is a better indicator of peak individual season performance than overall career longevity or cumulative stats.</li>325 <li>The <strong style='color: #00336b;'>low absolute values</strong> of most correlations, many below 0.2, strongly suggest that many other factors beyond combine athleticism, such as skill development, coaching, scheme fit, injury luck, and college production, play a significant role in a player's NFL success.</li>326 </ul>327 <p style='color: #0f2144;'><strong>Next Steps:</strong></p>328 <p style='color: #0f2144;'>To gain deeper insights, we encourage you to:</p>329 <ul style='color: #0f2144;'>330 <li>Explore the <strong style='color: #00336b;'>'Correlation Analysis' tab</strong> to see how these correlations vary by individual player position.</li>331 <li>Use the <strong style='color: #00336b;'>'Predict Player Success' tab</strong> to see how the model estimates future performance based on combine percentiles.</li>332 <li>Utilize the <strong style='color: #00336b;'>'Player Projection Tool' tab</strong> to find players similar to a hypothetical prospect and learn from their career trajectories.</li>333 </ul>334 """335 return introduction, fig, table_md, interpretation336 337 338def get_raw_value_from_percentile(percentile, metric, position):339 filtered_data = DATA["df_rethought_score"][(DATA["df_rethought_score"]["Position"] == position)][metric].dropna()340 if len(filtered_data) < 2:341 return np.nan342 q = 100 - percentile if metric == "40-yd Dash" else percentile343 return np.percentile(filtered_data, q)344 345 346def update_raw_value_display(percentile, position, metric):347 if percentile is None or not position:348 return ""349 raw_val = get_raw_value_from_percentile(percentile, metric, position)350 if pd.isna(raw_val):351 return f"Insufficient data for {position} {metric}"352 unit = " seconds" if metric == "40-yd Dash" else " inches" if "Jump" in metric or "Height" in metric else " lbs"353 return f"Estimated raw value: {raw_val:.2f}{unit}"354 355 356def predict_player_success(position, *percentiles):357 del position358 valid_percentiles = [p for p in percentiles if p is not None]359 if not valid_percentiles:360 return "Provide at least one percentile."361 if DATA["model"] is None:362 return "Not enough data to train a prediction model."363 score = np.mean(valid_percentiles)364 pred = DATA["model"].predict(pd.DataFrame({"Overall_Combine_Score_Rethought": [score]}))[0]365 return (366 f"### <span style='color: #00336b;'>Predicted Best Season PPR: {pred:.2f}</span><br>"367 f"Based on Overall Combine Score: {score:.2f}"368 )369 370 371def find_similar_players(hypothetical_player_percentiles, position, top_n=5):372 if not hypothetical_player_percentiles:373 return pd.DataFrame()374 375 hypo_valid_percentile_cols = {col: val for col, val in hypothetical_player_percentiles.items() if pd.notna(val)}376 if not hypo_valid_percentile_cols:377 return pd.DataFrame()378 379 filtered_real_players = DATA["df_merged_data"][DATA["df_merged_data"]["Position"] == position].copy()380 real_player_percentile_cols = [col for col in hypo_valid_percentile_cols if col in filtered_real_players.columns]381 if not real_player_percentile_cols:382 return pd.DataFrame()383 384 distances = []385 comparable_players = []386 for _, row in filtered_real_players.iterrows():387 hypo_vector_subset = []388 real_vector_subset = []389 for metric_col in real_player_percentile_cols:390 if pd.notna(row[metric_col]) and pd.notna(hypo_valid_percentile_cols.get(metric_col)):391 hypo_vector_subset.append(hypo_valid_percentile_cols[metric_col])392 real_vector_subset.append(row[metric_col])393 394 if hypo_vector_subset:395 distance = euclidean(np.array(hypo_vector_subset), np.array(real_vector_subset))396 normalized_distance = distance / np.sqrt(len(hypo_vector_subset))397 distances.append(normalized_distance)398 comparable_players.append(row.copy())399 400 if not comparable_players:401 return pd.DataFrame()402 403 comparable_players_df = pd.DataFrame(comparable_players)404 comparable_players_df["similarity_distance"] = distances405 output_columns = (406 ["cleaned_player_name", "Player", "Position"]407 + [col for col in hypothetical_player_percentiles if col in comparable_players_df.columns]408 + ["best_single_season_ppr", "similarity_distance"]409 )410 similar_players = comparable_players_df.sort_values(by="similarity_distance", ascending=True).head(top_n)411 return similar_players[[col for col in output_columns if col in comparable_players_df.columns]]412 413 414def project_player_ppr(415 position,416 dash_40_yd_percentile,417 vertical_jump_percentile,418 broad_jump_percentile,419 height_percentile,420 weight_percentile,421):422 hypothetical_player_percentiles = {423 "40-yd Dash_percentile": dash_40_yd_percentile,424 "Vertical Jump_percentile": vertical_jump_percentile,425 "Broad Jump_percentile": broad_jump_percentile,426 "Height_percentile": height_percentile,427 "Weight_percentile": weight_percentile,428 }429 valid_hypo_percentiles = {k: v for k, v in hypothetical_player_percentiles.items() if v is not None}430 if not valid_hypo_percentiles:431 return "Please provide at least one combine percentile to find similar players."432 433 similar_players_df = find_similar_players(valid_hypo_percentiles, position, top_n=5)434 if similar_players_df.empty:435 return "Sorry, no comparable players found. Please try again."436 437 positions_without_ppr_display = ["C", "OT", "OL"]438 output_text = "#### <span style='color: #00336b;'>Top 5 Most Similar Players:</span>\n"439 for _, row in similar_players_df.iterrows():440 closeness_percentage = max(0, 100 - row["similarity_distance"])441 output_text += (442 f"- <strong style='color: #3385c3;'>{row['Player']}</strong> "443 f"(<span style='color: #0066b3;'>{row['Position']}</span>): "444 f"Closeness: {closeness_percentage:.1f}%"445 )446 447 if row["Position"] not in positions_without_ppr_display:448 output_text += f", Best Season PPR: <span style='color: #3385c3;'>{row.get('best_single_season_ppr', 'N/A')}</span>"449 450 percentile_details = []451 for metric_col in hypothetical_player_percentiles:452 if metric_col in row.index and pd.notna(row[metric_col]):453 metric_name = metric_col.replace("_percentile", "")454 percentile_details.append(455 f"<span style='color: #788d9e;'>{metric_name}</span>: "456 f"<span style='color: #3385c3;'>{row[metric_col]:.1f}%</span>"457 )458 if percentile_details:459 output_text += f" (Combine Percentiles: {', '.join(percentile_details)})\n"460 else:461 output_text += "\n"462 463 output_text += (464 "\n<em style='color: #43576f;'>*Note: This tool identifies similar players based on combine percentiles. "465 "The prediction tab offers a generalized estimate from a linear regression model.*</em>"466 )467 return output_text468 469 470def format_number(value):471 return "N/A" if pd.isna(value) else f"{value:.2f}"472 473 474def search_player_info(player_name_input):475 cleaned_input_name = clean_name(player_name_input)476 player_data = DATA["df_player_search"][DATA["df_player_search"]["cleaned_player_name"] == cleaned_input_name]477 478 if player_data.empty:479 return f"Player '{player_name_input}' not found or no combine data available."480 481 player = player_data.iloc[0]482 return f"""483 <div style='border: 2px solid #0066b3; padding: 15px; border-radius: 8px; background-color: #f0f2f5; font-family: sans-serif;'>484 <h3 style='color: #00336b; margin-top: 0;'>Player: {player['Player']}</h3>485 <p style='margin-bottom: 5px;'><strong>Position:</strong> {player['Position']}</p>486 <h4 style='color: #0066b3;'>Combine Performance:</h4>487 <ul style='list-style-type: none; padding: 0;'>488 <li><strong>Overall Combine Score:</strong> {format_number(player['Overall_Combine_Score_Rethought'])}</li>489 <li><strong>40-yd Dash Percentile:</strong> {format_number(player['40-yd Dash_percentile'])}%</li>490 <li><strong>Vertical Jump Percentile:</strong> {format_number(player['Vertical Jump_percentile'])}%</li>491 <li><strong>Broad Jump Percentile:</strong> {format_number(player['Broad Jump_percentile'])}%</li>492 <li><strong>Height Percentile:</strong> {format_number(player['Height_percentile'])}%</li>493 <li><strong>Weight Percentile:</strong> {format_number(player['Weight_percentile'])}%</li>494 </ul>495 <h4 style='color: #0066b3;'>NFL Career Success (PPR):</h4>496 <ul style='list-style-type: none; padding: 0;'>497 <li><strong>Best Single Season PPR:</strong> {format_number(player['best_single_season_ppr'])}</li>498 <li><strong>Best Single Season PPR Percentile:</strong> {format_number(player['best_single_season_ppr_percentile'])}%</li>499 <li><strong>Career Fantasy Points (PPR):</strong> {format_number(player['career_fantasy_points_ppr'])}</li>500 </ul>501 </div>502 """503 504 505custom_theme = gr.themes.Base(506 primary_hue=gr.themes.Color(507 name="primary_blue",508 c50="#e6f0f7",509 c100="#cce0ed",510 c200="#99c2df",511 c300="#66a3d1",512 c400="#3385c3",513 c500="#0066b3",514 c600="#0059a1",515 c700="#004d8f",516 c800="#00407d",517 c900="#00336b",518 c950="#002659",519 ),520 secondary_hue=gr.themes.Color(521 name="secondary_gray",522 c50="#f0f2f5",523 c100="#e1e5ea",524 c200="#c8d0da",525 c300="#aeb9c7",526 c400="#93a3b4",527 c500="#788d9e",528 c600="#5e7284",529 c700="#43576f",530 c800="#293c59",531 c900="#c8d0da",532 c950="#e1e5ea",533 ),534 neutral_hue=gr.themes.Color(535 name="neutral_light_gray",536 c50="#fcfcfc",537 c100="#f5f5f5",538 c200="#e5e5e5",539 c300="#d4d4d4",540 c400="#c4c4c4",541 c500="#b3b3b3",542 c600="#8c8c8c",543 c700="#666666",544 c800="#404040",545 c900="#1a1a1a",546 c950="#0a0a0a",547 ),548 font=gr.themes.GoogleFont("Inter"),549 font_mono=gr.themes.GoogleFont("IBM Plex Mono"),550).set(551 body_background_fill="#e1e5ea",552 background_fill_primary="#f0f2f5",553 background_fill_secondary="#c8d0da",554 border_color_primary="#0066b3",555 color_accent_soft="#3385c3",556 link_text_color="#004d8f",557 button_primary_background_fill="#0066b3",558 button_primary_text_color="#ffffff",559 button_secondary_background_fill="#aeb9c7",560 button_secondary_text_color="#ffffff",561)562 563 564def build_missing_data_app():565 missing_list = "".join(f"<li><code>{filename}</code></li>" for filename in MISSING_FILES)566 message = f"""567 <h1>NFL Combine Analytics</h1>568 <p>This Space is ready, but it needs the source CSV files before the dashboard can run.</p>569 <p>Add these files to the Space root folder or a <code>data/</code> folder:</p>570 <ul>{missing_list}</ul>571 """572 with gr.Blocks(title="NFL Combine Analytics", theme=custom_theme) as missing_data_demo:573 gr.HTML(message)574 return missing_data_demo575 576 577def build_app():578 player_names_for_dropdown = sorted(DATA["df_player_search"]["Player"].dropna().unique().tolist())579 filtered_prediction_positions = DATA["filtered_prediction_positions"]580 projection_tool_positions = DATA["projection_tool_positions"]581 582 with gr.Blocks(title="NFL Combine Analytics", theme=custom_theme) as demo:583 gr.Markdown("<h1 style='color: #00336b;'>NFL Combine Performance Analytics</h1>")584 gr.Markdown(585 "<p style='color: #0f2144;'>Explore NFL combine data, player career stats, correlations, and predicted player success.</p>"586 )587 588 with gr.Tab("Home"):589 intro_md, overall_corr_fig_obj, overall_corr_table_md, interpretation_md = display_home_page_content()590 gr.Markdown(intro_md)591 gr.Plot(overall_corr_fig_obj, label="Overall Correlation: Combine Score vs. NFL Metrics")592 gr.Markdown(overall_corr_table_md)593 gr.Markdown(interpretation_md)594 595 with gr.Tab("Player Search"):596 gr.Markdown("### <span style='color: #00336b;'>Search for a Player's Combine & NFL Stats</span>")597 gr.Markdown(598 "<p style='color: #0f2144;'>Enter a player's name to view combine percentile scores and key NFL career stats.</p>"599 )600 player_name_input = gr.Dropdown(601 choices=player_names_for_dropdown,602 label="Select or Type Player Name",603 allow_custom_value=True,604 filterable=True,605 )606 search_button = gr.Button("Search Player")607 player_search_output = gr.Markdown(label="Player Combine & NFL Stats")608 search_button.click(fn=search_player_info, inputs=player_name_input, outputs=player_search_output)609 610 with gr.Tab("Predict Player Success"):611 gr.Markdown("### <span style='color: #00336b;'>Predict Player Success</span>")612 gr.Markdown(613 "<p style='color: #0f2144;'>Enter a hypothetical player's combine metrics and position to predict best single-season PPR.</p>"614 )615 616 position_input = gr.Dropdown(617 choices=filtered_prediction_positions,618 label="Player Position",619 value=filtered_prediction_positions[0] if filtered_prediction_positions else None,620 interactive=True,621 )622 dash_40_yd_percentile_input = gr.Slider(0, 100, step=1, label="40-yd Dash Percentile (0-100)")623 dash_40_yd_raw_output = gr.Markdown()624 vertical_jump_percentile_input = gr.Slider(0, 100, step=1, label="Vertical Jump Percentile (0-100)")625 vertical_jump_raw_output = gr.Markdown()626 broad_jump_percentile_input = gr.Slider(0, 100, step=1, label="Broad Jump Percentile (0-100)")627 broad_jump_raw_output = gr.Markdown()628 height_percentile_input = gr.Slider(0, 100, step=1, label="Height Percentile (0-100)")629 height_raw_output = gr.Markdown()630 weight_percentile_input = gr.Slider(0, 100, step=1, label="Weight Percentile (0-100)")631 weight_raw_output = gr.Markdown()632 633 dash_40_yd_percentile_input.change(634 fn=lambda p, pos: update_raw_value_display(p, pos, "40-yd Dash"),635 inputs=[dash_40_yd_percentile_input, position_input],636 outputs=dash_40_yd_raw_output,637 )638 vertical_jump_percentile_input.change(639 fn=lambda p, pos: update_raw_value_display(p, pos, "Vertical Jump"),640 inputs=[vertical_jump_percentile_input, position_input],641 outputs=vertical_jump_raw_output,642 )643 broad_jump_percentile_input.change(644 fn=lambda p, pos: update_raw_value_display(p, pos, "Broad Jump"),645 inputs=[broad_jump_percentile_input, position_input],646 outputs=broad_jump_raw_output,647 )648 height_percentile_input.change(649 fn=lambda p, pos: update_raw_value_display(p, pos, "Height"),650 inputs=[height_percentile_input, position_input],651 outputs=height_raw_output,652 )653 weight_percentile_input.change(654 fn=lambda p, pos: update_raw_value_display(p, pos, "Weight"),655 inputs=[weight_percentile_input, position_input],656 outputs=weight_raw_output,657 )658 659 predict_btn = gr.Button("Predict Best Single Season PPR")660 prediction_output = gr.Markdown(label="Prediction Results")661 predict_btn.click(662 fn=predict_player_success,663 inputs=[664 position_input,665 dash_40_yd_percentile_input,666 vertical_jump_percentile_input,667 broad_jump_percentile_input,668 height_percentile_input,669 weight_percentile_input,670 ],671 outputs=prediction_output,672 )673 674 with gr.Tab("Player Projection Tool"):675 gr.Markdown("### <span style='color: #00336b;'>Player Projection Tool</span>")676 gr.Markdown(677 "<p style='color: #0f2144;'>Enter combine metric percentiles to find similar players and estimate comparable outcomes.</p>"678 )679 680 position_input_proj = gr.Dropdown(681 choices=projection_tool_positions,682 label="Player Position",683 value=projection_tool_positions[0] if projection_tool_positions else None,684 interactive=True,685 )686 dash_40_yd_percentile_input_proj = gr.Slider(0, 100, step=1, label="40-yd Dash Percentile (0-100)")687 dash_40_yd_raw_output_proj = gr.Markdown()688 vertical_jump_percentile_input_proj = gr.Slider(0, 100, step=1, label="Vertical Jump Percentile (0-100)")689 vertical_jump_raw_output_proj = gr.Markdown()690 broad_jump_percentile_input_proj = gr.Slider(0, 100, step=1, label="Broad Jump Percentile (0-100)")691 broad_jump_raw_output_proj = gr.Markdown()692 height_percentile_input_proj = gr.Slider(0, 100, step=1, label="Height Percentile (0-100)")693 height_raw_output_proj = gr.Markdown()694 weight_percentile_input_proj = gr.Slider(0, 100, step=1, label="Weight Percentile (0-100)")695 weight_raw_output_proj = gr.Markdown()696 697 dash_40_yd_percentile_input_proj.change(698 fn=lambda p, pos: update_raw_value_display(p, pos, "40-yd Dash"),699 inputs=[dash_40_yd_percentile_input_proj, position_input_proj],700 outputs=dash_40_yd_raw_output_proj,701 )702 vertical_jump_percentile_input_proj.change(703 fn=lambda p, pos: update_raw_value_display(p, pos, "Vertical Jump"),704 inputs=[vertical_jump_percentile_input_proj, position_input_proj],705 outputs=vertical_jump_raw_output_proj,706 )707 broad_jump_percentile_input_proj.change(708 fn=lambda p, pos: update_raw_value_display(p, pos, "Broad Jump"),709 inputs=[broad_jump_percentile_input_proj, position_input_proj],710 outputs=broad_jump_raw_output_proj,711 )712 height_percentile_input_proj.change(713 fn=lambda p, pos: update_raw_value_display(p, pos, "Height"),714 inputs=[height_percentile_input_proj, position_input_proj],715 outputs=height_raw_output_proj,716 )717 weight_percentile_input_proj.change(718 fn=lambda p, pos: update_raw_value_display(p, pos, "Weight"),719 inputs=[weight_percentile_input_proj, position_input_proj],720 outputs=weight_raw_output_proj,721 )722 723 project_btn = gr.Button("Project Similar Players")724 projection_output = gr.Markdown(label="Projection Results")725 project_btn.click(726 fn=project_player_ppr,727 inputs=[728 position_input_proj,729 dash_40_yd_percentile_input_proj,730 vertical_jump_percentile_input_proj,731 broad_jump_percentile_input_proj,732 height_percentile_input_proj,733 weight_percentile_input_proj,734 ],735 outputs=projection_output,736 )737 738 with gr.Tab("Correlation Analysis"):739 gr.Markdown(740 "### <span style='color: #00336b;'>Overall Correlation between Overall Combine Score and NFL Performance Metrics</span>"741 )742 overall_plot_output_fig, overall_table_output = display_overall_correlations()743 gr.Plot(overall_plot_output_fig)744 gr.Markdown(overall_table_output)745 746 gr.Markdown("### <span style='color: #00336b;'>Position-Specific Correlations</span>")747 position_dropdown = gr.Dropdown(748 choices=filtered_prediction_positions,749 label="Select Position",750 value=filtered_prediction_positions[0] if filtered_prediction_positions else None,751 )752 position_corr_plot_output = gr.Plot(753 label="Position Correlation",754 value=display_position_correlations(filtered_prediction_positions[0])755 if filtered_prediction_positions756 else None,757 )758 position_dropdown.change(759 fn=display_position_correlations,760 inputs=position_dropdown,761 outputs=position_corr_plot_output,762 )763 764 return demo765 766 767demo = build_missing_data_app() if DATA is None else build_app()768 769if __name__ == "__main__":770 demo.launch()771 