Chapter 9 Travel and Accomodation

9.1 Topic Modelling and Sentiment Analysis on Hotel Reviews Data in Europe

9.1.1 Background

Benua Eropa merupakan benua yang menjadi destinasi impian bagi para wisatawan di berbagai belahan dunia. Hal ini karena Benua Eropa memiliki banyak situs wisata yang menarik perhatian akan keunikan dan keindahannya. Tentunya para wisatawan membutuhkan tempat untuk singgah sementara selama menikmati liburan. Untuk meningkatkan kepuasan para wisatawan, dibutuhkan pemilihan hotel yang tepat agar dapat memberikan wisatawan pengalaman beristirahat yang terbaik. Untuk itu, melihat seberapa baik tingkat pelayanan hotel secara teliti tentunya diperlukan. Tingkat pelayanan hotel dapat dilihat salah-satunya melalui rating dari hotel tersebut. Meskipun demikian, rating dari hotel tidak sepenuhnya merepresentasikan tingkat kelayakan dari hotel tersebut, terutama untuk fasilitas-fasilitasnya secara spesifik. Maka dari itu, perlu dilakukan analisis sentimen berdasarkan ulasan dari wisatawan lain yang pernah singgah di hotel tersebut. Sentimen analisis diperlukan untuk memberikan penilaian terhadap fasilitas-fasilitas dan pelayanan hotel yang disediakan karena sering kali kita tidak ingin membaca setiap ulasan hotel-hotel tersebut satu per satu. Harapannya, dengan analisis ini, hasil penilaian dari setiap aspek pelayanan dari hotel-hotel ini dapat dijadikan pertimbangan dalam memilih hotel dengan pengalaman menginap yang terbaik.

9.1.2 Data Preparation

