Marcel0123 commited on
Commit
c03643c
·
verified ·
1 Parent(s): 743e465

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +226 -168
app.py CHANGED
@@ -1,222 +1,280 @@
1
- # app.py – Titanic Data Explorer Night Sky Edition (Gradio 4.44 compatibel)
2
  import gradio as gr
3
  import pandas as pd
4
  import numpy as np
5
  import os
6
  import plotly.express as px
7
- import plotly.graph_objects as go
8
  from sklearn.model_selection import train_test_split
9
  from sklearn.preprocessing import LabelEncoder
10
- from sklearn.metrics import accuracy_score, confusion_matrix, roc_auc_score
11
  from sklearn.ensemble import RandomForestClassifier
12
 
13
- # =======================
14
- # DATA
15
- # =======================
 
 
16
  def load_data(path="Titanic-Dataset.csv"):
17
  if not os.path.exists(path):
18
  raise FileNotFoundError("❌ Titanic-Dataset.csv niet gevonden in de rootmap.")
19
  df = pd.read_csv(path)
20
  df.columns = [c.lower().strip() for c in df.columns]
 
 
 
21
 
22
- req = {"survived", "pclass", "sex", "age", "sibsp", "parch", "fare", "embarked"}
23
- miss = req - set(df.columns)
24
- if miss:
25
- raise ValueError(f"Ontbrekende kolommen: {miss}")
26
-
27
  for col in df.columns:
28
  if df[col].isna().any():
29
  if df[col].dtype == "object":
30
- df[col] = df[col].fillna(df[col].mode()[0])
31
  else:
32
  df[col] = df[col].fillna(df[col].median())
33
 
 
34
  df["family_size"] = df["sibsp"] + df["parch"] + 1
 
35
  df["sex"] = df["sex"].astype(str).str.title()
36
  df["embarked"] = df["embarked"].astype(str).str.upper()
37
- df["status"] = df["survived"].map({0: "Niet overleefd", 1: "Overleefd"})
38
  return df
39
 
40
  df = load_data()
41
 
42
- # =======================
43
- # PLOTS
44
- # =======================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  def make_plot(fig, title):
46
  fig.update_layout(
47
  title=title,
48
- paper_bgcolor="rgba(0,0,0,0)",
49
- plot_bgcolor="rgba(0,0,0,0)",
50
- font=dict(color="#EAF2FF"),
51
- title_font=dict(size=18, color="#FFD26A"),
52
- margin=dict(l=40, r=40, t=60, b=40)
 
53
  )
54
  return fig
55
 
56
- def plot_class_distribution(x):
57
- f = px.pie(x, names="pclass", color="pclass", color_discrete_sequence=px.colors.sequential.Blues)
58
- return make_plot(f, "Verdeling per Klasse")
59
-
60
- def plot_survival_heatmap(x):
61
- pivot = x.pivot_table(index="sex", columns="pclass", values="survived", aggfunc="mean")
62
- f = go.Figure(data=go.Heatmap(
63
- z=pivot.values,
64
- x=[str(c) for c in pivot.columns],
65
- y=pivot.index,
66
- colorscale="YlGnBu",
67
- zmin=0,
68
- zmax=1
69
- ))
70
- return make_plot(f, "Overlevingspercentage per Geslacht en Klasse")
71
-
72
- def plot_density_age_fare(x):
73
- f = px.density_contour(x, x="age", y="fare", color="status",
74
- marginal_x="histogram", marginal_y="histogram")
75
- return make_plot(f, "Leeftijd vs Ticketprijs (dichtheidsverdeling)")
76
-
77
- def plot_bubble_family_fare(x):
78
- f = px.scatter(
79
- x, x="fare", y="family_size", size="age", color="status",
80
- hover_data=["sex", "pclass"], size_max=40,
81
- color_discrete_sequence=px.colors.qualitative.Set3
82
  )
