DEV Community

Taylor Higgins
Taylor Higgins

Posted on

ETL with scraped Airbnb Data

import geojson
import geopandas
import pandas as pd
import numpy as np
import statistics as st
import matplotlib as plt
import folium
from folium.features import CustomIcon
import csv
from IPython.display import display, HTML
from pathlib import Path
import os
from datetime import date, time, datetime
from dateutil import tz #for dealing with timezone
from geopy import distance
from collections import defaultdict
from django.contrib.gis.geos import GEOSGeometry, Point
import math
from django.db import models
from django.contrib.gis.db import models

Step 1.) EXTRACT Download the raw csv file from Inside Airbnb and load them into a dataframe

def load_files(ia):
#load and create dataframes
raw_df_02_22 = pd.read_csv(ia)
print(raw_df_02_22.head())
#write to file so we can work on it in next function
raw_df_02_22.to_csv("../suwa_maps/airbnb_jupyter/raw_file.csv")
return(raw_df_02_22)
raw_df_02_22 = load_files("../suwa_maps/airbnb_jupyter/listings_feb_2022.csv")

*Step 2.) TRANSFORM Run the processing script on the raw file dataframe to clean and create new variables
*

`def clean_file(raw_df, raw_file):
#clean file
#drop columns
raw_df.drop(columns=["scrape_id", "bathrooms_text","host_verifications", "host_neighbourhood","property_type", "amenities", "host_verifications", "neighbourhood", "host_listings_count", "picture_url","host_url", "description", "neighborhood_overview", "host_about", "host_response_time", "host_response_rate", 'host_acceptance_rate', 'host_is_superhost', 'host_thumbnail_url', 'host_picture_url', 'host_has_profile_pic', 'host_identity_verified', 'calendar_updated', 'has_availability', 'calendar_last_scraped', 'first_review', 'last_review', 'review_scores_rating', 'review_scores_accuracy', 'review_scores_cleanliness', 'review_scores_checkin', 'review_scores_communication', 'review_scores_location', 'review_scores_value', 'minimum_nights', 'maximum_nights', 'minimum_minimum_nights', 'maximum_minimum_nights', 'minimum_maximum_nights', 'maximum_maximum_nights', 'neighbourhood_group_cleansed', 'beds', 'maximum_nights_avg_ntm', 'bathrooms', 'calculated_host_listings_count_entire_homes', 'calculated_host_listings_count_private_rooms', 'calculated_host_listings_count_shared_rooms'], inplace=True)
raw_df.to_csv("../suwa_maps/airbnb_jupyter/clean_file.csv")

#adjust price column
raw_df['price'] = raw_df['price'].str.replace("$", "").str.replace(',', '')
raw_df['price'] = pd.to_numeric(raw_df['price'])


cleaned_df = raw_df
# print(cleaned_df.head)

cleaned_df.to_csv("../suwa_maps/airbnb_jupyter/cleaned_file.csv")
return(cleaned_df)
Enter fullscreen mode Exit fullscreen mode

cleaned_df_02_22 = clean_file(raw_df_02_22, "../suwa_maps/airbnb_jupyter/raw_file.csv")`

