<jupyter_start><jupyter_text>Loading Data<jupyter_code>import warnings
warnings.filterwarnings("ignore")

from pyspark import SparkConf, SparkContext, SQLContext
conf = SparkConf().setAppName("AirBnB")
sc = SparkContext(conf=conf)
sql = SQLContext(sc)

import pandas as pd
import numpy as np
pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 200)

import pyspark.sql.functions as Fn
import pyspark.sql.types as Tp
'''
Airbnb data for US from 

https://public.opendatasoft.com/explore/dataset/airbnb-listings/export/
?disjunctive.host_verifications&disjunctive.amenities&disjunctive.features&refine.country=United+States


LOADING THIS DIRECTLY AS A DATAFRAME WAS CAUSING COLUMNS TO SHIFT, SO LOADING THIS FILE AS RDD FIRST

'''
air =sc.textFile('/Users/mannu/Desktop/Big Data/Group Project/airbnb-listings.csv')
air.take(2)
header = air.first()
air_split = air.filter(lambda r: r!=header).map(lambda r: r.lower().split(';'))
cols = [c.replace(' ', '_') for c in header.lower().split(';')]
cols
print("Initial file has",air_split.count(),"rows and",len(cols),"columns")<jupyter_output>Initial file has 276682 rows and 89 columns<jupyter_text>Creating DataFrame<jupyter_code>air_actual = air_split.filter(lambda l: len(l)==len(cols))
print("After splitting all rows into 89 columns and filtering out rows which had more than 89 columns,")
print("we now have",air_actual.count(), "rows and",len(cols),"columns")

#only 32% rows remain
air_df = sql.createDataFrame(air_actual,cols)
air_df.show(1,False)
#creating a function for shape of df
def shape(df):
    print("Shape of dataframe is {0} x {1}".format(df.count(),len(df.columns)))
    
shape(air_df) 
#Since 'Price' is our Target variable dropping rows with no price
air_df = air_df.dropna(subset=['price'])
shape(air_df)
import string
d = string.digits + '.'

def extract_number(v):
    z = ''
    for ch in v:
        if ch in d:
            z+=ch
    try:
        return float(z)
    except:
        return 0
    
numPrice = Fn.udf(lambda v: extract_number(v), Tp.FloatType())
air_df = air_df.withColumn("price", numPrice(air_df.price))
shape(air_df)
#Also, no negative price
air_df = air_df.where(Fn.col('price')>0)
shape(air_df)<jupyter_output>Shape of dataframe is 86552 x 89<jupyter_text>Selecting Columns<jupyter_code>#Dropping ID and Url columns
id_url = [k for k in air_df.columns if 'id' in k or 'url' in k]
print(id_url)

air_select = air_df.drop(*[k for k in air_df.columns if 'id' in k or 'url' in k])
shape(air_select)
#Since the dataframe was converted from an RDD after splitting the string,
#there wont be any null values but empty '' values

#Creating a function to see number of empty cells per column
def empty(df):
    return df.select([Fn.count(Fn.when(Fn.col(c)=="", c)).alias(c) for c in df.columns]) \
        .toPandas().to_dict('records')[0]

empty(air_select)
#Dropping columns with more than 50% None values.
d = empty(air_select)
thresh = air_select.count()/2 #43276
air_select = air_select.drop(*[k for k in d.keys() if d[k]>thresh])
shape(air_select) #10 columns dropped
air_select.printSchema() #all strings except price
#Lets see distinct values for each column
air_select.agg(*(Fn.countDistinct(Fn.col(c)).alias(c) for c in air_select.columns)).toPandas().T
#dropping last_scraped for its insignificance
#dropping experiences for no variability
#dropping host_name for its insignificance
#dropping host_location, host_neighborhood, street, neighborhood, neighbourhood_cleansed, city, smart_location,
#     country_code, country, latitude, longitude, geolocation and keeping just state and zipcode
#dropping calendar_updated, calendar_last_scraped for their insignificance
air_select = air_select.drop(*['last_scraped','experiences_offered','host_name','host_neighbourhood',\
                               'host_location','street','neighbourhood','neighbourhood_cleansed','city',\
                               'smart_location','country','country_code','latitude','longitude','geolocation',\
                               'calendar_updated','calendar_last_scraped'])
