Published
Edited
Apr 6, 2021
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dataset = d3.csvParse(await FileAttachment("dataset-malleable-lies.csv").text(), rowConverter);
Insert cell
// Format each CSV row into a more suitable JSON object
rowConverter = (row) => {
let formattedRow = {
// SE.EP format
episode: row["Episode"],
// Contestant's name
name: row["Name"],
// How many shows were already aired publicly before the contestant's game
// transmissions: parseInt(row["Transmissions"]),
// Age of the contestant, estimated if not explicitly stated
// Estimates are the mean of 2-3 coders' guesses for how old the contestant is
ageEstimate: parseFloat(row["Age est."]),
// M for male,
// F for female
gender: row["Gender"],
// true if the contestant is white,
// false otherwise
// isWhite: (row["Race white"] == 1),
// true if the contestant lives in an urban area with >250000 population,
// false otherwise
// livesInCity: (row["City"] == 1),
// true if the contestant lives in the Greater London Urban Area,
// false otherwise
// livesInUrban: (row["London Urban Area"] == 1),
// H for high - based on self-reported job titles that indicate a college education requirement
// L for low
education: row["Education est."],
// true if the contestant is an undergrad or postgrad student,
// false otherwise
isStudent: (row["Student"] == 1),
// true if the other final contestant attempted to vote them off in an earlier rounds,
// false if the other final contestant voted with them throughout both preceding rounds
opponentAttemptedVotingOff: (row["Vote received opp."] == 1),
// Optimal jackpot outcome from drawing 5 of the 10 final golden balls
// maxJackpot: parseFloat(row["Maximum amount"]),
// Final jackpot outcome after drawing 5 of the 10 final golden balls
jackpot: parseFloat(row["Prize pool"]),
// "Split" if the player chose to split, "Steal" if they chose to steal
splitOrSteal: row["Decision"],
// Number of times the contestant made explicit unconditional (EU) statements that
// they will split (e.g. "I will split", "I will not steal", etc.)
EUStatements: parseInt(row["EU"]),
// Number of times the contestant made implicit unconditional (IU) statements that
// they will split (not explicit, but a reference to character, past intentions, etc.)
IUStatements: parseInt(row["IU"]),
// Number of times the contestant made an IU that they will not steal due to
// their character (e.g. "My conscience will not allow me to steal")
IUCharacterStatements: parseInt(row["IU character"]),
// Number of times the contestant made an IU statement about past intentions
// of not wanting to steal (e.g. "I came here to split")
IUPastIntentionStatements: parseInt(row["IU past intention"]),
// Number of times the contestant made an IU statement that they have
// a personal preference for not stealing (e.g. "I want to split with you")
IUPreferenceStatements: parseInt(row["IU preference"]),
// Number of times the contestant made an IU statement by expressing that
// half of the jackpot is good enough (e.g. "30,000 pounds is enough")
IUMoneyStatements: parseInt(row["IU money"]),
// Number of times the contestant makes an IU statement about not wanting to steal
// due to judgement of the viewers (e.g. "Everyone who knows me would be disgusted")
IUViewers: parseInt(row["IU viewers"]),
// Number of times the contestant made an IU statement regarding a plea of trust (e.g. "You can trust me")
IUPleaForTrust: parseInt(row["IU plea for trust"]),
// Number of times the contestant made an explicit conditional statement on splitting
// (e.g. "I will split if you split")
ECStatements: parseInt(row["EC"]),
// Number of times the contestant made an implicit conditional statement on splitting
// (e.g. "I would like us to split")
ICStatements: parseInt(row["IC"]),
// true if the contestant revealed before the show that their choice depends on their opponent,
// false if their choice is unconditional
unconditionalSplitOrSteal: (row["Final depends O"] == 1),
};
return formattedRow;
}
Insert cell
// Generate a templated pie chart with the given subset of data and config params
function makePieChart(subset, config) {
// Unpack config variables and calculate the width/height constants
const margins = config.margins;
const innerRadius = config.dimensions.innerRadius;
const outerRadius = config.dimensions.outerRadius;
const width = (outerRadius * 2) + margins.left + margins.right;
const height = (outerRadius * 2) + margins.top + margins.bottom;

// Create the SVG element for the chart
const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);

// Create a D3 pie layout that converts the dataset into one appropriate for pie charts
const pie = d3.pie()
.padAngle(margins.between)
.value(d => d.value)
.sort(null); // Avoid sorting the pie chart by largest size

