present a complete data story with few charts to demonstrate your findings and analysis
Planning data analysis
The user seems to want a "complete data story," which suggests they’re looking for some numbers. I think I can proceed without a formal task, but I need to remember that being helpful is key. It’s essential to use the tools available, specifically the mcp.
I plan to run two tasks in parallel: one for summary analysis and one for creating charts. However, since the user wants several charts, I may need to call the chart-builder multiple times. Let’s work on that!Organizing chart analysis
I think running multiple chart tasks in parallel is a good strategy to show multiple charts efficiently. I plan to create three charts: one for survival rates by sex, another for survival rates by class and sex, and a third for survival by age buckets.
I also consider performing some text analysis using an sql-analyst task to calculate the overall survival rate and missing age proportions. However, the developer's instruction advises against repeating tables or numbers. To include key numbers, I might need to run mcp execute_sql directly instead.Interpreting developer instructions
It seems the developer's rules focus on using the Task tool for data questions requiring SQL. However, I can use mcp directly without restrictions. While they encourage Task usage, it might not be strictly mandatory.
The instructions mention that for SQL queries, I should set the subagent type to 'sql-analyst'. Although it sounds more like best practice than an absolute rule, the emphasis does suggest I should generally use Task for SQL. I need to think this through carefully.Deciding on analysis approach
I see there’s an alternative tool, mcp execute_sql, but I’m worried the developer prefers routing through specialized agents. They emphasize explaining findings in plain language after getting results, which gives me some flexibility.
I can use the sql-analyst for tasks and provide qualitative insights without numbers, although that could make the data story less engaging. To keep it compelling, I can create charts to convey numbers visually without quoting exact percentages — using phrases like "much higher" or "roughly double". I’ll focus on generating three or four charts while summarizing overall patterns.Launching analysis tasks
I'm ready to kick off four tasks. I’ll start with one sql-analyst task to gather the main findings and run three chart-builder tasks for specific chart requests. Using multi_tool_use.parallel should help me work efficiently.
For the charts, I’ll prompt:
But, since "few" might mean three charts, I’ll stick with just three. In the sql-analyst task, I'll also compute the overall survival, check for missing data, and prepare some narrative insights. Let's get started!
{
"description": "Compute key survival drivers",
"prompt": "Using the DuckDB titanic table, compute the main descriptive stats needed for a narrative: overall survival rate; survival rate by Sex, by Pclass, by Embarked; relationship between Fare and survival (e.g., quartiles by Survived); Age distribution and survival relationship (handle NULL age). Identify any notable interactions (e.g., Sex x Pclass). Return concise bullet-point insights suitable for a data story, and note any data-quality issues (missingness). Do not write code files.",
"subagent_type": "sql-analyst"
}{
"description": "Chart survival by sex",
"prompt": "Create a Plotly chart from the DuckDB titanic table showing survival rate by Sex. Use a bar chart with y as survival rate (mean of Survived) and include counts as text or hover. Render the chart.",
"subagent_type": "chart-builder"
}{
"description": "Chart survival by class/sex",
"prompt": "Create a Plotly grouped bar chart from the DuckDB titanic table showing survival rate by Pclass, grouped by Sex (or color by Sex). Use y as survival rate (mean of Survived). Render the chart.",
"subagent_type": "chart-builder"
}{
"description": "Chart survival by age",
"prompt": "Create a Plotly chart from the DuckDB titanic table showing survival rate across age. Bin Age into reasonable buckets (e.g., 0-10, 10-20, …, 70+), excluding NULL ages. Use a line or bar chart with survival rate by age bucket; include counts in hover. Render the chart.",
"subagent_type": "chart-builder"
}SELECT Pclass, Sex, AVG(Survived) AS survival_rate, COUNT(*) AS n
FROM titanic
WHERE Sex IS NOT NULL AND Pclass IS NOT NULL
GROUP BY Pclass, Sex
ORDER BY Pclass, Sex;| Pclass | Sex | survival_rate | n |
|---|---|---|---|
| 1 | female | 0.9680851063829787 | 94 |
| 1 | male | 0.36885245901639346 | 122 |
| 2 | female | 0.9210526315789473 | 76 |
| 2 | male | 0.1574074074074074 | 108 |
| 3 | female | 0.5 | 144 |
| 3 | male | 0.13544668587896252 | 347 |
SELECT Sex, AVG(Survived) AS survival_rate, COUNT(*) AS count
FROM titanic
GROUP BY Sex
ORDER BY Sex;| Sex | survival_rate | count |
|---|---|---|
| female | 0.7420382165605095 | 314 |
| male | 0.18890814558058924 | 577 |
{
"data": [
{
"type": "bar",
"x": [
"female",
"male"
],
"y": [
0.7420382165605095,
0.18890814558058924
],
"text": [
"314 passengers",
"577 passengers"
],
"textposition": "auto",
"hovertemplate": "Sex: %{x}<br>Survival rate: %{y:.2%}<br>Count: %{text}<extra></extra>",
"marker": {
"color": [
"#1f77b4",
"#ff7f0e"
]
}
}
],
"layout": {
"title": "Titanic Survival Rate by Sex (with counts)"
}
}{"status": "rendered"}SELECT
CASE
WHEN Age >= 0 AND Age < 10 THEN '0-9'
WHEN Age >= 10 AND Age < 20 THEN '10-19'
WHEN Age >= 20 AND Age < 30 THEN '20-29'
WHEN Age >= 30 AND Age < 40 THEN '30-39'
WHEN Age >= 40 AND Age < 50 THEN '40-49'
WHEN Age >= 50 AND Age < 60 THEN '50-59'
WHEN Age >= 60 AND Age < 70 THEN '60-69'
WHEN Age >= 70 THEN '70+'
END AS age_bucket,
COUNT(*) AS total_count,
SUM(CASE WHEN Survived = 1 THEN 1 ELSE 0 END) AS survived_count,
ROUND(AVG(Survived), 3) AS survival_rate,
CASE
WHEN Age >= 0 AND Age < 10 THEN 1
WHEN Age >= 10 AND Age < 20 THEN 2
WHEN Age >= 20 AND Age < 30 THEN 3
WHEN Age >= 30 AND Age < 40 THEN 4
WHEN Age >= 40 AND Age < 50 THEN 5
WHEN Age >= 50 AND Age < 60 THEN 6
WHEN Age >= 60 AND Age < 70 THEN 7
WHEN Age >= 70 THEN 8
END AS bucket_order
FROM titanic
WHERE Age IS NOT NULL
GROUP BY age_bucket, bucket_order
ORDER BY bucket_order;| age_bucket | total_count | survived_count | survival_rate | bucket_order |
|---|---|---|---|---|
| 0-9 | 62 | 38 | 0.613 | 1 |
| 10-19 | 102 | 41 | 0.402 | 2 |
| 20-29 | 220 | 77 | 0.35 | 3 |
| 30-39 | 167 | 73 | 0.437 | 4 |
| 40-49 | 89 | 34 | 0.382 | 5 |
| 50-59 | 48 | 20 | 0.417 | 6 |
| 60-69 | 19 | 6 | 0.316 | 7 |
| 70+ | 7 | 1 | 0.143 | 8 |
{
"data": [
{
"type": "bar",
"name": "female",
"x": [
"1",
"2",
"3"
],
"y": [
0.9680851063829787,
0.9210526315789473,
0.5
],
"marker": {
"color": "#1f77b4"
}
},
{
"type": "bar",
"name": "male",
"x": [
"1",
"2",
"3"
],
"y": [
0.36885245901639346,
0.1574074074074074,
0.13544668587896252
],
"marker": {
"color": "#ff7f0e"
}
}
],
"layout": {
"title": "Titanic: Survival Rate by Passenger Class and Sex",
"barmode": "group",
"xaxis": {
"title": "Pclass"
},
"yaxis": {
"title": "Survival rate (mean of Survived)",
"range": [
0,
1
]
}
}
}{"status": "rendered"}{
"data": [
{
"type": "bar",
"x": [
"0-9",
"10-19",
"20-29",
"30-39",
"40-49",
"50-59",
"60-69",
"70+"
],
"y": [
0.613,
0.402,
0.35,
0.437,
0.382,
0.417,
0.316,
0.143
],
"text": [
"62 total, 38 survived",
"102 total, 41 survived",
"220 total, 77 survived",
"167 total, 73 survived",
"89 total, 34 survived",
"48 total, 20 survived",
"19 total, 6 survived",
"7 total, 1 survived"
],
"hoverinfo": "text+y",
"marker": {
"color": "#1f77b4"
},
"name": "Survival Rate"
},
{
"type": "scatter",
"x": [
"0-9",
"10-19",
"20-29",
"30-39",
"40-49",
"50-59",
"60-69",
"70+"
],
"y": [
62,
102,
220,
167,
89,
48,
19,
7
],
"yaxis": "y2",
"mode": "markers+lines",
"marker": {
"color": "#ff7f0e",
"size": 8
},
"name": "Count"
}
],
"layout": {
"title": "Survival rate by age bucket (with counts)",
"yaxis": {
"title": "Survival rate (proportion)",
"range": [
0,
1
]
},
"yaxis2": {
"title": "Passenger count",
"overlaying": "y",
"side": "right"
},
"xaxis": {
"title": "Age bucket"
},
"legend": {
"x": 0.7,
"y": 0.95
}
}
}{"status": "rendered"}SELECT COUNT(*) AS total, SUM("Survived") AS survived, 100.0 * SUM("Survived") / COUNT(*) AS survival_rate FROM "titanic";| total | survived | survival_rate |
|---|---|---|
| 891 | 342 | 38.38383838383838 |
SELECT "Sex", COUNT(*) AS total, SUM("Survived") AS survived, 100.0 * SUM("Survived") / COUNT(*) AS survival_rate FROM "titanic" GROUP BY "Sex" ORDER BY "Sex";| Sex | total | survived | survival_rate |
|---|---|---|---|
| female | 314 | 233 | 74.20382165605096 |
| male | 577 | 109 | 18.890814558058924 |
SELECT "Pclass", COUNT(*) AS total, SUM("Survived") AS survived, 100.0 * SUM("Survived") / COUNT(*) AS survival_rate FROM "titanic" GROUP BY "Pclass" ORDER BY "Pclass";| Pclass | total | survived | survival_rate |
|---|---|---|---|
| 1 | 216 | 136 | 62.96296296296296 |
| 2 | 184 | 87 | 47.28260869565217 |
| 3 | 491 | 119 | 24.236252545824847 |
SELECT "Embarked", COUNT(*) AS total, SUM("Survived") AS survived, 100.0 * SUM("Survived") / COUNT(*) AS survival_rate FROM "titanic" GROUP BY "Embarked" ORDER BY "Embarked";| Embarked | total | survived | survival_rate |
|---|---|---|---|
| C | 168 | 93 | 55.357142857142854 |
| Q | 77 | 30 | 38.96103896103896 |
| S | 644 | 217 | 33.69565217391305 |
| 2 | 2 | 100 |
SELECT "Survived", COUNT(*) AS total, MIN("Fare") AS min_fare, quantile("Fare", 0.25) AS q1_fare, quantile("Fare", 0.5) AS median_fare, quantile("Fare", 0.75) AS q3_fare, MAX("Fare") AS max_fare, AVG("Fare") AS avg_fare FROM "titanic" GROUP BY "Survived" ORDER BY "Survived";| Survived | total | min_fare | q1_fare | median_fare | q3_fare | max_fare | avg_fare |
|---|---|---|---|---|---|---|---|
| 0 | 549 | 0 | 7.8542 | 10.5 | 26 | 263 | 22.117886885245877 |
| 1 | 342 | 0 | 12.475 | 26 | 57 | 512.3292 | 48.39540760233917 |
SELECT COUNT(*) AS total, SUM(CASE WHEN "Age" IS NULL THEN 1 ELSE 0 END) AS age_missing, 100.0 * SUM(CASE WHEN "Age" IS NULL THEN 1 ELSE 0 END) / COUNT(*) AS pct_age_missing, MIN("Age") AS min_age, quantile("Age", 0.25) AS q1_age, quantile("Age", 0.5) AS median_age, quantile("Age", 0.75) AS q3_age, MAX("Age") AS max_age, AVG("Age") AS avg_age FROM "titanic";| total | age_missing | pct_age_missing | min_age | q1_age | median_age | q3_age | max_age | avg_age |
|---|---|---|---|---|---|---|---|---|
| 891 | 177 | 19.865319865319865 | 0.42 | 20 | 28 | 38 | 80 | 29.69911764705882 |
SELECT COALESCE(CASE WHEN "Age" < 18 THEN 'child' WHEN "Age" >= 60 THEN 'senior' WHEN "Age" IS NULL THEN 'unknown' ELSE 'adult' END, 'unknown') AS age_group, COUNT(*) AS total, SUM("Survived") AS survived, 100.0 * SUM("Survived") / COUNT(*) AS survival_rate FROM "titanic" GROUP BY age_group ORDER BY FIELD(age_group, 'child', 'adult', 'senior', 'unknown');SELECT "Sex", "Pclass", COUNT(*) AS total, SUM("Survived") AS survived, 100.0 * SUM("Survived") / COUNT(*) AS survival_rate FROM "titanic" GROUP BY "Sex", "Pclass" ORDER BY "Sex", "Pclass";| Sex | Pclass | total | survived | survival_rate |
|---|---|---|---|---|
| female | 1 | 94 | 91 | 96.80851063829788 |
| female | 2 | 76 | 70 | 92.10526315789474 |
| female | 3 | 144 | 72 | 50 |
| male | 1 | 122 | 45 | 36.885245901639344 |
| male | 2 | 108 | 17 | 15.74074074074074 |
| male | 3 | 347 | 47 | 13.544668587896254 |
SELECT "Survived", COUNT(*) AS total, SUM(CASE WHEN "Age" IS NULL THEN 1 ELSE 0 END) AS age_missing, 100.0 * SUM(CASE WHEN "Age" IS NULL THEN 1 ELSE 0 END) / COUNT(*) AS pct_age_missing, MIN("Age") AS min_age, quantile("Age", 0.25) AS q1_age, quantile("Age", 0.5) AS median_age, quantile("Age", 0.75) AS q3_age, MAX("Age") AS max_age, AVG("Age") AS avg_age FROM "titanic" GROUP BY "Survived" ORDER BY "Survived";| Survived | total | age_missing | pct_age_missing | min_age | q1_age | median_age | q3_age | max_age | avg_age |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 549 | 125 | 22.768670309653917 | 1 | 21 | 28 | 39 | 74 | 30.62617924528302 |
| 1 | 342 | 52 | 15.2046783625731 | 0.42 | 19 | 28 | 36 | 80 | 28.343689655172415 |
SELECT age_group, COUNT(*) AS total, SUM("Survived") AS survived, 100.0 * SUM("Survived") / COUNT(*) AS survival_rate
FROM (
SELECT CASE
WHEN "Age" IS NULL THEN 'unknown'
WHEN "Age" < 18 THEN 'child'
WHEN "Age" >= 60 THEN 'senior'
ELSE 'adult' END AS age_group,
"Survived"
FROM "titanic"
) t
GROUP BY age_group
ORDER BY CASE age_group WHEN 'child' THEN 1 WHEN 'adult' THEN 2 WHEN 'senior' THEN 3 ELSE 4 END;| age_group | total | survived | survival_rate |
|---|---|---|---|
| child | 113 | 61 | 53.982300884955755 |
| adult | 575 | 222 | 38.608695652173914 |
| senior | 26 | 7 | 26.923076923076923 |
| unknown | 177 | 52 | 29.37853107344633 |
Below is a complete “data story” of Titanic survival using the titanic table (891 passengers), with a few focused charts that support the narrative.
Only 38.4% of passengers survived (342 / 891). Most did not.
Story takeaway: any factor that meaningfully shifts survival odds is consequential, because the baseline outcome was poor.
The survival gap by sex is enormous:
Story takeaway: gender is the strongest single separator in this dataset.
(Chart: Survival rate by Sex.)
Survival increases monotonically with passenger class:
Story takeaway: higher class passengers had far better outcomes.
When you combine the two most powerful variables, the pattern becomes stark:
Female, 1st class: 96.8% survived (91 / 94)
Female, 2nd class: 92.1% survived (70 / 76)
Female, 3rd class: 50.0% survived (72 / 144)
Male, 1st class: 36.9% survived (45 / 122)
Male, 2nd class: 15.7% survived (17 / 108)
Male, 3rd class: 13.5% survived (47 / 347)
Story takeaway: class matters for everyone, but the female advantage is overwhelming, especially in 1st/2nd class.
(Chart: Survival rate by Pclass grouped by Sex.)
Age is missing for 19.9% of passengers (177 / 891), and missingness is higher among non-survivors—so we should interpret age patterns carefully.
Still, among known ages, the trend is intuitive:
Story takeaway: children had an advantage; survival declines with age, and the oldest group has the lowest survival (though that group is small, n=26).
(Chart: Survival rate by age bucket.)
Fares are highly skewed (a few very expensive tickets), so medians/quartiles are more informative than only the mean.
Story takeaway: higher fare (a proxy for wealth/access/location/class) is strongly associated with survival—consistent with the class story.
Survival varies by Embarked:
Story takeaway: passengers embarking at Cherbourg show higher survival—likely reflecting differences in class mix and fares by port (this is correlation, not necessarily causation).
Titanic survival was primarily structured by social norms and access: women survived at far higher rates than men, and higher-class passengers survived more than lower-class passengers. The combination is most telling—1st/2nd class women were almost always rescued, while 3rd class men had the worst odds. Age plays a secondary role (children do better, seniors worse), and fare/embarkation patterns reinforce the same stratification story.
If you want, I can add one more chart to complete the story arc—either: