Published
Edited
Sep 2, 2022
Importers
21 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
pop = {
// Here I'm assuming that 66% of the 720 student population opts for blended learning.
const pop = new Random_School_Population(["K", "1", "2", "3", "4", "5"], 720 * .66)
return pop
}
Insert cell
Insert cell
Insert cell
Insert cell
import { vl } from "@vega/vega-lite-api"
Insert cell
Insert cell
simulations = {
// Simulate 100 outbreaks
const runs = []
const saved_state = [...pop.classrooms]
//return saved_state

for (let method of ['random', 'network']) {
pop.build_classrooms(method)
for (let i of d3.range(100)) {
if (Math.random() < .1) {
// Rebuild the classrooms occasionally
pop.build_classrooms(method)
}
let total
for (let n_infected of simulate_outbreak(infectivity, false)) {
total = n_infected
}
runs.push({
method,
infected: total
})
}
}
pop.remove_all_classrooms()
pop.set_classrooms(saved_state)
return runs
}
Insert cell
classes = pop.build_classrooms(assign_students_to_classes)
Insert cell
import { drag } from '@d3/force-directed-graph@149';
Insert cell
color = d3.scaleOrdinal(d3.schemeCategory10);
Insert cell
make_data = function(person_filter = (x) => true) {
// Create d3-suitable network data.
const links = [];
pop.make_links()
const students = pop.students.filter(person_filter)
const set_ids = new Set(students.map(student => student.id))
const nodes = students.map(student => {
student.links.forEach(linkage => {
const studentB = linkage.student
const link_type = linkage.link_type
if (studentB.id < student.id) {
if (set_ids.has(studentB.id)) {
links.push(
{"source": student.id, "target": studentB.id, link_type}
)
}
}
})
return student
})
return {nodes, links}
}
Insert cell
Insert cell
class Random_School_Population extends School_Population {
constructor(grades = ["K", "1", "2", "3", "4", "5"], students = 720) {
super()
this.grades = grades
for (let i of d3.range(students)) {
d3.shuffle(grades)
this.students.push(new Student(grades[0]))
}
this.give_students_random_links()
this.n_classrooms = {
"K": 4,
"1": 4,
"2": 3,
"3": 4,
"4": 3,
"5": 3
}
}
get classrooms() {

if (this._classrooms) {return this._classrooms}
this._classrooms = []
for (let grade of this.grades) {
for (let i of d3.range(this.n_classrooms[grade])) {
for (let day of ["W", "Th", "Fr"]) {
this._classrooms.push({
day: day,
teacher_number: i,
grade: grade,
students: [],
max_size: 9
})
}
}
}
return this._classrooms
}
assign_learning_pods(fraction = SHARE_OF_STUDENTS_IN_PODS, sizes = [2, 3, 4, 5, 6, 7]) {
for (let grade of this.grades) {
const kids = this.get_grade(grade).filter(kids => kids.format != "remote")
const pods = [[]]
d3.shuffle(kids)
d3.shuffle(sizes)
let next_pod_size = sizes[0]
for (let kid of kids) {
if (Math.random() > fraction) {
continue
}
if (pods[0].length == next_pod_size) {
pods.unshift([])
next_pod_size = d3.shuffle(sizes)[0]
}
pods[0].unshift(kid)
}
for (let pod of pods) {
this.groups.push(new Pod(pod))
}
}
}
assign_siblings(sibshare = SHARE_OF_STUDENTS_WITH_SIBLINGS) {
d3.shuffle(this.students)
const families = this.students.reduceRight((full_list, next_student) => {
// 1 in four times, a student is assumed to be related to the one
// next to them; the rest of the time, they're a new family.
if (Math.random() < sibshare) {
full_list[0].unshift(next_student)
} else {
full_list.unshift([next_student])
}
return full_list
}, [[]])
families.filter(family => family.length > 1).map(family => {
// For prettiness, give them all a last name.
family.map(student => student.last = family[0].last)
this.groups.push(new Family(family))
})
}
give_students_random_links() {
// Learning pods
this.assign_learning_pods()
this.assign_siblings()
}
}
Insert cell
import {checkbox, radio, slider} from "@jashkenas/inputs"
Insert cell
class School_Population {
constructor() {
this.students = []
this.groups = []
}
make_links() {
for (const student of this.students) {
student.links = []
}
for (const group of this.groups) {
group.make_links()
}
}
get_grade(grade) {
return this.students.filter(student => student.grade == grade)
}
remove_all_classrooms() {
this._classrooms = undefined
this.groups = this.groups.filter(d => d.link_type != "classroom")
this.students.forEach(student => {
student.links = student.links.filter(d => d.link_type != "classroom")
})

}

set_classrooms(list_of_rooms) {
this._classrooms = list_of_rooms
for (let classroom of list_of_rooms) {
this.groups.push(new Classroom(classroom.students))
}

}
build_classrooms(order = "random") {
let students
this.remove_all_classrooms()
if (order === "random") {
students = this.grades.map(grade => {
const students_in_grade = this.get_grade(grade);
d3.shuffle(students_in_grade);
return students_in_grade
})
}
if (order === "network") {
// Get the order from the current network.
students = this.networked_groups
}
// Builds empty classrooms.
const classrooms = this.classrooms;
for (let set_of_students of students) {
if (set_of_students === undefined) {continue}
// Sort the classrooms by how filled they are. This sorting will persist through the currently
// active group of students--for instance, a pod or something. Unless the classroom is filled.
classrooms.sort((a, b) => a.students.length - b.students.length)
for (let student of set_of_students) {
const grade_classes = classrooms.filter(room => room.grade === student.grade)
const unfilled_classes = grade_classes.filter(room => room.students.length < room.max_size)
let destination_class;
if (unfilled_classes.length > 0) {
destination_class = unfilled_classes[0]
} else {
grade_classes.sort((a, b) => a.length - b.length);
destination_class = grade_classes[0]
}
destination_class.students.push(student)
}
}
this.set_classrooms(classrooms)
return this._classrooms
}

get networked_groups() {
let current_group = -1
const completed = new Set()
// Approach in a random order.
d3.shuffle(this.students)
// Reset values.
this.students.forEach(student => student._assigned_group = undefined)
const queue = [...this.students]
const groups = []
while (queue.length > 0) {
// pop the first element off the queue.
const kid = queue.shift()
if (completed.has(kid.id)) {
// We've already done the kid.
continue
}
// All kids start unassigned; but students added
// b/c of links will get added earlier.
if (kid._assigned_group === undefined) {
kid._assigned_group = current_group++
groups[current_group] = []
}
for (const link of kid.links) {
const {student, link_type} = link
// Put the kid in this group;
student._assigned_group = current_group;
// move the kid to the top of the queue, so that all of
// their linkages will be followed before we move on.
queue.unshift(student)
}
completed.add(kid.id);
if (groups[current_group] === undefined) {
groups[current_group] = []
}
groups[current_group].push(kid);
}
return groups
}

}
Insert cell
class Pod extends Group {
constructor(students) {
super("pod", ...students)
}
}
Insert cell
class Family extends Group {
constructor(people) {
super("family", ...people)
}
}
Insert cell
class Classroom extends Group {
constructor(students, teacher) {
if (teacher === undefined) {
teacher = new Person()
}
super("classroom", teacher, ...students)
this.teacher = teacher;
}
}
Insert cell
d3.shuffle(pop.groups)
Insert cell
class Group {
constructor(type_of_link, ...students) {
this.students = [...students]
this.link_type = type_of_link
}
make_links() {
for (let studentA of this.students) {
for (let studentB of this.students) {
if (studentA.id == studentB.id) {
continue // Don't link students to themselves.
}
studentA.links.push({student: studentB, link_type: this.link_type})
}
}
}
}
Insert cell
new Student().name
Insert cell
random_name()
Insert cell
class Person {
constructor(name) {
this.id = uuid.v4()
this.links = []
if (name === undefined) {
let [f, l] = random_name()
this.first = f;
this.last = l;
} else {
this.first, this.last = name
}
}
get name() {
return `${this.first} ${this.last}`
}
}
Insert cell
class Teacher extends Person {
constructor(name, grade) {
}
}
Insert cell
class Student extends Person {
constructor(grade, name) {
super(name)
if (grade == undefined) {
grade = d3.shuffle(["K", "1", "2", "3", "4", "5"])[0]
}
this.grade = grade
// All students that this child is linked to.
}
}
Insert cell
G = {
return
const G = new jsnx.Graph();
const students = [];
let i = 0;
for (let student of pop.students) {
students.push([i++, student])
student._jsnxid = i
}
const edges = []
const link_set = new Set(link_restrictions)
for (let student of pop.students) {
for (let link of student.links) {
if (link_set.has(link.link_type)) {
edges.push([student._jsnxid, link.student._jsnxid])
}
}
}
G.addNodesFrom(students);
G.addEdgesFrom(edges);

jsnx.allPairsShortestPathLength(G)
return G
}
Insert cell
md`# General imports and functions`
Insert cell
function random_name() {
const i = parseInt(Math.random() * first_names.length)
const j = parseInt(Math.random() * last_names.length)
return [first_names[i], last_names[j]]
}
Insert cell
uuid = require('uuid@8.3.0/dist/umd/uuid.min.js')
Insert cell
first_names = {
const promises = []
for (let gender of ["male", "female"]) {
for (let language of ["fr", "es", "en"]) {
promises.push(d3.json(`https://raw.githubusercontent.com/AlessandroMinoccheri/human-names/master/data/${gender}-human-names-${language}.json`))
}
}
let first_names = Promise.all(promises).then(
l => l.flat()
)
return first_names
}
Insert cell
d3 = require("d3@5")
Insert cell
last_names = d3.json("https://raw.githubusercontent.com/dominictarr/random-name/master/names.json")
Insert cell
jsnx = require('https://bundle.run/jsnetworkx@0.3.4')
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