Skip to content

Visualizalising tabular data (Airbnb)

लेखक: अच्युतुनी श्री हर्ष
इस विज़ुअलाइजेशन में, हम डेटा टाइप टेबल पर विभिन्न विज़ुअलाइजेशन प्रकारों को देखेंगे।

Matplotlib Python में विज़ुअलाइजेशन के लिए सबसे लोकप्रिय लाइब्रेरी है। Seaborn इसके ऊपर बनी है जिसमें एकीकृत विश्लेषण, विशेष प्लॉट और Pandas के साथ अच्छी इंटीग्रेशन है। Plotly express भी एक और विज़ुअलाइजेशन लाइब्रेरी है।

इसके अलावा Seaborn का पूरा गैलरी या Matplotlib देखें।

#disable some annoying warnings
import warnings
warnings.filterwarnings('ignore', category=FutureWarning)

#plots the figures in place instead of a new window
%matplotlib inline

import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px

import pandas as pd
import numpy as np

इस ब्लॉग में, हम लंदन के Airbnb डेटा पर नज़र डालेंगे। हम कुछ ट्रेंड, पैटर्न और डेटा पर मौसमी प्रभाव देखेंगे।
पहले, चलिए Airbnb की लोकप्रियता पर समय के साथ विचार करते हैं। लोकप्रियता समीक्षाओं की संख्या के समानुपाती है। reviews डेटा सेट को इंपोर्ट करते हैं:

reviews = pd.read_csv('reviews.csv.gz', parse_dates=['date'])
reviews.head()
listing_id id date reviewer_id reviewer_name comments
0 11551 30672 2010-03-21 93896 Shar-Lyn The flat was bright, comfortable and clean and...
1 11551 32236 2010-03-29 97890 Zane We stayed with Adriano and Valerio for a week ...
2 11551 41044 2010-05-09 104133 Chase Adriano was a fantastic host. We felt very at ...
3 11551 48926 2010-06-01 122714 John & Sylvia We had a most wonderful stay with Adriano and ...
4 11551 58352 2010-06-28 111543 Monique I'm not sure which of us misunderstood the s...

Scatter plot

समीक्षाओं की संख्या जानने के लिए, हम हर दिन कुल समीक्षाओं को जोड़ते हैं, और फिर इसे समय के साथ एक स्कैटर प्लॉट के रूप में प्लॉट करते हैं।

fs, axs = plt.subplots(1, figsize=(15,10))
plt.title("Airbnb London: Number of reviews across time", fontsize=30)

reviews.groupby('date').aggregate({'listing_id':'count'}).reset_index().plot.scatter(x = 'date', y = 'listing_id', alpha = 0.25, ax = axs)
axs.set_ylabel('Number of reviews', fontsize=20)
axs.set_xlabel('Time', fontsize=20)
plt.show()

png

इस प्लॉट से, हम निम्नलिखित देख सकते हैं:
1. महामारी से पहले व्यापार में एक गुणात्मक वृद्धि है और कोविड से संबंधित प्रतिबंधों के शुरू होने के बाद इसमें अचानक गिरावट आई है।
2. हर साल के भीतर मौसमी प्रभाव स्पष्ट है।
इन ट्रेंड्स को विस्तार से समझने के लिए, हमें प्लॉट के दो भागों पर ज़ूम करना चाहिए। पहले हमें डेटा के मौसमी प्रभाव और ट्रेंड को खोजना चाहिए, और फिर महामारी से पहले के एक वर्ष पर ज़ूम करना चाहिए। दूसरे, हम 2020-21 में ज़ूम कर सकते हैं ताकि कोविड से संबंधित लॉकडाउन के पैटर्न की पहचान कर सकें।

# getting the total number of reviews per day
reviews_time = reviews.groupby('date').aggregate({'listing_id':'count'}).reset_index()
reviews_time['year'] = reviews_time.date.dt.year

# Pre covid data
reviews_time_proper = reviews_time[(reviews_time.year < 2020)]

हम पूर्व-महामारी डेटा में मौसमी प्रभाव और ट्रेंड को पहचान सकते हैं (इस ब्लॉग में समझाया नहीं गया)। डेटा को ट्रेंड, मौसमी और यादृच्छिक घटक में विभाजित करने पर हमें निम्नलिखित मिलता है:

from statsmodels.tsa.seasonal import seasonal_decompose
result = seasonal_decompose(reviews_time_proper.listing_id, model='additive', period = 365)
print(result.plot())

png

हम डेटा में एक गुणात्मक ट्रेंड और एक दोहराने वाली स्थिर मौसमीता देख सकते हैं। पूर्व-महामारी ट्रेंड की पहचान और भविष्यवाणी करना और 2020-21 के लिए भविष्यवाणी करना।

y_values = result.trend[182:3136]
x_values = range(182, 3136)
coeffs = np.polyfit(x_values, y_values, 2)
poly_eqn = np.poly1d(coeffs)
y_hat = poly_eqn(range(365, len(reviews_time[reviews_time.year < 2020])))
y_hat1 = poly_eqn(range(len(reviews_time[reviews_time.year < 2020]), len(reviews_time)))

मौसमीता का अनुमान लगाने के लिए एक बहुपद समीकरण का उपयोग करना।

y_values = reviews_time[reviews_time.year == 2019].listing_id
x_values = range(365)
coeffs = np.polyfit(x_values, y_values, 15)
poly_eqn = np.poly1d(coeffs)
y_hat_seasonal = poly_eqn(range(365))

2020-21 में पैटर्न का अनुमान लगाने के लिए एक बहुपद समीकरण का उपयोग करना।

reviews_covid = reviews_time[reviews_time.year >= 2020]
y_values = reviews_covid.listing_id
x_values = range(len(reviews_covid.listing_id))
coeffs = np.polyfit(x_values, y_values, 17)
poly_eqn = np.poly1d(coeffs)
y_hat_covid = poly_eqn(range(25, len(reviews_covid.listing_id)-15))
from mpl_toolkits.axes_grid1.inset_locator import mark_inset, inset_axes
from matplotlib.patches import ConnectionPatch # making lines from top lot to below plot

# Two plots, the main on the top with height 20 inches and the bottom one is 10 inches.
fs, axs = plt.subplots(2, figsize=(20,30), gridspec_kw={'height_ratios': [2, 1]}, constrained_layout=True)

# Title
plt.suptitle("Airbnb London: Number of reviews across time", fontsize=30)

# First plot, main scatterplot
reviews.groupby('date').aggregate({'listing_id':'count'}).\
    reset_index().plot.scatter(x = 'date', y = 'listing_id', alpha = 0.25, ax = axs[0])

# adding the trend lines
axs[0].plot(reviews_time[365:len(reviews_time[reviews_time.year < 2020])].date,y_hat, color='red')
axs[0].plot(reviews_time[len(reviews_time[reviews_time.year < 2020]):].date,y_hat1, color='red', linestyle='dashed')

# Modifying the labels and title
axs[0].set_ylabel('Number of reviews', fontsize=15)
axs[0].set_xlabel('Time', fontsize=15)
axs[0].set_title('Total reviews of all types of rooms across London. A trend line is plotted taking the exponential growth of the business before Covid 19 and projecting the same trend during Covid. \n'+
'Seasonality before Covid is shown by zooming for 2019 (sample year). The affect of covid related lockdowns is also shown by zooming from 2020 onwards.',
                      fontsize=15, loc='left')

# Plotting the data within 2019 as a semantic zooming
axins = inset_axes(axs[0], 8, 5, loc=2, bbox_to_anchor=(0.15, 0.925),
                  bbox_transform=axs[0].figure.transFigure)

# Semantic zooming plot, scatterplot
reviews.groupby('date').aggregate({'listing_id':'count'}).reset_index().\
    plot.scatter(x = 'date', y = 'listing_id', alpha = 0.5, ax = axins)
#adding the trend lines, labels and title
plt.plot(reviews_time[reviews_time.year == 2019].date,y_hat_seasonal, color='red')
plt.ylabel('Number of reviews', fontsize=15)
plt.xlabel('Date', fontsize=15)
plt.title('Sesonality in a year (before COVID 19)', fontsize=20)

# Seasonality plot x and y limits
x1 = min(reviews_time[reviews_time.year == 2019].date)
x2 = max(reviews_time[reviews_time.year == 2019].date)
axins.set_xlim(x1, x2)
axins.set_ylim(0, 2000)
mark_inset(axs[0], axins, loc1=1, loc2=3, fc="none", ec="0.5")


# Second plot
x1 = min(reviews_time[reviews_time.year == 2020].date)
x2 = max(reviews_time[reviews_time.year >= 2020].date)

reviews_time[reviews_time.year >= 2020].plot.scatter(x = 'date', y = 'listing_id', alpha = 0.75, ax = axs[1])
axs[1].set_ylabel('Number of reviews', fontsize=15)
axs[1].set_xlabel('Date', fontsize=15)
axs[1].set_ylim(0, 1200)
axs[1].set_xlim(x1, x2)
axs[1].set_title('Effect of Covid19 on number of reviews', fontsize=20)

axs[1].plot(reviews_covid.date[25:-15],y_hat_covid, color='red')

# Adding annotations in the plot
axs[1].annotate(
        text = 'First Covid 19 advisory \n 3-16-2020', # the text
        xy=('3-16-2020', 500), #what to annotate
        xytext=('3-16-2020', 700), # where the text should be
        arrowprops=dict(arrowstyle="->",
                       connectionstyle="angle3,angleA=-90,angleB=0"),
        fontsize=15
    )
axs[1].annotate(
        'First Lockdown \n 3-23-2020', 
        xy=('3-23-2020', 350),
        xytext=('5-10-2020', 600), 
        arrowprops=dict(arrowstyle="->"),
        fontsize=15
    )
axs[1].annotate(
        'Easing restrictions \n 7-4-2020', 
        xy=('7-4-2020', 50), 
        xytext=('6-15-2020', 700),
        arrowprops=dict(arrowstyle="->"),
        fontsize=15
    )
axs[1].annotate(
        'Restrictions eased further \n 8-14-2020',
        xy=('8-14-2020', 250), 
        xytext=('8-1-2020', 600),
        arrowprops=dict(arrowstyle="->"),
        fontsize=15
    )
axs[1].annotate(
        'Second Lockdown \n 10-31-2020', 
        xy=('10-31-2020', 165),
        xytext=('10-1-2020', 700), 
        arrowprops=dict(arrowstyle="->"),
        fontsize=15
    )
axs[1].annotate(
        'Easing restrictions \n 12-2-2020', 
        xy=('12-2-2020', 120), 
        xytext=('11-10-2020', 600), 
        arrowprops=dict(arrowstyle="->"),
        fontsize=15
    )
axs[1].annotate(
        'Christmas \n 12-25-2020',
        xy=('12-25-2020', 160), 
        xytext=('12-25-2020', 700), 
        arrowprops=dict(arrowstyle="->"),
        fontsize=15
    )
axs[1].annotate(
        'Third Lockdown \n 1-6-2021',
        xy=('1-6-2021', 140),
        xytext=('1-20-2021', 600), 
        arrowprops=dict(arrowstyle="->"),
        fontsize=15
    )
axs[1].annotate(
        'Schools reopen \n 3-8-2021', 
        xy=('3-8-2021', 125), 
        xytext=('2-25-2021', 700), 
        arrowprops=dict(arrowstyle="->"),
        fontsize=15
    )
axs[1].annotate(
        'All restrictions removed \n 6-21-2021', 
        xy=('6-21-2021', 400),
        xytext=('5-21-2021', 700), 
        arrowprops=dict(arrowstyle="->"),
        fontsize=15
    )
axs[1].annotate(
        'Non essentials reopen \n 4-12-2021', 
        xy=('4-12-2021', 270), 
        xytext=('4-1-2021', 600),
        arrowprops=dict(arrowstyle="->"),
        fontsize=15
    )

# plotting the connections between the two plots
con = ConnectionPatch(xyA=(x1,-105), xyB=(x1,1200), coordsA="data", coordsB="data",
                      axesA=axs[0], axesB=axs[1])
axs[1].add_artist(con)
# con = ConnectionPatch(axesA=axs[0], axesB=axs[1])
con = ConnectionPatch(xyA=(x2,-105), xyB=(x2,1200), coordsA="data", coordsB="data",
                      axesA=axs[0], axesB=axs[1])
axs[1].add_artist(con)

plt.show()

png

इस प्लॉट से, हम निम्नलिखित देख सकते हैं:
1. समय के साथ समीक्षाओं में परिवर्तन और महामारी से पहले का ट्रेंड कैद किया गया है। ट्रेंड को 2020-21 में बढ़ने के लिए बढ़ाया गया है, जो दिखाता है कि अगर महामारी नहीं होती तो वृद्धि होती।
2. डेटा में मौसमी प्रभाव को एक नमूना वर्ष में सेमांटिक ज़ूमिंग द्वारा दिखाया गया है। हम देख सकते हैं कि Airbnb जुलाई, सितंबर और जनवरी में अधिक लोकप्रिय है।
3. हम महामारी के प्रभाव को समीक्षाओं की संख्या पर भी देख सकते हैं। हम 2020 के पहले कुछ महीनों में एक तेज गिरावट देख सकते हैं, और फिर लॉकडाउन और उद्घाटन ने कुल समीक्षाओं की संख्या को कैसे प्रभावित किया।

Sunburst और pie charts

अब हम डेटा में गहराई से जाएंगे और उन प्रकार की लिस्टिंग और स्थानों को देखेंगे जिन्होंने इस वृद्धि में योगदान दिया है। पूर्ण listings डेटा सेट को इंपोर्ट करते हैं।

listing_detailed = pd.read_csv('listings.csv.gz')
pd.options.display.max_columns = None # to show all the columns
listing_detailed.head()
id listing_url scrape_id last_scraped name description neighborhood_overview picture_url host_id host_url host_name host_since host_location host_about host_response_time host_response_rate host_acceptance_rate host_is_superhost host_thumbnail_url host_picture_url host_neighbourhood host_listings_count host_total_listings_count host_verifications host_has_profile_pic host_identity_verified neighbourhood neighbourhood_cleansed neighbourhood_group_cleansed latitude longitude property_type room_type accommodates bathrooms bathrooms_text bedrooms beds amenities price minimum_nights maximum_nights minimum_minimum_nights maximum_minimum_nights minimum_maximum_nights maximum_maximum_nights minimum_nights_avg_ntm maximum_nights_avg_ntm calendar_updated has_availability availability_30 availability_60 availability_90 availability_365 calendar_last_scraped number_of_reviews number_of_reviews_ltm number_of_reviews_l30d 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 license instant_bookable calculated_host_listings_count calculated_host_listings_count_entire_homes calculated_host_listings_count_private_rooms calculated_host_listings_count_shared_rooms reviews_per_month
0 11551 https://www.airbnb.com/rooms/11551 20210706215658 2021-07-08 Arty and Bright London Apartment in Zone 2 Unlike most rental apartments my flat gives yo... Not even 10 minutes by metro from Victoria Sta... https://a0.muscache.com/pictures/b7afccf4-18e5... 43039 https://www.airbnb.com/users/show/43039 Adriano 2009-10-03 London, England, United Kingdom Hello, I'm a friendly Italian man with a posit... within an hour 100% 85% f https://a0.muscache.com/im/pictures/user/5f182... https://a0.muscache.com/im/pictures/user/5f182... Brixton 0.0 0.0 ['email', 'phone', 'reviews', 'jumio', 'offlin... t t London, United Kingdom Lambeth NaN 51.46095 -0.11758 Entire apartment Entire home/apt 4 NaN 1 bath 1.0 3.0 ["Hot water", "Hair dryer", "Smoke alarm", "Fi... $99.00 2 1125 2.0 2.0 1125.0 1125.0 2.0 1125.0 NaN t 0 30 58 290 2021-07-08 193 1 0 2011-10-11 2018-04-29 4.57 4.62 4.58 4.78 4.85 4.53 4.52 NaN f 3 3 0 0 1.63
1 13913 https://www.airbnb.com/rooms/13913 20210706215658 2021-07-08 Holiday London DB Room Let-on going My bright double bedroom with a large window h... Finsbury Park is a friendly melting pot commun... https://a0.muscache.com/pictures/miso/Hosting-... 54730 https://www.airbnb.com/users/show/54730 Alina 2009-11-16 London, England, United Kingdom I am a Multi-Media Visual Artist and Creative ... within a few hours 100% 100% f https://a0.muscache.com/im/users/54730/profile... https://a0.muscache.com/im/users/54730/profile... LB of Islington 3.0 3.0 ['email', 'phone', 'facebook', 'reviews', 'off... t t Islington, Greater London, United Kingdom Islington NaN 51.56861 -0.11270 Private room in apartment Private room 2 NaN 1 shared bath 1.0 0.0 ["Host greets you", "Dryer", "Hot water", "Sha... $65.00 1 29 1.0 1.0 29.0 29.0 1.0 29.0 NaN t 30 60 90 365 2021-07-08 21 0 0 2011-07-11 2011-09-13 4.85 4.79 4.84 4.79 4.89 4.63 4.74 NaN f 2 1 1 0 0.17
2 15400 https://www.airbnb.com/rooms/15400 20210706215658 2021-07-08 Bright Chelsea Apartment. Chelsea! Lots of windows and light. St Luke's Gardens ... It is Chelsea. https://a0.muscache.com/pictures/428392/462d26... 60302 https://www.airbnb.com/users/show/60302 Philippa 2009-12-05 Kensington, England, United Kingdom English, grandmother, I have travelled quite ... NaN NaN NaN f https://a0.muscache.com/im/users/60302/profile... https://a0.muscache.com/im/users/60302/profile... Chelsea 1.0 1.0 ['email', 'phone', 'reviews', 'jumio', 'govern... t t London, United Kingdom Kensington and Chelsea NaN 51.48780 -0.16813 Entire apartment Entire home/apt 2 NaN 1 bath 1.0 1.0 ["Dryer", "Hot water", "Shampoo", "Hair dryer"... $75.00 10 50 10.0 10.0 50.0 50.0 10.0 50.0 NaN t 0 14 44 319 2021-07-08 89 0 0 2012-07-16 2019-08-10 4.79 4.84 4.88 4.87 4.82 4.93 4.73 NaN t 1 1 0 0 0.81
3 17402 https://www.airbnb.com/rooms/17402 20210706215658 2021-07-08 Superb 3-Bed/2 Bath & Wifi: Trendy W1 You'll have a wonderful stay in this superb mo... Location, location, location! You won't find b... https://a0.muscache.com/pictures/39d5309d-fba7... 67564 https://www.airbnb.com/users/show/67564 Liz 2010-01-04 Brighton and Hove, England, United Kingdom We are Liz and Jack. We manage a number of ho... within a day 70% 90% f https://a0.muscache.com/im/users/67564/profile... https://a0.muscache.com/im/users/67564/profile... Fitzrovia 18.0 18.0 ['email', 'phone', 'reviews', 'jumio', 'offlin... t t London, Fitzrovia, United Kingdom Westminster NaN 51.52195 -0.14094 Entire apartment Entire home/apt 6 NaN 2 baths 3.0 3.0 ["Dryer", "Hot water", "Shampoo", "Hair dryer"... $307.00 4 365 4.0 4.0 365.0 365.0 4.0 365.0 NaN t 6 6 17 218 2021-07-08 43 1 1 2011-09-18 2019-11-02 4.69 4.80 4.68 4.66 4.66 4.85 4.59 NaN f 15 15 0 0 0.36
4 17506 https://www.airbnb.com/rooms/17506 20210706215658 2021-07-08 Boutique Chelsea/Fulham Double bed 5-star ensuite Enjoy a chic stay in this elegant but fully mo... Fulham is 'villagey' and residential – a real ... https://a0.muscache.com/pictures/11901327/e63d... 67915 https://www.airbnb.com/users/show/67915 Charlotte 2010-01-05 London, England, United Kingdom Named best B&B by The Times. Easy going hosts,... NaN NaN NaN f https://a0.muscache.com/im/users/67915/profile... https://a0.muscache.com/im/users/67915/profile... Fulham 3.0 3.0 ['email', 'phone', 'jumio', 'selfie', 'governm... t t London, United Kingdom Hammersmith and Fulham NaN 51.47935 -0.19743 Private room in townhouse Private room 2 NaN 1 private bath 1.0 1.0 ["Air conditioning", "Carbon monoxide alarm", ... $150.00 3 21 3.0 3.0 21.0 21.0 3.0 21.0 NaN t 29 59 89 364 2021-07-08 0 0 0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN f 2 0 2 0 NaN