`def new_variables(cleaned_file_input, file_output, cleaned_dataframe):
#convert categorical to numeric
all = []
with open(cleaned_file_input, 'r') as file:
with open(file_output, 'w', encoding="utf-8") as newfile:
csvwriter = csv.writer(newfile,lineterminator='\n')
csvreader = csv.reader(file)
#extract headers
header = next(csvreader)
new_headers = ['days_rented_ltm', 'rounded_revenue_ltm', 'occupancy_rate_approx', 'is_hotel', 'is_entire', 'many_listings', 'only_1_listing', 'only_2_listings', 'host_florence', 'has_liscense', 'is_instant_bookable', 'dist_duomo', 'buffer_zone', 'is_centro', 'is_gavinana', 'is_isolotto', 'is_rifredi', 'is_campo', 'listing_revenue_exceed_LTR', 'effected_by_policy_1', 'effected_by_policy_2', 'effected_by_policy_3', 'commercial', 'very_likely_commercial', 'tourist_tax', 'unpaid_tourist_tax', 'geom']
for item in new_headers:
header.append(item)
all.append(header)
for row in csvreader:

            min_night_avg = float(row[17]) #minimum_nights_avg_ntm 
            name_of_listing = row[4]
            #filter out long term and inactive listings len(row[24]) > 0 is saying reviews for last 30days are not null, buthow about if reviews for last year are not 0?
            if int(row[23]) > 0 and min_night_avg < 29:
                listing_row_id = int(row[0])
                listing_id = int(row[1])

                #year and month of data scraped
                date_scraped = row[3]
                year = date_scraped.split('-')[0]
                month = date_scraped.split('-')[1]
                day = date_scraped.split('-')[2]


                #dist_duomo lat, long 
                duomo_coords = (43.7731, 11.2560)

                #listing_coords = lat, long
                listing_coords = (row[11], row[12]) # ia_df_clean['latitude'], ia_df_clean['longitude']

                ##dist_duomo = duomo_coords - listing_coords
                dist_duomo = (distance.distance(duomo_coords, listing_coords).m)

                #buffer zone circle 5
                # buffer_zone = 0
                if dist_duomo <= 1500:
                    buffer_zone = 1
                elif dist_duomo > 1500 and dist_duomo <= 3000:
                    buffer_zone = 2
                elif dist_duomo > 3000 and dist_duomo <= 4500:
                    buffer_zone = 3
                elif dist_duomo > 4500 and dist_duomo <= 6000:
                    buffer_zone = 4
                elif dist_duomo > 6000 and dist_duomo <= 7500:
                    buffer_zone = 5

                # geom = ((row[11], row[12]), srid=4326)
                # pnt = GEOSGeometry('POINT(5 23)') # WKT 
                # gedjango is long, lat 
                coords_wkt = (round(float(row[12]),4) , round(float(row[11]),4) )
                coords_wkt = Point(coords_wkt)
                geom = coords_wkt

                #policy considerations
                #has_liscense
                if len(row[25]) > 0: 

                    has_liscense = 1
                else:
                    has_liscense = 0

                #is_instant_bookable
                if row[26] == 't': 
                    is_instant_bookable = 1
                else:
                    is_instant_bookable = 0

                #is in florence host
                if ("Florence" or "Firenze") in row[8]: #row[8] ia_df_clean['host_location']
                    host_florence = 1
                else:
                    host_florence = 0
                    #print(host_florence)



                #to calculate rent burden from statistica averages, refine
                centro_neighborhood_rent = 1238.70 * 12
                campo_neighborhood_rent = 1078.00 * 12
                rif_neighborhood_rent = 954.80 * 12
                iso_neighborhood_rent = 939.40 * 12
                gav_neighborhood_rent = 1070.30 * 12


                #which neighborhoods
                #centro
                if "Centro" in row[10]: #row[10] ia_df_clean['neighbourhood_cleansed']
                    is_centro = 1
                else:
                    is_centro = 0

                #campo
                if "Campo" in row[10]:
                    is_campo = 1
                else:
                    is_campo = 0

                #rifredi
                if "Rifredi" in row[10]:
                    is_rifredi = 1
                else:
                    is_rifredi = 0

                #isolotto
                if "Isolotto" in row[10]:
                    is_isolotto = 1
                else:
                    is_isolotto = 0


                #gavinana
                if "Gavinana" in row[10]:
                    is_gavinana = 1
                else:
                    is_gavinana = 0

                #occupancy rate from SF model
                #did reviews a year divided by how likely someone gives a review (airbnb data) * avg stay in florence (2.6)
                review_rate = .50
                avg_length = 3.2 


                if len(row[24]) > 0: #if number of reviews in last month more than 0 then it's active or there was an error
                    # print(row[23])
                    num_reviews = float(row[23]) #number_of_reviews_ltm
                    #test here for long term
                    if min_night_avg > 29:
                        print("min_night_avg > 29: is {} on row {}".format(min_night_avg,listing_row_id))
                    elif min_night_avg > avg_length:
                        avg_days = min_night_avg
                    else:
                        avg_days = avg_length
                    num_bookings = review_rate * num_reviews
                    days_rented_ltm = num_bookings * avg_days
                    #test for days

                    #adjusting to make occupancy rate 70 to be conservative according to Murray's suggestions
                    if days_rented_ltm > 255:
                        #print("days_rented_ltm > 365: is {} on row {}".format(days_rented_ltm,listing_row_id))
                        days_rented_ltm = 255
                    tourist_tax = (days_rented_ltm * 3) *2 #3 euro which is lower end, 3 start hotel is 3.80 euro and 2 persons conservative 
                    occupancy_rate_approx = (days_rented_ltm/365) * 100

                    if occupancy_rate_approx > 70.00:
                        print("occupancy_rate_approx > 70: is {} on row {}".format(occupancy_rate_approx,listing_row_id))
                        occupancy_rate_approx = 70.00

                    #revenue #have to strip the string for price then turn into float
                    price_stripped_dollar = row[16].strip('$') #price
                    price_replaced_comma = price_stripped_dollar.replace(',','')
                    new_price = float(price_replaced_comma)
                    if new_price < 1.0:
                        print("new_price is basically zero: is {} on row {}".format(new_price,listing_row_id))


                    #yearly revenue
                    revenue_ltm = days_rented_ltm * new_price
                    rounded_revenue_ltm = round(revenue_ltm, 2)
                    if rounded_revenue_ltm >0.0 and rounded_revenue_ltm < new_price:
                        print("rounded_revenue_ltm < {}: and is {} on row {}".format(new_price, rounded_revenue_ltm,listing_row_id))
                    # print(rounded_revenue_ltm)

                else:
                    days_rented_ltm = 'n/a'
                    rounded_revenue_ltm = 'n/a'
                    # listing_revenue_exceed_LTR = 'n/a'

                if is_centro == 1:
                    if rounded_revenue_ltm == 'n/a':
                        listing_revenue_exceed_LTR = 'n/a'
                    elif rounded_revenue_ltm > centro_neighborhood_rent:
                        listing_revenue_exceed_LTR = 1
                    else:
                        listing_revenue_exceed_LTR = 0
                elif is_gavinana == 1:
                    if rounded_revenue_ltm == 'n/a':
                        listing_revenue_exceed_LTR = 'n/a'
                    elif rounded_revenue_ltm > gav_neighborhood_rent:
                        listing_revenue_exceed_LTR = 1
                    else:
                        listing_revenue_exceed_LTR = 0
                elif is_isolotto == 1:
                    if rounded_revenue_ltm == 'n/a':
                        listing_revenue_exceed_LTR = 'n/a'
                    elif rounded_revenue_ltm > iso_neighborhood_rent:
                        listing_revenue_exceed_LTR = 1
                    else:
                        listing_revenue_exceed_LTR = 0
                elif is_rifredi ==1:
                    if rounded_revenue_ltm == 'n/a':
                        listing_revenue_exceed_LTR = 'n/a'
                    elif rounded_revenue_ltm > rif_neighborhood_rent:
                        listing_revenue_exceed_LTR = 1
                    else:
                        listing_revenue_exceed_LTR = 0
                elif is_campo == 1:
                    if rounded_revenue_ltm == 'n/a':
                        listing_revenue_exceed_LTR = 'n/a'
                    elif rounded_revenue_ltm > campo_neighborhood_rent:
                        listing_revenue_exceed_LTR = 1
                    else:
                        listing_revenue_exceed_LTR = 0

                # listing_revenue_exceed_LTR = 'n/a'   

                #many_listings 
                # print(type(row[9]))
                try:
                    if float(row[9]) > 2: # ia_df_clean['host_total_listings_count']
                        many_listings = 1
                    else:
                        many_listings = 0
                    #only_1 success
                    if float((row[9])) == 1:
                        only_1_listing = 1
                    else: 
                        only_1_listing = 0
                    #only_2 success
                    if float((row[9])) == 2:
                        only_2_listings = 1
                    else:
                        only_2_listings = 0
                except ValueError:
                    many_listings = 'na'
                    only_1_listing = 'na'
                    only_2_listings = 'na'

                #is_entire success 
                if row[13] == 'Entire home/apt':  # ia_df_clean['room_type']
                    is_entire = 1
                else:
                    is_entire = 0
                #is_hotel success
                if row[13] == 'Hotel room':
                    is_hotel = 1
                else:
                    is_hotel = 0

                #commercial
                if cleaned_dataframe.loc[listing_row_id, 'host_total_listings_count'] > 4:
                    commercial = 1
                elif host_florence == 0:
                    commercial = 1
                else:
                    commercial = 0

                #very_likely_commercial
                if cleaned_dataframe.loc[listing_row_id, 'host_total_listings_count'] > 2 and cleaned_dataframe.loc[listing_row_id, 'instant_bookable'] == 't' and cleaned_dataframe.loc[listing_row_id, 'room_type']== 'Entire home/apt':
                    very_likely_commercial = 1
                else:
                    very_likely_commercial = 0

                #likely unpaid touist tax for non liscenced, tourist tax was calculated by (3*days rented ltm)
                if has_liscense == 0:
                    unpaid_tourist_tax = tourist_tax

                #effected_by_policy_x (1-liscense,2- -is entire, 3-2 listing max, 4-day limit 90) #switched 2 and 4 from origingal 
                if has_liscense == 0:
                    effected_by_policy_1 = 1
                else: 
                    effected_by_policy_1 = 0
                if is_entire == 1:
                    effected_by_policy_2 = 1
                else:
                    effected_by_policy_2 = 0

                #more than 2 listings means many
                if many_listings == 1:
                    effected_by_policy_3 = 1
                else:
                    effected_by_policy_3 = 0


                new_variables = [days_rented_ltm, rounded_revenue_ltm, occupancy_rate_approx, is_hotel, is_entire, many_listings, only_1_listing, only_2_listings, host_florence, has_liscense, is_instant_bookable, dist_duomo, buffer_zone, is_centro, is_gavinana, is_isolotto, is_rifredi, is_campo, listing_revenue_exceed_LTR, effected_by_policy_1, effected_by_policy_2, effected_by_policy_3, commercial, very_likely_commercial, tourist_tax, unpaid_tourist_tax, geom]
                for item in new_variables:
                    row.append(item)
                #adding entire row to all 
                all.append(row)
            else:
                # print("either inactive or long term listing so removing {} from file".format(name_of_listing))
                pass
        #writing all rows to new out file
        csvwriter.writerows(all)
return csvwriter
Enter fullscreen mode Exit fullscreen mode

new_var_df = new_variables('../suwa_maps/airbnb_jupyter/cleaned_file.csv', '../suwa_maps/airbnb_jupyter/new_variables_file.csv', cleaned_df_02_22)`

