Published
Edited
Dec 21, 2021
1 fork
Importers
1 star
Insert cell
Insert cell
Insert cell
Insert cell
data = FileAttachment("DVS_cleaned_survey_results_2019.csv").csv({typed: true})
Insert cell
Insert cell
sheet = processSheet(data)
Insert cell
Insert cell
Insert cell
processSheet = function(data, multiseparator = ",") {
let rawColumnNames = Object.keys(data[0])
let safeColumnNames = safeQuestions(rawColumnNames)
let questions = {};
safeColumnNames.map((q, i) => {
let question = rawColumnNames[i]
let type = questionType(question, data, multiseparator)
questions[q] = {
id: q,
question,
data, // redundant reference to make it easier to pass questions around
type,
domain: domain(question, data, type === "multi" ? multiseparator : false),
// we optionally allow processing on different data for same question (filtering etc)
getDomain: function(d = data, multiselect = type === "multi") {
return domain(question, d, multiselect ? multiseparator : false)
},
getCounts: function(d = data, multiselect = type === "multi") {
// we give the user (the vis component's user) the option to override
// whether this question is a multiselect, as it changes the logic of counting significantly
return getCounts(question, d, multiselect ? multiseparator : false)
},
multiSplit: function(answer) {
return multiSplit(answer, multiseparator)
},
}
})
return { data, questions, multiseparator }
}
Insert cell
// Convert the question text into a safe keyword
safe = function(question) {
let s = question.toLowerCase()
.replace(/[^a-z0-9]/gmi, "_")
.replace(/ /g, "_")
.replace(/\W/g, "")
if(!s.slice(0,1).match(/[A-Za-z]/)) s = "_" + s
return s
}
Insert cell
safeQuestions = function(columns) {
let ids = []
let c, s, i;
for(c of columns) {
s = safe(c)
// make sure duplicate column names get unique ids
while(ids.indexOf(s) >= 0) {
s += "_"
}
ids.push(s)
}
return ids
}
Insert cell
getCounts = function(question, qdata, multiselect) {
// multiselect can be a string to indicate the separator
let counts = d3.group(qdata.flatMap(d => multiselect ? multiSplit(d[question], multiselect) : d[question]), d => d)
return Array.from(counts).map(d => {
return {
id: d[0],
question,
answer: d[0],
count: d[1].length,
pct: d[1].length / qdata.length
}
}).sort((a,b) => b.count - a.count)
}
Insert cell
// Split answers into multiple entries
function multiSplit(value, separator = ",") {
return ((value || "")+"").split(separator).map(s => s.trim())
}
Insert cell
// Get the unique elements of the answer (making it easier to manually set the domain)
domain = function(question, qdata, multiselect) {
return getCounts(question, qdata, multiselect).map(d => d.answer)
}
Insert cell
// support filtering questions by Other
function filterRow(answer, row, question, domain) {
if(answer == "Other") {
let answers = getAnswers(question, row)
for(let ai of answers) {
if(domain.indexOf(ai) < 0) return true
}
return false
} else {
return getAnswers(question, row).indexOf(answer) >= 0
}
}
Insert cell
// get a list of answers for a given response in case its a multiselect
function getAnswers(q, d) {
if(q.type == "multi") {
return q.multiSplit(d[q.question])
}
return [d[q.question]]
}
Insert cell
Insert cell
questionType = function(question, qdata, multiseparator) {
let ur = uniqueRatio(question, qdata)
let cr = commaRatio(question, qdata, multiseparator)
if(ur > 0.5 && cr < 0.5) {
return "free"
}
if (ur < 0.25 && cr < 0.5) {
return "choice"
}
if(cr > 0.5) {
return "multi"
}
return "choice"
}
Insert cell
commaRatio = function(question, qdata, separator = ",") {
// don't parse multiple choice commas
let c = getCounts(question, qdata, false)
// cound how many of these have commas
let comma = c.filter(d => !!d.answer && d.answer.indexOf && d.answer.indexOf(separator) >= 0)
return comma.length / c.length
}
Insert cell
uniqueRatio = function(question, qdata) {
let c = getCounts(question, qdata, false)
let nocount = c.filter(d => !d.answer)[0]
let no = nocount ? nocount.count : 0
return c.filter(d => !!d.answer).length / (qdata.length - no)
}
Insert cell
Insert cell
regularq = sheet.questions.are_you_able_to_choose_your_own_tools_or_are_the_choices_made_for_you
Insert cell
multiq = sheet.questions.who_do_you_make_data_visualizations_for_select_all_that_apply
Insert cell
freeq = sheet.questions.what_is_your_biggest_frustration_with_doing_data_visualization_in_your_job
Insert cell
getCounts(freeq.question, sheet.data)
Insert cell
getCounts(regularq.question, sheet.data, ",")
Insert cell
getCounts(multiq.question, sheet.data, ",")
Insert cell
getCounts(multiq.question, sheet.data)
Insert cell
commaRatio(multiq.question, sheet.data)
Insert cell
commaRatio(regularq.question, sheet.data)
Insert cell
commaRatio(freeq.question, sheet.data)
Insert cell
uniqueRatio(regularq.question, sheet.data)
Insert cell
uniqueRatio(freeq.question, sheet.data)
Insert cell
uniqueRatio(multiq.question, sheet.data)
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