83
- return make_plot(f, "Bubble Chart — Fare vs Family Size vs Age")
84
-
85
- def plot_sunburst(x):
86
- f = px.sunburst(x, path=["sex", "pclass", "status"], color="status",
87
- color_discrete_map={"Overleefd": "#FFD26A", "Niet overleefd": "#1E3E78"})
88
- return make_plot(f, "Sunburst — Geslacht → Klasse → Overleving")
89
-
90
- def plot_treemap(x):
91
- f = px.treemap(x, path=["embarked", "pclass", "status"], values="fare",
92
- color="status", color_discrete_map={"Overleefd": "#FFD26A", "Niet overleefd": "#1E3E78"})
93
- return make_plot(f, "Treemap — Vertrekhaven → Klasse → Overleving")
94
-
95
- def plot_corr_heatmap(x):
96
- corr = x[["age", "fare", "family_size", "pclass", "sibsp", "parch", "survived"]].corr()
97
- f = go.Figure(data=go.Heatmap(z=corr.values, x=corr.columns, y=corr.columns,
98
- colorscale="Blues", zmin=-1, zmax=1))
99
- return make_plot(f, "Correlatiematrix (numerieke variabelen)")
100
-
101
- # =======================
102
- # MACHINE LEARNING
103
- # =======================
104
- def train_and_evaluate(x):
105
- X = x[["pclass", "sex", "age", "fare", "embarked", "family_size", "sibsp", "parch"]].copy()
106
- y = x["survived"].astype(int)
107
- for c in X.select_dtypes("object").columns:
108
- le = LabelEncoder()
109
- X[c] = le.fit_transform(X[c])
110
 
111
- X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
112
- model = RandomForestClassifier(n_estimators=300, random_state=42)
113
- model.fit(X_train, y_train)
114
- y_pred = model.predict(X_test)
115
- acc = accuracy_score(y_test, y_pred)
116
- auc = roc_auc_score(y_test, model.predict_proba(X_test)[:, 1])
117
- cm = confusion_matrix(y_test, y_pred)
118
-
119
- fig_cm = go.Figure(data=go.Heatmap(z=cm, text=cm, texttemplate="%{text}", colorscale="Blues"))
120
- fig_cm = make_plot(fig_cm, "Confusion Matrix")
121
-
122
- return f"🎯 **Nauwkeurigheid:** {acc:.2%} | **ROC AUC:** {auc:.3f}", fig_cm
123
-
124
- # =======================
125
- # DASHBOARD
126
- # =======================
127
- def dashboard():
128
- acc_text, cm_fig = train_and_evaluate(df)
129
- return (
130
- f"{len(df)}", f"{df['survived'].sum()}",
131
- f"{df['survived'].mean()*100:.1f}%", ", ".join(map(str, sorted(df['pclass'].unique()))),
132
- plot_class_distribution(df),
133
- plot_survival_heatmap(df),
134
- plot_density_age_fare(df),
135
- plot_bubble_family_fare(df),
136
- plot_sunburst(df),
137
- plot_treemap(df),
138
- plot_corr_heatmap(df),
139
- acc_text, cm_fig,
140
- df.head(200)
141
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
- # =======================
144
- # CSS THEMA
145
- # =======================
 
 
 
 
 
 
146
  CUSTOM_CSS = """
147
- body {
148
- background-image: url('titanic_bg.png');
149
- background-size: cover;
150
- background-position: center;
151
- color: #EAF2FF;
152
- }
153
- .gradio-container {
154
- background: rgba(10, 16, 26, 0.75);
155
  }
156
- .gradio-container::before {
157
- content: "";
158
- position: fixed;
159
- top: 0; right: 0;
160
- width: 40vw; height: 40vh;
161
- background: radial-gradient(circle at top right, rgba(255,190,120,0.4) 0%, transparent 70%);
162
- pointer-events: none;
163
- }
164
- .kpi {background: rgba(20,28,42,0.8); border-radius: 12px; padding: 12px; text-align:center;}
165
- .kpi .value {font-size:1.6rem; font-weight:800; color:#FFD26A;}
166
- .kpi .label {font-size:0.9rem; color:#C4D7F0;}
167
- .section-title {font-size:1.3rem; font-weight:800; color:#FFD26A; margin-top:12px;}
168
- .dataframe-container {
169
- height: 320px;
170
- overflow-y: auto;
171
- background: rgba(20,28,42,0.7);
172
- border-radius: 10px;
173
- padding: 5px;
174
  }
 
 
175
  """
176
 
177
- # =======================
178
- # UI
179
- # =======================
180
- with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(primary_hue="blue", secondary_hue="blue")) as demo:
181
- gr.HTML("<h1 style='text-align:center;margin-top:10px;'>🛳️ Titanic Data Explorer – Night Sky Edition</h1>")
182
- gr.HTML("<p style='text-align:center;color:#C4D7F0;'>Interactieve visualisatie & machine learning analyse</p>")
 
 
183
 
 
184
  with gr.Row():
