Airbnb_tool / app.py
Qiwei97's picture
Upload app.py
3b88404
raw
history blame
No virus
14.6 kB
import streamlit as st
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import numpy_financial as npf
import pandas as pd
from streamlit_folium import folium_static
import leafmap.foliumap as leafmap
import folium
from shapely.geometry import Point, Polygon
import geopandas
import geopy
from geopy.geocoders import Nominatim
from geopy.extra.rate_limiter import RateLimiter
from scipy.spatial import cKDTree
#-----------------------------------------
# Set page settings
st.set_page_config(layout="wide")
#-----------------------------------------
# Sidebar
with st.sidebar:
st.header('Welcome to the Airbnb Investment Tool!')
nav = st.selectbox('Navigation', ['Heuristic Pricing',
'Investment Analysis',
'Customer View'])
#-----------------------------------------
# Additional Functions
def p_title(title):
st.markdown(f'<h3 style="text-align: left; color:#F63366; font-size:28px;">{title}</h3>', unsafe_allow_html=True)
# Function to return a GeoPandas DataFrame containing the listings
# that are within a specified radius from a specified lat, long.
def getNearbyListings(gdf_proj, input_long, input_lat, radius):
# Build Tree
airbnbCoords = np.array(list(gdf_proj.geometry.apply(lambda x: (x.x, x.y))))
airbnbTree = cKDTree(airbnbCoords)
# Convert lat-long to projected coords
gdf_input = geopandas.GeoSeries.from_xy(x=[input_long], y=[input_lat], crs=4326)
gdf_input_proj = gdf_input.to_crs(crs=32634)
coords = np.array(list((gdf_input_proj.x[0], gdf_input_proj.y[0])))
# Returns list of indices whose distance is <= radius
neighbours_indices = airbnbTree.query_ball_point(coords, radius)
gdf_neighbours_proj = gdf_proj.iloc[neighbours_indices, :]
gdf_neighbours = gdf_neighbours_proj.to_crs(crs=4326)
return gdf_neighbours
# Function to return IRR.
# Financial Modelling Tool.
def investment_tool(house_price, loan_amount, loan_period, percentage_loan_interest_annual,
rental_charged_monthly, percentage_rental_tax, percentage_increase_in_rental_yearly, utilisation_rate,
yearly_refurbishment_costs, percentage_increase_in_refurbishment_yearly, ending_value_of_house):
#expected format of percentage parameters is whole number and not decimals i.e., 5 instead of 0.05
#all non-% parameters are expected to be positive
house_price = int(house_price)
loan_amount = int(loan_amount)
loan_period = int(loan_period)
percentage_loan_interest_annual = int(percentage_loan_interest_annual)
rental_charged_monthly = int(rental_charged_monthly)
percentage_rental_tax = int(percentage_rental_tax)
percentage_increase_in_rental_yearly = int(percentage_increase_in_rental_yearly)
percentage_utilisation_rate = int(utilisation_rate)
yearly_refurbishment_costs = int(yearly_refurbishment_costs)
percentage_increase_in_refurbishment_yearly = int(percentage_increase_in_refurbishment_yearly)
ending_value_of_house = int(ending_value_of_house)
#ensuring the figures make sense
if loan_amount > house_price:
return print("Loan Amount cannot exceed House Price")
#creating the list of cash flows to be used to calculate internal rate of return
initial_cashflow = -(1 - loan_amount/house_price) * house_price
cashflow_list = [initial_cashflow]
#finding the annual mortgage assuming equal amortization
mortgage = npf.pmt(percentage_loan_interest_annual / 100, loan_period, loan_amount) #the np.pmt function will automatically put mortgage as a negative cashflow
#finding the annual cashflows & loan balance changes during the loan period and appending them to the respective lists
for i in range(loan_period):
rental = 12 * rental_charged_monthly * ((1 + (percentage_increase_in_rental_yearly / 100)) ** i) * (1 - (percentage_rental_tax / 100)) * utilisation_rate / 100
refurbishment_cost = -1 * yearly_refurbishment_costs * ((1 + (percentage_increase_in_refurbishment_yearly / 100)) ** i)
#the condition here is to include the salvage/ending value of the house to cashflows after loan repayments are finished
if i == (loan_period-1):
yearly_cashflow = ending_value_of_house + rental + mortgage + refurbishment_cost
else:
yearly_cashflow = rental + mortgage + refurbishment_cost
cashflow_list.append(yearly_cashflow)
#finding the internal rate of return
irr = round(npf.irr(cashflow_list), 4)
#-----------------------------------
#Dataframe for plotting of graph
loan_dict = {'Year': [0], 'Starting Loan Balance': [0], 'Cumulative Interest Paid': [0], 'Cumulative Principal Paid': [0], 'Remaining Loan Balance': [0]}
# Create DataFrame
loan_dataframe = pd.DataFrame(loan_dict)
#finding the annual mortgage assuming equal amortization
mortgage = npf.pmt(percentage_loan_interest_annual / 100, loan_period, loan_amount) #the np.pmt function will automatically put mortgage as a negative cashflow
#updating the global dataframe
loan_dataframe.loc[0,'Starting Loan Balance'] = loan_amount
for i in range(loan_period):
loan_dataframe.loc[i,'Year'] = i+1
#the condition here is to calculate principal and interest paid
if i == 0:
loan_dataframe.loc[i,'Cumulative Interest Paid'] = loan_dataframe.loc[i,'Starting Loan Balance'] * (percentage_loan_interest_annual / 100)
loan_dataframe.loc[i,'Cumulative Principal Paid'] = (-1 * mortgage) - (loan_dataframe.loc[i,'Starting Loan Balance'] * (percentage_loan_interest_annual / 100))
else:
loan_dataframe.loc[i,'Cumulative Interest Paid'] = loan_dataframe.loc[i,'Starting Loan Balance'] * (percentage_loan_interest_annual / 100) + loan_dataframe.loc[i-1,'Cumulative Interest Paid']
loan_dataframe.loc[i,'Cumulative Principal Paid'] = (-1 * mortgage) - (loan_dataframe.loc[i,'Starting Loan Balance'] * (percentage_loan_interest_annual / 100)) + loan_dataframe.loc[i-1,'Cumulative Principal Paid']
loan_dataframe.loc[i,'Remaining Loan Balance'] = loan_dataframe.loc[i,'Starting Loan Balance'] + (loan_dataframe.loc[i,'Starting Loan Balance'] * (percentage_loan_interest_annual / 100)) + mortgage
#condition to update starting loan balance
if i != loan_period-1:
loan_dataframe.loc[i+1,'Starting Loan Balance'] = loan_dataframe.loc[i,'Remaining Loan Balance']
loan_dataframe['Remaining Loan Balance'] = pd.to_numeric(loan_dataframe['Remaining Loan Balance'])
return irr, loan_dataframe
#-----------------------------------------
# Load Airbnb listings data
df_raw = pd.read_csv("data/listings_sf_withamenities.csv")
df = df_raw.copy()
gdf = geopandas.GeoDataFrame(
df,
geometry=geopandas.points_from_xy(df.longitude, df.latitude),
crs=4326)
gdf_proj = gdf.to_crs(crs=32634)
#-----------------------------------------
# Tab 1: Heuristic Pricing
if nav == 'Heuristic Pricing':
st.markdown("<h3 style='text-align: center; color:grey;'>Airbnb &#127968;</h3>", unsafe_allow_html=True)
st.text('')
p_title('Heuristic Pricing')
st.text('')
# Get address inputs
st.caption('Enter your address:')
with st.form("heuristics_form"):
col1, col2 = st.columns(2)
with col1:
postalcode = st.text_input("Postal Code", "94109")
street = st.text_input("Street", "1788 Clay Street")
city = st.selectbox("City", ["San Francisco"])
with col2:
state = st.selectbox("State", ["California"])
country = st.selectbox("Country", ["United States"])
radius = st.slider("Distance of nearest listings (metres)", min_value=500, max_value=2000, value=500, step=500)
submitted = st.form_submit_button("Submit")
if submitted:
# Get geolocation
geolocator = Nominatim(user_agent="GTA Lookup")
geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)
location = geolocator.geocode({"postalcode": postalcode, "street": street, "city": city, "state": state, "country": country})
# If the search address yields no result, set to default coords of San Fran
if location is None:
lat = 37.773972
lon = -122.431297
st.error("Address is not found. Please try again.")
else:
lat = location.latitude
lon = location.longitude
# Compute Stats
st.markdown('___')
st.caption('Recommended Pricing:')
gdf_nearby_listings = getNearbyListings(gdf_proj, lon, lat, radius=radius)
if len(gdf_nearby_listings) == 0:
st.error("There are no nearby listings.")
else:
col3, col4 = st.columns(2)
with col3:
df_nearby_stats = gdf_nearby_listings[["price"]].describe()
st.table(df_nearby_stats)
with col4:
# Plot Stats
fig = plt.figure(figsize=(10, 4))
sns.boxplot(x="price", data=gdf_nearby_listings, showfliers=False)
st.pyplot(fig)
# Plot using leafmap. Responsive width.
m = leafmap.Map(tiles="OpenStreetMap", location=[lat, lon], zoom_start=15)
m.add_marker(location=[lat, lon])
m.add_points_from_xy(gdf_nearby_listings, x="longitude", y="latitude",
popup=["id", "price", "review_scores_rating"],
color_options=['red'])
m.add_heatmap(data=gdf_nearby_listings,
latitude="latitude", longitude="longitude",
value="price", min_opacity=0.1,
name="Price Heatmap", blue=50)
m.to_streamlit()
#-----------------------------------------
# Tab 2: Investment Analysis
if nav == 'Investment Analysis':
st.markdown("<h3 style='text-align: center; color:grey;'>Airbnb &#127968;</h3>", unsafe_allow_html=True)
st.text('')
p_title('Investment Analysis')
# Financial Projections
st.caption("Enter data here")
with st.form("investment_form"):
col1_2, col2_2, col3_2 = st.columns(3)
with col1_2:
house_price = st.number_input("Purchase Price of House ($)", min_value=0, value=250000)
loan_amount = st.number_input("Loan Amount ($)", min_value=0, value=150000)
loan_period = st.number_input("Loan Period (Years)", min_value=0, value=15)
percentage_loan_interest_annual = st.number_input("Annual Loan I/R (%)", min_value=0.0, max_value=100.0, value=2.1)
with col2_2:
rental_charged_monthly = st.number_input("Monthly Rental ($)", min_value=0, value=2000)
percentage_rental_tax = st.number_input("Rental Tax (%)", min_value=0.0, value=0.0)
percentage_increase_in_rental_yearly = st.number_input("Annual Rental Increase (%)", min_value=0.0, value=1.0)
utilisation_rate = st.number_input("Utilisation Rate (%)", min_value=0.0, value=50.0)
with col3_2:
yearly_refurbishment_costs = st.number_input("Yearly Refurbishment Costs ($)", min_value=0, value=3000)
percentage_increase_in_refurbishment_yearly = st.number_input("Yearly Refurbishment Costs Increase (%)", min_value=0.0, value=2.0)
ending_value_of_house = st.number_input("Ending Value of House ($)", min_value=0, value=300000)
submitted2 = st.form_submit_button("Submit")
if submitted2:
irr, loan_dataframe = investment_tool(house_price, loan_amount, loan_period, percentage_loan_interest_annual,
rental_charged_monthly, percentage_rental_tax, percentage_increase_in_rental_yearly, utilisation_rate,
yearly_refurbishment_costs, percentage_increase_in_refurbishment_yearly, ending_value_of_house)
st.markdown('___')
st.caption("Expected Internal Rate of Return")
st.text("{:.2%}".format(irr))
# Print plots
fig = plt.figure(figsize=(10, 4))
plt.bar(loan_dataframe['Year'], loan_dataframe['Cumulative Principal Paid'], color='lightcoral')
plt.bar(loan_dataframe['Year'], loan_dataframe['Cumulative Interest Paid'], bottom=loan_dataframe['Cumulative Principal Paid'], color='lightsalmon')
plt.plot(loan_dataframe['Year'], loan_dataframe['Remaining Loan Balance'], color='crimson')
plt.ylabel('Amount')
plt.title('Loan Balance')
plt.legend(('Loan Balance Remaining','Cumulative Principal Paid', 'Cumulative Interest Paid'))
st.pyplot(fig)
if nav == "Customer View":
st.markdown("<h3 style='text-align: center; color:grey;'>Airbnb &#127968;</h3>", unsafe_allow_html=True)
st.text('')
p_title('Customer View')
customer_tableau_embed_code = "<div class='tableauPlaceholder' id='viz1650723513927' style='position: relative'><noscript><a href='#'><img alt=' ' src='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;Da&#47;Dashboard_1_16505589498970&#47;EDA&#47;1_rss.png' style='border: none' /></a></noscript><object class='tableauViz' style='display:none;'><param name='host_url' value='https%3A%2F%2Fpublic.tableau.com%2F' /> <param name='embed_code_version' value='3' /> <param name='site_root' value='' /><param name='name' value='Dashboard_1_16505589498970&#47;EDA' /><param name='tabs' value='yes' /><param name='toolbar' value='yes' /><param name='static_image' value='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;Da&#47;Dashboard_1_16505589498970&#47;EDA&#47;1.png' /> <param name='animate_transition' value='yes' /><param name='display_static_image' value='yes' /><param name='display_spinner' value='yes' /><param name='display_overlay' value='yes' /><param name='display_count' value='yes' /><param name='language' value='en-US' /></object></div> <script type='text/javascript'> var divElement = document.getElementById('viz1650723513927'); var vizElement = divElement.getElementsByTagName('object')[0]; if ( divElement.offsetWidth > 800 ) { vizElement.style.width='1600px';vizElement.style.height='1150px';} else if ( divElement.offsetWidth > 500 ) { vizElement.style.width='1600px';vizElement.style.height='1150px';} else { vizElement.style.width='100%';vizElement.style.height='2850px';} var scriptElement = document.createElement('script'); scriptElement.src = 'https://public.tableau.com/javascripts/api/viz_v1.js'; vizElement.parentNode.insertBefore(scriptElement, vizElement); </script>"
st.components.v1.html(customer_tableau_embed_code, height=2000, scrolling=True)