Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pandas as pd | |
| # ββ Page Config ββ | |
| st.set_page_config( | |
| page_title="Race Results Explorer", | |
| page_icon="π", | |
| layout="wide", | |
| initial_sidebar_state="expanded", | |
| ) | |
| # ββ Custom CSS ββ | |
| st.markdown(""" | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=Space+Mono:wght@700&display=swap'); | |
| .stApp { | |
| background: linear-gradient(145deg, #0f172a 0%, #1e293b 50%, #0f172a 100%); | |
| } | |
| [data-testid="stSidebar"] { | |
| background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%); | |
| } | |
| .main-title { | |
| font-family: 'Space Mono', monospace; | |
| font-size: 2.4rem; | |
| font-weight: 700; | |
| background: linear-gradient(90deg, #f97316, #fb923c, #fbbf24); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| text-align: center; | |
| margin-bottom: 0; | |
| letter-spacing: -1px; | |
| } | |
| .sub-title { | |
| text-align: center; | |
| color: #94a3b8; | |
| font-size: 0.95rem; | |
| margin-top: 0; | |
| } | |
| .metric-card { | |
| background: rgba(255,255,255,0.04); | |
| border: 1px solid rgba(255,255,255,0.08); | |
| border-radius: 12px; | |
| padding: 16px 20px; | |
| text-align: center; | |
| } | |
| .metric-val { | |
| font-family: 'Space Mono', monospace; | |
| font-size: 1.8rem; | |
| font-weight: 700; | |
| } | |
| .metric-label { | |
| font-size: 0.75rem; | |
| color: #64748b; | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| margin-top: 2px; | |
| } | |
| .sidebar-header { | |
| font-family: 'Space Mono', monospace; | |
| font-size: 1.1rem; | |
| color: #f97316; | |
| letter-spacing: 1px; | |
| margin-bottom: 8px; | |
| } | |
| div[data-testid="stDataFrame"] { | |
| border-radius: 12px; | |
| overflow: hidden; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ββ Data Loading ββ | |
| def load_data(): | |
| df = pd.read_csv("new_data_01__2_.csv") | |
| # Drop the junk row at the bottom (non-numeric Place) | |
| df = df[pd.to_numeric(df["Place"], errors="coerce").notna()].copy() | |
| # Clean columns | |
| df["Place"] = df["Place"].astype(int) | |
| df["Bib"] = pd.to_numeric(df["Bib"], errors="coerce").astype("Int64") | |
| df["Gender"] = df["Gender"].str.strip().str.upper() | |
| df["City"] = df["City"].fillna("NOT SPECIFIED").str.strip().str.upper() | |
| df["State"] = df["State"].fillna("NOT SPECIFIED").str.strip().str.upper() | |
| df["Team"] = df["Team"].fillna("").str.strip() | |
| df["Time"] = df["Time"].astype(str).str.strip() | |
| df["Gun Time"] = df["Gun Time"].astype(str).str.strip() | |
| # Keep only valid genders | |
| df = df[df["Gender"].isin(["M", "F"])].copy() | |
| # Drop unnamed index column if present | |
| if "Unnamed: 0" in df.columns: | |
| df.drop(columns=["Unnamed: 0"], inplace=True) | |
| return df | |
| df = load_data() | |
| # ββ Helper: parse time string to total seconds for filtering ββ | |
| def time_to_seconds(t): | |
| try: | |
| parts = str(t).split(":") | |
| if len(parts) == 3: | |
| h, m, s = parts | |
| return int(h) * 3600 + int(m) * 60 + int(s) | |
| elif len(parts) == 2: | |
| m, s = parts | |
| return int(m) * 60 + int(s) | |
| except Exception: | |
| return None | |
| return None | |
| df["_time_sec"] = df["Time"].apply(time_to_seconds) | |
| df["_gun_sec"] = df["Gun Time"].apply(time_to_seconds) | |
| min_time = int(df["_time_sec"].min()) if df["_time_sec"].notna().any() else 0 | |
| max_time = int(df["_time_sec"].max()) if df["_time_sec"].notna().any() else 7200 | |
| min_gun = int(df["_gun_sec"].min()) if df["_gun_sec"].notna().any() else 0 | |
| max_gun = int(df["_gun_sec"].max()) if df["_gun_sec"].notna().any() else 7200 | |
| def seconds_to_label(s): | |
| h = s // 3600 | |
| m = (s % 3600) // 60 | |
| sec = s % 60 | |
| if h > 0: | |
| return f"{h}:{m:02d}:{sec:02d}" | |
| return f"{m}:{sec:02d}" | |
| # ββββββββββββββββββββββββββββββββββββββ | |
| # SIDEBAR β FILTER CHAMBER | |
| # ββββββββββββββββββββββββββββββββββββββ | |
| with st.sidebar: | |
| st.markdown('<div class="sidebar-header">π― FILTER CHAMBER</div>', unsafe_allow_html=True) | |
| st.markdown("---") | |
| # ββ 1. Gender ββ | |
| st.markdown("##### π€ Gender") | |
| gender_opts = ["All"] + sorted(df["Gender"].unique().tolist()) | |
| gender_sel = st.selectbox("Select Gender", gender_opts, index=0, label_visibility="collapsed") | |
| # ββ 2. City ββ | |
| st.markdown("##### ποΈ City") | |
| city_list = sorted(df["City"].unique().tolist()) | |
| city_sel = st.multiselect("Select City", city_list, default=[], placeholder="All cities") | |
| # ββ 3. State ββ | |
| st.markdown("##### π State") | |
| state_list = sorted(df["State"].unique().tolist()) | |
| state_sel = st.multiselect("Select State", state_list, default=[], placeholder="All states") | |
| # ββ 4. Team ββ | |
| st.markdown("##### π Team") | |
| team_mode = st.radio("Team filter mode", ["All Runners", "Team Members Only", "Independent Only", "Specific Team"], index=0, label_visibility="collapsed") | |
| specific_team = None | |
| if team_mode == "Specific Team": | |
| team_list = sorted([t for t in df["Team"].unique() if t]) | |
| specific_team = st.selectbox("Choose team", team_list, label_visibility="collapsed") | |
| st.markdown("---") | |
| # ββ 5. Chip Time Range ββ | |
| st.markdown("##### β±οΈ Chip Time Range") | |
| time_range = st.slider( | |
| "Chip Time", | |
| min_value=min_time, | |
| max_value=max_time, | |
| value=(min_time, max_time), | |
| step=60, | |
| format="%d sec", | |
| label_visibility="collapsed", | |
| ) | |
| st.caption(f"From **{seconds_to_label(time_range[0])}** to **{seconds_to_label(time_range[1])}**") | |
| # ββ 6. Gun Time Range ββ | |
| st.markdown("##### π« Gun Time Range") | |
| gun_range = st.slider( | |
| "Gun Time", | |
| min_value=min_gun, | |
| max_value=max_gun, | |
| value=(min_gun, max_gun), | |
| step=60, | |
| format="%d sec", | |
| label_visibility="collapsed", | |
| ) | |
| st.caption(f"From **{seconds_to_label(gun_range[0])}** to **{seconds_to_label(gun_range[1])}**") | |
| st.markdown("---") | |
| # ββ 7. Name Search ββ | |
| st.markdown("##### π Search by Name") | |
| name_search = st.text_input("Name", placeholder="e.g. JARED WILSON", label_visibility="collapsed") | |
| # ββ Reset ββ | |
| if st.button("π Reset All Filters", use_container_width=True): | |
| st.rerun() | |
| # ββββββββββββββββββββββββββββββββββββββ | |
| # APPLY FILTERS | |
| # ββββββββββββββββββββββββββββββββββββββ | |
| filtered = df.copy() | |
| # Gender | |
| if gender_sel != "All": | |
| filtered = filtered[filtered["Gender"] == gender_sel] | |
| # City | |
| if city_sel: | |
| filtered = filtered[filtered["City"].isin(city_sel)] | |
| # State | |
| if state_sel: | |
| filtered = filtered[filtered["State"].isin(state_sel)] | |
| # Team | |
| if team_mode == "Team Members Only": | |
| filtered = filtered[filtered["Team"] != ""] | |
| elif team_mode == "Independent Only": | |
| filtered = filtered[filtered["Team"] == ""] | |
| elif team_mode == "Specific Team" and specific_team: | |
| filtered = filtered[filtered["Team"] == specific_team] | |
| # Chip Time | |
| filtered = filtered[ | |
| (filtered["_time_sec"] >= time_range[0]) & (filtered["_time_sec"] <= time_range[1]) | |
| | filtered["_time_sec"].isna() | |
| ] | |
| # Gun Time | |
| filtered = filtered[ | |
| (filtered["_gun_sec"] >= gun_range[0]) & (filtered["_gun_sec"] <= gun_range[1]) | |
| | filtered["_gun_sec"].isna() | |
| ] | |
| # Name | |
| if name_search: | |
| filtered = filtered[filtered["Name"].str.contains(name_search.upper(), case=False, na=False)] | |
| # ββββββββββββββββββββββββββββββββββββββ | |
| # MAIN CONTENT | |
| # ββββββββββββββββββββββββββββββββββββββ | |
| st.markdown('<h1 class="main-title">π RACE RESULTS EXPLORER</h1>', unsafe_allow_html=True) | |
| st.markdown(f'<p class="sub-title">Showing <strong>{len(filtered)}</strong> of <strong>{len(df)}</strong> runners</p>', unsafe_allow_html=True) | |
| # ββ Metric Cards ββ | |
| c1, c2, c3, c4, c5 = st.columns(5) | |
| with c1: | |
| st.markdown(f""" | |
| <div class="metric-card"> | |
| <div class="metric-val" style="color:#f97316">{len(filtered)}</div> | |
| <div class="metric-label">Total</div> | |
| </div>""", unsafe_allow_html=True) | |
| with c2: | |
| m_count = len(filtered[filtered["Gender"] == "M"]) | |
| st.markdown(f""" | |
| <div class="metric-card"> | |
| <div class="metric-val" style="color:#3b82f6">{m_count}</div> | |
| <div class="metric-label">Male</div> | |
| </div>""", unsafe_allow_html=True) | |
| with c3: | |
| f_count = len(filtered[filtered["Gender"] == "F"]) | |
| st.markdown(f""" | |
| <div class="metric-card"> | |
| <div class="metric-val" style="color:#ec4899">{f_count}</div> | |
| <div class="metric-label">Female</div> | |
| </div>""", unsafe_allow_html=True) | |
| with c4: | |
| team_count = filtered[filtered["Team"] != ""]["Team"].nunique() | |
| st.markdown(f""" | |
| <div class="metric-card"> | |
| <div class="metric-val" style="color:#10b981">{team_count}</div> | |
| <div class="metric-label">Teams</div> | |
| </div>""", unsafe_allow_html=True) | |
| with c5: | |
| state_count = filtered["State"].nunique() | |
| st.markdown(f""" | |
| <div class="metric-card"> | |
| <div class="metric-val" style="color:#a78bfa">{state_count}</div> | |
| <div class="metric-label">States</div> | |
| </div>""", unsafe_allow_html=True) | |
| st.markdown("") | |
| # ββ Data Table ββ | |
| display_cols = ["Place", "Bib", "Name", "Gender", "City", "State", "Time", "Gun Time", "Team"] | |
| display_df = filtered[display_cols].reset_index(drop=True) | |
| st.dataframe( | |
| display_df, | |
| use_container_width=True, | |
| height=520, | |
| column_config={ | |
| "Place": st.column_config.NumberColumn("π Place", width="small"), | |
| "Bib": st.column_config.NumberColumn("π’ Bib", width="small"), | |
| "Name": st.column_config.TextColumn("π€ Name", width="medium"), | |
| "Gender": st.column_config.TextColumn("β§ Gender", width="small"), | |
| "City": st.column_config.TextColumn("ποΈ City", width="medium"), | |
| "State": st.column_config.TextColumn("π State", width="small"), | |
| "Time": st.column_config.TextColumn("β±οΈ Chip Time", width="small"), | |
| "Gun Time": st.column_config.TextColumn("π« Gun Time", width="small"), | |
| "Team": st.column_config.TextColumn("π Team", width="medium"), | |
| }, | |
| ) | |
| # ββ Charts Row ββ | |
| st.markdown("") | |
| st.markdown("### π Distribution Insights") | |
| tab1, tab2, tab3, tab4 = st.tabs(["Gender Split", "Top Cities", "Top Teams", "Time Distribution"]) | |
| with tab1: | |
| gender_counts = filtered["Gender"].value_counts().reset_index() | |
| gender_counts.columns = ["Gender", "Count"] | |
| gender_counts["Gender"] = gender_counts["Gender"].map({"M": "Male", "F": "Female"}) | |
| st.bar_chart(gender_counts.set_index("Gender"), color="#f97316", horizontal=True) | |
| with tab2: | |
| city_counts = filtered["City"].value_counts().head(10).reset_index() | |
| city_counts.columns = ["City", "Count"] | |
| st.bar_chart(city_counts.set_index("City"), color="#3b82f6") | |
| with tab3: | |
| team_data = filtered[filtered["Team"] != ""]["Team"].value_counts().reset_index() | |
| team_data.columns = ["Team", "Count"] | |
| if not team_data.empty: | |
| st.bar_chart(team_data.set_index("Team"), color="#10b981") | |
| else: | |
| st.info("No team members in current filter.") | |
| with tab4: | |
| time_data = filtered[filtered["_time_sec"].notna()].copy() | |
| if not time_data.empty: | |
| time_data["Minutes"] = time_data["_time_sec"] / 60 | |
| st.bar_chart( | |
| time_data["Minutes"].value_counts(bins=20).sort_index().reset_index().rename( | |
| columns={"index": "Time Bin", "count": "Runners"} | |
| ).set_index("Time Bin"), | |
| color="#fbbf24", | |
| ) | |
| else: | |
| st.info("No time data available for current filter.") | |
| # ββ Download ββ | |
| st.markdown("---") | |
| col_dl1, col_dl2, _ = st.columns([1, 1, 3]) | |
| with col_dl1: | |
| csv_out = display_df.to_csv(index=False).encode("utf-8") | |
| st.download_button("π₯ Download Filtered CSV", csv_out, "filtered_results.csv", "text/csv", use_container_width=True) | |
| with col_dl2: | |
| st.download_button("π₯ Download Full CSV", df[display_cols].to_csv(index=False).encode("utf-8"), "full_results.csv", "text/csv", use_container_width=True) | |
| st.markdown(""" | |
| <div style="text-align:center; color:#475569; font-size:0.8rem; margin-top:24px;"> | |
| Built with Streamlit β’ Race Results Explorer | |
| </div> | |
| """, unsafe_allow_html=True) | |