Airbnb_tool / app.py
Qiwei97's picture
Update app.py
d3d18f8
raw
history blame contribute delete
No virus
12.5 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'])
#-----------------------------------------
# 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().round(2)
df_nearby_stats.columns = pd.Index(["Price"])
st.table(df_nearby_stats.style.format("{:.2f}"))
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)