185
- kpi1 = gr.HTML("<div class='kpi'><div class='value'>–</div><div class='label'>Totaal passagiers</div></div>")
186
- kpi2 = gr.HTML("<div class='kpi'><div class='value'>–</div><div class='label'>Overlevenden</div></div>")
187
- kpi3 = gr.HTML("<div class='kpi'><div class='value'>–</div><div class='label'>% Overleefd</div></div>")
188
- kpi4 = gr.HTML("<div class='kpi'><div class='value'>–</div><div class='label'>Klassen aanwezig</div></div>")
 
 
 
 
 
 
 
 
189
 
190
- gr.HTML("<div class='section-title'>📊 Verkenning & Patronen</div>")
191
- with gr.Row():
192
- fig1 = gr.Plot(label="Klasse")
193
- fig2 = gr.Plot(label="Heatmap")
194
- with gr.Row():
195
- fig3 = gr.Plot(label="Density")
196
- fig4 = gr.Plot(label="Bubble Chart")
197
  with gr.Row():
198
- fig5 = gr.Plot(label="Sunburst")
199
- fig6 = gr.Plot(label="Treemap")
200
- with gr.Row():
201
- fig7 = gr.Plot(label="Correlaties")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
- gr.HTML("<div class='section-title'>🤖 Machine Learning</div>")
204
- acc_md = gr.Markdown()
205
- fig_cm = gr.Plot(label="Confusion Matrix")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
- gr.HTML("<div class='section-title'>🗂️ Data voorbeeld</div>")
208
- with gr.Column(elem_classes=["dataframe-container"]):
209
- table = gr.Dataframe(interactive=False)
 
 
 
 
 
 
 
210
 
211
- def update_dashboard():
212
- return dashboard()
 
 
 
 
 
 
213
 
214
- demo.load(
215
- fn=update_dashboard,
216
- inputs=[],
217
- outputs=[kpi1, kpi2, kpi3, kpi4,
218
- fig1, fig2, fig3, fig4, fig5, fig6, fig7,
219
- acc_md, fig_cm, table]
220
  )
221
 
222
  demo.launch()
 
1
+ # app.py – Titanic Data Adventure (wit thema, verhalend, centrale afbeelding, interactieve overlevingskans)
2
  import gradio as gr
3
  import pandas as pd
4
  import numpy as np
5
  import os
6
  import plotly.express as px
 
7
  from sklearn.model_selection import train_test_split
8
  from sklearn.preprocessing import LabelEncoder
 
9
  from sklearn.ensemble import RandomForestClassifier
10
 
11
+ # =========================
12
+ # Data laden en voorbereiden
13
+ # =========================
14
+ REQUIRED = {"survived","pclass","sex","age","sibsp","parch","fare","embarked"}
15
+
16
  def load_data(path="Titanic-Dataset.csv"):
17
  if not os.path.exists(path):
18
  raise FileNotFoundError("❌ Titanic-Dataset.csv niet gevonden in de rootmap.")
19
  df = pd.read_csv(path)
20
  df.columns = [c.lower().strip() for c in df.columns]
21
+ missing = REQUIRED - set(df.columns)
22
+ if missing:
23
+ raise ValueError(f"Ontbrekende kolommen in dataset: {', '.join(sorted(missing))}")
24
 
25
+ # Missende waarden invullen
 
 
 
 
26
  for col in df.columns:
27
  if df[col].isna().any():
28
  if df[col].dtype == "object":
29
+ df[col] = df[col].fillna(df[col].mode().iloc[0])
30
  else:
31
  df[col] = df[col].fillna(df[col].median())
32
 
33
+ # Afgeleide features
34
  df["family_size"] = df["sibsp"] + df["parch"] + 1
35
+ df["status"] = df["survived"].map({0:"Niet overleefd", 1:"Overleefd"})
36
  df["sex"] = df["sex"].astype(str).str.title()
37
  df["embarked"] = df["embarked"].astype(str).str.upper()
 
38
  return df
39
 
40
  df = load_data()
41
 
42
+ # =========================
43
+ # Model trainen (1x bij start)
44
+ # =========================
45
+ def train_model(dfx: pd.DataFrame):
46
+ X = dfx[["pclass","sex","age","sibsp","parch","fare","embarked","family_size"]].copy()
47
+ y = dfx["survived"].astype(int)
48
+
49
+ # Encode categorisch
50
+ for c in X.select_dtypes("object").columns:
51
+ le = LabelEncoder()
52
+ X[c] = le.fit_transform(X[c])
53
+
54
+ X_train, X_test, y_train, y_test = train_test_split(
55
+ X, y, test_size=0.25, random_state=42, stratify=y
56
+ )
57
+ model = RandomForestClassifier(n_estimators=300, random_state=42)
58
+ model.fit(X_train, y_train)
59
+ acc = model.score(X_test, y_test)
60
+ return model, acc
61
+
62
+ model, model_acc = train_model(df)
63
+
64
+ # =========================
65
+ # Plots (duidelijk en informatief)
66
+ # =========================
67
  def make_plot(fig, title):