कमरे के प्रकार और स्थान के अनुसार समीक्षाओं की संख्या को देखते हुए, हमारे पास है:

listing_detailed.groupby('room_type').\
    aggregate({'number_of_reviews':'sum'}).\
    plot.pie(y='number_of_reviews', figsize=(10, 10), 
             autopct='%1.1f%%' # to add the percentages text
            )
plt.ylabel("")
plt.title("Airbnb London: Number of reviews by room type", fontsize=20)
plt.show()

png

# Function to combine last few classes ito one class
total_other_reviews = 0
def combine_last_n(df):
    df1 = df.copy()
    global total_other_reviews 
    total_other_reviews = (sum(df[df.number_of_reviews <= 5000].number_of_reviews))
    df1.loc[df.number_of_reviews <= 10000, 'number_of_reviews'] = 0
    return df1.number_of_reviews


listing_detailed.groupby('neighbourhood_cleansed').\
    aggregate({'number_of_reviews':'sum'}).\
    sort_values('number_of_reviews', ascending = False).\
    assign(no_reviews_alt = combine_last_n).\
    append(pd.Series({'number_of_reviews': 0, 'no_reviews_alt':total_other_reviews}, name='Others')).\
    plot.pie(y='no_reviews_alt', figsize=(10, 10), legend=None, rotatelabels=True, 
             wedgeprops=dict(width=.5) # for donut shape
            )
plt.ylabel("")

png

हम देख सकते हैं कि प्राइवेट रूम सबसे लोकप्रिय है जिसमें सबसे अधिक समीक्षाएं हैं, इसके बाद पूरा घर/अपार्टमेंट है। अन्य अप्रासंगिक हैं। इसी तरह, वेस्टमिंस्टर और कैम्बडेन लंदन में शीर्ष दो स्थान हैं। एक सनबर्स्ट चार्ट का उपयोग करके, हम इन दोनों को एक साथ देख सकते हैं।

listings_sunburst = listing_detailed.groupby(['room_type', 'neighbourhood_cleansed']).\
    aggregate({'number_of_reviews':'sum'}).\
    sort_values('number_of_reviews', ascending = False).reset_index()

fig = px.sunburst(listings_sunburst, path=['room_type', 'neighbourhood_cleansed'], values='number_of_reviews',
                  hover_data = ['room_type', 'number_of_reviews'], hover_name = 'neighbourhood_cleansed',
                 title = 'Airbnb London: Popularity sunburst chart', width = 900, height = 900)
fig.show()

png

Parallel Coordinates

इस चार्ट से, हम देख सकते हैं कि वेस्टमिंस्टर सभी कमरे के प्रकारों के लिए सबसे लोकप्रिय स्थान है, दूसरे और तीसरे लोकप्रिय स्थान विभिन्न कमरे के प्रकारों के लिए अलग हैं। चलिए केंसिंग्टन और चेल्सी का उदाहरण लेते हैं, हम इस क्षेत्र की हर कमरे के प्रकार में रैंकिंग देख सकते हैं।

top_20_names = list(listings_sunburst.sort_values('number_of_reviews', ascending = False).head(30)['neighbourhood_cleansed'])

listings_sunburst_wide = listings_sunburst.\
    pivot(index='neighbourhood_cleansed', columns='room_type', values='number_of_reviews').reset_index()
listings_sunburst_wide = listings_sunburst_wide.replace(np.nan, 0)

fig = px.parallel_coordinates(
    listings_sunburst_wide
)

# add the pink line to highlight Kensington
fig.data[0]['dimensions'][0]['constraintrange'] = [50000, 60000]

fig.update_layout(title_text='Kensington and Chelsea (in blue) popularity ranking across room types', title_x=0.7, title_y = 0.05)

fig.show()

png

