Public
Edited
May 1
Insert cell
Insert cell
// Load CSV
data = FileAttachment("tested.csv").csv({ typed: true })
Insert cell
Insert cell
// Calculate median age to fill in missing age values
medianAge = {
const ages = data.map(d => d.Age).filter(v => v !== null && !isNaN(v));
ages.sort((a, b) => a - b);
const mid = Math.floor(ages.length / 2);
return ages.length % 2 === 0 ? (ages[mid - 1] + ages[mid]) / 2 : ages[mid];
}
Insert cell
// Preprocess the data (used a LLM to help with this, it's my first time using js)
processedData = data.map(d => ({
...d,
// Input missing values
Age: d.Age !== null && !isNaN(d.Age) ? d.Age : medianAge,
// Set up data types
PassengerId: Number(d.PassengerId),
Survived: Number(d.Survived),
Pclass: String(d.Pclass),
Name: String(d.Name),
Sex: String(d.Sex),
SibSp: Number(d.SibSp),
Parch: Number(d.Parch),
Ticket: String(d.Ticket),
Fare: Number(d.Fare),
Cabin: d.Cabin ? String(d.Cabin) : null,
Embarked: String(d.Embarked)
}))
Insert cell
// Create an array of objects to store pclass survival rates
survivalByClass = {
const surv = {};
processedData.forEach(d => {
const pclass = d.Pclass;
if (!surv[pclass]) surv[pclass] = { survived: 0, total: 0};
surv[pclass].total += 1;
surv[pclass].survived += d.Survived;
});
return Object.keys(surv).map(pclass => ({
Pclass: pclass,
SurvivalRate: surv[pclass].survived / surv[pclass].total
}));
}
Insert cell
// Bar plot to show survival rate by pclass
Plot.plot({
marks: [
Plot.barY(survivalByClass, {
x: "Pclass",
y: "SurvivalRate",
fill: "orange"
})
],
x: { label: "Passenger Class" },
y: { label: "Survival Rate", domain: [0, 1] },
title: "Survival Rate by Passenger Class"
})
Insert cell
Insert cell
Insert cell
// Consolidate counts into bins for fare and age
ageFareCount = {
// Set up bins for fare
const binFare = fare => {
if (fare < 10) return "0-10";
if (fare < 20) return "10-20";
if (fare < 50) return "20-50";
if (fare < 100) return "50-100";
return "100+";
};

// Set up bins for age
const binAge = age => {
if (age < 20) return "0-20";
if (age < 40) return "20-40";
if (age < 60) return "40-60";
return "60+";
};

// Parse through data to fill bins
const bin = {};
processedData.forEach(d => {
const fareBin = binFare(d.Fare);
const ageBin = binAge(d.Age);
const key = `${fareBin}_${ageBin}`;
if (!bin[key]) bin[key] = { count: 0 };
bin[key].count += 1;
});
return Object.keys(bin).map(key => {
const [fareBin, ageBin] = key.split("_");
return {
FareBin: fareBin,
AgeBin: ageBin,
Count: bin[key].count
};
});

}
Insert cell
/* Heatmap to show passenger counts by age and fare range
Used a LLM to set this one up because I could not get the heatmap to work on my own
*/
Plot.plot({
marks: [
Plot.cell(ageFareCount, {
x: "FareBin",
y: "AgeBin",
fill: "Count",
title: d => `Count: ${d.Count}`
}),
Plot.text(ageFareCount, {
x: "FareBin",
y: "AgeBin",
text: d => d.Count,
fill: "white",
textAnchor: "middle"
})
],
x: { label: "Fare Range ($)",
domain: ["0-10", "10-20", "20-50", "50-100", "100+"],
tickPadding: 10,
labelOffset: 50 },
y: {
label: "Age Range (Years)",
domain: ["0-20", "20-40", "40-60", "60+"],
tickPadding: 10,
labelOffset: 80
},
color: {
scheme: "Viridis",
legend: true,
label: "Passenger Count"
},
marginLeft: 80,
marginBottom: 60,
title: "Passenger Counts by Fare and Age Ranges",
width: 500,
height: 400,
style: { fontSize: 12 }
})
Insert cell
Insert cell
Insert cell
// Parallel coordinates plot (had an LLM help set up the structure, I decided the marks and channels)
{
const width = 800;
const height = 400;
const margin = { top: 30, right: 10, bottom: 10, left: 20 };

// Set up columns with desired attributes
const columns = ["Age", "Fare", "SibSp", "Survived"];
const data = processedData.map(d => ({
Age: d.Age,
Fare: d.Fare,
SibSp: d.SibSp,
Survived: d.Survived,
Sex: d.Sex
}));

// Create SVG
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);

// Scale each axis
const x = d3.scalePoint()
.domain(columns)
.range([margin.left, width - margin.right]);

const y = {};
columns.forEach(col => {
y[col] = d3.scaleLinear()
.domain(d3.extent(data, d => d[col]))
.range([height - margin.bottom, margin.top]);
});

// Generate lines
const line = d3.line()
.defined(([, value]) => !isNaN(value))
.x(([col]) => x(col))
.y(([col, value]) => y[col](value));

// Draw lines
svg.append("g")
.selectAll("path")
.data(data)
.join("path")
.attr("d", d => line(columns.map(col => [col, d[col]])))
.attr("fill", "none")
.attr("stroke", d => d.Sex === "male" ? "blue" : "orange")
.attr("stroke-opacity", 0.5 );

// Draw axes
svg.append("g")
.selectAll("g")
.data(columns)
.join("g")
.attr("transform", d => `translate(${x(d)},0)`)
.each(function(d) { d3.select(this).call(d3.axisLeft(y[d])); })
.append("text")
.attr("y", margin.top - 10)
.attr("text-anchor", "middle")
.text(d => d)
.style("fill", "black");

return svg.node();
}
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more