TITANIC-RAMP / app.py
Marcel0123's picture
Update app.py
cbd3079 verified
raw
history blame
17.7 kB
# app.py — Titanic Data Adventure (met uitgebreide introductie naast foto)
import gradio as gr
import pandas as pd
import numpy as np
import os
import plotly.express as px
import plotly.graph_objects as go # voor de gauge
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.decomposition import PCA
# ======================================================
# DATA LADEN
# ======================================================
REQUIRED = {"survived","pclass","sex","age","sibsp","parch","fare","embarked"}
def load_data(path="Titanic-Dataset.csv"):
if not os.path.exists(path):
raise FileNotFoundError("❌ Titanic-Dataset.csv niet gevonden in de rootmap.")
df = pd.read_csv(path)
df.columns = [c.lower().strip() for c in df.columns]
missing = REQUIRED - set(df.columns)
if missing:
raise ValueError(f"Ontbrekende kolommen: {', '.join(sorted(missing))}")
for c in df.columns:
if df[c].isna().any():
df[c] = df[c].fillna(df[c].mode()[0] if df[c].dtype=='O' else df[c].median())
df["family_size"] = df["sibsp"] + df["parch"] + 1
df["status"] = df["survived"].map({0:"Niet overleefd", 1:"Overleefd"})
df["sex"] = df["sex"].astype(str).str.title()
df["embarked"] = df["embarked"].astype(str).str.upper()
return df
df = load_data()
MODEL = None
MODEL_ACC = None
# ======================================================
# HULPFUNCTIES
# ======================================================
def hero_path():
for n in ["titanic_bg.png","titanic_bg.jpg","titanic_bg.jpeg"]:
if os.path.exists(n):
return n
return None
def make_plot(fig, title):
fig.update_layout(
title=title,
paper_bgcolor="rgba(255,255,255,0)",
plot_bgcolor="rgba(255,255,255,0)",
font=dict(color="#0B1C3F"),
title_font=dict(size=18, color="#1B4B91"),
margin=dict(l=40, r=40, t=50, b=40),
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
)
return fig
# ======================================================
# MODELTRAINING + 2D VISUALISATIE
# ======================================================
def train_and_embed_solid():
global MODEL, MODEL_ACC
features = ["pclass","sex","age","sibsp","parch","fare","embarked","family_size"]
X = df[features].copy()
y = df["survived"].astype(int)
cat_cols = ["sex","embarked"]
num_cols = [c for c in features if c not in cat_cols]
pre = ColumnTransformer([
("num", StandardScaler(), num_cols),
("cat", OneHotEncoder(handle_unknown="ignore"), cat_cols),
])
pipe = Pipeline([
("prep", pre),
("clf", RandomForestClassifier(n_estimators=300, random_state=42))
])
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)
pipe.fit(Xtr, ytr)
MODEL = pipe
MODEL_ACC = pipe.score(Xte, yte)
Z = pre.fit_transform(X)
Z = Z.toarray() if hasattr(Z, "toarray") else Z
emb = PCA(n_components=2, random_state=42).fit_transform(Z)
dvis = pd.DataFrame({"x": emb[:,0], "y": emb[:,1]})
dvis["Overleving"] = df["status"].values
dvis["Geslacht"] = df["sex"].values
dvis["Klasse"] = df["pclass"].values
dvis["Leeftijd"] = df["age"].values
dvis["Fare (£)"] = df["fare"].values
dvis["Familie"] = df["family_size"].values
for c in ["name","ticket","cabin"]:
if c in df.columns:
dvis[c.capitalize()] = df[c].values
fig = px.scatter(
dvis, x="x", y="y",
color="Overleving", symbol="Klasse",
hover_data=[col for col in dvis.columns if col not in ["x","y"]],
color_discrete_map={"Overleefd":"#1B4B91","Niet overleefd":"#A3B1C6"},
opacity=0.8
)
fig.update_traces(marker=dict(symbol="circle", size=8, line=dict(width=0.6, color="white")))
fig = make_plot(fig, "2D-projectie (PCA) — elk bolletje is een passagier")
status = f"✅ Model getraind (RandomForest) — nauwkeurigheid: **{MODEL_ACC:.2%}**. 2D-projectie gereed; hover voor details."
return status, fig
# ======================================================
# TEKST VOOR INTRODUCTIE (UITGEBREID)
# ======================================================
INTRO_MD = """
# 🛳️ Titanic Data Adventure
### Een datagedreven reis door hoop, hiërarchie en toeval
**April 1912.**
De RMS *Titanic* vertrekt richting New York: een drijvend paleis, gevuld met verwachtingen.
Aan boord: industriëlen in avondkleding, jonge gezinnen met één koffer, bemanningsleden met routine.
De zee is kalm; de toekomst lijkt maakbaar.
Meer dan een eeuw later kijken wij mee — niet met verrekijkers of logboeken, maar met **data**.
Elk record in deze dataset is een menselijk verhaal: iemand met een plek aan tafel, een ticket, een familie, een keuze.
Door de gegevens te verkennen, begrijpen we beter **wie overleefde — en waarom**.
---
### ⚓ De nacht die geschiedenis werd
Op **14 april 1912**, net voor middernacht, raakte de *Titanic* een **ijsberg** in de ijskoude Noord-Atlantische Oceaan.
Binnen drie uur zonk het schip – het “onzinkbare” symbool van vooruitgang bleek kwetsbaar.
Meer dan **1.500 mensen** kwamen om, slechts een derde van de opvarenden vond redding in de sloepen.
De verdeling tussen rijk en arm, man en vrouw, jong en oud bleek letterlijk van levensbelang.
In deze applicatie duiken we opnieuw die nacht in – niet met reddingsvesten, maar met grafieken, modellen en cijfers
die het menselijk verhaal achter de ramp zichtbaar maken.
"""
# ======================================================
# UITLEGTEKST NAAST DE 2D-PLOT
# ======================================================
EXPLAIN_MD_SIDE = """
### 📘 Wat je ziet
Bij het opstarten traint de computer een **RandomForest-model** dat leert wie op de Titanic **overleefde** – en waarom.
Het kijkt naar **klasse**, **geslacht**, **leeftijd**, **familieomvang**, **ticketprijs** en **haven van vertrek**.
De nauwkeurigheid (bijv. *74%*) betekent: in 74 van de 100 gevallen voorspelt het model correct.
---
### 🗺️ Landkaart van passagiers
Elk **bolletje** is één persoon. Met **PCA** brengen we veel kenmerken terug naar **2 dimensies**.
- **Blauw** → overleefd
- **Grijs** → niet overleefd
- **Vorm** → klasse
Dichter bij elkaar = vergelijkbare profielen. **Hover** voor details.
---
### 💬 Jij in dit verhaal
Vul onderaan je **eigen profiel** in, ontdek jouw kans en lees je scène uit die nacht.
"""
# ======================================================
# OVERIGE GRAFIEKEN
# ======================================================
def plot_age_hist(dfx):
f = px.histogram(dfx, x="age", color="status", nbins=30, barmode="overlay", opacity=0.75,
color_discrete_map={"Overleefd":"#1B4B91","Niet overleefd":"#A3B1C6"})
return make_plot(f, "Leeftijdsverdeling per overlevingsstatus")
def plot_gender(dfx):
f = px.pie(dfx, names="sex", color="sex",
color_discrete_map={"Male":"#A3B1C6","Female":"#1B4B91"}, hole=0.35)
return make_plot(f, "Verdeling geslacht (alle passagiers)")
def plot_fare_box(dfx):
f = px.box(dfx, x="pclass", y="fare", color="status",
color_discrete_map={"Overleefd":"#1B4B91","Niet overleefd":"#A3B1C6"})
return make_plot(f, "Ticketprijs per klasse (met overleving)")
# ======================================================
# INTERACTIEVE VOORSPELLING
# ======================================================
def predict_and_story(pclass, sex, age, sibsp, parch, fare, embarked):
if MODEL is None:
return "⏳ Het model initialiseert nog. Probeer het zo nog eens."
X_row = pd.DataFrame([{
"pclass": int(pclass), "sex": sex, "age": float(age),
"sibsp": int(sibsp), "parch": int(parch), "fare": float(fare),
"embarked": embarked, "family_size": int(sibsp)+int(parch)+1
}])
prob = float(MODEL.predict_proba(X_row)[0,1]); pct = prob*100
klasse_txt = {1:"eerste",2:"tweede",3:"derde"}[int(pclass)]
haven_txt = {"C":"Cherbourg","Q":"Queenstown","S":"Southampton"}[embarked]
rol_txt = "vrouw" if sex.lower().startswith("v") else "man"
if pct>=75:
tone, ending = ("Je kansen zijn uitzonderlijk goed.",
"Je bereikt de sloep; het schip helt achter je, maar je leeft.")
elif pct>=50:
tone, ending = ("Je kansen zijn behoorlijk goed.",
"In de chaos vind je een plek in een halfgevulde sloep.")
elif pct>=25:
tone, ending = ("De kansen zijn fifty-fifty.",
"Op het laatste moment spring je; de nacht is lang, maar de horizon gloeit.")
else:
tone, ending = ("Het ziet er somber uit.",
"Je klampt je vast terwijl de oceaan meedogenloos wordt.")
return f"""### 🔮 Jouw overlevingskans: **{pct:.1f}%**
**Situatie:** {rol_txt}, **{klasse_txt} klasse**, inscheping **{haven_txt}** — leeftijd **{int(age)}**, familie **{int(sibsp)}+{int(parch)}** (totaal {int(sibsp)+int(parch)+1}), ticket **£{float(fare):.2f}**.
**Analyse:** {tone} Het model weegt o.a. klasse, geslacht, leeftijd en familieomvang mee.
**Avontuur:** De nacht is stil; fluiten, geroep, voetstappen. {ending}
"""
# ======================================================
# LIVE GAUGE VOOR JOUW SCENARIO (met kleurbanden + threshold)
# ======================================================
def live_viz(pclass, sex, age, sibsp, parch, fare, embarked):
# Retourneer een gauge die live de kans toont (0–100%) met kleurbanden
if MODEL is None:
return make_plot(go.Figure(), "Jouw overlevingskans (live)")
X_row = pd.DataFrame([{
"pclass": int(pclass), "sex": sex, "age": float(age),
"sibsp": int(sibsp), "parch": int(parch), "fare": float(fare),
"embarked": embarked, "family_size": int(sibsp)+int(parch)+1
}])
prob = float(MODEL.predict_proba(X_row)[0,1]) * 100.0
fig = go.Figure(go.Indicator(
mode="gauge+number",
value=prob,
number={"suffix": "%", "valueformat": ".1f"},
gauge={
"axis": {"range": [0, 100]},
"bar": {"thickness": 0.25},
# Kleurbanden (0–25 rood, 25–50 oranje, 50–75 geel, 75–100 groen)
"steps": [
{"range": [0, 25], "color": "#FDECEC"},
{"range": [25, 50], "color": "#FFF2E0"},
{"range": [50, 75], "color": "#FFF9D6"},
{"range": [75, 100], "color": "#E8F6EA"},
],
# Threshold-lijn op actuele waarde
"threshold": {
"line": {"color": "#1B4B91", "width": 4},
"thickness": 0.9,
"value": prob
},
},
title={"text": "Jouw overlevingskans (live)"}
))
return make_plot(fig, "Jouw overlevingskans (live)")
# ======================================================
# UI + LAYOUT
# ======================================================
CUSTOM_CSS = """
body { background:#FFFFFF; color:#0B1C3F; }
.gradio-container { background:#FFFFFF; }
h1, h2, h3, h4 { color:#1B4B91; }
.panel, .intro-card { background:#F9FBFF; border:1px solid #E0E6F3; border-radius:12px; padding:16px; }
.hero-img img { border-radius:12px; border:1px solid #E0E6F3; }
.kpi { display:flex; flex-direction:column; align-items:center; justify-content:center;
background:#FFFFFF; border:1px solid #E0E6F3; border-radius:12px; padding:14px; }
.kpi .value { font-size:1.6rem; font-weight:800; color:#1B4B91; }
.kpi .label { font-size:.9rem; color:#3F557A; }
.explain-card { background:#EAF0FF; border-radius:12px; padding:18px; border:1px solid #D5E0FA; }
"""
with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Default(primary_hue="blue")) as demo:
# Header-intro + foto
with gr.Row():
with gr.Column(scale=2, min_width=420):
gr.Markdown(INTRO_MD, elem_classes=["intro-card"])
with gr.Column(scale=1, min_width=320):
hp = hero_path()
if hp: gr.Image(value=hp, interactive=False, show_label=False, elem_classes=["hero-img"])
else: gr.Markdown("⚠️ **Geen afbeelding gevonden.** Plaats `titanic_bg.png` of `titanic_bg.jpg` in de root.")
# NIEUW PANEEL: Passagierslijst (volledige dataset, scrollbaar)
with gr.Column(elem_classes=["panel"]):
gr.Markdown("## 👥 Passagierslijst — volledige dataset (scrollbaar)")
gr.DataFrame(
value=df, # volledige dataset
wrap=True,
interactive=False, # alleen-lezen
label="Titanic-passagiers",
max_height=320 # vaste hoogte -> scroll binnen de tabel
)
# Kleine spacer om overlap te voorkomen
gr.Markdown("")
# Panel: status + 2D-plot links en uitleg rechts
with gr.Column(elem_classes=["panel"]):
gr.Markdown("## 🔧 Initialisatie & Modeltraining")
status_md = gr.Markdown("⏳ Initialiseren…")
with gr.Row():
with gr.Column(scale=2, min_width=420):
train_plot = gr.Plot(label="2D-projectie — elk bolletje is een passagier")
with gr.Column(scale=1, min_width=320):
gr.Markdown(EXPLAIN_MD_SIDE, elem_classes=["explain-card"])
# KPIs
with gr.Row():
gr.HTML(f"<div class='kpi'><div class='value'>{len(df):,}</div><div class='label'>Totaal passagiers</div></div>")
gr.HTML(f"<div class='kpi'><div class='value'>{int(df['survived'].sum()):,}</div><div class='label'>Overlevenden</div></div>")
gr.HTML(f"<div class='kpi'><div class='value'>{df['survived'].mean()*100:.1f}%</div><div class='label'>% Overleefd</div></div>")
gr.HTML(f"<div class='kpi'><div class='value'>{', '.join(map(str, sorted(df['pclass'].unique())))}</div><div class='label'>Klassen</div></div>")
# Overige visualisaties
gr.Markdown("## 📊 Verken de data", elem_classes=["panel"])
with gr.Row():
g2 = gr.Plot(label="Leeftijdsverdeling per status")
g3 = gr.Plot(label="Geslachtsverdeling")
with gr.Row():
g4 = gr.Plot(label="Ticketprijs per klasse")
# Interactieve voorspelling
with gr.Column(elem_classes=["panel"]):
# Tekstblok + gauge naast elkaar
with gr.Row():
with gr.Column(scale=2, min_width=420):
gr.Markdown("""## 🔮 Jouw scenario — bereken je overlevingskans en lees je scène
Hier kun je ontdekken **hoe groot jouw kans op overleving** zou zijn geweest aan boord van de *Titanic* — en meteen het **verhaal van jouw nacht** lezen.
1. **Kies je profiel**
- **Klasse:** 1e, 2e of 3e klasse (je reiscomfort en dekpositie).
- **Geslacht:** man of vrouw — dit had invloed op reddingsvoorrang.
- **Leeftijd:** jouw leeftijd in jaren.
- **Broers/zussen** en **ouders/kinderen**: hoeveel familieleden reisden met je mee.
- **Ticketprijs (£):** hoe duur je passage was.
- **Vertrekhaven:** Cherbourg (C), Queenstown (Q) of Southampton (S).
2. **Klik op de knop “🎲 Bereken én vertel mijn verhaal”**
Het model schat jouw **overlevingskans** op basis van historische patronen.
3. **Lees je persoonlijke scène**
Onder de knop verschijnt een korte beschrijving die je meeneemt naar die nacht —
gebaseerd op jouw ingevulde profiel en de berekende kans.
> 💡 *De voorspelling is een statistische schatting, geen oordeel.
> Ze helpt je zien hoe factoren zoals klasse, geslacht en leeftijd destijds iemands lot konden bepalen.*""")
with gr.Column(scale=1, min_width=320):
viz_plot = gr.Plot(label="Jouw overlevingskans (live)")
with gr.Row():
ui_pclass = gr.Slider(1, 3, value=2, step=1, label="Klasse (1=1e, 3=3e)")
ui_sex = gr.Radio(["Man","Vrouw"], value="Man", label="Geslacht")
ui_age = gr.Slider(0, 80, value=30, label="Leeftijd")
with gr.Row():
ui_sibsp = gr.Slider(0, 8, value=1, step=1, label="Broers/Zussen aan boord")
ui_parch = gr.Slider(0, 6, value=0, step=1, label="Ouders/Kinder(en) aan boord")
ui_fare = gr.Slider(0, 600, value=50, label="Ticketprijs (£)")
ui_emb = gr.Radio(["C","Q","S"], value="S", label="Vertrekhaven")
btn = gr.Button("🎲 Bereken én vertel mijn verhaal", variant="primary")
story_out = gr.Markdown()
# Loads & acties
demo.load(fn=train_and_embed_solid, inputs=[], outputs=[status_md, train_plot])
demo.load(lambda: (plot_age_hist(df), plot_gender(df), plot_fare_box(df)), inputs=[], outputs=[g2, g3, g4])
# Initiele gauge (na modeltraining): gebruik de default UI-waarden
demo.load(lambda: live_viz(2, "Man", 30, 1, 0, 50, "S"), inputs=[], outputs=[viz_plot])
# Live updates bij elke wijziging
for comp in [ui_pclass, ui_sex, ui_age, ui_sibsp, ui_parch, ui_fare, ui_emb]:
comp.change(live_viz,
inputs=[ui_pclass, ui_sex, ui_age, ui_sibsp, ui_parch, ui_fare, ui_emb],
outputs=viz_plot)
# Ook updaten bij de knop
btn.click(predict_and_story,
inputs=[ui_pclass, ui_sex, ui_age, ui_sibsp, ui_parch, ui_fare, ui_emb],
outputs=story_out)
btn.click(live_viz,
inputs=[ui_pclass, ui_sex, ui_age, ui_sibsp, ui_parch, ui_fare, ui_emb],
outputs=viz_plot)
demo.launch()