*Step 3.) LOAD Migrate the csv data into postgis spatial database. It was very important that we made a GEOM point column from the lat long attributes in the last step. The next code block won't run since environment variables need to be set up but I provided it to show how the database schema is set up to store the airbnb listing data we just processed. Then run python manage.py migrate and python manage.py check and finally the script to load our data into the database table we just created with the django migration function. *

`#models.py file

This is an auto-generated Django model module created by ogrinspect.

class AirbnbListings(models.Model): #issues here ...

Regular Django fields corresponding to the attributes in the IA csv file

id = models.IntegerField(primary_key=True)
listing_url = models.CharField(max_length=1000)
last_scraped = models.DateField(max_length=1000)
name = models.CharField(max_length=1000)
host_id = models.IntegerField()
host_name = models.CharField(max_length=1000)
host_since = models.CharField(max_length=1000)
host_location = models.CharField(max_length=1000)
host_total_listings_count = models.IntegerField()
neighbourhood_cleansed = models.CharField(max_length=1000)
latitude = models.FloatField()
longitude = models.FloatField()
room_type = models.CharField(max_length=1000)
accommodates = models.IntegerField()
bedrooms = models.IntegerField()
price = models.FloatField()
minimum_nights_avg_ntm = models.FloatField()
availability_30 = models.IntegerField()
availability_60 = models.IntegerField()
availability_90 = models.IntegerField()
availability_365 = models.IntegerField()
number_of_reviews = models.IntegerField()
number_of_reviews_ltm = models.IntegerField()
number_of_reviews_l30d = models.IntegerField()
license = models.CharField(max_length=1000)
instant_bookable = models.CharField(max_length=1000)
calculated_host_listings_count = models.IntegerField()
reviews_per_month = models.FloatField()
days_rented_ltm = models.FloatField()
rounded_revenue_ltm = models.FloatField()
occupancy_rate_approx = models.FloatField()
is_hotel = models.IntegerField()
is_entire = models.IntegerField()
many_listings = models.IntegerField()
only_1_listing = models.IntegerField()
only_2_listings = models.IntegerField()
host_florence = models.IntegerField()
has_liscense = models.IntegerField()
is_instant_bookable = models.IntegerField()
dist_duomo = models.FloatField()
is_centro = models.IntegerField()
is_gavinana = models.IntegerField()
is_isolotto = models.IntegerField()
is_rifredi = models.IntegerField()
is_campo = models.IntegerField()
listing_revenue_exceed_LTR = models.IntegerField()
effected_by_policy_1 = models.IntegerField()
effected_by_policy_2 = models.IntegerField()
effected_by_policy_3 = models.IntegerField()
commercial = models.IntegerField()
# editing to see if this is the problem JULY not so putting back
geom = models.PointField(verbose_name='geo',srid = 4326) #    # GeoDjango-specific: a geometry field (MultiPolygonField)
#could be points, line, polygon, multipolygon, raster etc 
# Returns the string representation of the model. maybe add this because it seems to be in docs.
# def __str__(self):
#     return self.name
Enter fullscreen mode Exit fullscreen mode

Auto-generated LayerMapping dictionary for AirbnbListings model

augmented_airbnblistings_mapping = {
'id': 'id',
'listing_url': 'listing_url',
'last_scraped': 'last_scraped',
'name': 'name',
'host_id': 'host_id',
'host_name': 'host_name',
'host_since': 'host_since',
'host_location': 'host_location',
'host_total_listings_count': 'host_total_listings_count',
'neighbourhood_cleansed': 'neighbourhood_cleansed',
'latitude': 'latitude',
'longitude': 'longitude',
'room_type': 'room_type',
'accommodates': 'accommodates',
'bedrooms': 'bedrooms',
'price': 'price',
'minimum_nights_avg_ntm': 'minimum_nights_avg_ntm',
'availability_30': 'availability_30',
'availability_60': 'availability_60',
'availability_90': 'availability_90',
'availability_365': 'availability_365',
'number_of_reviews':'number_of_reviews',
'number_of_reviews_ltm': 'number_of_reviews_ltm',
'number_of_reviews_l30d': 'number_of_reviews_l30d',
'license': 'license',
'instant_bookable': 'instant_bookable',
'calculated_host_listings_count': 'calculated_host_listings_count',
'reviews_per_month': 'reviews_per_month',
'days_rented_ltm':'days_rented_ltm',
'rounded_revenue_ltm':'rounded_revenue_ltm',
'occupancy_rate_approx':'occupancy_rate_approx',
'is_hotel':'is_hotel',
'is_entire':'is_entire',
'many_listings':'many_listings',
'only_1_listing':'only_1_listing',
'only_2_listings':'only_2_listings',
'host_florence':'host_florence',
'has_liscense':'has_liscense',
'is_instant_bookable':'is_instant_bookable',
'dist_duomo':'dist_duomo',
'is_centro':'is_centro',
'is_gavinana':'is_gavinana',
'is_isolotto':'is_isolotto',
'is_rifredi':'is_rifredi',
'is_campo':'is_campo',
'listing_revenue_exceed_LTR':'listing_revenue_exceed_LTR',
'effected_by_policy_1':'effected_by_policy_1',
'effected_by_policy_2':'effected_by_policy_2',
'effected_by_policy_3':'effected_by_policy_3',
'commercial':'commercial',
'geom': ['latitude', 'longitude'],
}`

