Public
Edited
Sep 25, 2023
Insert cell
Insert cell
Object.entries(column_averages).filter(([col, avg]) => !isNaN(avg)).map(([column, avg]) => column + ": " + avg).join("\n")
Insert cell
Insert cell
clean = it => ("" + it).trim().toLowerCase();
Insert cell
sum = nums => nums.reduce((acc, n) => acc + n, 0)
Insert cell
mean = nums => {
const result = sum(nums) / nums.length;
if (isNaN(result)) console.log("NaN sum of", { nums });
return result;
}
Insert cell
clean_values = values => values.filter(value => !["undefined", "null", "don't know", "unsure", ""].includes(("" + value).trim().toLowerCase()))
Insert cell
mean([1, 1, 4])
Insert cell
clean(" Good")
Insert cell
xlsx = require("xlsx")
Insert cell
ab2023 = FileAttachment("2023RawData.xls").arrayBuffer()
Insert cell
xls2023 = xlsx.readFileSync(ab2023)
Insert cell
arr2023 = xlsx.utils.sheet_to_json(xls2023.Sheets.Sheet1, {})
Insert cell
arr2023_cleaned = arr2023.slice(1) // drop second row which is just column descriptions
Insert cell
// convert clean values to numbers
arr2023_with_numbers = arr2023_cleaned.map(row => Object.fromEntries(Object.entries(row).map(([column, value]) => {
if (["Very Bad", "Very Unsafe", "Very Dissatisfied"].includes(value)) value = 1;
else if (["Bad", "Unsafe", "Somewhat Dissatisfied"].includes(value)) value = 2;
else if (["Neutral"].includes(value)) value = 3;
else if (["Good", "Safe", "Satisfied", "Somewhat Satisfied"].includes(value)) value = 4;
else if (["Very Good", "Very Safe", "Very Satisfied"].includes(value)) value = 5;
else if (["Don't Know", "undefined", ""].includes(value)) value = null;
return [column, value]
})));
Insert cell
column_names = Object.keys(arr2023_cleaned[0])
Insert cell
// transpose rows into columns
column_values = Object.fromEntries(column_names.map(column => [column, arr2023_with_numbers.map(row => row[column])]))
Insert cell
cleaned_column_values = Object.fromEntries(Object.entries(column_values).map(([column, values]) => [column, clean_values(values)]))
Insert cell
cleaned_column_counts = Object.fromEntries(Object.entries(cleaned_column_values).map(([column, values]) => [column, count(values)]))
Insert cell
column_averages = Object.fromEntries(Object.entries(cleaned_column_values).map(([column, values]) => [column, mean(values)]))
Insert cell
columns = Object.keys(arr2023_cleaned[0])
Insert cell
scales = ({
very_bad_to_very_good: ["Very Bad", "Bad", "Neutral", "Good", "Very Good"],
very_unsafe_to_very_safe: ["Very Unsafe", "Unsafe", "Neutral", "Safe", "Very Safe"],
very_dissasi: ["Very Dissatisfied", "Somewhat Dissatisfied", "Neutral", "Satisfied", "Very Satisfied"]
})
Insert cell
lowered_scales = Object.fromEntries(Object.entries(scales).map(([name, values]) => [name, values.map(clean)]))
Insert cell
apply_scale = (scale, value) => scale.map(clean).indexOf(clean(value)) + 1
Insert cell
apply_scale(scales.good_bad, "bad")
Insert cell
findMatchingScales = (values) => {
try {
const uniques = Array.from(new Set(values)).sort();
const filtered = uniques.filter(value => !should_skip(value));
const lowered = filtered.map(value => value.toLowerCase());
// try if match each scale
const matches = [];
for (let name in lowered_scales) {
if (filtered.every(v => lowered_scales[name].includes(v.toLowerCase()))) {
matches.push(name);
}
}
return matches;
} catch (error) {
return { values };
}
}
Insert cell
findMatchingScales(["Safe"])
Insert cell
findOneOrNoneMatchingScale = values => {
const matches = findMatchingScales(values);
if (matches.length >= 2) throw new Error("too many matching scales");
if (matches.length === 1) return matches[0];
}
Insert cell
prepareScale = (values) => {
const matches = findMatchingScales(values);
if (matches.length === 0) throw new Error("unable to find matching scale");
if (matches.length > 1) throw new Error("found more than one matching scale");
const scale_name = matches[0];
const ordered_values = lowered_scales[scale_name];
return value => {
const i = ordered_values.indexOf(value);
if (i === -1) throw new Error("unable to find corresponding numerical value for " + value);
return i + 1;
};
}
Insert cell
should_skip = (value) => ["undefined", "null", "don't know", "unsure"].includes(("" + value).toLowerCase())
Insert cell
Insert cell
col_scales = Object.fromEntries(columns.map(c => ([c, findOneOrNoneMatchingScale(arr2023_cleaned.map(r => r[c]))])))
Insert cell
csv = arr2023_cleaned.map(row => {
return Object.fromEntries(Object.entries(row).map(([c, v]) => {
if (col_scales[c]) v = apply_scale(lowered_scales[col_scales[c]], v) + " - " + v
return [c, v];
}));
});
Insert cell
hist2023 = Object.fromEntries(columns.map(col => [col, count(arr2023_cleaned.map(row => row[col]))]))
Insert cell
hist2023_vectorized = Object.fromEntries(Object.entries(hist2023).map(([col, counts]) => {
const vectorized = {};
for (let value in counts) {
if (!should_skip(value)) {
vectorized
}
}
return vectorized;
}))
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