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 &#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)