Simulation — Rodent Plague in Manhattan

Yingjun Mou
Data Mining the City
7 min readOct 17, 2018

--

A simulation of a rodent plague influence and the emergency response in Manhattan

Authors: Yingjun Mou, Shuyang Huang, Yue Dong

We aim to do a project simulating the spread of potential rodent plague in Manhattan. Within 311 complaint data, we are able to recognize the locations where the source of infection might be. The population dynamics simulation in Processing will be the tool for us to illustrate the spreading trend of the epidemics and help us to know how to apply direct and immediate response toward the emergent accident. The questions we are concerned about are:

  1. Who will ultimately get infected according to the existing condition and the assumption?
  2. What is the minimum number of infected people that will result in the whole neighborhood getting infected?
  3. If the medical resource is limited, which means there is a limitation on the vaccines, who should we choose to get the vaccine to minimize the ultimate infection?

Regarding the rodent plague as a case study, this prototype is to serve as a template for other epidemics.

Data Input:

A. Demographic Data of Manhattan:

B. Physical Street data (the modeling of the city)

C. Rodent outbreak history record

Data Output:

A. Calculate the “Critical area”. i.e. if there is limited medical resources (imagine a new type of panacea which can cure infected people instantly), in which neighborhood should government use the panacea, in order to stop the spreading of disease, and minimize the number of infected people?

B. Visualize the general distribution of infected people throughout Manhattan, given a specific climate condition.

C. Show whether the given population fluidity and cure rate can curb the spreading of disease or aggravate it

Data Structure:

We will have two interfaces: City and People. And while the City has three subclasses, the class People will have three different states.

A. City:

1. Neighborhood — — medium population number, low dynamic level
2. Street — — high population number, high dynamic level
3. Park — — low population number, low dynamic level

B. People:

There are 4 different states of a person (represented by the color of pixel)

1. Healthy (Green pixel)
2. Being infected (Yellow pixel, and will become red the next day)
3. Infected (Red pixel)
4. Dead (Pixel removed)

Rule of Infection:

(1) If there are >= 2 out of 4 nearby people infected, then the persons will be infected the next day (each people is represented as a square pixel)

(2) After 50 days (one day each frame), an infected person will die

(3) There is a preset probability of being cured, and a preset probability of moving around(and to spread the disease).

Below are 5 different scenarios that were simulated based on different combinations of given disease cure rates and population fluidity.

Scenario A: (Cure Rate=2.5%, Population Fluidity=10%)
Scenario B: (Cure Rate=2.5%, Population Fluidity=50%)
Scenario C: (Cure Rate=10%, Population Fluidity=10%)
Scenario D: (Cure Rate=10%, Population Fluidity=50%)
Scenarios E: (Cure Rate=25%, Population Fluidity=50%)

Conclusion:

(1) Based on the history record of rodent plague outbreak, and the population distribution in Manhattan, there are several areas which has a higher probability of suffering from rodent plague, such as Southern Bronx, Upper West Side, Midtown West, East Village, and Lower East Side.

(2) In terms of quenching the spread of plague, the Higher Cure Rate works better than the Lower Population Fluidity. Population Fluidity is more about how fast the plague will spread.

(3) Assume that the Population Fluidity is 10%(means there are 10% of the total population that constantly moving around), the city need to achieve a cure rate of at least 10% to stop the plague from spreading. Assume that the Population Fluidity is 50%, then a cure rate of 25% is enough for quenching the plague in the city.

Below are the codes:

from random import randint
import csv
import operator
import collections
class Person(object):
#=========CONSTRUCTORS==========
#x if the row index, y is the column index, state is an int indicate the health condition
#state=0 healthy(green), state=1 being infected(yellow), state=2 infected state=2+50=52 dead
#Currently the time between getting infected to dead is set to 50===========================

def __init__(self, state):
self.state = state

#=========OBSERVERS==========
#return the health state of a person
def checkState(self):
return self.state
#=========MODIFIERS==========
#return the health state of a person
def changeState(self, my_state):
self.state = my_state

class Population(object):
#=========CONSTRUCTORS==========
def __init__(self, maxX, maxY):
self.maxX = maxX
self.maxY = maxY
row = [None]*maxX
self.data = []
for i in range(maxX):
row = []
for j in range(maxY):
row.append([None])
self.data.append(row)