68
  fig.update_layout(
69
  title=title,
70
+ paper_bgcolor="rgba(255,255,255,0)",
71
+ plot_bgcolor="rgba(255,255,255,0)",
72
+ font=dict(color="#0B1C3F"),
73
+ title_font=dict(size=18, color="#1B4B91"),
74
+ margin=dict(l=40, r=40, t=50, b=40),
75
+ legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
76
  )
77
  return fig
78
 
79
+ def plot_overleving_per_klasse(dfx):
80
+ f = px.bar(
81
+ dfx, x="pclass", color="status", barmode="group",
82
+ category_orders={"pclass":[1,2,3]},
83
+ color_discrete_map={"Overleefd":"#1B4B91","Niet overleefd":"#A3B1C6"},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  )
85
+ return make_plot(f, "Overleving per klasse")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
+ def plot_leeftijdsverdeling(dfx):
88
+ f = px.histogram(
89
+ dfx, x="age", color="status", nbins=30, barmode="overlay", opacity=0.75,
90
+ color_discrete_map={"Overleefd":"#1B4B91","Niet overleefd":"#A3B1C6"},
91
+ )
92
+ return make_plot(f, "Leeftijdsverdeling per overlevingsstatus")
93
+
94
+ def plot_geslacht(dfx):
95
+ f = px.pie(
96
+ dfx, names="sex", color="sex",
97
+ color_discrete_map={"Male":"#A3B1C6","Female":"#1B4B91"},
98
+ hole=0.35
99
+ )
100
+ return make_plot(f, "Verdeling geslacht (alle passagiers)")
101
+
102
+ def plot_fare_vs_klasse(dfx):
103
+ f = px.box(
104
+ dfx, x="pclass", y="fare", color="status",
105
+ color_discrete_map={"Overleefd":"#1B4B91","Niet overleefd":"#A3B1C6"},
 
 
 
 
 
 
 
 
 
 
 
106
  )