`#script to load up data in database adapted from https://towardsdatascience.com/use-python-scripts-to-insert-csv-data-into-django-databases-72eee7c6a433
def load_csv_db():
with open('"../suwa_maps/airbnb_jupyter/cleaned_file.csv"') as file:
reader = csv.reader(file)
next(reader) # Advance past the header

    AirbnbListings.objects.all().delete()

    for row in reader:
        print(row)


        listing = AirbnbListings(
            id=row[0],
            listing_url=row[1],
            last_scraped=row[2],
            name=row[3],
            host_id=row[4],
            host_name=row[5],
            host_since=row[6],
            host_location=row[7],
            host_total_listings_count=row[8],
            neighbourhood_cleansed=row[9],
            latitude=row[10],
            longitude=row[11],
            room_type=row[12],
            accommodates=row[13],
            bedrooms=row[14],
            price=row[15],
            minimum_nights_avg_ntm=row[16],
            availability_30=row[17],
            availability_60=row[18],
            availability_90=row[19],
            availability_365=row[20],
            number_of_reviews=row[21],
            number_of_reviews_ltm=row[22],
            number_of_reviews_l30d=row[23],
            license=row[24],
            instant_bookable=row[25],
            calculated_host_listings_count=row[26],
            reviews_per_month=row[27],
            days_rented_ltm=row[28],
            rounded_revenue_ltm=row[29],
            occupancy_rate_approx=row[30],
            is_hotel=row[31],
            is_entire=row[32],
            many_listings =row[33],
            only_1_listing=row[34],
            only_2_listings=row[35],
            host_florence=row[36],
            has_liscense=row[37],
            is_instant_bookable=row[38],
            dist_duomo=row[39],
            is_centro=row[40],
            is_gavinana=row[41],
            is_isolotto=row[42],
            is_rifredi=row[43],
            is_campo=row[44],
            listing_revenue_exceed_LTR=row[45],
            effected_by_policy_1=row[46],
            effected_by_policy_2=row[47],
            effected_by_policy_3=row[48],
            commercial=row[49],
            geom=row[50],
                    )
        listing.save()`