#=========OBSERVERS==========
#count the number of adjacent infected people

def countAdj(self, x, y):
count =0
#case 0: no person at x,y
if (self.data[x][y]==None):
return -1
#case 1: x==0, y==0
elif (x==0 and y==0):
if (self.data[x+1][y] != [None] and self.data[x+1][y].checkState() >= 2):
count += 1
if (self.data[x][y+1] != [None] and self.data[x][y+1].checkState() >= 2):
count += 1
return count
#case 2: x==maxX, y==maxY
elif (x==self.maxX-1 and y==self.maxY-1):
if (self.data[x-1][y] != [None] and self.data[x-1][y].checkState() >= 2):
count += 1
if (self.data[x][y-1] != [None] and self.data[x][y-1].checkState() >= 2):
count += 1
return count
#case 3: x==0, y==maxY
elif (x==0 and y==self.maxY-1):
if (self.data[x+1][y] != [None] and self.data[x+1][y].checkState() >= 2):
count += 1
if (self.data[x][y-1] != [None] and self.data[x][y-1].checkState() >= 2):
count += 1
return count
#case 4: x==maxX, y==0
elif (x==self.maxX-1 and y==0):
if (self.data[x-1][y] != [None] and self.data[x-1][y].checkState() >= 2):
count += 1
if (self.data[x][y+1] != [None] and self.data[x][y+1].checkState() >= 2):
count += 1
return count
#case 5: x==0
elif (x==0):
if (self.data[x+1][y] != [None] and self.data[x+1][y].checkState() >= 2):
count += 1
if (self.data[x][y-1] != [None] and self.data[x][y-1].checkState() >= 2):
count += 1
if (self.data[x][y+1] != [None] and self.data[x][y+1].checkState() >= 2):
count += 1
return count
#case 6: x==maxX
elif (x==self.maxX-1):
if (self.data[x-1][y] != [None] and self.data[x-1][y].checkState() >= 2):
count += 1
if (self.data[x][y-1] != [None] and self.data[x][y-1].checkState() >= 2):
count += 1
if (self.data[x][y+1] != [None] and self.data[x][y+1].checkState() >= 2):
count += 1
return count
#case 7: y==maxY
elif (y==self.maxY-1):
if (self.data[x-1][y] != [None] and self.data[x-1][y].checkState() >= 2):
count += 1
if (self.data[x][y-1] != [None] and self.data[x][y-1].checkState() >= 2):
count += 1
if (self.data[x+1][y] != [None] and self.data[x+1][y].checkState() >= 2):
count += 1
return count
#case 8: y==0
elif (y==0):
if (self.data[x-1][y] != [None] and self.data[x-1][y].checkState() >= 2):
count += 1
if (self.data[x][y+1] != [None] and self.data[x][y+1].checkState() >= 2):
count += 1
if (self.data[x+1][y] != [None] and self.data[x+1][y].checkState() >= 2):
count += 1
return count
#case 9: others
else:
if (self.data[x-1][y] != [None] and self.data[x-1][y].checkState() >= 2):
count += 1
if (self.data[x+1][y] != [None] and self.data[x+1][y].checkState() >= 2):
count += 1
if (self.data[x][y-1] != [None] and self.data[x][y-1].checkState() >= 2):
count += 1
if (self.data[x][y+1] != [None] and self.data[x][y+1].checkState() >= 2):
count += 1
return count


#=========MODIFIERS==========
def infect(self,x,y):
#Pre-condition: data[x][y] != None
if(self.data[x][y] != [None]):
#case 1: from healthy to being infected
if (self.data[x][y].checkState()==0 and self.countAdj(x,y)>=2):
self.data[x][y].changeState(1)
#case 2: from being infected to infected
elif (self.data[x][y].checkState()==1):
self.data[x][y].changeState(2)
#case 3: after getting infected, disease becoming aggrevated if it's not cured
elif (self.data[x][y].checkState()>=2 and self.data[x][y].checkState()<52):
self.data[x][y].changeState(self.data[x][y].checkState()+1)
else:
return

