Public
Edited
Jun 10, 2023
Insert cell
Insert cell
5193 - summer day camp demographics.xlsx - 2022.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
demographics_2022 = demographics_2022_raw.map((d) => ({
...d,
ZIPCODE: d.ZIPCODE.toString(),
ETHNICITY: d.ETHNICITY === "NULL" ? null : d.ETHNICITY,
EDUCATION: d.EDUCATION === "NULL" ? null : d.EDUCATION,
"FAMILY STATUS": d["FAMILY STATUS"] === "NULL" ? null : d["FAMILY STATUS"],
"FAMILY INCOME": d["FAMILY INCOME"] === "NULL" ? null : d["FAMILY INCOME"],
...parseIncome(d["FAMILY INCOME"])
}))
Insert cell
demographics_2022
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
function parseIncome(family_income_string) {
var family_income_lower = null;
var family_income_upper = null;
if (family_income_string === "UNDER $25,000") {
family_income_lower = 0;
family_income_upper = 25000;
} else if (family_income_string === "$25,001 TO $50,000") {
family_income_lower = 25001;
family_income_upper = 50000;
} else if (family_income_string === "$50,001 TO $75,000") {
family_income_lower = 50001;
family_income_upper = 75000;
} else if (family_income_string === "$75,001 TO $100,000") {
family_income_lower = 75001;
family_income_upper = 100000;
} else if (family_income_string === "$100,001 TO $150,000") {
family_income_lower = 100001;
family_income_upper = 150000;
} else if (family_income_string === "OVER $150,000") {
family_income_lower = 150001;
family_income_upper = Infinity;
}
return { family_income_lower, family_income_upper };
}
Insert cell
binned = {
const rollup = d3
.flatRollup(
demographics_2022,
(v) => v.length,
(d) => d.family_income_lower,
(d) => d.family_income_upper,
(d) => d.ZIPCODE
)
.map(([lower, upper, zipcode, count]) => ({ zipcode, lower, upper, count }))
.filter((d) => d.lower !== null);
return rollup.map((d) => ({
...d,
density: d.count / zip_totals.get(d.zipcode),
dataset: "parks"
}));
}
Insert cell
zip_totals = d3.rollup(
demographics_2022,
(v) => v.length,
(d) => d.ZIPCODE
)
Insert cell
Plot.plot({
facet: { data: all_binned, y: "zipcode" },

marks: [
Plot.rectY(all_binned, {
x1: "lower",
x2: "upper",
y: (d) => d.density / (d.upper - d.lower),
fillOpacity: 0.4,
fill: "red",
filter: (d) => d.dataset === "parks"
}),
Plot.rectY(all_binned, {
x1: "lower",
x2: "upper",
y: (d) => d.density / (d.upper - d.lower),
fillOpacity: 0.4,
fill: "blue",
filter: (d) => d.dataset === "acs"
})
]
})
Insert cell
all_binned = [...binned, ...binned_census]
Insert cell
medians = Array.from(
d3.rollup(
all_binned,
(data) => {
const sorted_data = data.sort((a, b) => a.lower - b.lower);
const total = d3.sum(data, (d) => d.count);
var cumsum = 0;
var bounds;
for (const row of sorted_data) {
bounds = [row.lower, row.upper];
cumsum = cumsum + row.count;
if (cumsum > total / 2) {
break;
}
}
return bounds;
},

(d) => d.zipcode,
(d) => d.dataset
)
)
.map(([zipcode, map]) => ({ zipcode, ...Object.fromEntries(map) }))
.filter((d) => d.parks && d.acs)
Insert cell
medians.filter((d) => (d.parks[0] = d.acs[0]))
Insert cell
census_income_raw = Promise.all(
Array.from(zip_totals.keys()).map((zip) =>
acs5client.zcta(["group(B19131)"], zip, 2021)
)
)
Insert cell
census_income = census_income_raw
.flat()
.filter(
(d) =>
d.type === "estimate" &&
d.definition.label.includes("With own") &&
d.definition.label.includes("$")
)
.map((d) => ({ ...d, ...parseLabel(d.definition.label) }))
.map((d) => ({ ...d, ...comparableRange(d) }))
Insert cell
function comparableRange(d) {
var comparable_lower, comparable_upper;
if (d.income_upper < 25000) {
comparable_lower = 0;
comparable_upper = 25000;
} else if (d.income_upper < 50000) {
comparable_lower = 25001;
comparable_upper = 50000;
} else if (d.income_upper < 75000) {
comparable_lower = 50001;
comparable_upper = 75000;
} else if (d.income_upper < 100000) {
comparable_lower = 75001;
comparable_upper = 100000;
} else if (d.income_upper < 150000) {
comparable_lower = 100001;
comparable_upper = 150000;
} else {
comparable_lower = 150001;
comparable_upper = Infinity;
}
return { comparable_lower, comparable_upper };
}
Insert cell
function parseLabel(label) {
const elements = label.split("!!");

var family_type, income_range_str;
if (elements.length === 5) {
family_type = elements[2];
income_range_str = elements[4];
} else if (elements.length === 6) {
family_type = elements[3];
income_range_str = elements[5];
}

var income_lower, income_upper;
if (income_range_str === "Less than $10,000") {
income_lower = 0;
income_upper = 9999;
} else if (income_range_str === "$200,000 or more") {
income_lower = 200000;
income_upper = Infinity;
} else {
[income_lower, income_upper] = income_range_str
.split(" to ")
.map((d) => parseInt(d.replace(/[^0-9]/g, "")));
}
return {
family_type: family_type.replace(":", ""),
income_lower,
income_upper
};
}
Insert cell
binned_census = {
const rollup = d3
.flatRollup(
census_income,
(v) => d3.sum(v, (d) => d.value),
(d) => d.comparable_lower,
(d) => d.comparable_upper,
(d) => d["zip code tabulation area"]
)
.map(([lower, upper, zipcode, count]) => ({
zipcode,
lower,
upper,
count
}));
const zip_totals = d3.rollup(
census_income,
(v) => d3.sum(v, (d) => d.value),
(d) => d["zip code tabulation area"]
);
return rollup.map((d) => ({
...d,
density: d.count / zip_totals.get(d.zipcode),
dataset: "acs"
}));
}
Insert cell
acs5client = new ACS5Client()
Insert cell
import { censusChicagoGroupYears } from "@fgregg/census-api-helper-functions"
Insert cell
import { ACS1Client, ACS5Client } from "@fgregg/census-api-wrappers"
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