इस चार्ट से हम देख सकते हैं कि केंसिंग्टन (नीले रंग में हाइलाइट किया गया) 'Entire Home Apartment' के लिए शीर्ष 2 में है जबकि यह साझा कमरे के लिए शीर्ष 5 में नहीं है। यह चार्ट, साथ ही ऊपर का सनबर्स्ट, दिखाता है कि विभिन्न कमरे के प्रकारों के लिए कौन से स्थान लोकप्रिय हैं।

Bar chart और Steam graphs

इस लोकप्रियता के अनुपात में समय के साथ कैसे बदलाव आता है? इसे देखने का एक तरीका स्टैक्ड बार चार्ट का उपयोग करना है।

reviews_detailed = pd.merge(listing_detailed[['id', 'neighbourhood_cleansed', 'room_type']], reviews, left_on = 'id', right_on = 'listing_id')
fs, axs = plt.subplots(1, figsize=(15,10))
reviews_detailed['year'] = reviews_detailed.date.dt.year
reviews_detailed.groupby(['year', 'room_type']).\
    aggregate({'listing_id':'count'}).\
    unstack().reset_index().\
    plot.bar(x = 'year', y = 'listing_id', ax = axs, stacked = True)
plt.title('Popularity of different rooms across the years', fontsize = 25)
plt.legend(loc='upper right', title="Type of room", fontsize='medium',fancybox=True)
axs.set_ylabel('Number of reviews', fontsize=20)
axs.set_xlabel('Years', fontsize=20)

plt.show()

png

इस बार चार्ट से हम वही वृद्धि देख सकते हैं जो हमने स्कैटर प्लॉट में देखी थी, यानी 2019 तक एक गुणात्मक वृद्धि, और महामारी के कारण एक बाद की गिरावट। इसे देखने का एक और अच्छा तरीका स्ट्रीमग्राफ के माध्यम से है। स्ट्रीमग्राफ में, हम वर्गों के भीतर मौसमी प्रभाव देख सकते हैं।

fs, ax = plt.subplots(1, figsize=(15,10))
reviews_room = reviews_detailed.groupby(['date', 'room_type']).\
    aggregate({'listing_id':'count'}).\
    unstack().reset_index()
reviews_room.columns = ['date', 'Entire home/apt', 'Hotel room', 'Private room', 'Shared room']
ax.stackplot(reviews_room.date, 
            list(reviews_room[['Entire home/apt', 'Hotel room', 'Private room', 'Shared room']].fillna(0).\
                 to_numpy().transpose()),
            baseline='wiggle')
plt.title('Popularity of different rooms across the years', fontsize=20, y=1.05,loc='left')
ax.text("2010",1050,'Streamgraph of the number of reviews across time',ha='left', fontsize=12)

plt.legend(['Entire home/apt', 'Hotel room', 'Private room', 'Shared room'], loc='upper left', title="Type of room")
ax.set_xlabel('Years', fontsize=20)

plt.show()

png

Heatmap

कौन से स्थान बेहतर हैं, और कौन से स्थानों को अपनी रेटिंग में सुधार करना चाहिए? हम स्थान के भीतर हर मेज़बान के लिए रेटिंग का औसत निकालकर स्थान के औसत रेटिंग को खोज सकते हैं।

location_rating = listing_detailed.groupby(['neighbourhood_cleansed', 'room_type']).\
    aggregate({'review_scores_rating':'mean'}).unstack().reset_index()
location_rating.columns = ['neighbourhood', 'Entire home/apt', 'Hotel room', 'Private room', 'Shared room']
location_rating.index = location_rating.neighbourhood

औसत रेटिंग को विज़ुअलाइज़ करने के लिए एक हीटमैप का उपयोग करने के कुछ तरीके हैं।

fig, ax = plt.subplots(figsize=[20,len(location_rating)/3.3])
sns.heatmap(data=location_rating[['Entire home/apt', 'Hotel room', 'Private room', 'Shared room']],
            annot=False, cbar_kws={"shrink":0.5,"orientation":'vertical'},linewidths=0.004,linecolor='grey',vmin=0,vmax=5, center = 0.25)

plt.show()

png

हालांकि यह हीटमैप हमें प्रत्येक जिले के लिए औसत रेटिंग प्रदान करता है, हम स्पष्टता के लिए निम्नलिखित विवरण और जोड़ सकते हैं:
1. लंदन में जिले का स्थान (जैसे: केंद्रीय लंदन)
2. प्रत्येक स्थान के भीतर सबसे अच्छे से लेकर सबसे खराब रेटेड जिलों तक व्यवस्थित
3. स्केल और मानव रेटिंग मनोविज्ञान के आधार पर उचित रंग चयन: औसत मानव रेटिंग 2.5 से नीचे का मतलब खराब रेटिंग है और 4.5 से ऊपर का मतलब बहुत अच्छी रेटिंग है (5 में से)। नकारात्मक-पॉजिटिव संबंध को प्रदर्शित करने के लिए एक विभाजन लाल-हरे स्केल का उपयोग करना अधिक स्वाभाविक है।

