Public
Edited
May 5
Insert cell
import {select} from "@observablehq/inputs";
Insert cell
d3Array = import("https://cdn.jsdelivr.net/npm/d3-array/+esm")
Insert cell
plotModule = import("https://cdn.jsdelivr.net/npm/@observablehq/plot/+esm")

Insert cell
d3Exports = await d3Array;

Insert cell
plotExports = await plotModule;
Insert cell
rollups = d3Exports.rollups;

Insert cell
mean = d3Exports.mean;

Insert cell
plot = plotExports.plot;

Insert cell
barX = plotExports.barX;

Insert cell
barY = plotExports.barY;

Insert cell
line = plotExports.line;

Insert cell
dot = plotExports.dot;
Insert cell
text = plotExports.text;

Insert cell
jobImpact = FileAttachment("My_Data@1.csv").csv({typed: true});

Insert cell
newsSentiment = FileAttachment("robot-ai-all-public.csv").csv({typed: true});
Insert cell
studentSurvey = FileAttachment("Survey_AI.csv").csv({typed: true});
Insert cell
publicOpinion = FileAttachment("FAI DATASET 2.xlsx").xlsx({typed: true});

Insert cell
// domainStats: Array of [domain, avgImpact]
domainStats = {
const cleaned = jobImpact.map(d => ({
...d,
impact: +d["AI Impact"].replace("%","")
}));
return rollups(
cleaned,
vs => mean(vs, d => d.impact),
d => d.Domain
);
}

Insert cell
viewof domainFilter =
(await import("https://cdn.jsdelivr.net/npm/@observablehq/inputs/+esm"))
.select({
label: "Filter to domain",
options: ["All", ...domainStats.map(([d]) => d)],
value: "All"
});


Insert cell
{
// 1) Copy your stats so you can filter in place
let stats = domainStats.slice();
// 2) If the user picked a specific domain, filter down
if (domainFilter !== "All") {
stats = stats.filter(([d]) => d === domainFilter);
}

// 3) Draw the bar chart
return plot({
width: 800,
marginLeft: 200,
y: {
domain: stats.map(([d]) => d),
label: "Job Domain",
tickSize: 0,
padding: 0.2
},
x: {
label: "Avg AI Impact (%)",
grid: true
},
marks: [
// Bars with hover‐tooltips
barX(stats, {
x: ([,v]) => v,
y: ([d]) => d,
fill: "#1f77b4",
title: ([d,v]) => `${d}: ${v.toFixed(1)}%`
}),
// Numeric labels at the tip of each bar
text(stats, {
x: ([,v]) => v,
y: ([d]) => d,
text: ([,v]) => `${v.toFixed(1)}%`,
dx: 4,
dy: "0.35em",
align: "left"
})
]
});
}

Insert cell
{
// 1) Clean and coerce the “%” values
const cleaned = jobImpact.map(d => ({
...d,
impact: +d["AI Impact"].replace("%","")
}));

// 2) Roll up the average impact by domain
let stats = rollups(
cleaned,
vs => mean(vs, d => d.impact),
d => d.Domain
);

// 3) Apply the dropdown filter
if (domainFilter !== "All") {
stats = stats.filter(([domain]) => domain === domainFilter);
}

// 4) Draw the chart
return plot({
width: 800,
marginLeft: 200,
y: {
domain: stats.map(([d]) => d),
label: "Job Domain",
tickSize: 0,
padding: 0.2
},
x: {
label: "Avg AI Impact (%)",
grid: true
},
marks: [
// Bars with tooltips
barX(stats, {
x: ([,v]) => v,
y: ([d]) => d,
fill: "#1f77b4",
title: ([d,v]) => `${d}: ${v.toFixed(1)}%`
}),
// Numeric labels at the end of each bar
text(stats, {
x: ([,v]) => v,
y: ([d]) => d,
text: ([,v]) => `${v.toFixed(1)}%`,
dx: 4,
dy: "0.35em",
align: "left"
})
]
});
}

Insert cell
// clean “%” off jobImpact
cleaned = jobImpact.map(d => ({
...d,
"AI Impact": +d["AI Impact"].replace("%","")
}));

Insert cell
// draw
plot({
y: {label: "Domain", padding: 5},
x: {label: "Avg AI Impact (%)"},
marks: [
barX(domainStats, { x: ([,v])=>v, y: ([k])=>k, fill:"steelblue" })
]
});

Insert cell

utilityStats = rollups(
studentSurvey,
vs => mean(vs, d => +d["Q7.Utility_grade"]),
d => d["Q1.AI_knowledge"]
);
Insert cell
plot({
x: {label: "AI Knowledge (1–10)"},
y: {label: "Avg Utility"},
marks: [
line(utilityStats, { x: ([k]) => +k, y: ([,v]) => v, stroke: "green" }),
dot (utilityStats, { x: ([k]) => +k, y: ([,v]) => v, fill: "green" })
]
});
Insert cell


// --- Job Threat Index: Average AI Impact by Domain ---
jobImpact.forEach(d => d["AI Impact"] = +d["AI Impact"].replace("%", ""))
const domainStats = d3.rollups(jobImpact, v => d3.mean(v, d => d["AI Impact"]), d => d.Domain)

html`<h3>Average AI Impact by Job Domain</h3>`
Plot.plot({
y: {label: "Domain", padding: 5},
x: {label: "Avg AI Impact (%)"},
marks: [
Plot.barX(domainStats, {
x: ([, v]) => v,
y: ([k]) => k,
fill: "steelblue"
})
]
})

// --- Student Survey: Utility by Familiarity ---
const utilityByKnowledge = d3.rollups(
studentSurvey,
v => d3.mean(v, d => +d["Q7.Utility_grade"]),
d => d["Q1.AI_knowledge"]
)

html`<h3>Perceived AI Utility by Knowledge Level</h3>`
Plot.plot({
x: {label: "AI Knowledge (1-10)"},
y: {label: "Avg Utility"},
marks: [
Plot.line(utilityByKnowledge, {x: d => +d[0], y: d => d[1], stroke: "green"}),
Plot.dot(utilityByKnowledge, {x: d => +d[0], y: d => d[1], fill: "green"})
]
})

// --- Public Opinion: Trust by Familiarity (Stacked Bar) ---
const trustCounts = d3.rollups(
publicOpinion,
v => v.length,
d => d["What_is_your_level_of_familiarity_with_AI?"],
d => d["How_much_do_you_trust_AI_to_make_decisions_in_your_daily_life?"]
)
const trustFormatted = trustCounts.flatMap(([fam, sub]) => sub.map(([trust, count]) => ({familiarity: fam, trust, count})))

html`<h3>Trust in AI by Familiarity</h3>`
Plot.plot({
x: {label: "Familiarity"},
y: {label: "Count"},
color: {legend: true},
marks: [
Plot.barY(trustFormatted, {
x: "familiarity",
y: "count",
fill: "trust"
})
]
})

// --- Public Sentiment: Opinion by Knowledge Level (Stacked Bar) ---
const feelings = d3.rollups(
studentSurvey,
v => v.length,
d => d["Q1.AI_knowledge"],
d => d["Q5.Feelings"]
)
const feelingsFormatted = feelings.flatMap(([know, sub]) => sub.map(([sent, count]) => ({knowledge: know, sentiment: sent, count})))

html`<h3>Overall AI Sentiment by Knowledge Level</h3>`
Plot.plot({
x: {label: "AI Knowledge"},
y: {label: "Count"},
color: {legend: true},
marks: [
Plot.barY(feelingsFormatted, {
x: "knowledge",
y: "count",
fill: "sentiment"
})
]
})

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