Spaces:
Runtime error
Runtime error
File size: 12,500 Bytes
3b88404 e7c7bc7 3b88404 a96c9c5 3b88404 e7c7bc7 d3d18f8 0b146f3 3b88404 e7c7bc7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
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 🏠</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 🏠</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) |