def add_regions(df, borough_col_name):
    """
    This function takes as input a dataframe with a column which includes London's borough name
    Then returns the same dataframw with sub regions names added for each borough
    """
    central = ['Camden', 'City of London', 'Kensington and Chelsea', 'Islington', 'Lambeth', 'Southwark', 'Westminster']
    east = ['Barking and Dagenham', 'Bexley', 'Greenwich', 'Hackney', 'Havering', 'Lewisham', 'Newham', 'Redbridge', 'Tower Hamlets', 'Waltham Forest']
    north = ['Barnet', 'Enfield', 'Haringey']
    south = ['Bromley', 'Croydon', 'Kingston upon Thames', 'Merton', 'Sutton', 'Wandsworth']
    west = ['Brent', 'Ealing', 'Hammersmith and Fulham', 'Harrow', 'Richmond upon Thames', 'Hillingdon', 'Hounslow']


    df['sub_regions'] = np.where((df[borough_col_name] == central[0]) | 
                                       (df[borough_col_name] == central[1]) |
                                       (df[borough_col_name] == central[2]) |
                                       (df[borough_col_name] == central[3]) |
                                       (df[borough_col_name] == central[4]) |
                                       (df[borough_col_name] == central[5]) |
                                       (df[borough_col_name] == central[6]) 
                                       , 'Central', 'no')

    df['sub_regions'] = np.where((df[borough_col_name] == east[0]) | 
                                       (df[borough_col_name] == east[1]) |
                                       (df[borough_col_name] == east[2]) |
                                       (df[borough_col_name] == east[3]) |
                                       (df[borough_col_name] == east[4]) |
                                       (df[borough_col_name] == east[5]) |
                                       (df[borough_col_name] == east[6]) |
                                       (df[borough_col_name] == east[7]) |
                                       (df[borough_col_name] == east[8]) |
                                       (df[borough_col_name] == east[9]) 
                                       , 'East', df['sub_regions'])

    df['sub_regions'] = np.where((df[borough_col_name] == north[0]) | 
                                       (df[borough_col_name] == north[1]) |
                                       (df[borough_col_name] == north[2]) 
                                       , 'North', df['sub_regions'])

    df['sub_regions'] = np.where((df[borough_col_name] == south[0]) | 
                                       (df[borough_col_name] == south[1]) | 
                                       (df[borough_col_name] == south[2]) | 
                                       (df[borough_col_name] == south[3]) | 
                                       (df[borough_col_name] == south[4]) | 
                                       (df[borough_col_name] == south[5])  
                                       , 'South', df['sub_regions'])

    df['sub_regions'] = np.where((df[borough_col_name] == west[0]) | 
                                       (df[borough_col_name] == west[1]) | 
                                       (df[borough_col_name] == west[2]) | 
                                       (df[borough_col_name] == west[3]) | 
                                       (df[borough_col_name] == west[4]) | 
                                       (df[borough_col_name] == west[5]) | 
                                       (df[borough_col_name] == west[6]) 
                                       , 'West', df['sub_regions'])

    return df
def sort_data(df):
    """
    Groups the data by location and sorts the data based on average rating within the location. 
    Different locations are sorted by average rating.
    """
    df1 = df.copy()
    df1['average_rating'] = (df1['Entire home/apt']+ df1['Hotel room'] + df1['Private room']+df1['Shared room'])/4
    df1['location_average'] = df1.groupby('sub_regions')['average_rating'].transform('mean')
    df1 = df1.sort_values(['location_average', 'average_rating'], ascending=False)
    return df1[['Private room', 'Entire home/apt', 'Hotel room', 'Shared room', 'sub_regions']]

def prepare_reg_annotation_lists():
    """
    Creates the annotation in the form of groups within the data. Displays this on the right hand side of the heatmap.
    """
    reg_sorted_list = location_rating.sub_regions.unique()
    reg_len = location_rating.sub_regions.value_counts().to_dict()

    sorted_len = []
    cum_len = []
    arrow_style_str_list = []

    # here we define the width of the bracket used, which is proportional to the number of boroughs within a sub region
    for i in range(5):
        if i==0:
            value = reg_len[reg_sorted_list[i]]
            cum_value = value
        else:
            value = reg_len[reg_sorted_list[i]]
            cum_value += value

        sorted_len.append(value)
        cum_len.append(cum_value)
        arrow_style_str_list.append('-[,widthB='+str((value/1.2)-0.5)+',lengthB=0.7')

    # here we define ticks which represent the center location of each sub region relative to the heatmap
    Ticks = []
    for i in range(5):
        if i == 0:
            Ticks.append(1-((cum_len[i]/2)/33))
        else:
            Ticks.append(1-((((cum_len[i]-cum_len[i-1])/2)+cum_len[i-1])/33))

    return Ticks,arrow_style_str_list,reg_sorted_list


location_rating = location_rating.replace(np.nan, 2.5)
location_rating = add_regions(location_rating, 'neighbourhood')
location_rating = sort_data(location_rating)
Ticks_h,arrow,region = prepare_reg_annotation_lists()

एक विभाजन लाल-हरे पैलेट को अच्छे समीक्षाओं और खराब समीक्षाओं का प्रतिनिधित्व करने के लिए चुना गया है।

red_green_cmap = sns.diverging_palette(10, 133,as_cmap=True)
red_green_cmap
png

fig, ax = plt.subplots(figsize=[20,len(location_rating)/3.3])
sns.heatmap(data=location_rating[['Private room', 'Entire home/apt', 'Hotel room', 'Shared room']],
            annot=False, cbar_kws={"shrink":0.5,"orientation":'vertical'},linewidths=0.004,linecolor='grey',vmin=2.25,vmax=4.75,
           cmap = red_green_cmap)
plt.title("Average ratings for different locations in London", fontsize=20, y=1.1,loc='left')
plt.text(0,-1,'Heatmap depicting the ratings among different locations in London. If no rating is available, minimum rating of 2.5 is assumed. \n Good ratings are ratings above 3.5 while bad ratings are below. The data is grouped by location (right) and sorted by average rating.',ha='left', fontsize=12)
ax.set_ylabel('')

#annotation for the borough
for i in range(5):
    ax.annotate(region[i],xy=(1.01,Ticks_h[i]), xytext=(1.02,Ticks_h[i]), xycoords='axes fraction',
                ha='left',va='center', arrowprops=dict(arrowstyle=arrow[i],lw=1))       

png

हम देख सकते हैं कि रेटिंग प्राइवेट रूम और पूरे घर के लिए अच्छी हैं। प्रत्येक क्षेत्र में सबसे अच्छा स्थान है:
- पश्चिम: रिचमंड अपॉन थेम्स
- केंद्रीय: कैम्बडेन
- उत्तर: एनफील्ड
- पूर्व: हैकनी
- दक्षिण: क्रॉयडन

Treemap

इस संदर्भ में, विभिन्न स्थानों की रेटिंग की तुलना करना उचित नहीं है क्योंकि हमने देखा है कि उनकी लोकप्रियता अलग है। इसलिए एक स्थान में 10 समीक्षाएं हो सकती हैं जबकि दूसरे में 100 समीक्षाएं हो सकती हैं। उन्हें संयोजित करने के लिए, हम एक ट्रेमैप का उपयोग कर सकते हैं।