shape(air_select)
#Segregating cols on types
text_cols = ['name','summary','space','description','neighborhood_overview','transit','access',\
             'interaction','house_rules','host_about','host_verifications','market','amenities','features']
date_cols = ['host_since','first_review','last_review']
cat_cols = ['host_response_time','state','zipcode','property_type','room_type','bed_type','cancellation_policy']
int_cols = ['host_listings_count', 'host_total_listings_count',\
            'accommodates','bedrooms','beds','cleaning_fee',\
            'guests_included','extra_people','minimum_nights','maximum_nights',\
            'availability_30','availability_60','availability_90','availability_365',\
            'number_of_reviews','review_scores_rating','review_scores_accuracy','review_scores_cleanliness',\
            'review_scores_checkin','review_scores_communication','review_scores_location',\
            'review_scores_value','calculated_host_listings_count']
float_cols = ['host_response_rate','bathrooms','reviews_per_month']

for col_name in date_cols:
    air_select = air_select.withColumn(col_name, Fn.col(col_name).cast(Tp.DateType()))

for col_name in int_cols:
    air_select = air_select.withColumn(col_name, Fn.col(col_name).cast(Tp.IntegerType()))
    
for col_name in float_cols:
    air_select = air_select.withColumn(col_name, Fn.col(col_name).cast(Tp.FloatType()))

air_select.printSchema()
def nans(df,cols):
    return df.select([Fn.count(Fn.when(Fn.isnan(c) | Fn.col(c).isNull(), c)).alias(c) for c in cols]) \
        .toPandas().to_dict('records')[0]

d = nans(air_select,int_cols+float_cols)
print(d)
thresh = air_select.count()/2 #43276
air_select = air_select.drop(*[k for k in d.keys() if d[k]>thresh])
shape(air_select)<jupyter_output>{'host_listings_count': 256, 'host_total_listings_count': 256, 'accommodates': 44, 'bedrooms': 94, 'beds': 172, 'cleaning_fee': 26655, 'guests_included': 0, 'extra_people': 0, 'minimum_nights': 0, 'maximum_nights': 0, 'availability_30': 0, 'availability_60': 0, 'availability_90': 0, 'availability_365': 0, 'number_of_reviews': 0, 'review_scores_rating': 23897, 'review_scores_accuracy': 24053, 'review_scores_cleanliness': 24021, 'review_scores_checkin': 24112, 'review_scores_communication': 24019, 'review_scores_location': 24113, 'review_scores_value': 24134, 'calculated_host_listings_count': 0, 'host_response_rate': 23090, 'bathrooms': 263, 'reviews_per_month': 22734}
Shape of dataframe is 86552 x 51<jupyter_text>Imputing Values<jupyter_code>#Imputing values
air_imputed = air_select
for c in text_cols+cat_cols:
    air_imputed = air_imputed.withColumn(c, Fn.when(Fn.trim(Fn.col(c))=="","unknown_"+c).otherwise(Fn.col(c)))
empty(air_imputed)
#As the average of columns are susceptible to outliers, imputing with median instead
cols = int_cols+float_cols
medians = {}
for col in cols:
    medians[col] = air_imputed.approxQuantile(col, [0.5], 0.05)[0]
air_imputed = air_imputed.fillna(medians)
nans(air_imputed,int_cols+float_cols)<jupyter_output><empty_output><jupyter_text>Handling outliers<jupyter_code>air_imputed.select(int_cols + float_cols).describe().toPandas().T
#There as some outliers especially in 'maximum_nights', lets cap them

bounds = {}
air_bounded = air_imputed
cols = int_cols + float_cols
for c in cols: 
    quantiles = air_bounded.approxQuantile(c, [0.25, 0.75], 0.005)
    IQR = quantiles[1] - quantiles[0]
    bounds[c] = [quantiles[0] - 1.5 * IQR, quantiles[1] + 1.5 * IQR]
    air_bounded = air_bounded.withColumn(c, Fn.when(Fn.col(c)<bounds[c][0],bounds[c][0]). \
                                         when(Fn.col(c)>bounds[c][1],bounds[c][1]).otherwise(Fn.col(c)))
