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

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