def cured(self,x,y):
if(self.data[x][y] != [None]):
prob = randint(0,4)
if(prob==0):
if (self.data[x][y].checkState()>0): # and self.data[x][y].checkState()<52
self.data[x][y].changeState(0)
def move(self, x, y):
#randomly swap population pixels,
if(density_map[x/5][y/5] != 0.0 and self.data[x][y] == [None]):
direction = [0,1,2,3]
if(y-1>=0):
if(self.data[x][y-1] == [None] or density_map[x/5][(y-1)/5] <0.3):
direction.remove(0)
if(y+1<=self.maxY):
if(self.data[x][y+1] == [None] or density_map[x/5][(y+1)/5] <0.3):
direction.remove(1)
if(x-1>=0):
if(self.data[x-1][y] == [None] or density_map[(x-1)/5][y/5] <0.3):
direction.remove(2)
if(x+1<self.maxX):
if(self.data[x+1][y] == [None] or density_map[(x+1)/5][y/5] <0.3):
direction.remove(3)
if(x+1>=self.maxX):
direction.remove(3)
prob = randint(0,2)
if(len(direction) >0 and prob<1):
l = len(direction)
random_index = randint(0,l-1)
random_direction = direction[random_index]
if(random_direction==0):
self.data[x][y] = self.data[x][y-1]
self.data[x][y-1] = [None]
elif(random_direction==1):
self.data[x][y] = self.data[x][y+1]
self.data[x][y+1] = [None]
elif(random_direction==2):
self.data[x][y] = self.data[x-1][y]
self.data[x-1][y] = [None]
else:
if(x+1<self.maxX):
self.data[x][y] = self.data[x+1][y]
self.data[x+1][y] = [None]



#INITIALIZATION OF THE POPULATION SYSTEM
global cell_width, cell_gap, my_population, denstiy_map
canvas_width = 1600
canvas_height = 600
cell_width = 4
cell_gap = 1
density_map = []
#Read CSV file and load the density data
with open('density and outbreak.csv') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
line_count = 0
current_i = -1
for row in csv_reader:
if line_count != 0:
if int(row[0]) != current_i:
density_map.append([])
current_i = int(row[0])
density_map[current_i].append(float(row[2]))
line_count += 1

unit_size = cell_width + cell_gap
num1 = canvas_width / unit_size
num2 = canvas_height / unit_size
my_population = Population(num1, num2)
for i in range(num1):
for j in range(num2):
#Probability of the outbreak of plauge
if(density_map[i/5][j/5] != 0):
threshold = int(float(density_map[i/5][j/5])*10)
prob = randint(0, 10)
if(prob <= threshold):
outbreak = randint(0,10)
if(outbreak==0):
my_population.data[i][j] = Person(2)
else:
my_population.data[i][j] = Person(0)
def setup():
frameRate(96)
size (canvas_width,canvas_height)
smooth()
background(0)
def draw():
global cell_width, cell_gap, my_population,img
stroke (127)
strokeWeight(1)
strokeCap(SQUARE)
background(0)

#display of the population
for i in range(num1):
for j in range(num2):
#CHECK NULL
if (my_population.data[i][j] != None and my_population.data[i][j] != [None]):
if (my_population.data[i][j].checkState() == 0):
fill(0,255,0)
rect(i*unit_size, j*unit_size, cell_width, cell_width)
elif(my_population.data[i][j].checkState() == 1):
fill(255,255,0)
rect(i*unit_size, j*unit_size, cell_width, cell_width)
#ADD DIFFERENT GRADIENT OF RED
elif(my_population.data[i][j].checkState() >= 2 and my_population.data[i][j].checkState() < 52):
delta = my_population.data[i][j].checkState()-2
fill(255-delta*5,0,0)
rect(i*unit_size, j*unit_size, cell_width, cell_width)
elif(my_population.data[i][j].checkState() ==52):
fill(127)
rect(i*unit_size, j*unit_size, cell_width, cell_width)
else:
print("Exception")

for i in range(num1):
for j in range(num2):
my_population.infect(i,j)
my_population.cured(i,j)

for i in range(num1):
for j in range(num2):
my_population.move(i,j)

title()
saveFrame("outputE/plagueE_####.png")

def title():
fill(255);
textSize(16);
text("Cure Rate=25% \nPopulation Fluidity=50%", 30, 30);

--

--

Yingjun Mou
Data Mining the City

Architecture student minor in computer science. Currently at Columbia University’s Graduate School of Architecture, Planning & Preservation.