for col_name in int_cols:
    air_bounded = air_bounded.withColumn(col_name, Fn.col(col_name).cast(Tp.IntegerType()))

air_bounded.select(int_cols + float_cols).describe().toPandas().T
#Some columns have no variability left, dropping those columns
cols = ['host_response_rate','bathrooms','review_scores_accuracy', \
        'review_scores_checkin','review_scores_communication','review_scores_location']
air_bounded = air_bounded.drop(*cols)
shape(air_bounded)<jupyter_output>Shape of dataframe is 86552 x 45<jupyter_text>Column Transformations<jupyter_code>#Rather than date columns, we will create new column for each having days since that date
def date_to_num_days(df, cols):
    df = df.withColumn("today", Fn.current_date())
    for c in cols:
        df = df.withColumn("num_days_"+c, Fn.datediff('today',Fn.col(c))) 
    return df.drop(*cols).drop("today")

air_transformed = date_to_num_days(air_bounded,date_cols)
#checking nans now,
nans(air_transformed,air_transformed.columns)
#imputing the new columns NaNs with -1
air_transformed = air_transformed.na.fill(-1)
shape(air_transformed)
#Lets take a look at our label column i.e. price

label_histogram = air_transformed.select('price').rdd.flatMap(lambda x: x).histogram(11)
pd.DataFrame(list(zip(*label_histogram)), columns=['bin', 'frequency']).set_index('bin').plot(kind='bar')<jupyter_output><empty_output><jupyter_text>Regression's underlying assumption is that the dependent variable should have a normal distribution. However from above, our dependent variable. doesnt follow that. So we need to transform the variable in order to achieve it. Let's take a log of price and see the distribution.<jupyter_code>label_histogram = air_transformed.select(Fn.log('price')).rdd.flatMap(lambda x: x).histogram(11)
pd.DataFrame(list(zip(*label_histogram)), columns=['bin', 'frequency']).set_index('bin').plot(kind='bar')

#A bit negatively skewed but this is still close to normal distribution, 
#so we create our label with Log(price)
air_transformed = air_transformed.withColumn('label', Fn.log('price'))
shape(air_transformed) 

#Note at this point we have 2 columns which are not features<jupyter_output>Shape of dataframe is 86552 x 46<jupyter_text>Correlation Analysis<jupyter_code>#Easiest way to see if there are some relationships among independent variables is to make Scatter Plots

import matplotlib.pyplot as plt

numeric_cols = [t[0] for t in air_transformed.dtypes if t[1] != 'string' and t[0] != 'price']
sampled_data = air_transformed.select(numeric_cols).sample(False, 0.1, 1).toPandas()
axs = pd.scatter_matrix(sampled_data, figsize=(10, 10))
n = len(sampled_data.columns)
for i in range(n):
    v = axs[i, 0]
    v.yaxis.label.set_rotation(0)
    v.yaxis.label.set_ha('right')
    v.set_yticks(())
    h = axs[n-1, i]
    h.xaxis.label.set_rotation(90)
    h.set_xticks(())
    
#SINCE THIS IS NOT SO EASY TO READ, 
#WE WILL MAKE A CORRELATION HEATMAP TO SEE THE RELATIONSHIPS, IF ANY, AMONG THE INDEPENDENT VARIABLES
import seaborn as sns

def heatcorr(df, cols):
    sampled_data = df.select(cols).sample(False, 0.1,1).toPandas()
    sns.set(style="white")
    corr = sampled_data.corr()
    mask = np.zeros_like(corr, dtype=np.bool)
    mask[np.triu_indices_from(mask)] = True
    f, ax = plt.subplots(figsize=(11, 9))
    cmap = sns.diverging_palette(220, 10, as_cmap=True)
    sns.heatmap(corr, mask=mask, cmap=cmap, vmax=.3, center=0,
                square=True, linewidths=.5, cbar_kws={"shrink": .5})