107
+ return make_plot(f, "Ticketprijs per klasse (met overleving)")
108
+
109
+ # =========================
110
+ # Hero-afbeelding pad bepalen (png/jpg/jpeg fallback)
111
+ # =========================
112
+ def get_hero_image_path():
113
+ for name in ["titanic_bg.png", "titanic_bg.jpg", "titanic_bg.jpeg"]:
114
+ if os.path.exists(name):
115
+ return name
116
+ return None # geen afbeelding gevonden
117
+
118
+ HERO_PATH = get_hero_image_path()
119
+
120
+ # =========================
121
+ # Interactieve voorspelling + avontuur-tekst
122
+ # =========================
123
+ def predict_and_story(pclass, sex, age, sibsp, parch, fare, embarked):
124
+ # Encode invoer net als model
125
+ sex_enc = 1 if str(sex).lower().startswith("v") else 0 # Vrouw=1, Man=0
126
+ embarked_enc = {"C":0,"Q":1,"S":2}.get(embarked, 2)
127
+ family_size = int(sibsp) + int(parch) + 1
128
+
129
+ X_row = [[int(pclass), sex_enc, float(age), int(sibsp), int(parch), float(fare), embarked_enc, family_size]]
130
+ prob = float(model.predict_proba(X_row)[0,1])
131
+ pct = prob * 100
132
+
133
+ # Avontuur-tekst op basis van invoer en kans
134
+ klasse_txt = {1:"eerste", 2:"tweede", 3:"derde"}.get(int(pclass), "onbekende")
135
+ haven_txt = {"C":"Cherbourg","Q":"Queenstown","S":"Southampton"}.get(embarked, "een onbekende haven")
136
+ rol_txt = "vrouw" if sex_enc==1 else "man"
137
+
138
+ if pct >= 75:
139
+ tone = "Je kansen zijn uitzonderlijk goed."
140
+ ending = "De kou snijdt, maar je bereikt de sloep. Wanneer het schip achter je kantelt, drijf je weg, stil – ademloos, maar levend."
141
+ elif pct >= 50:
142
+ tone = "Je kansen zijn behoorlijk goed."
143
+ ending = "Het is chaotisch, mensen duwen en roepen. Je vindt een plek in een halfgevulde sloep. De nacht is lang, maar je overleeft."
144
+ elif pct >= 25:
145
+ tone = "De kansen zijn fifty-fifty; spanning stijgt."
146
+ ending = "Je wacht, je aarzelt – en dan komt een laatste gelegenheid. Je springt. De zee is donker, maar de horizon gloeit zwak."
147
+ else:
148
+ tone = "Het ziet er somber uit; hartverscheurend."
149
+ ending = "De dekken hellen over. Je klampt je vast. De oceaan is meedogenloos, maar je verhaal – en dat van zovelen – wordt nooit vergeten."
150
+
151
+ story = f"""
152
+ ### 🔮 Jouw overlevingskans: **{pct:.1f}%**
153
+
154
+ **Situatie:** Je bent een **{rol_txt}** in de **{klasse_txt} klasse**, ingescheept in **{haven_txt}**.
155
+ Je bent **{int(age)}** jaar oud, reist met **{int(sibsp)}** broer(s)/zus(sen) en **{int(parch)}** ouder(s)/kind(eren).
156
+ Je ticket kostte **£{float(fare):.2f}** en je **familiegrootte** is **{family_size}**.
157
+
158
+ **Analyse:** {tone} Het model weegt o.a. klasse, geslacht, leeftijd en familieomvang mee—patronen in de historische data.
159
 
160
+ **Avontuur:**
161
+ De nacht is stil, sterren spiegelen in een vlakke zee. Het schip siddert. Fluiten, geroep, voetstappen.
162
+ Je voelt de houten reling koud onder je hand. {ending}
163
+ """
164
+ return story
165
+
166
+ # =========================
167
+ # UI (vast lay-out, wit thema, veel uitleg)
168
+ # =========================
169
  CUSTOM_CSS = """
170
+ body { background: #FFFFFF; color: #0B1C3F; }
171
+ .gradio-container { background: #FFFFFF; }
172
+ h1, h2, h3, h4 { color: #1B4B91; }
173
+ .intro-card, .section {
174
+ background: #F9FBFF;
175
+ border: 1px solid #E0E6F3;
176
+ border-radius: 12px;
177
+ padding: 16px;
178
  }
179
+ .subtitle { color: #4F6FA9; }
180
+ .hero-img img { border-radius: 12px; border: 1px solid #E0E6F3; }
181
+ .kpi {
182
+ display:flex; flex-direction:column; align-items:center; justify-content:center;
183
+ background:#FFFFFF; border:1px solid #E0E6F3; border-radius:12px; padding:14px;
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  }
185
+ .kpi .value { font-size:1.6rem; font-weight:800; color:#1B4B91; }
186
+ .kpi .label { font-size:.9rem; color:#3F557A; }
187
  """
188
 
189
+ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Default(primary_hue="blue")) as demo:
190
+ # Titel
191
+ gr.Markdown("# 🛳️ Titanic Data Adventure")
192
+ gr.Markdown(
193
+ "<p class='subtitle' style='text-align:center;'>"
194
+ "Een verhalende ontdekkingstocht door data, besluitvorming en overlevingskansen."
195
+ "</p>"
196
+ )
197
 
198
+ # Intro: afbeelding + verhaal
199
  with gr.Row():
200
+ with gr.Column(scale=1, min_width=320):
201
+ gr.HTML("<div class='intro-card'><h3>📖 Proloog</h3>"
202
+ "<p>April 1912. De RMS Titanic verlaat Europa richting New York. "
203
+ "We kijken mee door de lens van data: wie stapten aan boord? "
204
+ "Wie overleefden – en waarom?</p>"
205
+ "<p>Links zie je een historisch beeld van het schip (jouw geüploade afbeelding). "
206
+ "Rechts nemen we je mee langs inzichten en uiteindelijk jouw persoonlijke scenario.</p></div>")
207
+ with gr.Column(scale=1, min_width=320):
208
+ if HERO_PATH:
209
+ hero = gr.Image(value=HERO_PATH, interactive=False, show_label=False, elem_classes=["hero-img"])
210
+ else:
211
+ gr.Markdown("⚠️ **Geen afbeelding gevonden.** Plaats `titanic_bg.png` óf `titanic_bg.jpg` in de root.")
212
 
213
+ # Kerncijfers
 
 
 
 
 
 
214
  with gr.Row():