Dataset yang digunakan adalah dataset yang bersumber dari kaggle (https://www.kaggle.com/jiashenliu/515k-hotel-reviews-data-in-europe) yang memuat 515 ribu ulasan dari hotel-hotel yang berada di ibukota negara-negara di Benua Eropa.

import sys
print(sys.executable)
#> /Users/ariqleesta/opt/anaconda3/envs/ariq/bin/python
# Environment
import os

# Data Manipulation Tools
import pandas as pd
import numpy as np
from fuzzymatcher import link_table, fuzzy_left_join
import difflib
import re # for regular expressions
pd.options.display.max_rows = 1000
pd.options.display.max_columns = 100

# Data Visualization Tools
import matplotlib.pyplot as plt
import seaborn as sns
#%matplotlib inline
plt.style.use('bmh')

# libraries for displaying images
from IPython.display import HTML, Image 
from IPython.core.display import HTML 
from IPython.display import display
import selenium

# NLP Module
import nltk # Natural language processing toolkit
from nltk import FreqDist # Frequency distribution
from nltk.corpus import stopwords
from wordcloud import WordCloud,STOPWORDS 
from textblob import TextBlob
from nltk.stem import WordNetLemmatizer 
from nltk import sent_tokenize
from math import pi
import pickle 
import pyLDAvis


# Network
#> /Library/Frameworks/R.framework/Versions/4.1/Resources/library/reticulate/python/rpytools/loader.py:39: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
#>   module = _import(
from nltk import bigrams, ngrams
import networkx as nx 
import itertools
import collections

# Location Modules
import folium

# Ignore Warnings
import warnings
warnings.filterwarnings("ignore")
warnings.filterwarnings("ignore", category= DeprecationWarning)
df = pd.read_csv('https://archive.org/download/hotel-reviews/Hotel_Reviews.csv')
print(df.shape)
#> (515738, 17)
df.head(2)
#>                                        Hotel_Address  \
#> 0   s Gravesandestraat 55 Oost 1092 AA Amsterdam ...   
#> 1   s Gravesandestraat 55 Oost 1092 AA Amsterdam ...   
#> 
#>    Additional_Number_of_Scoring Review_Date  Average_Score   Hotel_Name  \
#> 0                           194    8/3/2017            7.7  Hotel Arena   
#> 1                           194    8/3/2017            7.7  Hotel Arena   
#> 
#>   Reviewer_Nationality                                    Negative_Review  \
#> 0              Russia    I am so angry that i made this post available...   
#> 1             Ireland                                         No Negative   
#> 
#>    Review_Total_Negative_Word_Counts  Total_Number_of_Reviews  \
#> 0                                397                     1403   
#> 1                                  0                     1403   
#> 
#>                                      Positive_Review  \
#> 0   Only the park outside of the hotel was beauti...   
#> 1   No real complaints the hotel was great great ...   
#> 
#>    Review_Total_Positive_Word_Counts  \
#> 0                                 11   
#> 1                                105   
#> 
#>    Total_Number_of_Reviews_Reviewer_Has_Given  Reviewer_Score  \
#> 0                                           7             2.9   
#> 1                                           7             7.5   
#> 
#>                                                 Tags days_since_review  \
#> 0  [' Leisure trip ', ' Couple ', ' Duplex Double...            0 days   
#> 1  [' Leisure trip ', ' Couple ', ' Duplex Double...            0 days   
#> 
#>          lat       lng  
#> 0  52.360576  4.915968  
#> 1  52.360576  4.915968
len(df['Hotel_Address'].unique())
#> 1493

Dataset ini memiliki 515738 baris dan 17 kolom dengan jumlah hotel sebanyak 1493. Sebelum memulai analisis lebih lanjut, kita perlu mengetahui tipe data dari setiap kolom dataset tersebut.

df.dtypes
#> Hotel_Address                                  object
#> Additional_Number_of_Scoring                    int64
#> Review_Date                                    object
#> Average_Score                                 float64
#> Hotel_Name                                     object
#> Reviewer_Nationality                           object
#> Negative_Review                                object
#> Review_Total_Negative_Word_Counts               int64
#> Total_Number_of_Reviews                         int64
#> Positive_Review                                object
#> Review_Total_Positive_Word_Counts               int64
#> Total_Number_of_Reviews_Reviewer_Has_Given      int64
#> Reviewer_Score                                float64
#> Tags                                           object
#> days_since_review                              object
#> lat                                           float64
#> lng                                           float64
#> dtype: object

Kemudian kita perlu meninjau mengenai missing value atau data yang hilang dari dataset tersebut. Pengamatan data yang hilang dapat dilakukan secara visual dengan bantuan library missingno.

import missingno as msno
plt.figure()
msno.matrix(df, figsize = (20,8))
#> <AxesSubplot:>
plt.show()

df.isnull().sum()
#> Hotel_Address                                    0
#> Additional_Number_of_Scoring                     0
#> Review_Date                                      0
#> Average_Score                                    0
#> Hotel_Name                                       0
#> Reviewer_Nationality                             0
#> Negative_Review                                  0
#> Review_Total_Negative_Word_Counts                0
#> Total_Number_of_Reviews                          0
#> Positive_Review                                  0
#> Review_Total_Positive_Word_Counts                0
#> Total_Number_of_Reviews_Reviewer_Has_Given       0
#> Reviewer_Score                                   0
#> Tags                                             0
#> days_since_review                                0
#> lat                                           3268
#> lng                                           3268
#> dtype: int64

Kolom lat dan lng yang merupakan komponen koordinat dari dataset tidak tersedia untuk beberapa hotel. Jumlah baris yang tidak memiliki data koordinat sangat sedikit jika dibandingkan total data keseluruhan sehingga baris-baris ini dapat dihilangkan karena koordinat diperlukan untuk memberikan informasi lokasi dari hotel tersebut kepada para wisatawan.

# drop null coordinates
df.dropna(inplace = True)

9.1.3 Exploratory Data Analysis

Kata No Negative pada kolom Negative Review dan No Positive pada kolom Positive Review menandakan bahwa review tersebut tidak mengandung ulasan positif atau ulasan negatif. Sehingga kata No Negative dan No Positive dapat diganti dengan empty string saja.

# replace No Negative and No Positive with empty string
df['Negative_Review'] = df['Negative_Review'].replace({'No Negative': ''})
df['Positive_Review'] = df['Positive_Review'].replace({'No Positive': ''})

Berikut adalah distribusi dari nilai rating rata-rata yang diberikan oleh pemberi ulasan terkait hotel-hotel pada dataset ini. Average Score memiliki rentang 5.2 untuk nilai terendah dan 9.9 untuk nilai tertinggi.

# Plot average score

data_plot = df[["Hotel_Name","Average_Score"]].drop_duplicates()
fig, ax = plt.subplots(figsize= (30,7))
sns.countplot(ax = ax,x = "Average_Score",data=data_plot)
#> <AxesSubplot:xlabel='Average_Score', ylabel='count'>
ax.set_xlabel('Average Score')
#> Text(0.5, 0, 'Average Score')
ax.set_ylabel('Score Counts')
#> Text(0, 0.5, 'Score Counts')
plt.show()

Berikut akan dilakukan analisis teks, tahap-tahap yang akan dilakukan antara lain: * Mendefinisikan Stopword
Stopword adalah kata-kata yang tidak atau sedikit mengandung makna dari kalimat keseluruhan. Stopword biasanya berupa partikel (eg: a, an, the, is, am, are) atau kata penghubung (eg: and, or, of). * Membuat semua kata menjadi huruf kecil/lowercase
Untuk mempermudah memproses data tekstual, perlu dilakukan lowercasing karena algoritma dalam text processing membedakan huruf besar dan huruf kecil atau case sensitive. * Menghilangkan character yang tidak diinginkan
Character yang tidak diinginkan bisa berupa simbol simbol seperti titik, koma, tanda seru, tanda tanya, dan lain-lain. * Menghilangkan kata-kata yang pendek (kurang dari 4 huruf) * Lemmatizing
Lemmatizing adalah mengembalikan kosa kata menjadi kata dasar dari kata tersebut. Sebagai contoh,, kata studying dan studied memiliki kata dasar yang sama yaitu study. * Menghilangkan Stopwords

  • Tokenization Tokenization adalah proses memecah kalimat menjadi kumpulan kata-kata. Kumpulan kata-kata dalam satu kalimat dimuat dalam satu buah iterable atau list. Sebagai contoh, untuk kata yang telah di lemmatizing, “Hotel service good” menjadi [“Hotel,” “Service,” “Good”].
# A function to remove stopwords
def remove_stopwords(rev):
    rev_new = " ".join([i for i in rev if i not in stop_words])
    return rev_new

# A function to count the most frequent words
def freq_words(x, terms = 30):
    all_words = ' '.join([text for text in x])
    all_words = all_words.split()

    fdist = FreqDist(all_words)
    words_df = pd.DataFrame({'word':list(fdist.keys()), 'count':list(fdist.values())})

    # selecting top 20 most frequent words
    d = words_df.nlargest(columns="count", n = terms) 
    plt.figure(1, figsize=(20,5))
    ax = sns.barplot(data=d, x= "word", y = "count")
    ax.set(ylabel = 'Count')
    plt.xticks(rotation = 90)
    plt.show()

# A function to draw word cloud
def wordcloud_draw(data, color = 'black'):
    words = ' '.join(data)
    cleaned_word = ' '.join([word for word in words.split()
                            if 'http' not in word
                                and not word.startswith('@')
                                and not word.startswith('#')
                                and word != 'RT'
                            ])
    
    wordcloud = WordCloud(collocations=False,
              stopwords=STOPWORDS,
              background_color=color,
              width=500,
              height=100,).generate(cleaned_word)
    plt.figure(1,figsize=(13, 13))
    plt.imshow(wordcloud)
    plt.axis('off')
    plt.show()
    
# Create a function to get the subjectivity
def getSubjectivity(text):
    return TextBlob(text).sentiment.subjectivity

# Create a function to get the polarity
def getPolarity(text):
    return TextBlob(text).sentiment.polarity
df_reviews = df[['Hotel_Name', 'Negative_Review', 'Positive_Review']]
df_reviews.head()

# Define stopwords
#>     Hotel_Name                                    Negative_Review  \
#> 0  Hotel Arena   I am so angry that i made this post available...   
#> 1  Hotel Arena                                                      
#> 2  Hotel Arena   Rooms are nice but for elderly a bit difficul...   
#> 3  Hotel Arena   My room was dirty and I was afraid to walk ba...   
#> 4  Hotel Arena   You When I booked with your company on line y...   
#> 
#>                                      Positive_Review  
#> 0   Only the park outside of the hotel was beauti...  
#> 1   No real complaints the hotel was great great ...  
#> 2   Location was good and staff were ok It is cut...  
#> 3   Great location in nice surroundings the bar a...  
#> 4    Amazing location and building Romantic setting
stop_words = stopwords.words('english')

# make entire text lowercase
df_reviews['Positive_Review'] = [r.lower() for r in df_reviews['Positive_Review']] 
df_reviews['Negative_Review'] = [r.lower() for r in df_reviews['Negative_Review']]

# Remove unwanted characters, numbers and symbols
df_reviews['Positive_Review'] = df_reviews['Positive_Review'].str.replace("[^a-zA-Z#]", " ")
df_reviews['Negative_Review'] = df_reviews['Negative_Review'].str.replace("[^a-zA-Z#]", " ")

# remove short words (length < 4)
df_reviews['Positive_Review'] = df_reviews['Positive_Review'].apply(lambda x: ' '.join([w for w in x.split() if len(w)>3]))
df_reviews['Negative_Review'] = df_reviews['Negative_Review'].apply(lambda x: ' '.join([w for w in x.split() if len(w)>3]))

# Lemmatizing 
lemmatizer = WordNetLemmatizer() 
df_reviews['Positive_Review'] = [' '.join([lemmatizer.lemmatize(word) for word in r.split(' ')]) for r in df_reviews['Positive_Review']]
df_reviews['Negative_Review'] = [' '.join([lemmatizer.lemmatize(word) for word in r.split(' ')]) for r in df_reviews['Negative_Review']]

# remove stopwords from the text
df_reviews['Positive_Review'] = [remove_stopwords(r.split()) for r in df_reviews['Positive_Review']]
df_reviews['Negative_Review'] = [remove_stopwords(r.split()) for r in df_reviews['Negative_Review']]


# Tokenization
df_reviews['Positive_Review'] = [r.split() for r in df_reviews['Positive_Review']]
df_reviews['Negative_Review'] = [r.split() for r in df_reviews['Negative_Review']]
def flatten(data):
    l = []
    for i in data:
        for word in i:
            l.append(word)
    return l
pos = flatten(df_reviews['Positive_Review'].tolist())       
neg = flatten(df_reviews['Negative_Review'].tolist())  

Berikut adalah grafik frekuensi tertinggi untuk kata-kata yang berada pada kolom Positive Review dan Negative Review. Pada kolom Positive Review, kata yang paling banyak diucapkan adalah staff, location, dan room, sedangkan Pada kolom Negative Review, kata yang paling banyak diucapkan adalah room, hotel, dan breakfast.

freq_words(pos, 30)# Frequency distribution of common words in positive reviews

freq_words(neg, 30) # Checking frequency of most used words in negative reviews

Kita dapat melihat kata-kata yang sering muncul dengan bantuan visualisasi wordcloud. Kata yang lebih sering muncul akan berukuran lebih besar daripada yang lain.

# Using wordcloud to visually represent the text data
print("Positive reviews")
#> Positive reviews
wordcloud_draw(pos,'white')

# Using wordcloud to visually represent the text data
print("Negative reviews")
#> Negative reviews
wordcloud_draw(neg)

### Modelling

Dalam menyarankan para wisatawan mengenai hotel dengan pelayanan terbaik, kita perlu meninjau terlebih dahulu aspek-aspek apa yang relevan dalam ulasan-ulasan tersebut sebelum bisa dilanjutkan ke penilaian. Aspek-aspek ini dapat berupa fasilitas-fasilitas atau pelayanan yang diberikan oleh hotel tersebut tergantung seberapa banyak dan relevan hal tersebut dibicarakan di dalam ulasan. Sebagai contoh, anggaplah kita tidak mengetahui fasilitas-fasilitas apa yang diberikan hotel. Dari review tersebut ada yang membicarakan mengenai kamar, kolam renang, kafe, rollercoaster bahkan kebun binatang. Bagaimana kita tahu bahwa topik yang relevan adalah kamar, kolam renang, dan kafe? Bisa saja suatu hotel memiliki rollercoaster atau berada di dekat kebun binatang. Untuk menentukan topik-topik yang relevan dalam penilaian, dibutuhkan suatu proses bernama Topic Modelling. Topic Modelling menilai seberapa relevan suatu kata terhadap kalimat yang membawa pengaruh terbesar dalam pemaknaan suatu kalimat. Salah satu metode dalam Topic Modelling dan yang akan digunakan dalam analisis ini adalah metode Latent Dirichlet Allocation (LDA). LDA memodelkan topik berdasarkan probabilitas dengan mengasumsikan setiap topik merupakan gabungan dari beberapa pasangan kata, dan setiap dokumen merupakan gabungan dari beberapa topik.

Untuk melakukan Topic Modelling dengan metode LDA dapat dilakukan dengan bantuan suatu library bernama gensim.

import gensim
from gensim.utils import simple_preprocess
LDA = gensim.models.ldamodel.LdaModel
import gensim.corpora as corpora
# Create Dictionary
id2word_pos = corpora.Dictionary(df_reviews['Positive_Review'])
id2word_neg = corpora.Dictionary(df_reviews['Negative_Review'])
# Create Corpus
texts_pos = df_reviews['Positive_Review']
texts_neg = df_reviews['Negative_Review']
# Term Document Frequency
corpus_pos = [id2word_pos.doc2bow(text) for text in texts_pos]
corpus_neg = [id2word_neg.doc2bow(text) for text in texts_neg]
from pprint import pprint
# number of topics
num_topics = 10
# Build LDA model
lda_model_pos = gensim.models.LdaMulticore(corpus=corpus_pos,
                                       id2word=id2word_pos,
                                       num_topics=num_topics)

lda_model_neg = gensim.models.LdaMulticore(corpus=corpus_neg,
                                       id2word=id2word_neg,
                                       num_topics=num_topics)

Berikut adalah hasil pemilihan 10 kumpulan topik untuk ulasan positif

pprint(lda_model_pos.print_topics())
#> [(0,
#>   '0.067*"hotel" + 0.035*"staff" + 0.025*"excellent" + 0.023*"room" + '
#>   '0.022*"location" + 0.018*"comfortable" + 0.015*"best" + 0.013*"helpful" + '
#>   '0.012*"recommend" + 0.012*"barcelona"'),
#>  (1,
#>   '0.057*"room" + 0.032*"view" + 0.029*"staff" + 0.025*"hotel" + '
#>   '0.023*"amazing" + 0.019*"great" + 0.019*"lovely" + 0.011*"beautiful" + '
#>   '0.010*"birthday" + 0.010*"friendly"'),
#>  (2,
#>   '0.057*"room" + 0.023*"nice" + 0.021*"hotel" + 0.015*"staff" + 0.014*"check" '
#>   '+ 0.012*"coffee" + 0.012*"free" + 0.009*"good" + 0.008*"small" + '
#>   '0.008*"night"'),
#>  (3,
#>   '0.043*"hotel" + 0.033*"staff" + 0.028*"stay" + 0.028*"service" + '
#>   '0.016*"everything" + 0.016*"time" + 0.012*"every" + 0.012*"excellent" + '
#>   '0.010*"best" + 0.009*"thing"'),
#>  (4,
#>   '0.172*"staff" + 0.106*"friendly" + 0.094*"helpful" + 0.069*"location" + '
#>   '0.038*"great" + 0.030*"room" + 0.025*"hotel" + 0.016*"clean" + 0.014*"nice" '
#>   '+ 0.014*"excellent"'),
#>  (5,
#>   '0.041*"well" + 0.034*"staff" + 0.031*"hotel" + 0.028*"comfy" + 0.025*"room" '
#>   '+ 0.023*"really" + 0.020*"helpful" + 0.016*"great" + 0.013*"like" + '
#>   '0.013*"bed"'),
#>  (6,
#>   '0.107*"breakfast" + 0.087*"good" + 0.052*"location" + 0.052*"staff" + '
#>   '0.042*"excellent" + 0.027*"great" + 0.026*"room" + 0.025*"hotel" + '
#>   '0.021*"friendly" + 0.020*"value"'),
#>  (7,
#>   '0.116*"room" + 0.061*"clean" + 0.060*"location" + 0.056*"comfortable" + '
#>   '0.054*"great" + 0.042*"nice" + 0.041*"good" + 0.035*"staff" + '
#>   '0.023*"excellent" + 0.022*"everything"'),
#>  (8,
#>   '0.078*"hotel" + 0.029*"room" + 0.029*"nice" + 0.022*"design" + '
#>   '0.016*"clean" + 0.014*"staff" + 0.013*"street" + 0.013*"modern" + '
#>   '0.012*"nothing" + 0.011*"liked"'),
#>  (9,
#>   '0.092*"location" + 0.040*"station" + 0.037*"good" + 0.034*"close" + '
#>   '0.026*"great" + 0.026*"metro" + 0.022*"hotel" + 0.019*"walk" + 0.016*"city" '
#>   '+ 0.016*"near"')]

Berikut adalah hasil pemilihan 10 kumpulan topik untuk ulasan negatif

pprint(lda_model_neg.print_topics())
#> [(0,
#>   '0.041*"room" + 0.026*"pool" + 0.025*"parking" + 0.023*"window" + '
#>   '0.023*"hotel" + 0.022*"expensive" + 0.021*"noise" + 0.019*"price" + '
#>   '0.019*"night" + 0.014*"open"'),
#>  (1,
#>   '0.036*"room" + 0.030*"bathroom" + 0.030*"coffee" + 0.020*"breakfast" + '
#>   '0.017*"could" + 0.017*"door" + 0.014*"would" + 0.014*"shower" + '
#>   '0.014*"food" + 0.013*"facility"'),
#>  (2,
#>   '0.075*"room" + 0.035*"hotel" + 0.011*"night" + 0.010*"could" + '
#>   '0.009*"booked" + 0.008*"double" + 0.007*"would" + 0.007*"star" + '
#>   '0.007*"staff" + 0.007*"time"'),
#>  (3,
#>   '0.053*"room" + 0.038*"shower" + 0.026*"need" + 0.021*"floor" + '
#>   '0.020*"water" + 0.019*"hotel" + 0.018*"bathroom" + 0.017*"lift" + '
#>   '0.015*"good" + 0.012*"clean"'),
#>  (4,
#>   '0.144*"room" + 0.096*"small" + 0.083*"breakfast" + 0.018*"expensive" + '
#>   '0.015*"little" + 0.014*"view" + 0.014*"size" + 0.011*"price" + '
#>   '0.011*"bathroom" + 0.010*"included"'),
#>  (5,
#>   '0.032*"room" + 0.028*"hotel" + 0.021*"little" + 0.020*"location" + '
#>   '0.018*"could" + 0.016*"area" + 0.013*"pillow" + 0.011*"breakfast" + '
#>   '0.011*"city" + 0.011*"small"'),
#>  (6,
#>   '0.034*"staff" + 0.032*"hotel" + 0.014*"breakfast" + 0.012*"reception" + '
#>   '0.011*"room" + 0.011*"would" + 0.011*"service" + 0.010*"check" + '
#>   '0.009*"time" + 0.008*"booking"'),
#>  (7,
#>   '0.079*"room" + 0.034*"wifi" + 0.016*"time" + 0.012*"free" + 0.012*"check" + '
#>   '0.011*"water" + 0.010*"work" + 0.010*"even" + 0.008*"slow" + 0.008*"cold"'),
#>  (8,
#>   '0.274*"nothing" + 0.058*"everything" + 0.034*"like" + 0.027*"really" + '
#>   '0.021*"perfect" + 0.020*"great" + 0.019*"thing" + 0.016*"hotel" + '
#>   '0.016*"anything" + 0.014*"good"'),
#>  (9,
#>   '0.074*"room" + 0.027*"night" + 0.025*"noisy" + 0.017*"service" + '
#>   '0.015*"floor" + 0.014*"hotel" + 0.012*"morning" + 0.012*"door" + '
#>   '0.010*"work" + 0.009*"noise"')]

Berikut adalah potongan kode untuk melakukan visualisasi pemilihan topik dengan model LDA.

# Visualize the topics

def ldaviz(lda_model, corpus, id2word, num_topics, filename):
    pyLDAvis.enable_notebook()
    LDAvis_data_filepath = os.path.join(os.getcwd()+str(num_topics))
    # # this is a bit time consuming - make the if statement True
    # # if you want to execute visualization prep yourself
    if True:
        LDAvis_prepared = pyLDAvis.gensim.prepare(lda_model, corpus, id2word)
        with open(LDAvis_data_filepath, 'wb') as f:
            pickle.dump(LDAvis_prepared, f)
    # load the pre-prepared pyLDAvis data from disk
    with open(LDAvis_data_filepath, 'rb') as f:
        LDAvis_prepared = pickle.load(f)
    return pyLDAvis.save_html(LDAvis_prepared, os.getcwd()+ '/' + filename +'.html')
    #return LDAvis_prepared
# Visualization for Positive Reviews
# ldaviz(lda_model_pos, corpus_pos, id2word_pos, num_topics, 'ldavizpos')
# Visualization for Negative Reviews
# daviz(lda_model_neg, corpus_neg, id2word_neg, num_topics, 'ldavizneg')

Topik-topik tersebut di extract dan dihitung skor kumulatif untuk masing-masing topik.

# Extract all topics from negative and positive

topics_pos = []
topics_neg = []

scores_pos = []
scores_neg = []

for pos, neg in zip(lda_model_pos.print_topics(), lda_model_neg.print_topics()):
    
    topic_pos = re.findall("(?<=\")[a-z]+(?=\")",pos[1])
    topics_pos = topics_pos + topic_pos
    
    score_pos = re.findall("\d\.\d+",pos[1])
    scores_pos = scores_pos + score_pos

    topic_neg = re.findall("(?<=\")[a-z]+(?=\")",neg[1])
    topics_neg = topics_neg + topic_neg
    
    score_neg = re.findall("\d\.\d+",pos[1])
    scores_neg = scores_neg + score_neg
    
positive_topics = pd.DataFrame({'Topics':topics_pos, 'Score':scores_pos})
positive_topics['Score'] = positive_topics['Score'].astype(float)
positive_topics = positive_topics.groupby('Topics').sum().reset_index()\
.sort_values('Score',ascending = False)

negative_topics = pd.DataFrame({'Topics':topics_neg, 'Score':scores_neg})
negative_topics['Score'] = negative_topics['Score'].astype(float)
negative_topics = negative_topics.groupby('Topics').sum().reset_index()\
.sort_values('Score',ascending = False)

Maka diperoleh kumpulan topik beserta score seberapa relevan suatu topik terhadap ulasan yang diberikan yang ditabulasikan sebagai berikut.

#inner join to see topics that belongs to each other
pd.merge(positive_topics, negative_topics, 'inner', 'Topics')
#>         Topics  Score_x  Score_y
#> 0        staff    0.419    0.115
#> 1         room    0.363    0.687
#> 2        hotel    0.337    0.221
#> 3     location    0.295    0.028
#> 4        great    0.180    0.014
#> 5         good    0.174    0.021
#> 6    breakfast    0.107    0.187
#> 7        clean    0.093    0.009
#> 8   everything    0.038    0.029
#> 9         view    0.032    0.030
#> 10     service    0.028    0.060
#> 11      really    0.023    0.022
#> 12        city    0.016    0.013
#> 13        time    0.016    0.089
#> 14       check    0.014    0.079
#> 15        like    0.013    0.029
#> 16      coffee    0.012    0.029
#> 17     nothing    0.012    0.078
#> 18        free    0.012    0.056
#> 19       thing    0.009    0.013
#> 20       small    0.008    0.119
#> 21       night    0.008    0.073

Dalam hal ini, kita dapat memilih secara manual atau memilih dengan mengambil topik dengan skor yang paling besar. Tahap ini disebut Topic Labelling. Namun dalam hal ini kita cukup memilih topik secara manual dengan mengandalkan pengetahuan kita mengenai fasilitas apa yang sering atau selalu disediakan oleh pihak hotel.

# Topic labelling
aspects = ['staff', 'room', 'breakfast', 'service', 'view', 'restaurant', 'bathroom', 'pool']

Untuk memperoleh hasil yang merepresentasikan tiap hotel, alangkah lebih semua review dijadikan menjadi satu dokumen untuk tiap hotel. Sehingga kita dapat memperoleh satu buah dokumen penuh berisi review untuk tiap-tiap hotel.

df_reviews = df_reviews.groupby('Hotel_Name')['Negative_Review','Positive_Review'].sum()

Dari aspek-aspek ini, kita akan menentukan word correlation dari aspek yang kita inginkan, yaitu mencari kata sebelum dan sesudah dari topik yang diinginkan. Hal ini bertujuan untuk melihat hubungan dari aspek (fasilitas) yang diberikan hotel terhadap kata yang diberikan oleh wisatawan melalui ulasannnya. Sebagai contoh, kita ingin melihat ruangan, namun kita perlu tahu ruangan itu seperti apa menurut para wisatawan. Bisa jadi ruangan itu baik, buruk, sempit, luas, mengecewakan, dan lain-lain. Metode ini bernama ngram yaitu memasangkan kata dengan kata disebelahnya (baik sebelum atau sesudahnya).

Kita ambil contoh untuk satu dokumen (satu buah hotel). Misalkan aspek yang ingin dilihat adalah room dengan jumlah kata dalam satu pasangan n adalah 2.

# let's take a look at example
# if we want to extract only room

n = 2
example = df_reviews['Negative_Review'][2]
terms_ngram = [list(ngrams(w,n)) for w in [example]]
room_ngram = [i for i in terms_ngram[0] if 'room' in i]
room_ngram[:5]
#> [('disappointing', 'room'), ('room', 'really'), ('time', 'room'), ('room', 'furniture'), ('special', 'room')]

Untuk mendapatkan sebagian dokumen yang hanya membicarakan room, kita perlu menggabungkan semua ngram menjadi satu dokumen dengan metode chain.

# Flatten list of room ngrams in clean list

room_ngram_flatten = list(itertools.chain(*room_ngram))
room_ngram_flatten[:6]
#> ['disappointing', 'room', 'room', 'really', 'time', 'room']

Kita dapat menghitung jumlah pasangan kata yang sama dalam satu dokumen dengan menggunakan method collections.Counter.

ngram_counts = collections.Counter(room_ngram)
ngram_df = pd.DataFrame(ngram_counts.most_common(20),
                             columns=['ngram', 'count'])

ngram_df.head()
#>              ngram  count
#> 0    (room, small)     27
#> 1    (small, room)     19
#> 2   (room, little)     10
#> 3  (room, service)      7
#> 4   (room, really)      6

Setelah itu, kita akan melakukan visualisasi mengenai word correlation dari aspek room yang kita pilih sebelumnya dengan bantuan library networkx. Sebelum, melakukan visualisasi, tabel frekuensi ngram_df alangkah lebih baik diubah ke dalam bentuk dictionary. Hasil dari visualisasi i ni menampilkan korelasi kata-kata terhadap aspek yang kita pilih. Semakin dekat kata-kata tersebut satu sama lain, semakin tinggi frekuensi dari kemunculan pasangan kata tersebut.

# we visualize
# transform to dict first
d = ngram_df.set_index('ngram').T.to_dict('records')
d[0]
#> {('room', 'small'): 27, ('small', 'room'): 19, ('room', 'little'): 10, ('room', 'service'): 7, ('room', 'really'): 6, ('facility', 'room'): 6, ('room', 'room'): 6, ('room', 'could'): 5, ('room', 'quite'): 5, ('room', 'floor'): 5, ('clothes', 'room'): 4, ('kettle', 'room'): 4, ('room', 'night'): 4, ('view', 'room'): 4, ('room', 'breakfast'): 4, ('machine', 'room'): 4, ('great', 'room'): 3, ('room', 'size'): 3, ('chair', 'room'): 3, ('standard', 'room'): 3}
# Create network plot 
G = nx.Graph()

# Create connections between nodes
for k, v in d[0].items():
    G.add_edge(k[0], k[1], weight=(v * 10))

fig, ax = plt.subplots(figsize=(10, 8))

pos = nx.spring_layout(G, k=2)

# Plot networks
nx.draw_networkx(G, pos,
                 font_size=16,
                 width=3,
                 edge_color='grey',
                 node_color='purple',
                 with_labels = False,
                 ax=ax)

# Create offset labels
for key, value in pos.items():
    x, y = value[0]+.135, value[1]+.045
    ax.text(x, y,
            s=key,
            bbox=dict(facecolor='red', alpha=0.25),
            horizontalalignment='center', fontsize=13)
    
#> Text(0.14436154090192493, 0.06555010897030616, 'room')
#> Text(0.28680637251916874, -0.17276045649959254, 'small')
#> Text(0.47002549236053687, 0.25281448052717426, 'little')
#> Text(-0.01847479663151988, -0.4204990266494702, 'service')
#> Text(-0.3705627484918037, -0.046311204674404194, 'really')
#> Text(0.3338863524039992, 0.6175683243713621, 'facility')
#> Text(0.8250464326914789, -0.03044772100437307, 'could')
#> Text(-0.10193372231177167, 0.6471794795180892, 'quite')
#> Text(-0.47186554967651484, 0.3415539660921315, 'floor')
#> Text(-0.44472746553720455, 0.7377357891708914, 'clothes')
#> Text(0.863841141665385, -0.37479852558852506, 'kettle')
#> Text(-0.5643140145322153, -0.3907875873310931, 'night')
#> Text(0.1692306925681048, 0.9618663785160028, 'view')
#> Text(0.9868681356503629, 0.3148908193849318, 'breakfast')
#> Text(0.7444601619841278, 0.7061054057529598, 'machine')
#> Text(0.21873476803355257, -0.8945533008493735, 'great')
#> Text(0.672590186510921, -0.7501811063538283, 'size')
#> Text(-0.8649999999999999, 0.10037108836747693, 'chair')
#> Text(-0.31397298010853186, -0.8102969117206656, 'standard')
plt.show()

Agar script dapat digunakan berulang-ulang, alangkah lebih baik jika dimuat dalam bentuk function. Dengan function, kita juga dapat mengganti nilai n dalam ngrams sesuai kebutuhan.

## create functions to be utilized for further observation

# Function to create ngrams (for only 1 topics)
def create_ngrams(text_array, n, aspect):
    terms_ngram = [list(ngrams(w,n)) for w in [text_array]]
    ngram = [i for i in terms_ngram[0] if aspect in i]
    return ngram

#create_ngrams(example, 2, aspect = 'room')

def ngrams_flatten(ngram):
    # Flatten list of bigrams in clean tweets
    ngram = list(itertools.chain(*ngram))
    return ngram

# Function to count the frequency
def ngrams_frequency(ngram,  num_most_common):
    ngram_counts = collections.Counter(ngram)
    ngram_df = pd.DataFrame(ngram_counts.most_common(num_most_common),
                             columns=['ngram', 'count'])
    return ngram_df

#ngrams_frequency(create_ngrams(example, 2, aspect = 'room'), 10)

# Create network plot 

def plot_network(dataframe):
    
    try:
        # transform to dict first
        d = dataframe.set_index('ngram').T.to_dict('records')
        d[0]
        G = nx.Graph()

        # Create connections between nodes
        for k, v in d[0].items():
            G.add_edge(k[0], k[1], weight=(v * 10))

        fig, ax = plt.subplots(figsize=(10, 8))
    
        pos = nx.spring_layout(G, k=2)

        # Plot networks
        nx.draw_networkx(G, pos,
                         font_size=16,
                         width=3,
                         edge_color='grey',
                         node_color='purple',
                         with_labels = False,
                         ax=ax)

        # Create offset labels
        for key, value in pos.items():
            x, y = value[0]+.135, value[1]+.045
            ax.text(x, y,
                    s=key,
                    bbox=dict(facecolor='red', alpha=0.25),
                    horizontalalignment='center', fontsize=13)
    
        plt.show()
        
    except:
        return "No Particular Topic"
#let's see what they said about staff
plot_network(ngrams_frequency(create_ngrams(example, 3, aspect = 'staff'), 25))

# How about breakfast?
plot_network(ngrams_frequency(create_ngrams(example, 4, aspect = 'breakfast'), 25))

Kita telah mendapatkan aspek-aspek yang akan kita jadikan variabel penilaian terkait hotel di dataset ini yang telah disimpan di dalam variabel aspects. Berdasarkan aspek-aspek tersebut, dilakukan sentimen analisis untuk menilai seberapa baik fasilitas dan pelayanan hotel berdasarkan review yang disajikan dalam sebuah nilai. Hasil dari sentiment analysis memiliki rentang -1 (paling buruk) sampai 1 (paling baik) yang nantinya dinormalisasi dalam rentang 0 - 10.

# let's do sentiment analysis for each aspects of each hotels
aspects
#> ['staff', 'room', 'breakfast', 'service', 'view', 'restaurant', 'bathroom', 'pool']

Sebelum melakukan analisis sentimen, kolom Negative Review dan Positive Review digabungkan agar tidak terjadi bias dalam penilaian. Selanjutnya untuk setiap aspek dimuat dalam ngrams dengan n = 3 (biasa disebut trigrams). Hasil dari trigrams ini digabungkan (diratakan dengan metode flatten) sehingga didapat satu dokumen baru yang hanya membahas aspek tersebut. Satu dokumen tersebut dihitung skor sentimennya berdasarkan polarity atau ekspresi sentimen dari wisatawan terkait aspek tersebut.

# Combine the review
df_reviews['Review'] = df_reviews['Negative_Review'] + df_reviews['Positive_Review']

#plot_network(ngrams_frequency(create_ngrams(example, 3, aspect = 'staff'), 25))
# extract each ngrams from each Hotel Reviews based on aspects
from tqdm import tqdm

n = 3
for aspect in tqdm(aspects):
    # create empty list to store sentiment score
    sent_list = []
    for i in df_reviews['Review']:
        # create ngrams
        ngram_i = create_ngrams(i, n, aspect = aspect)
        # flatten ngrams to make clean list
        ngram_flatten_i = ngrams_flatten(ngram_i)
        # Get polarity
        polarity_i = getPolarity(' '.join(ngram_flatten_i))
        sent_list.append(polarity_i)
    
    df_reviews[aspect] = sent_list
#> 
  0%|          | 0/8 [00:00<?, ?it/s]
 12%|#2        | 1/8 [00:11<01:21, 11.71s/it]
 25%|##5       | 2/8 [00:31<01:37, 16.29s/it]
 38%|###7      | 3/8 [00:38<01:01, 12.36s/it]
 50%|#####     | 4/8 [00:42<00:35,  8.86s/it]
 62%|######2   | 5/8 [00:45<00:20,  6.71s/it]
 75%|#######5  | 6/8 [00:48<00:10,  5.47s/it]
 88%|########7 | 7/8 [00:51<00:04,  4.84s/it]
100%|##########| 8/8 [00:54<00:00,  3.99s/it]
100%|##########| 8/8 [00:54<00:00,  6.76s/it]

Sehingga didapat nilai untuk masing-masing aspek untuk setiap hotel yang dimuat pada dataframe berikut.

df_reviews.head(2)
#>                                                       Negative_Review  \
#> Hotel_Name                                                              
#> 11 Cadogan Gardens  [thought, prise, drink, little, excessive, not...   
#> 1K Hotel            [conditioning, room, work, despite, complainin...   
#> 
#>                                                       Positive_Review  \
#> Hotel_Name                                                              
#> 11 Cadogan Gardens  [particularly, impressed, warm, welcome, recei...   
#> 1K Hotel            [location, good, close, marais, arrondissement...   
#> 
#>                                                                Review  \
#> Hotel_Name                                                              
#> 11 Cadogan Gardens  [thought, prise, drink, little, excessive, not...   
#> 1K Hotel            [conditioning, room, work, despite, complainin...   
#> 
#>                        staff      room  breakfast   service      view  \
#> Hotel_Name                                                              
#> 11 Cadogan Gardens  0.502761  0.202778   0.119945  0.484459  0.177778   
#> 1K Hotel            0.421448  0.286418   0.104862  0.162868 -0.093750   
#> 
#>                     restaurant  bathroom  pool  
#> Hotel_Name                                      
#> 11 Cadogan Gardens    0.465476  0.018318   0.0  
#> 1K Hotel              0.448333  0.332811   0.0

Perlu diperhatikan bahwa nilai sentimen masih berupa nilai dalam rentang -1 hingga 1. Untuk mempermudah pemilihan hotel, alangkah lebih baik nilai sentimen dinormalisasi menjadi skor dengan rentang 0 - 10.

# Normalize sentiment in range of 0 - 10
def normalizeSentiment(x):
    if max(x) <= 1:
        return (x-(-1))/(1+1)*10
    else:
        return (x)

df_reviews[aspects] = df_reviews[aspects].apply(normalizeSentiment)
df_reviews.head(2)
#>                                                       Negative_Review  \
#> Hotel_Name                                                              
#> 11 Cadogan Gardens  [thought, prise, drink, little, excessive, not...   
#> 1K Hotel            [conditioning, room, work, despite, complainin...   
#> 
#>                                                       Positive_Review  \
#> Hotel_Name                                                              
#> 11 Cadogan Gardens  [particularly, impressed, warm, welcome, recei...   
#> 1K Hotel            [location, good, close, marais, arrondissement...   
#> 
#>                                                                Review  \
#> Hotel_Name                                                              
#> 11 Cadogan Gardens  [thought, prise, drink, little, excessive, not...   
#> 1K Hotel            [conditioning, room, work, despite, complainin...   
#> 
#>                        staff      room  breakfast   service      view  \
#> Hotel_Name                                                              
#> 11 Cadogan Gardens  7.513805  6.013889   5.599727  7.422297  5.888889   
#> 1K Hotel            7.107240  6.432088   5.524312  5.814342  4.531250   
#> 
#>                     restaurant  bathroom  pool  
#> Hotel_Name                                      
#> 11 Cadogan Gardens    7.327381  5.091588   5.0  
#> 1K Hotel              7.241667  6.664053   5.0

Gabungkan kolom-kolom yang memuat informasi tambahan yang dibutuhkan ke dataframe.

col_df = ['Average_Score', 'Total_Number_of_Reviews','Hotel_Name', 'Hotel_Address','lat','lng']

df_merge = pd.merge(df_reviews, df[col_df].drop_duplicates(), how = 'left', on = 'Hotel_Name')


def join_list(my_list):
    return " ".join(my_list)


df_merge['Review'] = df_merge['Review'].apply(lambda x: join_list(x))
df_merge['Negative_Review'] = df_merge['Negative_Review'].apply(lambda x: join_list(x))
df_merge['Positive_Review'] = df_merge['Positive_Review'].apply(lambda x: join_list(x))

df_merge.head(3)
#>                            Hotel_Name  \
#> 0                  11 Cadogan Gardens   
#> 1                            1K Hotel   
#> 2  25hours Hotel beim MuseumsQuartier   
#> 
#>                                      Negative_Review  \
#> 0  thought prise drink little excessive nothing p...   
#> 1  conditioning room work despite complaining fix...   
#> 2  breakfast included buffet really expensive bre...   
#> 
#>                                      Positive_Review  \
#> 0  particularly impressed warm welcome received l...   
#> 1  location good close marais arrondissement fitn...   
#> 2  cool vintage style middle museum quarter metro...   
#> 
#>                                               Review     staff      room  \
#> 0  thought prise drink little excessive nothing p...  7.513805  6.013889   
#> 1  conditioning room work despite complaining fix...  7.107240  6.432088   
#> 2  breakfast included buffet really expensive bre...  7.261356  6.139688   
#> 
#>    breakfast   service      view  restaurant  bathroom  pool  Average_Score  \
#> 0   5.599727  7.422297  5.888889    7.327381  5.091588   5.0            8.7   
#> 1   5.524312  5.814342  4.531250    7.241667  6.664053   5.0            7.7   
#> 2   7.106359  6.979958  7.849841    7.266420  6.738946   5.0            8.8   
#> 
#>    Total_Number_of_Reviews                                      Hotel_Address  \
#> 0                      393  11 Cadogan Gardens Sloane Square Kensington an...   
#> 1                      663  13 Boulevard Du Temple 3rd arr 75003 Paris France   
#> 2                     4324  Lerchenfelder Stra e 1 3 07 Neubau 1070 Vienna...   
#> 
#>          lat        lng  
#> 0  51.493616  -0.159235  
#> 1  48.863932   2.365874  
#> 2  48.206474  16.354630

9.1.4 Dashboard Ideas

Salah satu cara terbaik untuk memberikan informasi adalah melalui dashboard interaktif yang dapat dioperasikan langsung oleh penggunanya. Tentunya kemudahan yang diberikan akan meningkatkan penyerapan informasi oleh pengguna. berikut adalah beberapa ide-ide fitur yang akan dimuat di dalam dashboard tersebut.

# Function for dashboard

def radar_plot(data): # data categories must be stored as index

    plt.figure(figsize=(10,10))
    # number of variable
    categories=s.index
    N = len(s)
 
    # We are going to plot the first line of the data frame.
    # But we need to repeat the first value to close the circular graph:
    values= s.iloc[:,0].values.flatten().tolist()
    values += values[:1]
    values
 
    # What will be the angle of each axis in the plot? (we divide the plot / number of variable)
    angles = [n / float(N) * 2 * pi for n in range(N)]
    angles += angles[:1]
 
    # Initialise the spider plot
    ax = plt.subplot(111, polar=True)

    # Draw one axe per variable + add labels labels yet
    plt.xticks(angles[:-1], categories, color='blue', size=20)
 
    # Draw ylabels
    ax.set_rlabel_position(90)
    plt.yticks([2,4,6,8], ["2","4","6","8"], color="grey", size=20)
    plt.ylim(0,10)
 
    # Plot data
    ax.plot(angles, values, linewidth=2, linestyle='solid')
 
    # Fill area
    ax.fill(angles, values, 'b', alpha=0.1)
    
def plot_coordinate(lat, lng , name = pd.Series(['']), address = pd.Series(['']),  zoom_start = 10, color = 'red', fill_color = 'blue'):   
    m = folium.Map(location=[lat,lng], zoom_start=zoom_start)

    folium.CircleMarker(
            [lat.values[0], lng.values[0]],
            radius=10,
            color=color,
            popup='Name: ' + name.values[0] + '\n\n Address: ' + address.values[0],
            fill = True,
            fill_color = fill_color,
            fill_opacity=0.6
        ).add_to(m)
    return m

Search bar berfungsi untuk melakukan pemenggalan data (querying) untuk mengambil data yang menjadi fokus bagi penggunanya.

# Search Bar
search = '25hours Hotel beim MuseumsQuartier'

search_data = df_merge[df_merge['Hotel_Name'] == search]
search_data
#>                            Hotel_Name  \
#> 2  25hours Hotel beim MuseumsQuartier   
#> 
#>                                      Negative_Review  \
#> 2  breakfast included buffet really expensive bre...   
#> 
#>                                      Positive_Review  \
#> 2  cool vintage style middle museum quarter metro...   
#> 
#>                                               Review     staff      room  \
#> 2  breakfast included buffet really expensive bre...  7.261356  6.139688   
#> 
#>    breakfast   service      view  restaurant  bathroom  pool  Average_Score  \
#> 2   7.106359  6.979958  7.849841     7.26642  6.738946   5.0            8.8   
#> 
#>    Total_Number_of_Reviews                                      Hotel_Address  \
#> 2                     4324  Lerchenfelder Stra e 1 3 07 Neubau 1070 Vienna...   
#> 
#>          lat       lng  
#> 2  48.206474  16.35463

Average score menampilkan rating rata-rata yang diberikan oleh wisatawan terdahulu terhadap pengalaman singgah di hotel tersebut.

# Average Score
search_data['Average_Score'].values[0]
#> 8.8

Jumlah orang yang memberikan ulasan juga perlu ditampilkan. Semakin banyak ulasan, semakin reliable penilaian tersebut.

# Total Number of Reviews
search_data['Total_Number_of_Reviews'].values[0]
#> 4324

Titik koordinat diperlukan untuk menyampaikan informasi mengenai lokasi dari hotel tersebut. Hal ini juga bertujuan untuk memberikan informasi mengenai kondisi di sekitar hotel terkait seberapa strategis lokasi hotel tersebut (dekat dengan pusat wisata, pusat pembelanjaan, dan lain-lain).

plot_coordinate(search_data.lat, search_data.lng, search_data.Hotel_Name, search_data.Hotel_Address, zoom_start = 15)
Make this Notebook Trusted to load map: File -> Trust Notebook

Hasil dari analisis sentimen terkait aspek (fasilitas dan pelayanan) hotel dimuat dalam bentuk visualisasi radar plot untuk mempermudah pengambilan keputusan dalam memilih hotel yang akan dijadikan tempat singgah sementara.

# Sentiment Score

s = search_data[aspects].T
radar_plot(s)

Diagram jaringan (network diagram) untuk melihat korelasi kata-kata dengan aspek yang dipilih.

#let's see what they said about staff
plot_network(ngrams_frequency(create_ngrams(search_data['Review'].values[0].split(), 3, aspect = 'staff'), 20))

#let's see what they said about room
plot_network(ngrams_frequency(create_ngrams(search_data['Review'].values[0].split(), 3, aspect = 'room'), 20))

#let's see what they said about breakfast
plot_network(ngrams_frequency(create_ngrams(search_data['Review'].values[0].split(), 3, aspect = 'breakfast'), 20))

#let's see what they said about service
plot_network(ngrams_frequency(create_ngrams(search_data['Review'].values[0].split(), 3, aspect = 'service'), 20))

#let's see what they said about view
plot_network(ngrams_frequency(create_ngrams(search_data['Review'].values[0].split(), 3, aspect = 'view'), 20))

#let's see what they said about restaurant
plot_network(ngrams_frequency(create_ngrams(search_data['Review'].values[0].split(), 3, aspect = 'restaurant'), 20))

#let's see what they said about pool
plot_network(ngrams_frequency(create_ngrams(search_data['Review'].values[0].split(), 3, aspect = 'pool'), 20))

Wordcloud untuk review hotel tersebut untuk melihat kata-kata yang paling sering muncul dalam ulasan wisatawan.

# Draw WordCloud
wordcloud_draw(search_data['Review'].values[0].split(),'white')

Fungsi untuk mengekspor dataset yang telah diolah untuk pembuatan dashboard.

#df_merge.to_csv('dashboard_data.csv')
#df_merge.to_excel('dashboard_data.xlsx')

Hasil dari dashboard yang telah dirancang dapat dilihat di laman berikut: https://europehoteldashboard.herokuapp.com/

9.1.5 Conclusion

Hasil dari analisis ini berupa dashboard yang dapat digunakan para wisatawan untuk menentukan hotel mana yang terbaik dipilih sebagai tempat tinggal sementara selama menikmati liburan. Tentunya dengan adanya dashboard ini, diharapkan para wisatawan mendapatkan pengalaman terbaik dalam menginap. Dashboard ini menampilkan penilaian tidak hanya melalui rating score tapi juga mengenai nilai sentimen para wisatawan terdahulu terkait fasilitas-fasilitas dan pelayanan pada hotel tersebut. Fasilitas-fasilitas dan pelayanan yang menjadi aspek penilaian antara lain ruangan, staf hotel, kolam renang, kamar mandi, restoran, pemandangan, pelayanan, dan sarapan.