Enter fullscreen mode Exit fullscreen mode

NOTE: These next steps normally would call the data from the database

Step 4.) EXPLORE with summary statistics in pandas

`new_variables_df = pd.read_csv('../suwa_maps/airbnb_jupyter/new_variables_file.csv')

quant_labels = ['calculated_host_listings_count','accommodates', 'bedrooms', 'days_rented_ltm', 'rounded_revenue_ltm', 'occupancy_rate_approx', 'tourist_tax', 'unpaid_tourist_tax']
df_quant = df.loc[:, quant_labels]
qual_labels = ['id', 'listing_url', 'name', 'host_id', 'host_name', 'neighbourhood_cleansed', 'host_location', 'room_type', 'license', 'instant_bookable', 'is_hotel', 'is_entire', 'many_listings', 'only_1_listing', 'only_2_listings', 'host_florence', 'has_liscense', 'is_instant_bookable', 'effected_by_policy_1', 'effected_by_policy_2', 'effected_by_policy_3', 'commercial', 'very_likely_commercial',]
df_qual = df.loc[:, qual_labels].astype("category")
continuous_quant_labels = ['host_since', 'latitude', 'longitude', 'price', 'dist_duomo']
df_continuous = df.loc[:, continuous_quant_labels]`

df_quant.describe().transpose()