215
+ k1 = gr.HTML(f"<div class='kpi'><div class='value'>{len(df):,}</div><div class='label'>Totaal passagiers</div></div>")
216
+ k2 = gr.HTML(f"<div class='kpi'><div class='value'>{int(df['survived'].sum()):,}</div><div class='label'>Overlevenden</div></div>")
217
+ k3 = gr.HTML(f"<div class='kpi'><div class='value'>{df['survived'].mean()*100:.1f}%</div><div class='label'>% Overleefd</div></div>")
218
+ k4 = gr.HTML(f"<div class='kpi'><div class='value'>{', '.join(map(str, sorted(df['pclass'].unique())))}</div><div class='label'>Klassen</div></div>")
219
+
220
+ # Hoofdstuk 1: Wie zaten er aan boord?
221
+ with gr.Column(elem_classes=["section"]):
222
+ gr.Markdown("## Hoofdstuk 1 — Wie zaten er aan boord?")
223
+ gr.Markdown(
224
+ "We beginnen bij de samenstelling van de passagiers: klasse, leeftijd en geslacht. "
225
+ "Historisch gezien zijn dit belangrijke factoren voor kansen op redding."
226
+ )
227
+ with gr.Row():
228
+ g1 = gr.Plot(label="Overleving per klasse")
229
+ g2 = gr.Plot(label="Leeftijdsverdeling per status")
230
+ with gr.Row():
231
+ g3 = gr.Plot(label="Geslachtsverdeling")
232
+ g4 = gr.Plot(label="Ticketprijs per klasse")
233
 
234
+ # Hoofdstuk 2: Jouw scenario – interactieve kans + verhaal
235
+ with gr.Column(elem_classes=["section"]):
236
+ gr.Markdown("## Hoofdstuk 2 — Stel jezelf voor...")
237
+ gr.Markdown(
238
+ "Voer jouw gegevens in. We gebruiken een getraind model (Random Forest) om je overlevingskans te schatten "
239
+ "én we vertellen jouw verhaal — een korte scène op het dek."
240
+ )
241
+ with gr.Row():
242
+ ui_pclass = gr.Slider(1, 3, value=2, step=1, label="Klasse (1=1e, 3=3e)")
243
+ ui_sex = gr.Radio(["Man","Vrouw"], value="Man", label="Geslacht")
244
+ ui_age = gr.Slider(0, 80, value=30, label="Leeftijd")
245
+ with gr.Row():
246
+ ui_sibsp = gr.Slider(0, 8, value=1, step=1, label="Broers/Zussen aan boord")
247
+ ui_parch = gr.Slider(0, 6, value=0, step=1, label="Ouders/Kinder(en) aan boord")
248
+ ui_fare = gr.Slider(0, 600, value=50, label="Ticketprijs (£)")
249
+ ui_emb = gr.Radio(["C","Q","S"], value="S", label="Vertrekhaven")
250
+ btn = gr.Button("🎲 Bereken én vertel mijn verhaal")
251
+ story_out = gr.Markdown()
252
 
253
+ # Hoofdstuk 3: Hoe lezen we deze grafieken?
254
+ with gr.Column(elem_classes=["section"]):
255
+ gr.Markdown("## Hoofdstuk 3 — Wat leren we hieruit?")
256
+ gr.Markdown(
257
+ "- **Klasse** maakt een groot verschil: reddingsboten waren beter bereikbaar voor de 1e klasse.\n"
258
+ "- **Vrouwen en kinderen** kregen vaak voorrang, wat zichtbaar is in de verdeling naar geslacht.\n"
259
+ "- **Leeftijd** en **familiegrootte** spelen mee: jonge gezinnen hadden andere kansen en keuzes.\n"
260
+ "- **Ticketprijs** (fare) en **klasse** hangen sterk samen, en correleren indirect met overleving.\n\n"
261
+ "Let op: een model is een benadering van de historische patronen. Achter elke datapunt schuilt een persoonlijk verhaal."
262
+ )
263
 
264
+ # callbacks
265
+ def load_graphs():
266
+ return (
267
+ plot_overleving_per_klasse(df),
268
+ plot_leeftijdsverdeling(df),
269
+ plot_geslacht(df),
270
+ plot_fare_vs_klasse(df),
271
+ )
272
 
273
+ demo.load(load_graphs, [], [g1, g2, g3, g4])
274
+ btn.click(
275
+ predict_and_story,
276
+ inputs=[ui_pclass, ui_sex, ui_age, ui_sibsp, ui_parch, ui_fare, ui_emb],
277
+ outputs=story_out
 
278
  )
279
 
280
  demo.launch()