rairo commited on
Commit
397b75c
Β·
verified Β·
1 Parent(s): 65f6445

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +63 -77
src/streamlit_app.py CHANGED
@@ -71,65 +71,56 @@ def get_player_index_brscraper():
71
  return pd.DataFrame({'name': common})
72
 
73
  @st.cache_data(ttl=300)
74
- def get_player_career_stats_brscraper(player_name):
75
  """
76
- Uses BRScraper to get a player's career stats.
77
- Detects and renames the season column (or falls back to first column),
78
- applies mapping and numeric conversion.
79
  """
80
  if not BRSCRAPER_AVAILABLE:
81
  return pd.DataFrame()
82
-
83
- try:
84
- df = nba.get_player_stats(player_name)
85
- if df.empty:
86
- return df
87
-
88
- # 1) Try to autodetect a season column
89
- season_col = None
90
- for col in df.columns:
91
- low = col.lower().strip()
92
- if 'season' in low or 'year' in low:
93
- season_col = col
94
- break
95
-
96
- # 2) Fallback: if nothing detected, just take the first column
97
- if season_col is None:
98
- season_col = df.columns[0]
99
-
100
- # 3) Rename whatever that column is to exactly 'Season'
101
- df = df.rename(columns={season_col: 'Season'})
102
-
103
- # 4) Standard column mapping
104
- mapping = {
105
- 'G':'GP','GS':'GS','MP':'MIN',
106
- 'FG%':'FG_PCT','3P%':'FG3_PCT','FT%':'FT_PCT',
107
- 'TRB':'REB','AST':'AST','STL':'STL','BLK':'BLK','TOV':'TO',
108
- 'PF':'PF','PTS':'PTS','Age':'AGE','Tm':'TEAM_ABBREVIATION',
109
- 'Lg':'LEAGUE_ID','Pos':'POSITION',
110
- 'FG':'FGM','FGA':'FGA','3P':'FG3M','3PA':'FG3A',
111
- '2P':'FGM2','2PA':'FGA2','2P%':'FG2_PCT','eFG%':'EFG_PCT',
112
- 'FT':'FTM','FTA':'FTA','ORB':'OREB','DRB':'DREB'
113
- }
114
- df = df.rename(columns={o:n for o,n in mapping.items() if o in df.columns})
115
-
116
- # 5) Clean and format the Season column
117
- df['Season'] = df['Season'].astype(str).str.replace('-', '–').str.strip()
118
 
119
- # 6) Convert everything else numeric
120
- non_numeric = {'Season', 'TEAM_ABBREVIATION', 'LEAGUE_ID', 'POSITION'}
121
- for c in df.columns:
122
- if c not in non_numeric:
123
- df[c] = pd.to_numeric(df[c], errors='coerce')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- # 7) Tag with Player
126
- df['Player'] = player_name
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- return df
 
 
 
 
129
 
130
- except Exception as e:
131
- st.error(f"Error fetching stats for {player_name}: {e}")
132
- return pd.DataFrame()
133
 
134
  @st.cache_data(ttl=300)
135
  def get_team_season_stats_brscraper(year):
@@ -243,19 +234,19 @@ def player_vs_player():
243
  seasons = get_available_seasons()
244
  selected_seasons = st.multiselect("Select Seasons", seasons, default=[seasons[0]] if seasons else [])
245
 
246
- # ─── DEBUG PREVIEW ──────────────────────────────────────────────────────
247
  if selected_players:
248
- st.markdown("### πŸ§ͺ Raw Data Preview (Before Filtering)")
249
  for player in selected_players:
250
- try:
251
- raw_df = get_player_career_stats_brscraper(player)
252
- st.subheader(f"{player} β€” Raw Data")
253
- st.write("Columns:", raw_df.columns.tolist())
254
- st.dataframe(raw_df, use_container_width=True)
255
- except Exception as e:
256
- st.error(f"❌ Error fetching data for {player}: {e}")
257
  st.markdown("---")
258
- # ───────────────────────────────────────────────────────────────────────
259
 