reviews_treemap = listing_detailed.groupby(['neighbourhood_cleansed']).\
    aggregate({'review_scores_rating':'mean', 'number_of_reviews':'sum'}).reset_index()
reviews_treemap = add_regions(reviews_treemap, 'neighbourhood_cleansed')

#change col names for nice viz on hover
reviews_treemap.columns = ['neighbourhood', 'Average Reviews', 'Number of reviews', 'regions']

fig = px.treemap(reviews_treemap, path=[px.Constant("London"), 'regions', 'neighbourhood'], values='Number of reviews', 
           color = 'Average Reviews', color_continuous_scale='RdBu')
fig.update_layout(margin = dict(t=50, l=25, r=25, b=25))
fig.update_layout(title_text='Airbnb London: Ratings overview')
png

Word cloud

अब जब हमने रेटिंग को अच्छे और खराब रेटिंग में वर्गीकृत कर लिया है, चलिए इन रेटिंग में टेक्स्ट को देखते हैं और यह पहचानते हैं कि क्या कोई पैटर्न हैं।

reviews_detailed_text = pd.merge(listing_detailed[['id', 'description','neighborhood_overview', 'host_about', 'review_scores_rating']], reviews, left_on = 'id', right_on = 'listing_id')
reviews_detailed_positive_text = reviews_detailed_text[reviews_detailed_text.review_scores_rating > 3.75]
reviews_detailed_negative_text = reviews_detailed_text[reviews_detailed_text.review_scores_rating <= 3.75]

सकारात्मक और नकारात्मक सेट के लिए प्रत्येक 100 यादृच्छिक समीक्षाओं का चयन करते हैं।

pos_reviews_text = reviews_detailed_positive_text.sample(n=100, random_state = 2).comments.str.cat()
neg_reviews_text = reviews_detailed_negative_text.sample(n=100, random_state = 3).comments.str.cat()

सकारात्मक समीक्षाओं के लिए वर्ड क्लाउड

# !pip install wordcloud
from wordcloud import WordCloud, STOPWORDS, ImageColorGenerator
from PIL import Image
mask_pos = np.array(Image.open('thumbs-up-xxl.png'))

# word cloud, good vs bad ratings
stop_words = ["https", "co", "RT", 'br', '<br>', '<br/>', '\r', 'r'] + list(STOPWORDS)
wordcloud_pattern = WordCloud(stopwords = stop_words, background_color="white", max_words=2000, max_font_size=256,
               random_state=42, mask = mask_pos, width=mask_pos.shape[1], height=mask_pos.shape[0])
wordcloud_positive = wordcloud_pattern.generate(pos_reviews_text)

plt.imshow(wordcloud_positive, interpolation='bilinear')
plt.axis("off")
plt.show()

png

नकारात्मक समीक्षाओं के लिए वर्ड क्लाउड

mask_neg = np.array(Image.open('thumbs-down-xxl.png'))

# word cloud, good vs bad ratings
wordcloud_pattern = WordCloud(stopwords = stop_words, background_color="white", max_words=2000, max_font_size=256,
               random_state=42, mask = mask_neg, width=mask_neg.shape[1], height=mask_neg.shape[0])
wordcloud_neg = wordcloud_pattern.generate(neg_reviews_text)

plt.imshow(wordcloud_neg, interpolation='bilinear')
plt.axis("off")
plt.show()

png

सकारात्मक और नकारात्मक समीक्षाओं को एक प्लॉट में मिलाकर अंतर की तुलना करें:

fs, axs = plt.subplots(1, 2, figsize=(20,10))
plt.suptitle("Airbnb London: Wordcloud of positive and negative reviews", fontsize=20)
plt.figtext(0.5, 0.925, 'Wordcloud derived from a random sample of 100 positive and 100 negative reviews.',
            wrap=True, horizontalalignment='center', fontsize=12)

axs[0].imshow(wordcloud_positive, interpolation='bilinear')
axs[0].axis("off")

axs[1].imshow(wordcloud_neg, interpolation='bilinear')
axs[1].axis("off")

plt.show()

png

इन प्लॉट्स से, हम देख सकते हैं कि स्वचालित पोस्टिंग, मेज़बानों द्वारा रद्दीकरण, और आगमन के दौरान समस्याएं मुख्य मुद्दे हैं जिन पर Airbnb को ध्यान देना चाहिए।

Correlation matrix

डेटा के भीतर विभिन्न पैरामीटर कैसे संबंधित हैं। रेटिंग्स की उपलब्धता या अधिकतम रातों के साथ कैसे संबंध है? इसे एक संबंध प्लॉट का उपयोग करके समझाया जा सकता है।

listing_detailed['host_response_rate'] = listing_detailed['host_response_rate'].str.replace('%', '').astype(float)
listing_detailed['host_acceptance_rate'] = listing_detailed['host_acceptance_rate'].str.replace('%', '').astype(float)
listing_detailed['price'] = listing_detailed['price'].str.replace('$', '').str.replace(',', '').astype(float)
col_for_corr = ['review_scores_rating', 'review_scores_accuracy', 'review_scores_cleanliness', 'review_scores_checkin', 'review_scores_communication', 'review_scores_location', 'review_scores_value',
                'number_of_reviews', 'number_of_reviews_ltm', 'number_of_reviews_l30d', 'reviews_per_month',
                'availability_30', 'availability_60', 'availability_90', 'availability_365',
                'minimum_nights', 'maximum_nights', 'minimum_minimum_nights', 'maximum_minimum_nights', 'minimum_maximum_nights', 'maximum_maximum_nights', 'minimum_nights_avg_ntm', 'maximum_nights_avg_ntm',
                'bedrooms', 'beds','accommodates', 'price', 
                'host_response_rate', 'host_acceptance_rate', 'host_total_listings_count']


f = plt.figure(figsize=(20, 20))
plt.matshow(listing_detailed[col_for_corr].corr(), fignum = f, cmap = red_green_cmap, vmin=-1, vmax=1)
plt.xticks(range(listing_detailed[col_for_corr].select_dtypes(['number']).shape[1]), 
           listing_detailed[col_for_corr].select_dtypes(['number']).columns, rotation=90, fontsize = 15)
plt.yticks(range(listing_detailed[col_for_corr].select_dtypes(['number']).shape[1]), 
           listing_detailed[col_for_corr].select_dtypes(['number']).columns, rotation=0, fontsize = 15)
