Forecasting Real Estate Prices using ML: Time Series Modeling
By: Andrea Cabello
Tags: Real Estate Investments, Time Series Forecasting, Data Science, Programming, Python

In this blog post, I present the results of my experience working on a time series forecasting project using Python.

This is the fourth project I have officially completed as a Data Science student at Flatiron School and not to disappoint it was challenging. But I did it y’all! And so can you!

Let’s dive right in!

Top 5 Best Connecticut Zip Codes to Invest In

As customary, I approached the problem from the scientific method.

1. Overview

Case Scenario

I have been hired by a real-estate investment firm to help them understand:

How real estate prices have changed overtime and,

How to use this information to invest in areas with the most potential value increase.

To get me started I have been provided with a dataset from Zillow Research. (add hyperlink) The data contains typical home values in the contiguous US by zip code.

Home values

Zillow Home Value Index (ZHVI): A smoothed, seasonally adjusted measure of the typical home value and market changes across a given region and housing type. It reflects the typical value for homes in the 35th to 65th percentile range.

Housing Bubble

On December 30, 2008, the Case–Shiller home price index reported its largest price drop in its history. The credit crisis resulting from the bursting of the housing bubble is an important cause of the Great Recession in the United States.

2. Business Problem

What are the top 5 best zip codes for us to invest in? Let’s begin by defining best:

A 3 year (2015–2018) Return On Investment ratio was used to measure ‘best’.

After some preliminary analysis of the data, I decided to make Connecticut our test state to optimize computational runtime.

3. Cleaning and Obtaining Data

# Importing libraries

import pandas as pd

from pandas import Series

import seaborn as sns

import matplotlib as mpl

import matplotlib.pyplot as plt import plotly.express as px

import plotly.io as pio %matplotlib inline df = pd.read_csv('zillow_data.csv')

Connecticut

The firm is particularly interested in off-the-beaten-path areas of the North East Coast so I will focus my analysis on CT zip codes.

## selecting ct zipcodes

df_ct = df.loc[df['State']== 'CT'].reset_index()

df_ct.drop(['index', 'RegionID', 'City', 'State', 'CountyName'], axis=1, inplace=True)

df_ct.rename(columns={'RegionName': 'Zipcode'}, inplace=True)

Obtaining ROIs

## 5 year ROI

df_ct['ROI_5yr'] = round((df_ct['2018-04'] - df_ct['2013-01'])/ df_ct['2013-01'],4)

## 3 year ROI

df_ct['ROI_3yr'] = round((df_ct['2018-04'] - df_ct['2015-01'])/ df_ct['2015-01'],4)

Reshape from Wide to Long Format

def melt_data(df):

melted = pd.melt(df, id_vars=['Zipcode', 'Metro', 'SizeRank', 'ROI_5yr', 'ROI_3yr'],

var_name='Date')

melted['Date'] = pd.to_datetime(melted['Date'], infer_datetime_format=True)

melted = melted.dropna(subset=['value'])

return melted melted_df = melt_data(df_ct)

A Few More Format Improvements

#Change Zipcode dtype to 'str'

melted_df['Zipcode'] = melted_df['Zipcode'].astype(str) # Make sure the data type of the 'Date' column is datetime

melted_df['Date'] = pd.to_datetime(melted_df['Date'], format='%m/%y') # Set the 'Date' column as index

melted_df.set_index('Date', inplace=True)

The data includes home values by month since April 1996 to April 2018.

Given the 2008 Housing Bubble precedent, I have decided to slice the dates and start my analysis at January 2008. This leaves us with a 10 year time window.

melted_df = melted_df['2008-01-01':'2018-04-01']

Resampling Data

melted_df.resample('MS').mean().index

Our DataFrame

melted_df.info() <class 'pandas.core.frame.DataFrame'>

DatetimeIndex: 15376 entries, 2008-01-01 to 2018-04-01

Data columns (total 6 columns):

# Column Non-Null Count Dtype

--- ------ -------------- -----

0 Zipcode 15376 non-null object

1 Metro 15376 non-null object

2 SizeRank 15376 non-null int64

3 ROI_5yr 15376 non-null float64

4 ROI_3yr 15376 non-null float64

5 value 15376 non-null float64

dtypes: float64(3), int64(1), object(2)

memory usage: 840.9+ KB melted_df.head()

4. Exploratory Data Analysis

Let’s look into the average home price by metro.

metro_grp = melted_df.groupby('Metro')

metro_grp = metro_grp.value.mean()

metro_grp = metro_grp.sort_values(ascending=False).head(10) metro_grp.plot.barh(color='pink')