heatcorr(air_transformed, numeric_cols)
#much better
#Dropping 'host_total_listings_count','host_listings_count' for their correlation with 'calculated_host_listings_count'
#Adding accommodates_per_bedroom and dropping accommodates
#Dropping 'beds' because of its correlation with bedrooms
#Adding cleaning_fee_per_bedroom and dropping cleaning_fee
#Dropping 'guests_included' for its correlation with bedrooms
#Dropping 'availability_60','availability_90','availability_365', high correlation with availability_30
#Dropping 'number_of_reviews' for its correlation with 'reviews_per_month'
#Keeping 'review_scores_rating' and Dropping 'review_scores_value'
#Dropping 'num_days_first_review' for its correlation with other num_days columns
air_reduced = air_transformed
air_reduced = air_reduced.drop(*['host_total_listings_count','host_listings_count']) \
    .withColumn("accommodates_per_bedroom", \
                Fn.when(Fn.isnull(air_transformed.accommodates/air_transformed.bedrooms), \
                        air_transformed.accommodates) \
                .otherwise(air_transformed.accommodates/air_transformed.bedrooms)) \
    .drop(*['accommodates','beds']) \
    .withColumn("cleaning_fee_per_bedroom", \
                Fn.when(Fn.isnull(air_transformed.cleaning_fee/air_transformed.bedrooms), \
                        air_transformed.cleaning_fee) \
                .otherwise(air_transformed.cleaning_fee/air_transformed.bedrooms)) \
    .drop(*['cleaning_fee','guests_included','availability_60','availability_90','availability_365', \
            'number_of_reviews','review_scores_cleanliness','review_scores_value','num_days_first_review'])

numeric_cols = [c[0] for c in air_reduced.dtypes if c[1]!='string' and c[0] != 'price']
heatcorr(air_reduced, numeric_cols) 

#there are still correlations of ~0.3 but we will proceed as of now<jupyter_output><empty_output><jupyter_text>Handling descriptive text columns<jupyter_code>#lets see the sample
air_reduced.select(text_cols).sample(False, 0.0001,1).toPandas()
#Rather than 14 text columns, adding their respective wordcount columns
#This will give a sense of how price depends on number of words in a descriptive section

#Another thing we can add is the sentiment of these texts

#### NOTE #### 
#3 columns (host_verifications,amenities,features) are comma separated lists
#Each unique value in the list of these columns will be converted to separate columns
#the new columns will be binary indicating presence of that value in the list

comma_cols = ['host_verifications','amenities','features']
space_cols = list(set(text_cols) - set(comma_cols))

def wordcount(df, cols):
    for c in cols:
        df = df.withColumn('wc_'+c, Fn.size(Fn.split(Fn.col(c), ' ')))
    return df

import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer
sia = SentimentIntensityAnalyzer()

def get_sentiment(s):
    return sia.polarity_scores(s)['compound']

sentiment = Fn.udf(lambda t: get_sentiment(t), Tp.FloatType())

def sentiments(df, cols):
    for c in cols:
        df = df.withColumn('sentiment_'+c, sentiment(Fn.col(c)))
    return df.drop(*cols)

def feature_cols(df, cols):
    for col in cols:
        vals = sorted(list(df.select(Fn.explode(Fn.split(Fn.col(col), ','))).distinct().toPandas()['col']))
        for val in vals:
            df = df.withColumn(val.replace(' ','_'), Fn.when(Fn.col(col).like('%'+val+'%'), 1).otherwise(0))
    return df.drop(*cols)

air_expanded = wordcount(air_reduced, space_cols)
air_expanded = sentiments(air_expanded, space_cols)
air_expanded = feature_cols(air_expanded, comma_cols)
shape(air_expanded)

#lets see the sample again
air_expanded.sample(False, 0.0001,1).toPandas()<jupyter_output><empty_output><jupyter_text>Encoding categorical columns<jupyter_code>#cleaning Zipcode

import string
d = string.digits

def extractzip(v):
    z = ''
    for ch in v:
        if ch in d:
            z+=ch
    if (len(z)!=5 and len(z)!=9):
        z='unknown_zip'
    return z
    