df_qual.describe().transpose()

pivot_discrete_neighbourhood = pd.pivot_table(data=new_variables_df, index=["neighbourhood_cleansed"],columns=["room_type"], values='host_total_listings_count', aggfunc='mean')
print(pivot_discrete_neighbourhood)

`def original_airbnb_map(mapdf, filetitle):
#create bubble map
bub_map = folium.Map(location=[mapdf.latitude.mean(),mapdf.longitude.mean()], zoom_start=16, tiles='CartoDB Positron', control_scale=True)
folium.raster_layers.TileLayer('Stamen Toner').add_to(bub_map)
folium.raster_layers.TileLayer('Stamen Terrain').add_to(bub_map)
folium.raster_layers.TileLayer('CartoDB Positron').add_to(bub_map)
folium.LayerControl().add_to(bub_map)

for index, location_info in mapdf.iterrows():
# popup = [str(location_info["host_name"]), "http://www.airbnb.com/users/show/" + str(location_info["host_id"]), str(location_info["license"]), str(location_info["host_total_listings_count"]), str(location_info["global_total_listings"]), str(location_info["listing_url"]), str(location_info["id"]), str(location_info["name"]), str(location_info["neighbourhood_cleansed"]), str(location_info["room_type"]), str(location_info["bedrooms"]), str(location_info["rounded_revenue"]), str((location_info["rounded_revenue"])/12), str(location_info["price"]), str(location_info["days_rented"]),str(location_info["reviews_per_month"])]
popup = 'tbd'
tooltip = 'tbd'
folium.Circle([location_info["latitude"],location_info["longitude"]], radius=2, color="blue", opacity=.2, fill=True, fill_opacity=0.1, fill_color ="blue", popup=popup, tooltip=tooltip).add_to(bub_map)

fill_opacity=0.5,

bub_map.save('Out_Map/' + filetitle + '.html')

pass

return bub_map

Enter fullscreen mode Exit fullscreen mode




original_airbnb_map(mapdf, datadf, tileinfo)

bub_map = original_airbnb_map(cleaned_df_02_22, 'original_airbnb_map')
bub_map`

Top comments (0)