cb = plt.colorbar()
cb.ax.tick_params(labelsize=14)
plt.title("Airbnb London: Correlation between different parameters", fontsize=20,loc='left')
plt.text(0,32,'Correlation matrix displaying the different parameters within the data. DIverging green (+ve) red (-ve) scale is used to display the correlations.\n \
    Features considered: Review scores, Number of reviews, availability, maximum and minimum days of stay, host and room parameters.',
         ha='left', fontsize=12)

plt.show()

png

Scatterplot matrix

जबकि ऊपर का प्लॉट विभिन्न वेरिएबल्स के बीच संबंध दिखाता है, मैं रेटिंग्स में मूल्य परिवर्तन को गहराई से देखना चाहता हूं। मैं इसे एक स्कैटरप्लॉट मैट्रिक्स का उपयोग करके विज़ुअलाइज़ कर सकता हूं। इसके अतिरिक्त, मैं सबसे महंगे प्रॉपर्टीज और सबसे लोकप्रिय लेकिन सस्ती प्रॉपर्टीज को एनोटेट करना चाहता हूं।

def annotate_plot(x, y, **kwargs):

    if(x.name == 'price' and y.name == 'review_scores_rating'):
        ax = plt.gca()
        for index, obj in listing_detailed.nlargest(2, 'price').iterrows():
            plt.annotate(
                obj['name'], # the text
                xy=(obj.price, obj.review_scores_rating), 
                xytext=(7500, obj.review_scores_rating-0.5),
                arrowprops=dict(arrowstyle="->") 
            )
    elif(x.name == 'price' and y.name == 'number_of_reviews'):
        ax = plt.gca()
        for index, obj in listing_detailed.nlargest(3, 'number_of_reviews').iterrows():
            ax.text(obj.price, obj.number_of_reviews, obj['name'])
col_for_pairplot = ['review_scores_rating', 'number_of_reviews',  'price']

sns_plot = sns.pairplot(listing_detailed, vars = col_for_pairplot,  kind='scatter',  hue = 'room_type', diag_kind='kde',)
sns_plot.fig.set_size_inches(20, 20)
sns_plot._legend.set_bbox_to_anchor((0.15, 0.89))
sns_plot.map_upper(annotate_plot)
sns_plot.fig.suptitle("Airbnb London: Scatterplot matrix", fontsize = 20, y=1)

png

हम देख सकते हैं कि दो सबसे महंगी प्रॉपर्टीज या तो ऐतिहासिक अपार्टमेंट या एक हवेली हैं। तीन सबसे लोकप्रिय लेकिन सस्ती प्रॉपर्टीज छोटे और आकर्षक प्रॉपर्टीज हैं जो लोकप्रिय स्थलों के पास हैं।

Boxplot और Violin chart

कमरे के विभिन्न प्रकारों में रेटिंग में परिवर्तन को देखने के लिए, हम या तो बॉक्सप्लॉट या वायलिन प्लॉट का उपयोग कर सकते हैं जैसा कि दिखाया गया है।

fs, axs = plt.subplots(1, 2, figsize=(20,20))
listing_detailed.boxplot(column = 'review_scores_rating', by = 'room_type', figsize = (10,20), ax = axs[0])
sns.violinplot('room_type','review_scores_rating', data=listing_detailed, ax = axs[1])
plt.suptitle("Airbnb London: Average rating across room types", fontsize = 20, y=0.95)

plt.figtext(0.5, 0.925, 'Boxplot (left) and Violin plot (right) for the average review across room types',
            wrap=True, horizontalalignment='center', fontsize=12)

axs[0].set_title('')

for ax in axs:
    ax.set_ylim(-1, 6)
    ax.set_ylabel('Average Rating', fontsize=12)
    ax.set_xlabel('Room types', fontsize=12)

png

Cluster map

यदि हम कुछ विशेषताओं के आधार पर स्थानीयताओं को क्लस्टर करना चाहते हैं, तो क्लस्टर मैप सबसे अच्छा विकल्प है। नीचे के मैप में, हम विभिन्न स्थानों को एक प्रकार की प्रत्येक विशेषता के आधार पर क्लस्टर करते हैं। विशेषताओं को भी क्लस्टर किया जाता है ताकि डेटा के भीतर समानता दिखाई दे। अंततः, हम डेटा में भिन्नता को प्रदर्शित करने के लिए एक सफेद-नीला रंग पैलेट का उपयोग करते हैं।

from sklearn.preprocessing import MinMaxScaler
import seaborn as sns
col_for_corr = [ 'price', 'review_scores_rating', 'number_of_reviews', 'availability_90', 'minimum_nights_avg_ntm', 'bedrooms', 
                'host_response_rate']

df_cluster = listing_detailed.groupby('neighbourhood_cleansed').aggregate({
    'review_scores_rating':'mean',
    'number_of_reviews':'sum',
    'availability_90':'mean',
    'minimum_nights_avg_ntm':'mean',
    'bedrooms':'mean',
    'price':'mean', 
    'host_response_rate':'mean'
}).reset_index()

scaler = MinMaxScaler()
df_cluster1 = pd.DataFrame(scaler.fit_transform(df_cluster[col_for_corr]), columns=col_for_corr)
df_cluster1.index = df_cluster.neighbourhood_cleansed

crest_cmap = sns.color_palette("crest", as_cmap=True)
crest_cmap
png

g = sns.clustermap(df_cluster1, cmap = crest_cmap, vmin = 0, vmax = 1)
plt.title("Airbnb London: Clusters within London", fontsize=20,loc='left', y = 2, x = -25)
g.ax_cbar.set_position((1, .2, .03, .4))
g.ax_heatmap.set_ylabel("")

plt.show()

png

References

  1. Visualisation Analytics and Design, Tamara Munzner
  2. Class notes and assignments, Visualisation module, MSc Business Analytics, Imperial College London, Class 2020-22
  3. Ahmed Khedr, Ankit Mahajan, Harsha Achyuthuni, Shaked Atia Report: Visualizing demand,supply,prices and ratings for Airbnb in London
  4. Visual Analytics lab at JKU Linz
Back to top