// Create a D3 arc generator used for drawing arcs based on the start/stop angles
const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
// Get the total count of events for the full pie chart (for calculating percentages)
let totalCount = subset.reduce((acc, slice) => acc + slice.value, 0);
// Get a reference to the color scale from the config variable
const cScale = config.colorScale;

// Show the data subset as a pie chart table in the devtools console
// console.table(pie(subset));

// Create a <g> element for each element of data
const arcs = svg
.selectAll('g.arc')
.data(pie(subset))
.join('g')
.classed('arc', true)
.attr('transform', `translate(${outerRadius + margins.left}, ${outerRadius + margins.top})`);

// Append an SVG path to each g element for the pie wedge
arcs.append('path')
.attr('fill', (d, i) => cScale(i))
.attr('d', arc)
.append('title') // Alt-text for the pie slice
.text(d => d.data.name);

// Append labels to each pie wedge
arcs.append('text')
.attr('transform', d => `translate(${arc.centroid(d)})`)
.attr('text-anchor', 'middle')
.text(d => config.showPercentages ?
parseFloat(d.value / totalCount * 100).toFixed(1)+"%" // Show as ##.#%
: d.value // Show count of total event instances
)
.style('fill', config.labelColor);
// Break the chart into multiple lines for word wrapping
let titleArray = config.chartTitle.split('\n');
// Create a text container at the center of the chart
const title = svg.append('text')
// Move the start of the container 8px up for each 16px line
.attr('transform', `translate(${outerRadius + margins.left}, ${outerRadius + margins.top - (8 * titleArray.length)})`)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.style('font-size', '16pt');

// Write each line into the title container
for(let i = 0; i < titleArray.length; i++) {
title.append('tspan')
.text(titleArray[i])
.attr('x', 0)
// Don't give extra vertical space to the first line
.attr('dy', i == 0 ? '0em' : '1.2em');
}
// LEGEND - built using Susie Lu's d3.svg.legend package
const pieData = pie(subset);
// Create a legend using the name of the category and its associated indexed color
const legendScale = d3.scaleOrdinal()
.domain(pieData.map(d => d.data.name))
.range(config.colorScale.range());