260
  if st.button("Run Comparison"):
261
  if not selected_players:
@@ -263,20 +254,16 @@ def player_vs_player():
263
  return
264
 
265
  all_data, no_data = [], []
266
- with st.spinner("Fetching player data..."):
267
  for p in selected_players:
268
- try:
269
- df = get_player_career_stats_brscraper(p)
270
- if not df.empty:
271
- filt = df[df['Season'].isin(selected_seasons)]
272
- if not filt.empty:
273
- all_data.append(filt)
274
- else:
275
- no_data.append(p)
276
  else:
277
  no_data.append(p)
278
- except Exception as e:
279
- st.error(f"Error fetching stats for {p}: {e}")
280
  no_data.append(p)
281
 
282
  if no_data:
@@ -286,7 +273,7 @@ def player_vs_player():
286
  return
287
 
288
  comp_df = pd.concat(all_data, ignore_index=True)
289
- tabs = st.tabs(["Basic Stats","Advanced Stats","Visualizations"])
290
 
291
  with tabs[0]:
292
  st.subheader("Basic Statistics")
@@ -310,10 +297,10 @@ def player_vs_player():
310
  )
311
  if len(selected_seasons) > 1:
312
  df_adv = df_a.groupby('Player').mean(numeric_only=True).reset_index()
313
- cols = ['Player', 'PTS', 'REB', 'AST', 'FG_PCT', 'TS_PCT']
314
  else:
315
  df_adv = df_a.copy()
316
- cols = ['Player', 'Season', 'PTS', 'REB', 'AST', 'FG_PCT', 'TS_PCT']
317
  st.dataframe(df_adv[[c for c in cols if c in df_adv.columns]].round(3), use_container_width=True)
318
 
319
  with tabs[2]:
@@ -323,8 +310,7 @@ def player_vs_player():
323
  m = st.selectbox("Select Metric", metrics)
324
  if len(selected_players) == 1 and len(selected_seasons) > 1:
325
  trend = comp_df[comp_df['Player'] == selected_players[0]].sort_values('Season')
326
- fig = px.line(trend, x='Season', y=m, markers=True,
327
- title=f"{selected_players[0]} – {m} Trend")
328
  else:
329
  avg = comp_df.groupby('Player')[metrics].mean(numeric_only=True).reset_index()
330
  fig = px.bar(avg, x='Player', y=m, title=f"Average {m} Comparison")
 
71
  return pd.DataFrame({'name': common})
72
 
73
  @st.cache_data(ttl=300)
74
+ def get_player_career_stats_brscraper(player_name, seasons_to_check=10):
75
  """
76
+ Build a player's career stats by fetching per-game data season-by-season
77
+ via nba.get_stats, instead of nba.get_player_stats (which was returning empty).
 
78
  """
79
  if not BRSCRAPER_AVAILABLE:
80
  return pd.DataFrame()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
+ all_rows = []
83
+ seasons = get_available_seasons(seasons_to_check)
84
+ for season_str in seasons:
85
+ end_year = int(season_str.split('–')[1])
86
+ try:
87
+ df_season = nba.get_stats(end_year, info='per_game', playoffs=False, rename=False)
88
+ if 'Player' in df_season.columns:
89
+ row = df_season[df_season['Player'] == player_name]
90
+ if not row.empty:
91
+ row = row.copy()
92
+ # normalize the season label
93
+ row['Season'] = season_str
94
+ all_rows.append(row)
95
+ except Exception as e:
96
+ # skip any failures
97
+ st.warning(f"Could not fetch {season_str} for {player_name}: {e}")
98
+
99
+ if not all_rows:
100
+ return pd.DataFrame()
101
 