plt.title('10-Year Mean Value by Metro')

plt.xlabel('Value in Millions of Dollars ($)')

plt.show()

plt.savefig('10yr-metro-mean-value')

And now the overall mean value fluctuation over time:

monthly_data = melted_df.resample('MS').mean()['value']

monthly_data.plot()

plt.title('Typical Home Value by Month')

plt.ylabel('Value in US Dollars ($)')

plt.show()

Up next, we’ll plot our Top 10 Zip codes with the highest mean value between 2008 and 2018.

zipcode_grp = melted_df.groupby('Zipcode')

mean_value = zipcode_grp.value.mean()

mean_value = mean_value.sort_values(ascending=False).head(10) mean_value.plot.bar(color='pink')

plt.title('Top 10 Zipcodes

Highest Mean Value 2008-2018')

plt.ylabel('Value in Millions of Dollars ($)')

plt.show()

plt.savefig('Zipcodes')

Finally, let’s obtain the top 10 percent zip codes with the highest 3 year Return on Investment. We will select our top 5 out of these top 10 percent.

ninety_perc_ROI_3yr = melted_df['ROI_3yr'].quantile(q=0.90)

top_10_perc_3yr = melted_df.loc[melted_df['ROI_3yr']>=ninety_perc_ROI_3yr] df = top_10_perc_3yr.drop(columns=['Metro', 'SizeRank']) px.line(df, color='Zipcode')

Top 13 Zip Codes — Highest 3yr ROI

5. ARIMA Modeling

Data Preprocessing

# import libraries import fsds as fs

import pandas as pd

import numpy as np

import matplotlib as mpl

import matplotlib.pyplot as plt

import seaborn as sns

from fsds.imports import * ## Project Notebook Settings

pd.set_option('display.max_columns',0) import warnings

warnings.filterwarnings('ignore') plt.style.use('seaborn-notebook')

## transforming dataframe to fit model zipcode_list = df['Zipcode'].unique().tolist() TS = {}

for zipcode in zipcode_list:

temp_df = df.groupby('Zipcode').get_group(zipcode).sort_index()['value']

TS[zipcode] = temp_df #df.loc[district] ts_df = pd.DataFrame(TS)

ts_df.head()

Train Test Split

train_size = 0.8

split_idx = round(len(ts)* train_size)

split_idx ## Split

train = ts.iloc[:split_idx]

test = ts.iloc[split_idx:] ## Visualize split

fig,ax= plt.subplots()

kws = dict(ax=ax,marker='o')

train.plot(**kws)

test.plot(**kws)

ax.legend(bbox_to_anchor=[1,1])

plt.show()

Best Model

best_model = SARIMAX(ts,order=auto_model.order,

seasonal_order=auto_model.seasonal_order).fit()

display(best_model.summary())

best_model.plot_diagnostics();

Implementing Best Model in a Loop

RESULTS = {} for zipcode in zipcode_list:

print(zipcode)



## Make empty dict for district data

zipcode_d = {}



## Copy Time Series

ts = ts_df[zipcode].copy()





## Train Test Split Index

train_size = 0.8

split_idx = round(len(ts)* train_size) ## Split

train = ts.iloc[:split_idx]

test = ts.iloc[split_idx:] ## Get best params using auto_arima

gridsearch_model = auto_arima(ts,start_p=0,start_q=0)

best_model = SARIMAX(ts,order=gridsearch_model.order,

seasonal_order=gridsearch_model.seasonal_order).fit()



## Get predictions

pred = best_model.get_forecast(steps=36)#start=test.index[0],end=test.index[-1])

pred_df = forecast_to_df(pred,zipcode) ## Save info to dict

zipcode_d['pred_df'] = pred_df

zipcode_d['model'] = best_model

zipcode_d['train'] = train

zipcode_d['test'] = test



## Display Results

display(best_model.summary())

plot_train_test_pred(train,test,pred_df)

plt.xlabel('Year')

plt.ylabel('Value in US Dollars ($)')

plt.show()





## Save district dict in RESULTS

RESULTS[zipcode] = zipcode_d

print('---'*20,end='



')

6. Recommendation

Out of the 13 zip codes selected per historic ROI, I will recommend the 5 zip codes that show an upward trend in both the test and prediction curves.

06069

06069

06039

06069

06610

06610

06330

06630

06235

06235

7. Future Work

Explore home values for other zip codes in the North East Coast.

Obtain current data, apply the model and compare results.

Thank you for reading through my work. I hope you found it relevant.

Here is a link to the GitHub Repository that contains the full technical work.

Please do contact me if there are any questions and/or comments.