zipclean = Fn.udf(lambda v: extractzip(v), Tp.StringType())
air_encoded = air_expanded.withColumn("zipcode", zipclean(air_expanded.zipcode))
air_encoded.select(Fn.length(Fn.col('zipcode')).alias('ziplength')).groupBy('ziplength').count().show()
#NOT USING STRINGINDEXER AND ONE HOT ENCODER
#USING THIS FUNCTION SO THAT THE COLUMN CAN BE RECOGNIZED FROM THE COLUMN NAME

def categorical_cols(df, cols):
    for col in cols:
        vals = sorted(list(df.select(Fn.col(col)).distinct().toPandas()[col]))
        for val in vals:
            df = df.withColumn(val.replace(' ','_'), Fn.when(Fn.col(col).like('%'+val+'%'), 1).otherwise(0))
    return df.drop(*cols)

air_encoded = categorical_cols(air_encoded, cat_cols)
shape(air_encoded)
air_final = air_encoded.withColumnRenamed('translation_missing:_en.hosting_amenity_49','hosting_amenity_49') \
    .withColumnRenamed('translation_missing:_en.hosting_amenity_50','hosting_amenity_50')
air_final.columns 

#just showing columns as descriptive statistics was overloading the system
#dropping duplicate rows if any
air_final = air_final.dropDuplicates()
print("\nFinally,")
shape(air_final) #Note 2 of the columns are label and price which are not features<jupyter_output>Finally,
Shape of dataframe is 86498 x 1144<jupyter_text>Assembling & scaling features<jupyter_code>air_final.sample(False, 0.0001,1).toPandas()
#Dividing the data
(trainingData, validationData, testData) = air_final.randomSplit([0.6, 0.2, 0.2], seed = 1)
print("Training row count:",trainingData.count())
print("Validation row count:",validationData.count())
print("Testing row count:",testData.count())
#Combining features
import pyspark.ml.feature as Ft
features = [c for c in air_final.columns if c != 'label' and c != 'price']
assembler = Ft.VectorAssembler(inputCols=features, outputCol="assembled_features")

assembledData = assembler.transform(air_final).drop(*features)
assembledData.show(1,False) #1142
#Standardizing
scaler = Ft.MinMaxScaler(inputCol="assembled_features", outputCol="features").fit(assembledData)

scaledData = scaler.transform(assembledData).drop("assembled_features")
scaledData.show(1,False)<jupyter_output>+-----+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------[...]<jupyter_text>Model building & evaluation<jupyter_code>#Building model
from pyspark.ml.regression import LinearRegression
lrModel1 = LinearRegression().fit(scaledData) #default settings
result1 = lrModel1.transform(scaledData)
print("R-squared of model is",lrModel1.summary.r2)

def metrics(df):
    df = df.select('price', 'prediction', (Fn.col('price')-Fn.exp(Fn.col('prediction'))).alias('error'))
    df = df.withColumn('sq_error', Fn.pow(Fn.col('error'),2))
    df = df.withColumn('abs_error', Fn.abs(Fn.col('error')))
    print("MAE in predictions is",(df.agg({'abs_error':'sum'}).collect()[0]['sum(abs_error)'])/df.count())
    print("RMSE in predictions is",np.power((df.agg({'sq_error':'sum'}).collect()[0]['sum(sq_error)'])/df.count(),0.5))

