Spaces:
Runtime error
Runtime error
| 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) |