Public
Edited
Oct 12, 2023
Insert cell
Insert cell
Insert cell
rawSpeeches = FileAttachment("speeches.json").json()
Insert cell
Insert cell
bloodRegexes = [/\bblood\w*\b/gi, /\bbleed\w*\b/gi, /\bbled\b/gi, ]
Insert cell
Insert cell
bloodSpeeches = rawSpeeches
.map(({date, title, president, transcript}) => ({
date: new Date(date),
title,
president,
transcript
}))
.filter(({transcript}) => bloodRegexes.some(re => transcript.match(re)))
Insert cell
Select a data source…
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
countSpeeches = (ob, {president}) => {
if (president in ob) {
ob[president] += 1;
} else {
ob[president] = 1;
}
return ob
}
Insert cell
countKeys = (ob) => Object.keys(ob).length
Insert cell
Insert cell
numAllSpeakers = countKeys(rawCounts)
Insert cell
bloodCounts = bloodSpeeches.reduce(countSpeeches, {})
Insert cell
numBloodSpeakers = countKeys(bloodCounts)
Insert cell
nonBloodSpeakers = Object.keys(rawCounts).filter(president => !(president in bloodCounts))
Insert cell
bloodSpeechPercentages = Object.entries(bloodCounts).reduce((o, [president, bloodCount]) => {
o.push({ president, percentage: bloodCount / rawCounts[president]});
return o
}, [])
Insert cell
Insert cell
Insert cell
totalbloodcountgraph = Plot.plot({
marginLeft: 140,
marks: [
Plot.barX(
bloodSpeeches,
Plot.groupY(
{ x: "count" },
{ y: "president", fill: "president", sort: {y: "x"}, tip: true}
)
),
Plot.ruleX([0]),
]
})
Insert cell
Insert cell
Plot.plot({
marginLeft: 140,
// color: { legend: true },
marks: [
Plot.barX(bloodSpeechPercentages, {
y: "president",
x: "percentage",
fill: "president",
tip: {
format: {
president: true,
x: (percentage) => `${Math.round(percentage * 100)}%`
}
},
sort: { y: "x"}
}),
Plot.ruleY([0])
]
})
Insert cell
Insert cell
Insert cell
bloodyWords = bloodSpeeches.reduce((o, {president, transcript, date}, bloodSpeechesIndex) => {
const spaceLimit = 3
const punctuationRE = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?@\[\]^_`{|}~]/
for (const re of bloodRegexes) {
let words = []
let locations = []
let surroundingPhrases = [];
let mapper = (word) => ({
president, date, word: word.trim(), surroundingPhrases, locations, bloodSpeechesIndex
});
[...transcript.matchAll(re)]
.map(match => {
let matchLen = match[0].length
words.push(match[0])
locations.push(match.index)
let phraseStartIndex = match.index
let phraseEndIndex = match.index + matchLen
let startSpaces = 0
let endSpaces = 0
let currentChar = transcript[phraseStartIndex]
while (
phraseStartIndex > 0
&& startSpaces < spaceLimit
&& !punctuationRE.test(currentChar)
) {
phraseStartIndex -= 1
currentChar = transcript[phraseStartIndex]
if (/\s/.test(currentChar)) {
startSpaces += 1
}
}
currentChar = transcript[phraseEndIndex]
while (
phraseEndIndex < transcript.length
&& endSpaces < spaceLimit
&& !punctuationRE.test(currentChar)
) {
phraseEndIndex += 1
currentChar = transcript[phraseEndIndex]
// if (punctuationRE.test(currentChar)) { break }
if (/\s/.test(currentChar)) {
endSpaces += 1
}
}
surroundingPhrases.push(transcript.substring(phraseStartIndex, phraseEndIndex).trim())
})
o = o.concat(...words.map(mapper))

}
// let words = [...transcript.matchAll(/[^.]*blood|bleed*[^.]*\./g)].map(x => x[0]);
return o
}, [])
Insert cell
Insert cell
bloodyWords
X
date
Y
word
Color
word
Size
Facet X
Facet Y
Mark
dot
Type Chart, then Shift-Enter. Ctrl-space for more options.

Insert cell
wordsOverTimeChart = Plot.plot({
color: { legend: true },
x: { label: "Date of mention →"},
y: { label: "Word mentioned →"},
marginLeft: 89,
marks: [
Plot.dot(bloodyWords, { x: "date", y: "word", stroke: "word", tip: true })
]
})
Insert cell
Insert cell
bloodyWordFrequency = Plot.plot({
marginLeft: 140,
color: { legend: true },
x: { label: "Number of mentions by word →" },
marks: [
Plot.barX(
bloodyWords,
Plot.groupY({ x: "count" }, { y: "president", fill: "word", tip: true, sort: { y: "x"} })
),
Plot.ruleY([0])
]
})
Insert cell
Insert cell
## Oratorical Flexibility

Insert cell
wordDiversity = bloodyWords.reduce(
(arr, {president, word}) => {
let presidentIndex = arr.findIndex((entry) => entry.president == president)
let entry = arr[presidentIndex] ?? { president, counts: { word: 1 } }
if (word in entry.counts) {
entry.counts[word] += 1
} else {
entry.counts[word] = 1
}
if (presidentIndex > -1) {
arr[presidentIndex] = entry
} else {
arr.push(entry)
}
return arr
},
[]
)
Insert cell
Plot.plot({
// color: { legend: true },
marginLeft: 140,
marks: [
Plot.barX(wordDiversity, {
y: "president",
x: (d) => Object.keys(d.counts).length,
sort: { y: "x"},
fill: "president",
tip: true
})
]
})
Insert cell
Insert cell
Select a data source…
Type Chart, then Shift-Enter. Ctrl-space for more options.

Insert cell
phraseFilterer = (targetRE) => bloodyWords.filter(({president, surroundingPhrases}) => {
return surroundingPhrases.find((phrase) => {
return targetRE.test(phrase)
})
})
Insert cell
effusionOfBlood = phraseFilterer(/effusion of blood/gi)
Insert cell
violence = phraseFilterer(/violence/gi)
Insert cell
giving = phraseFilterer(/give|gave/gi)
Insert cell
nationality = phraseFilterer(/America|Iran|Europe|Africa|Asia/gi)
Insert cell
environment = phraseFilterer(/river|thunder|fire/gi)
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more