102
+ df = pd.concat(all_rows, ignore_index=True)
103
+
104
+ # Standardize columns
105
+ mapping = {
106
+ 'G':'GP','GS':'GS','MP':'MIN',
107
+ 'FG%':'FG_PCT','3P%':'FG3_PCT','FT%':'FT_PCT',
108
+ 'TRB':'REB','AST':'AST','STL':'STL','BLK':'BLK','TOV':'TO',
109
+ 'PF':'PF','PTS':'PTS','ORB':'OREB','DRB':'DREB',
110
+ 'FG':'FGM','FGA':'FGA','3P':'FG3M','3PA':'FG3A',
111
+ '2P':'FGM2','2PA':'FGA2','2P%':'FG2_PCT','eFG%':'EFG_PCT',
112
+ 'FT':'FTM','FTA':'FTA'
113
+ }
114
+ df = df.rename(columns={o:n for o,n in mapping.items() if o in df.columns})
115
 
116
+ # Convert numeric columns
117
+ non_num = {'Season','Player','Tm','Lg','Pos'}
118
+ for col in df.columns:
119
+ if col not in non_num:
120
+ df[col] = pd.to_numeric(df[col], errors='coerce')
121
 
122
+ df['Player'] = player_name
123
+ return df
 
124
 
125
  @st.cache_data(ttl=300)
126
  def get_team_season_stats_brscraper(year):
 
234
  seasons = get_available_seasons()
235
  selected_seasons = st.multiselect("Select Seasons", seasons, default=[seasons[0]] if seasons else [])
236
 
237
+ # ─── RAW DEBUG PREVIEW ──────────────────────────────────────────────────────
238
  if selected_players:
239
+ st.markdown("### πŸ§ͺ Raw Seasonal Data Preview")
240
  for player in selected_players:
241
+ st.subheader(player)
242
+ raw = get_player_career_stats_brscraper(player, seasons_to_check=6)
243
+ if raw.empty:
244
+ st.write("❌ No rows returnedβ€”player may be named differently or data unavailable.")
245
+ else:
246
+ st.write("Columns:", raw.columns.tolist())
247
+ st.dataframe(raw, use_container_width=True)
248
  st.markdown("---")
249
+ # ────────────────────────────────────────────────────────────────────────────
250
 
251
  if st.button("Run Comparison"):
252
  if not selected_players:
 
254
  return
255
 
256
  all_data, no_data = [], []
257
+ with st.spinner("Fetching filtered data..."):
258
  for p in selected_players:
259
+ df = get_player_career_stats_brscraper(p, seasons_to_check=6)
260
+ if not df.empty:
261
+ filt = df[df['Season'].isin(selected_seasons)]
262
+ if not filt.empty:
263
+ all_data.append(filt)
 
 
 
264
  else:
265
  no_data.append(p)
266
+ else:
 
267
  no_data.append(p)
268
 
269
  if no_data:
 
273
  return
274
 
275
  comp_df = pd.concat(all_data, ignore_index=True)
276
+ tabs = st.tabs(["Basic Stats", "Advanced Stats", "Visualizations"])
277
 
278
  with tabs[0]:
279
  st.subheader("Basic Statistics")
 
297
  )
298
  if len(selected_seasons) > 1:
299
  df_adv = df_a.groupby('Player').mean(numeric_only=True).reset_index()
300
+ cols = ['Player','PTS','REB','AST','FG_PCT','TS_PCT']
301
  else:
302
  df_adv = df_a.copy()
303
+ cols = ['Player','Season','PTS','REB','AST','FG_PCT','TS_PCT']
304
  st.dataframe(df_adv[[c for c in cols if c in df_adv.columns]].round(3), use_container_width=True)
305
 
306
  with tabs[2]:
 
310
  m = st.selectbox("Select Metric", metrics)
311
  if len(selected_players) == 1 and len(selected_seasons) > 1:
312
  trend = comp_df[comp_df['Player'] == selected_players[0]].sort_values('Season')
313
+ fig = px.line(trend, x='Season', y=m, markers=True, title=f"{selected_players[0]} – {m} Trend")
 
314
  else:
315
  avg = comp_df.groupby('Player')[metrics].mean(numeric_only=True).reset_index()
316
  fig = px.bar(avg, x='Player', y=m, title=f"Average {m} Comparison")