print("\nFor training,")
metrics(result1)
valresult1 = lrModel1.transform(scaler.transform(assembler.transform(validationData)))
print("\nFor validation,")
metrics(valresult1)
#For comparison,
result1.select("price").describe().show()<jupyter_output><empty_output><jupyter_text>Validation result is high but good that its not too high. This indicates the model is may be slightly over-trained.  Using Feature selector<jupyter_code>#REDUCING COUNT OF FEATURES TO 850
selector2 = Ft.ChiSqSelector(numTopFeatures=850, outputCol="selectedFeatures").fit(scaledData)
selectedData = selector2.transform(scaledData).drop('features')
lrModel2 = LinearRegression(featuresCol="selectedFeatures").fit(selectedData) #default settings
print("R-squared of model is",lrModel2.summary.r2)
print("\nFor training,")
result2 = lrModel2.transform(selectedData)
metrics(result2)
print("\nFor validation,")
valresult2 = lrModel2.transform(selector2.transform(scaler.transform(assembler.transform(validationData))))
metrics(valresult2)
#REDUCING COUNT OF FEATURES TO 500
selector3 = Ft.ChiSqSelector(numTopFeatures=500, outputCol="selectedFeatures").fit(scaledData)
selectedData = selector3.transform(scaledData).drop('features')
lrModel3 = LinearRegression(featuresCol="selectedFeatures").fit(selectedData) #default settings
print("R-squared of model is",lrModel3.summary.r2)
print("\nFor training,")
result3 = lrModel3.transform(selectedData)
metrics(result3)
print("\nFor validation,")
valresult3 = lrModel3.transform(selector3.transform(scaler.transform(assembler.transform(validationData))))
metrics(valresult3)
#REDUCING COUNT OF FEATURES TO 100
selector4 = Ft.ChiSqSelector(numTopFeatures=100, outputCol="selectedFeatures").fit(scaledData)
selectedData = selector4.transform(scaledData).drop('features')
lrModel4 = LinearRegression(featuresCol="selectedFeatures").fit(selectedData) #default settings
print("R-squared of model is",lrModel4.summary.r2)
print("\nFor training,")
result4 = lrModel4.transform(selectedData)
metrics(result4)
print("\nFor validation,")
valresult4 = lrModel4.transform(selector4.transform(scaler.transform(assembler.transform(validationData))))
metrics(valresult4)<jupyter_output><empty_output><jupyter_text>Trying other Models<jupyter_code>#Lets stick to 1133 features, and try other models
from pyspark.ml.regression import RandomForestRegressor
rfModel5 = RandomForestRegressor(seed=1).fit(scaledData)
print("\nFor training,")
result5 = rfModel5.transform(scaledData)
metrics(result5)
print("\nFor validation,")
valresult5 = rfModel5.transform(scaler.transform(assembler.transform(validationData)))
metrics(valresult5)
from pyspark.ml.regression import GBTRegressor
gbtModel6 = GBTRegressor(seed=1).fit(scaledData)
print("\nFor training,")
result6 = gbtModel6.transform(scaledData)
metrics(result6)
print("\nFor validation,")
valresult6 = gbtModel6.transform(scaler.transform(assembler.transform(validationData)))
metrics(valresult6)<jupyter_output><empty_output><jupyter_text>Parameter tuning for linear regression<jupyter_code>print(lrModel1.explainParams())
#Lets try L1 penalty... i.e. Lasso Regression
lrModel7 = LinearRegression(elasticNetParam=1).fit(scaledData)
print("R-squared of model is",lrModel7.summary.r2)
print("\nFor training,")
result7 = lrModel7.transform(scaledData)
metrics(result7)
print("\nFor validation,")
valresult7 = lrModel7.transform(scaler.transform(assembler.transform(validationData)))
metrics(valresult7)
len(list(lrModel7.coefficients)) #1133 coefficients
keys = [c for c in validationData.columns[:-7] if c!='price' and c!='label']
print(len(keys))
keys
values = list(lrModel7.coefficients)
len(values)
col_coeff = dict(zip(keys, values))
col_coeff
#########THESE HAVE ZERO COEFF
[k for k in col_coeff.keys() if col_coeff[k]==0]
for w in sorted(col_coeff, key=col_coeff.get, reverse=True):
    print (w, col_coeff[w])
##### OTHER THAN ZIPCODE, INFLUENTIAL FEATURES


for w in sorted(col_coeff, key=col_coeff.get, reverse=True):
    if isnumeric(w)==False and col_coeff[w]!=0:
        print (w, col_coeff[w])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1685364281108962, 'fraction_numerical': 0.018052869116698903, 'mean_word_length': 4.625241779497099, 'pattern_counts': {'":': 1, '<': 55, '<?xml version=': 0, '>': 58, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '498749', 'license': 'MIT', 'n_tokens_mistral': 7917, 'n_tokens_neox': 7404, 'n_words': 1607, 'path': 'AirBnB version 9.ipynb', 'repo_name': 'sindura93/Big-Data-Analytics---Course-Final-Project', 'size': '23265'}