Final : The Butterfly Effect

Suying Zhang
Data Mining the City
4 min readOct 17, 2018

Group: Adam Robert Swietek ; Aniket Dikshit; Sidharth Chotangada Somana; Suying Zhang.

Intro:

We are simulating a market dynamics of the apartment leasing process. We aimed to recreate the supply and demand,cyclical pattern.

Tenant:

  • Bid Price
  • Patience
  • Bid Growth

Landlord

  • Base Rent
  • Growth Rate

Actions

  • Tenants select a property
  • Tenant compares bid to LL’s rent
  • If LL’s rent is below asking, occupy property for 1 year
  • If Tenant cant find a property or if the market rent goes far beyond what they can afford — leave population

Drivers of Demand

  • LL’s can increase rent at the end of a lease
  • if multiple tenants land at a property, LL selects the tenant with the larger bid value

Future Implemenation

  • Create a bank for each Landlord, allowing them to expand property
  • User can drop disasters, natural or man-made; allows user to visualize effect on market conditions
  • Effect of new developments on market conditions

r = 10
h = 100txtsize = 25market_rent = 100class Block(object):def __init__(self, row, col,initial_marketrent):# self.index = indexself.row = rowself.col = colself.rent = int(round((randomGaussian()*10)) + initial_marketrent)self.occupied = Falseself.inquiries = 0self.n_leased = 0self.visitors = 0def display(self):fill(0,255,0)rect(h*self.row, h*self.col, h*.80,h*.80,10)# textSize(txtsize)# text('$'+str( grid[x][y].rent), h*x+h/8, h*y+h/2)class Tenant(object):def __init__(self, x_coord,y_coord, initial_marketrent):self.location = PVector(x_coord,y_coord)self.bid = int(round((randomGaussian()*10)) + initial_marketrent)self.age = int(round((randomGaussian()*2.5)) + 5)self.leased = Falseself.target = self.locationself.velocity = PVector(0,0)self.topspeed = 3self.in_pop = Trueself.leased_tl = 0self.target_x = self.target.x/hself.target_y = self.target.y/hself.comparison = Truedef move(self):self.acceleration = PVector.sub(self.target, self.location)# Set magnitude of accelerationself.acceleration.setMag(1)# Velocity changes according to acceleration# self.velocity.add(self.acceleration)# Limit the velocity by topspeed# self.velocity.limit(self.topspeed)# Location changes by velocityself.location.add(self.acceleration)# def compare(self,other):# if (self.target) == (other.target):# if self.bid < other.bid:# self.compare= False# else:# self.compare=Truedef display(self):fill(255,0,0)ellipse(self.location.x, self.location.y, r,r)# needs movement definition# needs display# Tenant Search funciton# Tenatn randomly selects 4 properties per month# Lease Up Function# tenant and LL compare bid and rent# if match, lease up for 12 months# else tenants goes back to search pool# release function# after 12 months, LL offers existing tenant first offer increased rent# tenants approve /deny# Price Adjustment# how much the tenant increases thie rbid after every unsucceful search# LL increases after leases, increases based on popularity of property defined by hits.# hit function# every time tenant visits property, hit goes up# hit goes down if property remains unoccupied after 2 months#population spawning# new population every month/time peroid# leave population function# if tenants age expires, tenant leaves population# Way Too High function# uf market price is more than 10% of tenants bid, leave automatically# Popularity Draw - complexdef makeGrid():global r, hgrid = []for i in xrange(r):# Create an empty list for each rowgrid.append([])for j in xrange(r):# Pad each column in each row with a 0grid[i].append(0)return griddef getMarketRent(grid):avg_rent=0for x in range(r):for y in range(r):avg_rent +=grid[x][y].rentreturn [('The Average Market Rent is: '+str(avg_rent/r**2)), avg_rent/r**2]def getPeople(number, rent, people):for i in range(number):people.append(Tenant(500,500,rent))return peopledef getBids(people):total = 0for i in range(len(people)):total += people[i].bidreturn ('The Average Tenant Bid is: ' + str(total/len(people)))def getTenantAge(people):total = 0for i in range(len(people)):total += people[i].agereturn ('The Average Tenant Searchtime Remaining is: ' + str(total/len(people)))def getTarget(tenant, grid):i = int(random(len(grid)))j = int(random(len(grid)))return [PVector(h*grid[i][j].row+h/2,h*grid[i][j].col+h/2),i,j]def startLeaseNego(tenant,block):# if block.vistors > 0:if block.rent<=tenant.bid and block.occupied==False:block.n_leased += 1tenant.leased=Truetenant.leased_tl = 12*100block.occupied = Truereturn tenant, blockdef checkLeased(tenant, block):if tenant.leased_tl == 0:tenant.leased = Falseblock.occupied = Falseblock.rent = block.rent/10 + block.rentblock.display()def makeGridagain(grid):for x in range(r):for y in range(r):if grid[x][y].occupied == True:fill(55,0,0)rect(h*x, h*y, h*.80,h*.80,10)textSize(txtsize)fill(255)text('$'+str( grid[x][y].rent), h*x+h/8, h*y+h/2)elif grid[x][y].occupied == False:fill(0)rect(h*x, h*y, h*.80,h*.80,10)textSize(txtsize)fill(255)text('$'+str( grid[x][y].rent), h*x+h/8, h*y+h/2)def kill(tenant,grid):if tenant.age == 0:tenant.in_pop = Falseif getMarketRent(grid)[1]>tenant.bid:if abs(getMarketRent(grid)[1]-tenant.bid) > (tenant.bid *.10):tenant.in_pop = Falsereturn tenantdef setup():global x, y, h,textsize,market_rent,people,available_grid,people_searching,people_in_leasesize(h*r, h*r+350)global h, r, counter,gridgrid =makeGrid()background(0)# i = 0people = []initial_population = 20for x in range(r):for y in range(r):fill(255)grid[x][y] = Block(x,y,market_rent)grid[x][y].display()rect(h*x, h*y, h*.80,h*.80,10)textSize(txtsize)fill(0)text('$'+str( grid[x][y].rent), h*x+h/8, h*y+h/2)available_grid = grid# print('To Start ' + getMarketRent(grid))people = getPeople(initial_population, market_rent,people)people_searching = peoplepeople_in_lease = []print('To Start ' + getBids(people))print('To Start ' + getTenantAge(people))def draw():frameRate(1000)global h, r,grid,txtsize,market_rent,people,available_grid,people_searching,people_in_lease# for x in range(r):# for y in range(r):# fill(255,255,255)# rect(h*x, h*y, h*.80,h*.80,10)# grid[x][y] = Block(x,y,market_rent)for tenant in people_in_lease:tenant.leased_tl -=1checkLeased(tenant,grid[int(tenant.target_x)][int(tenant.target_y)])for tenant in people_searching:if (round(tenant.location.x) == tenant.target.x)&(round(tenant.location.y)==tenant.target.y):# tenant.compare(people)# if tenant.comparison == True:# [tenant.target, tenant.target_x,tenant.target_y] = getTarget(tenant,grid)# tenant.comparison == False# else:startLeaseNego(tenant,grid[int(tenant.target_x)][int(tenant.target_y)])[tenant.target, tenant.target_x,tenant.target_y] = getTarget(tenant,grid)else:tenant.move()tenant.display()# tenant.age -= 1kill(tenant,grid)people = [tenant for tenant in people if tenant.in_pop == True]people_in_lease = [tenant for tenant in people if tenant.leased_tl >= 1]people_searching = [tenant for tenant in people if tenant.leased == False]# for x in range(r):# for y in range(r):makeGridagain(grid)#Update Gridefor x in range(r):for y in range(r):available_grid = [block for block in grid if grid[x][y].occupied == False]fill(0)rect(0, h*r,h*r,h*r+250)fill(255)text(str( getMarketRent(grid)[0]), width/4, h*r+50)text(str( getBids(people)),width/4, h*r+100)text(str(getTenantAge(people)),width/4, h*r+150)text("Number of People Searching" + str(len(people_searching)) + ' ' + str(len(people_searching)*100/len(people))+ '%' ,width/4,h*r+200)text("Total Population"+ str(len(people)),width/4, h*r+250)# text("Occupied Lots " + str(100 - len(available_grid)),width/4, h*r+300)# print(len(available_grid))

--

--

Suying Zhang
Data Mining the City

Presenting works for Columbia GSAPP 18 fall Data Mining the City