// see https://github.com/d3/d3-shape#symbols for information about d3 symbol shapes
var legendOrdinal = d3Legend.legendColor()
.shape('path', d3.symbol().type(d3.symbolSquare).size(60)())
.shapePadding(20)
.orient('vertical')
.scale(legendScale);
// Append the legend to the chart
svg.append('g')
.attr('transform', `translate(${(outerRadius * 2) + margins.left - config.dimensions.legendPaddingX},${config.dimensions.legendPaddingY})`)
.call(legendOrdinal);
// Return the chart's SVG element so the caller can display the chart
return svg.node();
}
Insert cell
// Generate a templated bar chart with the given subset of data and config params
function makeBarChart(subset, config) {
// Unpack the config JSON object for easier access in code
let width = config.dimensions.width;
let height = config.dimensions.height;
let margins = config.margins;

const svg = d3.create('svg')
.attr('width', width)
.attr('height', height);
// colorScale is predefined in config.colorScale so
// different bar charts can use different types of scales
const cScale = config.colorScale;

// create a scale for y-axis: use linear for the sleep data variable
const yScale = d3.scaleLinear()
.domain([0, 1])
.range([height - margins.bottom, margins.top]);

// create a scale for x-axis: use time for the date data variable set the end
// to be the day after the last date, so that the far right end of the scale
// is going to allow the last day within the data set to show within the range
const [dateMin, dateMax] = d3.extent(subset, d => d.value);
const xScale = d3.scaleBand()
.domain(d3.range(subset.length))
.range([margins.left, width - margins.right]);
// Get a reference to the constant width of the bars
const barWidth = (width - margins.left - margins.right) / subset.length - margins.between;

// Generate a Y-axis based on the domain and range of yScale
const yAxis = d3.axisLeft(yScale)
.ticks(10)
.tickFormat(d3.format(".0%"));
// create our x-axis and customize look with .ticks() and
// .tickFormat()
const xAxis = d3.axisBottom(xScale)
.ticks(subset.length)
.tickFormat((d) => subset[d].name);
// Append an element to the svg for the x-axis
const xAxisGroup = svg.append('g')
.attr('transform', `translate(0, ${height - margins.bottom})`)
.call(xAxis);

// Append an element to the svg for the y-axis
const yAxisGroup = svg.append('g')
.attr('transform', `translate(${margins.left}, 0)`)
.call(yAxis)
// For all axis ticks besides the bottom,
// make it a dashed tick that spans the width of the graph
.call(g => g.selectAll('.tick:not(:first-of-type) line')
.attr('stroke-opacity', 0.5)
.attr('stroke-dasharray', '2,2')
.attr('x1', -6)
.attr('x2', width - margins.right - margins.left));
// Append a title to the top-center of the chart
svg.append("text")
.text(config.axisLabels.title)
.attr("x", margins.left + ((width - margins.left - margins.right) / 2))
.attr("y", 25)
.attr("text-anchor", "middle")
.style('font-family', 'sans-serif')
.style("font-size", "20px")
.style('fill', 'black');
// Append an x-axis label centered underneath the bottom axis
xAxisGroup.append('text')
.attr('class', 'axis-label')
.text(config.axisLabels.x)
.attr('x', margins.left + (width - margins.left - margins.right) / 2)
.attr('y', 35) // Relative to the x axis position
.attr('fill', 'black')
.style('text-anchor', 'middle')
.style('font-size', '14px');
// Append a y-axis label centered to the left of the left axis
yAxisGroup.append('text')
.attr('class', 'axis-label')
.text(config.axisLabels.y)
.attr('transform', 'rotate(-90)')
.attr('x', -(margins.top + (height - margins.top - margins.bottom) / 2))
.attr('y', -38) // Relative to the y axis
.attr('fill', 'black')
.style('text-anchor', 'middle')
.style('font-size', '14px');
// Draw the bars onto the chart
svg.selectAll('.bars')
.data(subset, d => d.value)
.join('rect')
.classed('bars', true)
.attr('x', (d, i) => xScale(i) + margins.between / 2)
.attr('height', d => height - margins.bottom - yScale(d.value))
.attr('width', barWidth)
.attr('y', d => yScale(d.value))
.style('fill', (d, i) => cScale(d.value))
// Return the chart's SVG element so the caller can display the chart
return svg.node();
}
Insert cell
// Generate a subset of data for the split/steal distribution
function generateSubset_SplitOrSteals(dataset) {
// Create data structure framework
let subset = {
perContestant: [
{name: "Splits", value: 0},
{name: "Steals", value: 0},
],
perEpisode: [
{name: "Both split", value: 0},
{name: "One splits, one steals", value: 0},
{name: "Both steal", value: 0},
],
};
// Loop through each episode (2 rows each, one for each contestant)
for(let i = 0; i < dataset.length; i+=2) {
if(dataset[i].splitOrSteal == "Split" && dataset[i+1].splitOrSteal == "Split") {
// If both contestants from the 2 rows for an episode split
subset.perEpisode[0].value++;
subset.perContestant[0].value += 2;
} else if(dataset[i].splitOrSteal == "Steal" && dataset[i+1].splitOrSteal == "Steal") {
// If both contestants from the 2 rows steal
subset.perEpisode[2].value++;
subset.perContestant[1].value += 2;
} else {
// One of the contestants split, the other stole
subset.perEpisode[1].value++;
subset.perContestant[0].value++;
subset.perContestant[1].value++;
}
}
return subset;
}
Insert cell
// Generate a subset of data for how often contestants split compared to the jackpot size
function generateSubset_SplitPercentageVsJackpot(dataset) {
// Create data structure framework
let subset = {
counts: [
{name: "0 - 250", outcome: {splits: 0, steals: 0}},
{name: "251 - 500", outcome: {splits: 0, steals: 0}},
{name: "501 - 1k", outcome: {splits: 0, steals: 0}},
{name: "1k - 2k", outcome: {splits: 0, steals: 0}},
{name: "2k - 4k", outcome: {splits: 0, steals: 0}},
{name: "4k - 8k", outcome: {splits: 0, steals: 0}},
{name: "8k - 16k", outcome: {splits: 0, steals: 0}},
{name: "16k - 32k", outcome: {splits: 0, steals: 0}},
{name: "32k - 64k", outcome: {splits: 0, steals: 0}},
{name: "64k - 128k", outcome: {splits: 0, steals: 0}},
],
percentages: [
{name: "0 - 250", value: 0},
{name: "251 - 500", value: 0},
{name: "501 - 1k", value: 0},
{name: "1k - 2k", value: 0},
{name: "2k - 4k", value: 0},
{name: "4k - 8k", value: 0},
{name: "8k - 16k", value: 0},
{name: "16k - 32k", value: 0},
{name: "32k - 64k", value: 0},
{name: "64k - 128k", value: 0},
],
};
// Categorize each contestant's decision into a jackpot size grouping
for(let i = 0; i < dataset.length; i++) {
// Contestant's jackpot size
let jackpot = dataset[i].jackpot;
// Bracket of prize winnings:
// 0: 0.00 - 250.00
// 1: 250.01 - 500.00
// 2: 500.01 - 1000.00
// 3: 1000.01 - 2000.00
// 4: 2000.01 - 4000.00
// 5: 4000.01 - 8000.00
// 6: 8000.01 - 16000.00
// 7: 16000.01 - 32000.00
// 8: 32000.01 - 64000.00
// 9: 64000.01 - 128000.00
let bracketSize = 250;
for(let j = 0; j < subset.counts.length; j++) {
if(jackpot <= bracketSize) {
// Determine which choice the contestant made
// and use it as a field recerence to counts[i].outcome
let choice = dataset[i].splitOrSteal == 'Split' ? 'splits' : 'steals';
// j indexes to the proper bracket size
subset.counts[j].outcome[choice]++;
// Move on to the next contestant
break;
} else {
// Double bracketSize to increment to the next jackpot bracket
bracketSize *= 2;
}
}
}
// Process the counts into percentages
for(let i = 0; i < subset.counts.length; i++) {
let totalOutcomes = (subset.counts[i].outcome.splits + subset.counts[i].outcome.steals);
subset.percentages[i].value = subset.counts[i].outcome.splits / totalOutcomes;
}
// return the data structure
return subset;
}
Insert cell
// Create a subset of data of how often males/females split/steal
function generateSubset_MaleVsFemale(dataset) {
// Create data structure framework
let subset = [
{name: "Male - Split", value: 0},
{name: "Male - Steal", value: 0},
{name: "Female - Split", value: 0},
{name: "Female - Steal", value: 0},
];
let males = 0;
let females = 0;
// Loop through each contestant
for(let i = 0; i < dataset.length; i++) {
if(dataset[i].gender == 'M') {
// Male contestant
males++;
if(dataset[i].splitOrSteal == "Split") {
// The male contestant picked split
subset[0].value++;
} else {
// The male contestant picked steal
subset[1].value++;
}
} else {
// Female contestant
females++;
if(dataset[i].splitOrSteal == "Split") {
// The female contestant picked split
subset[2].value++;
} else {
// The female contestant picked steal
subset[3].value++;
}
}
}
// Divide the counts by the total number of male/female participants
// to convert from counts to percentages
subset[0].value /= males;
subset[1].value /= males;
subset[2].value /= females;
subset[3].value /= females;
// Return the data structure
return subset;
}
Insert cell
// Create a subset of data on how contestants chose compared to their education level
function generateSubset_Education(dataset) {
// Create data structure framework
let subset = {
counts: [
{name: "Low Education", outcome: {splits: 0, steals: 0}},
{name: "High Education", outcome: {splits: 0, steals: 0}},
{name: "Student", outcome: {splits: 0, steals: 0}},
],
percentages: [
{name: "Low Education", value: 0},
{name: "High Education", value: 0},
{name: "Student", value: 0},
],
};
// Loop through each contestant
for(let i = 0; i < dataset.length; i++) {
// Determine which choice the contestant made
// and use it as a field recerence to counts[i].outcome
let choice = dataset[i].splitOrSteal == 'Split' ? 'splits' : 'steals';
// Determine level of education
if(dataset[i].education == 'L') {
// Low education
subset.counts[0].outcome[choice]++;
} else {
// High education
subset.counts[1].outcome[choice]++;
}
// Is the contestant an active student pursuing higher education?
if(dataset[i].isStudent) {
subset.counts[2].outcome[choice]++;
}
}
// Process the counts into percentages
for(let i = 0; i < subset.counts.length; i++) {
let totalOutcomes = (subset.counts[i].outcome.splits + subset.counts[i].outcome.steals);
subset.percentages[i].value = subset.counts[i].outcome.splits / totalOutcomes;
}
// Return the data structure
return subset;
}
Insert cell
// Create a subset of data on how contestants chose compared to their estimated age
function generateSubset_Age(dataset) {
// Create data structure framework
let subset = {
counts: [
{name: "<25 years", outcome: {splits: 0, steals: 0}},
{name: "25-29 years", outcome: {splits: 0, steals: 0}},
{name: "30-34 years", outcome: {splits: 0, steals: 0}},
{name: "35-39 years", outcome: {splits: 0, steals: 0}},
{name: "40-44 years", outcome: {splits: 0, steals: 0}},
{name: "45-49 years", outcome: {splits: 0, steals: 0}},
{name: "50-54 years", outcome: {splits: 0, steals: 0}},
{name: "55+ years", outcome: {splits: 0, steals: 0}},
],
percentages: [
{name: "<25 years", value: 0},
{name: "25-29 years", value: 0},
{name: "30-34 years", value: 0},
{name: "35-39 years", value: 0},
{name: "40-44 years", value: 0},
{name: "45-49 years", value: 0},
{name: "50-54 years", value: 0},
{name: "55+ years", value: 0},
],
};
// Loop through each contestant
let bracketAgeIncrement = 5; // Width of age brackets
for(let i = 0; i < dataset.length; i++) {
let ageCutoff = 25; // Upper limit of the age bracket, exclusive
let bracketFound = false; // Boolean check for the final age bracket, which is >5 years (55+)
// Determine which choice the contestant made
// and use it as a field recerence to counts[i].outcome
let choice = dataset[i].splitOrSteal == 'Split' ? 'splits' : 'steals';
// Check for each age bracket <25, <30, <35, etc. up to <55 (but NOT <60, since it will be 55+)
for(let j = 0; j < subset.counts.length - 1; j++) {
if(dataset[i].ageEstimate < ageCutoff) {
// j is indexed to the correct age bracket
subset.counts[j].outcome[choice]++;
// Escape the for-loop to prevent unnecessary extra checks
bracketFound = true;
break;
} else {
// Check the next age bracket
ageCutoff += bracketAgeIncrement;
}
}
// Check the final age bracket
if(!bracketFound) {
// The final age bracket is the correct age bracket
subset.counts[subset.counts.length - 1].outcome[choice]++;
}
}
// Process the counts into percentages
for(let i = 0; i < subset.counts.length; i++) {
let totalOutcomes = (subset.counts[i].outcome.splits + subset.counts[i].outcome.steals);
subset.percentages[i].value = subset.counts[i].outcome.splits / totalOutcomes;
}
// Return the data structure
return subset;
}
Insert cell
// Create a subset of data based on what statements the contestant made in the final conversation before choosing
function generateSubset_ConversationStatements(dataset) {
// Create data structure framework
let subset = {
counts: [
{name: "EU", outcome: {splits: 0, steals: 0}},
{name: "IU Character", outcome: {splits: 0, steals: 0}},
{name: "IU Past Intent", outcome: {splits: 0, steals: 0}},
{name: "IU Preference", outcome: {splits: 0, steals: 0}},
{name: "IU Money", outcome: {splits: 0, steals: 0}},
{name: "IU Viewers", outcome: {splits: 0, steals: 0}},
{name: "IU Plea for Trust", outcome: {splits: 0, steals: 0}},
{name: "EC", outcome: {splits: 0, steals: 0}},
{name: "IC", outcome: {splits: 0, steals: 0}},
{name: "None Said", outcome: {splits: 0, steals: 0}},
],
percentages: [
{name: "EU", value: 0},
{name: "IU Character", value: 0},
{name: "IU Past Intent", value: 0},
{name: "IU Preference", value: 0},
{name: "IU Money", value: 0},
{name: "IU Viewers", value: 0},
{name: "IU Plea for Trust", value: 0},
{name: "EC", value: 0},
{name: "IC", value: 0},
{name: "None Said", value: 0},
],
};
// Loop through each contestant
for(let i = 0; i < dataset.length; i++) {
// Determine which choice the contestant made
// and use it as a field recerence to counts[i].outcome
let choice = dataset[i].splitOrSteal == 'Split' ? 'splits' : 'steals';
// If a contestant makes a type of statement at LEAST once, add them to the respective category
// Player explicitly states they would unconditionally split or never steal
if(dataset[i].EUStatements > 0)
subset.counts[0].outcome[choice]++;
// Player implies they would never steal since it goes against their own character
if(dataset[i].IUCharacterStatements > 0)
subset.counts[1].outcome[choice]++;
// Player implies they had a past intention to unconditionally split or avoid stealing
if(dataset[i].IUPastIntentionStatements > 0)
subset.counts[2].outcome[choice]++;
// Player implies they have a personal preference for unconditionally choosing split
if(dataset[i].IUPreferenceStatements > 0)
subset.counts[3].outcome[choice]++;
// Player implies they will unconditionally split since they are satisfied with half the jackpot
if(dataset[i].IUMoneyStatements > 0)
subset.counts[4].outcome[choice]++;
// Player implies they would unconditionally split due to judgement of the audience, viewers, friends, family, etc.
if(dataset[i].IUViewers > 0)
subset.counts[5].outcome[choice]++;
// Player implies they could be trusted to unconditionally split
if(dataset[i].IUPleaForTrust > 0)
subset.counts[6].outcome[choice]++;
// Player explicitly states they would split only on a given condition
if(dataset[i].ECStatements > 0)
subset.counts[7].outcome[choice]++;
// Player implicitly states they would split only on a given condition
if(dataset[i].ICStatements > 0)
subset.counts[8].outcome[choice]++;
// Player made no explicit or implicit statement that they would split/would not steal
if(dataset[i].EUStatements == 0 && dataset[i].IUStatements == 0)
subset.counts[9].outcome[choice]++;
}
// Process the counts into percentages
for(let i = 0; i < subset.counts.length; i++) {
let totalOutcomes = (subset.counts[i].outcome.splits + subset.counts[i].outcome.steals);
subset.percentages[i].value = subset.counts[i].outcome.splits / totalOutcomes;
}
// Return the data structure
return subset;
}
Insert cell
// Create a subset of data based on whether the player revealed their mind was already made
// on their decision before the show started, or if their opponent would affect their choice
function generateSubset_PredeterminedChoice(dataset) {
// Create data structure framework
let subset = [
{name: "Always split", value: 0},
{name: "Uncertain - split", value: 0},
{name: "Uncertain - steal", value: 0},
{name: "Always steal", value: 0},
];
// Loop through each contestant
for(let i = 0; i < dataset.length; i++) {
if(dataset[i].unconditionalSplitOrSteal) {
// The contestant already made their choice on whether they'd split or steal
// before meeting their opponent,
if(dataset[i].splitOrSteal == "Split") {
// The contestant was planning on splitting no matter what
subset[0].value++;
} else {
// The contestant was planning on stealing no matter what
subset[3].value++;
}
} else {
// The contestant waited to see whether they could trust their opponent to split
if(dataset[i].splitOrSteal == "Split") {
// The contestant decided it was worth splitting with their opponent
subset[1].value++;
} else {
// The contestant decided it was not worth splitting with their opponent
subset[2].value++;
}
}
}
// Return the data structure
return subset;
}
Insert cell
// Create a subset of data of how contestants voted if their opponent tried voting them out in an earlier round
function generateSubset_OppoentAttemptedVotingOff(dataset) {
// Create data structure framework
let subset = [
{name: "No vote against - Split", value: 0},
{name: "No vote against - Steal", value: 0},
{name: "Voted against - Split", value: 0},
{name: "Voted against - Steal", value: 0},
];
// Sum of splits and steals per category, stored so both subset values can use it for division
let votesNotAgainst = 0;
let votesAgainst = 0;
// Loop through each contestant
for(let i = 0; i < dataset.length; i++) {
if(dataset[i].opponentAttemptedVotingOff) {
// Other final contestant tried voting this contestant out in a previous round
votesAgainst++;
if(dataset[i].splitOrSteal == "Split") {
// The contestant voted against opted to steal
subset[2].value++;
} else {
// The contestant voted against opted to steal
subset[3].value++;
}
} else {
// The contestant's opponent never tried voting them out
votesNotAgainst++;
if(dataset[i].splitOrSteal == "Split") {
// The contestant opted to split
subset[0].value++;
} else {
// The contestant opted to steal
subset[1].value++;
}
}
}
// Divide the counts by the total number of male/female participants
// to convert from counts to percentages
subset[0].value /= votesNotAgainst;
subset[1].value /= votesNotAgainst;
subset[2].value /= votesAgainst;
subset[3].value /= votesAgainst;
// Return the data structure
return subset;
}
Insert cell
CustomCSS = html`
<style>
/* Style markdown code blocks (wrap code blocks in \`escaped backticks\`) */
code {
background: #eee;
padding: 0.2em 0.4em;
border-radius: 3px;
}
</style>
`
Insert cell
Insert cell
d3 = require("d3@5")
Insert cell
d3Legend = require('d3-svg-legend') // Used for generating pie chart legends
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