Published
Edited
Mar 16, 2021
6 forks
52 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
html`<iframe width="560" height="315" src="https://www.youtube.com/embed/3NyxtWgugJU" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`
Insert cell
Insert cell
// Require the d3 library
d3 = require("d3@v5")
Insert cell
Insert cell
// Load the Seattle building permit data using `d3.csv()`
data = d3.csv("https://data.seattle.gov/api/views/76t5-zqzr/rows.csv")
Insert cell
Insert cell
// Display a table showing a subset of the data
render_data_table(data.slice(0, 10))
Insert cell
Insert cell
Insert cell
// Inspect the data
data
Insert cell
Insert cell
Insert cell
// Get the first (zero-ith) element
data[0]
Insert cell
Insert cell
// Access the PermitClass key of the first element
data[0].PermitClass
Insert cell
Insert cell
Insert cell
Insert cell
// How many observations (building permits) are in the dataset?
data.length
Insert cell
Insert cell
// Get a list of features in our dataset from the first observation
Object.keys(data[0])
Insert cell
Insert cell
Insert cell
// Require the lodash package
_ = require('lodash')
Insert cell
Insert cell
// Get a list of the unique values of the `PermitClass` feature
permit_classes = _.uniqBy(data, d => d.PermitClass)
Insert cell
Insert cell
// Get a list of the unique values of the `PermitClass` feature (only returning the string values of interest)
permit_class_list = _.uniqBy(data, d => d.PermitClass).map(d => d.PermitClass)
Insert cell
Insert cell
// What is the total number of housing units added across the dataset?
data.reduce((sum, d) => (sum += +d.HousingUnitsAdded), 0)
Insert cell
Insert cell
// Get the sum of three values (an example of d3.sum _without_ an accessor function)
d3.sum([1, 2, 3])
Insert cell
// Get the sum of the total number of housing units added across the dataset
d3.sum(data, d => +d.HousingUnitsAdded) // seems like a lot!
Insert cell
Insert cell
// What is the average estimated project cost?
d3.mean(data, d => +d.EstProjectCost)
Insert cell
// What is the standard deviation across project cost?
d3.deviation(data, d => +d.EstProjectCost)
Insert cell
// What is the most expensive project?
d3.max(data, d => +d.EstProjectCost)
Insert cell
Insert cell
// First Permit Issue date
data[0].IssuedDate
Insert cell
Insert cell
// Create a time parsing function based on the structure of the date-strings in our data
parser = d3.timeParse("%Y-%m-%d")
Insert cell
Insert cell
// Parse the date of the first permit issued
parser(data[0].IssuedDate)
Insert cell
Insert cell
// Get the extent (min and max) of the dates in the dataset
date_range = d3.extent(data, d => parser(d.IssuedDate))
Insert cell
Insert cell
// Define a formatting function for *displaying* date objects
formatter = d3.timeFormat("%B %d, %Y")
Insert cell
Insert cell
// Write out a sentence describing the date range
`The permit data ranges from ${formatter(date_range[0])} to ${formatter(
date_range[1]
)}`
Insert cell
Insert cell
Insert cell
// How many projects have been completed? Only keep the objects where the StatusCurrent is "Completed"
num_completed_projects = data.filter(d => d.StatusCurrent === "Completed")
.length
Insert cell
// How many permits have been issued in 2020? Only keep the objects where year is 2020
// Note, we need to parse the date of each object, then get the *year* to perform the filter
num_permits_2020 = data.filter(d => {
// Note, some of the IssuedDate values are empty strings, so we need to check that first
// (here, we only keep the object -- return true -- if the date isn't "" and has a year of 2020)
return d.IssuedDate !== "" && parser(d.IssuedDate).getFullYear() === 2020;
}).length
Insert cell
// What is the description of the most expensive project?
// Writing this out in a few different lines for clarity
most_expensive_description = {
// Determine the *value* of most expensive project in the dataset
const highest_cost = d3.max(data, d => +d.EstProjectCost);

// Filter down the dataset to the observations where the estimated cost is equal to the max
// This will return an array with 1+ objects (more than one if multiple projects have the same cost)
const most_expensive = data.filter(d => +d.EstProjectCost === highest_cost);

// Extract the description from the first element in the array
const description = most_expensive[0].Description;
return description;
}
Insert cell
Insert cell
// Calculate permits per year using plain old JavaScript
permits_per_year = {
let per_year = []; // this is the variable that we'll return

// Iterate through the permits, adding new objects to the `per_year` array
data.forEach(permit => {
// Skip rows where the IssuedDate is not present
if (permit.IssuedDate === "") return;

// Store the year of this permit in a variable (for readability)
const year = parser(permit.IssuedDate).getFullYear();

// See if this year is present in the dataset
let this_year = per_year.find(d => d.year === year);

// If this year hasn't been added, add it -- otherwise, increment the number of permits by 1
if (this_year === undefined) {
per_year.push({ year: year, permits: 1 });
} else {
this_year.permits += 1;
}
});
return per_year;
}
Insert cell
Insert cell
// Calculate permits per year using lodash -- wow, so much easier!
permits_per_year_lodash = _.countBy(data, d => {
// Ok, it's a little tricky because we still have to deal with the missing date
if (d.IssuedDate === "") return "No date present";
return parser(d.IssuedDate).getFullYear();
})
Insert cell
Insert cell
// Use the Object.keys() method to convert our object into an array of objets
permits_per_year_lodash_array = Object.keys(permits_per_year_lodash).map(d => {
return { key: d, value: permits_per_year_lodash[d] };
})
Insert cell
Insert cell
// Create a function to nest data by a feature called "PermitClass"
nest_by_class = d3.nest().key(d => d.PermitClass)
Insert cell
Insert cell
// Using the method defined above, create a nested data structure out of our data (by PermitClass)
nested_by_class = nest_by_class.entries(data)
Insert cell
Insert cell
// Create a function that will count the number of elements in each PermitClass
count_by_class = d3
.nest()
.key(d => d.PermitClass)
.rollup(d => d.length) // return the number of observations in the class
Insert cell
Insert cell
// Count the number of elements in each class -- wow!
counted_by_class = count_by_class.entries(data)
Insert cell
Insert cell
// Create a nested data structure that has the number of permits, average cost, and highest cost
// for each PermitClass
summary_by_class = d3
.nest() // create a nesting function
.key(d => d.PermitClass) // Where we nest by the "PermitClass" value of each object
.rollup(d => {
// For each _group_, compute the following values
return {
num_permits: d.length, // get the number of permits in this class
highest_cost: d3.max(d, dd => +dd.EstProjectCost), // get the highest project cost for this group!
avg_cost: d3.mean(d, dd => +dd.EstProjectCost) // get the average project cost for this group!
};
})
.entries(data) // Pass our data in as we create the function!
Insert cell
Insert cell
// Load the vega-lite package
import { vl } from "@vega/vega-lite-api"
Insert cell
// Create a bar chart of the number of permits of each class
simpleBar = vl
.markBar() // Make a bar chart
.data(summary_by_class) // Using the summary data by Permit Class
.encode(
vl
.x()
.fieldQ("value.num_permits") // Encode the number of permits on the x axis
.axis({ title: "Number of Permits" }), // Set the axis title
vl
.y()
.fieldO("key") // Encode our Key on the y axis (Permit Class),
.sort(null) // Show the order that they appear in the data (for demonstration)
.axis({ title: "Permit Class" }) // Set the axis title
)
.render() // display the chart
Insert cell
Insert cell
Insert cell
// Clone our summary_by_class data so we can sort it (for demonstration only)
data_to_sort = _.clone(summary_by_class)
Insert cell
Insert cell
// Here is our sorting function to sort by the number of permits (recall data structure above)
sort_by_num_permits = (a, b) => +b.value.num_permits - a.value.num_permits
Insert cell
// Sort the data using the function described above (which could have been written in line)
data_to_sort.sort(sort_by_num_permits)
Insert cell
Insert cell
// Make a bar chart of the number of permits -- same code as above, but using the sorted_data
sorted_bar = vl
.markBar() // Make a bar chart
.data(data_to_sort) // Using the *sorted* data
.encode(
vl
.x()
.fieldQ("value.num_permits") // Encode the number of permits on the x axis
.axis({ title: "Number of Permits" }), // Set the axis title
vl
.y()
.fieldO("key") // Encode our Key on the y axis,
.sort(null) // Show the order that they appear in the data (for demonstration)
.axis({ title: "Permit Class" }) // Set the axis title
)
.render()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
carat = html`<svg width="8" height="8" class="observablehq--caret">
<path d="M7 4L1 8V0z" fill="currentColor"></path>
</svg>`
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