Housing Affordability in New York City

Hui Liu
Data Mining the City
3 min readOct 20, 2017

Housing Affordability is a very important issue for America. According to HUD, the definition of affordability is that “for a household to pay more that 30% of its income on housing”. In this project, I want to measure the affordability level of each neighborhood, and show the statistical and spatial disparity between income and rent in NYC. Since data is limited, I adopt the simplest way to measure the housing affordability: % income on monthly rent”= Median rent / Median Income. The higher percentage of income households pay for housing rent, the more unaffordable a neighborhood would be.

The upper bars display the median household income of this neighborhood, and the bars down show the median rent of this area. The red point indicates the percentage of income households pay on rent. The higher, the more unaffordable. When the mouse is being moved, we can see the parameters of each neighborhood. According to this graph, Upper East Side is the most affordable neighborhood in NYC, while Queensbridge and Williamburg are unaffodable in terms of housing.

The map above shows the spatial distribution of housing affordability in NYC. The large the ellipse is, the higher percentage of income households pay on monthly rent. Neighborhoods in South Bronx, Lower East Manhattan, North Brooklyn seem not to be so affodable according to this map.

Data Source

Median Rent Index(Zillow data): https://www.zillow.com/research/data/

Median Household Income(Renthop data): https://ny.curbed.com/2017/8/4/16099252/new-york-neighborhood-affordability

Articles: HPD Housing Policy Research & Program Evaluation

Code 1

import csv
def setup():
size(1100,1100)
with open('Affordability.csv') as f:
reader = csv.reader(f)
header = reader.next()

global medianrents
medianrents = []
global medianincomes
medianincomes = []
global Neighborhoods
Neighborhoods = []
global affordability
affordability = []
global incomerequireds
incomerequireds = []

for row in reader:
medianrent = float(row[2])
medianrents.append(medianrent)
medianincome = float(row[3])
medianincomes.append(medianincome)
Neighborhood = row[0]
Neighborhoods.append(Neighborhood)
afford = float(row[4])
affordability.append(afford)
incomerequired = float(row[5])
incomerequireds.append(incomerequired)

def draw ():
background(255)
HVscalar = 0.002
RVscalar=0.02
a = 80
b = 80
c = 80
d = 80
textSize(18)
fill(112, 114, 119)
text("The median rent is: $ per month", 50,120)
text("The median household income is: $ per month", 50,150)
text("The percentage income on rent is: %",50,180)
text("The neighborhood is",50,210)
textSize(15)
text("Source: Renthop dataset", 880,920)
textSize(30)
fill(0,0,0)
text("Housing Affordability by Neighborhood",50,80)

for medianrent in medianrents:
if mouseX < a+5 and mouseX > a:
fill(214, 36, 57)
textSize(18)
text(int(medianrent), 250, 120)
else:
noStroke()
fill(167, 209, 239)
rect(a, 750, 5, medianrent*RVscalar)
a = a+7

for medianincome in medianincomes:
if mouseX < b+5 and mouseX > b:
fill(50, 132, 94)
textSize(18)
text(int(medianincome)/12, 370, 150)
else:
noStroke()
fill(129, 193, 239)
rect(b, 750, 5, -medianincome*HVscalar)
b = b+7

for afford in affordability:
if mouseX < c+5 and mouseX > c:
fill(214, 36, 57)
textSize(18)
text(afford*100, 350, 180)
ellipse(c+2.5, 750-afford*300, 9,9)
else:
noFill()
strokeWeight(1)
stroke(214, 36, 57)
ellipse(c+2.5, 750-afford*300, 5,5)
c = c+7

for Neighborhood in Neighborhoods:
if mouseX < d+5 and mouseX > d:
fill(0)
textSize(18)
text(Neighborhood, 250, 210)
d = d+7

fill(112, 114, 119)
textSize(17)
stroke(112, 114, 119,62)
strokeWeight(1)
line(40, 750,1070, 750)
rotate(PI/2*3)
text("INCOME", -745, 60)
text("RENT", -800, 60)

Code 2

import spatialpixel.mapping.slippymapper as slippymapper
import csv
def setup():
size(1800, 1400, P2D)
global nyc
nyc = slippymapper.SlippyMapper(40.700107, -73.977984, 12, 'carto-light', width, height)
global incomepercentages
incomepercentages=[]
global neighborhoods
neighborhoods=[]
with open('withaddress.csv') as f:
reader=csv.reader(f)
header=reader.next()
for row in reader:
a={}
latitude=float(row[6])
a['lat']=latitude
longitude = float(row[7])
a['lng']=longitude
incomepercentage=float(row[4])
a['Aod']=incomepercentage
neighborhood=row[0]
a['Nei']=neighborhood
incomepercentages.append(a)

for a in incomepercentages:
nyc.addMarker(a['lat'], a['lng'], AffordabilityMarker(a))
nyc.render()
def draw():
background(255)
nyc.draw()
fill(38, 38, 38)
textSize(22)
text("Median Rent / Median Income is: %", 50,200)
text("The neighborhood is",50,250)
textSize(15)
text("Source: Renthop and Zillow dataset", 1500,1300)
textSize(40)
text("Housing Affordability in NYC", 50,100)

class AffordabilityMarker(slippymapper.DataMarker):
def drawMarker(self, x, y, marker):
diam = self.data['Aod']*50
marker.stroke(26, 117, 255)
marker.noFill()
marker.textSize(20)
if dist(x, y, mouseX, mouseY) < diam / 2:
marker.fill(26, 117, 255)
marker.text(self.data['Nei'], 420, 250)
marker.text(self.data['Aod']*100, 420, 200)
marker.ellipse(x, y, diam, diam)

--

--