Sentiment Analysis and Emotion Analysis In Python - April 2024¶

In [1]:
import pandas as pd
import sys
import re
import nltk
nltk.download('wordnet')
nltk.download('omw-1.4')
from nltk.stem import WordNetLemmatizer
pd.set_option('mode.chained_assignment', None)
[nltk_data] Downloading package wordnet to /Users/liam/nltk_data...
[nltk_data]   Package wordnet is already up-to-date!
[nltk_data] Downloading package omw-1.4 to /Users/liam/nltk_data...
[nltk_data]   Package omw-1.4 is already up-to-date!

Original dataset can be found here: https://www.kaggle.com/datasets/manishkumar7432698/airline-passangers-booking-data

In [2]:
data=pd.read_csv('AirlineScrappedReview.csv')
data.head()
Out[2]:
Passanger_Name Flying_month Route Rating Verified Review_title Review_content Traveller_type Class
0 Paige Boet June 2023 New Orleans to London 1.0 Trip Verified The airline lost my luggage The airline lost my luggage and was absolutely... Solo Leisure Economy Class
1 S Layne March 2023 London to Amman 1.0 Trip Verified fully refunded by our travel insurance We booked on the BA website, round trip flight... Couple Leisure Business Class
2 E Lanewoski Heathrow to Bodrum Business Class 2.0 Trip Verified no boarding drinks provided First time flying with BA business class, neve... A321 neo Solo Leisure
3 Joel Burman June 2023 Amman to London 4.0 Not Verified WiFi didn't work You can buy sandwiches and crisps but don't ex... Solo Leisure Economy Class
4 R Vines London City to Ibiza Business Class 7.0 Trip Verified stick with economy This is a two-for-one review covering economy ... Embraer 190 Family Leisure
In [3]:
data.describe()
Out[3]:
Rating
count 3575.000000
mean 4.790490
std 3.170323
min 1.000000
25% 2.000000
50% 4.000000
75% 8.000000
max 10.000000
In [4]:
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3580 entries, 0 to 3579
Data columns (total 9 columns):
 #   Column          Non-Null Count  Dtype  
---  ------          --------------  -----  
 0   Passanger_Name  3580 non-null   object 
 1   Flying_month    2815 non-null   object 
 2   Route           2816 non-null   object 
 3   Rating          3575 non-null   float64
 4   Verified        1270 non-null   object 
 5   Review_title    3580 non-null   object 
 6   Review_content  3580 non-null   object 
 7   Traveller_type  3580 non-null   object 
 8   Class           3579 non-null   object 
dtypes: float64(1), object(8)
memory usage: 251.8+ KB

Sentiment Analysis¶

After data description and the data information, we can see the data with no missing value, then we use the VADER Lexicon to do the Sentiment Analysis with the data.

In [5]:
pip install vaderSentiment
Requirement already satisfied: vaderSentiment in /Users/liam/anaconda3/lib/python3.11/site-packages (3.3.2)
Requirement already satisfied: requests in /Users/liam/anaconda3/lib/python3.11/site-packages (from vaderSentiment) (2.31.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /Users/liam/anaconda3/lib/python3.11/site-packages (from requests->vaderSentiment) (2.0.4)
Requirement already satisfied: idna<4,>=2.5 in /Users/liam/anaconda3/lib/python3.11/site-packages (from requests->vaderSentiment) (3.4)
Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/liam/anaconda3/lib/python3.11/site-packages (from requests->vaderSentiment) (1.26.16)
Requirement already satisfied: certifi>=2017.4.17 in /Users/liam/anaconda3/lib/python3.11/site-packages (from requests->vaderSentiment) (2023.11.17)
Note: you may need to restart the kernel to use updated packages.

The output of Vader is a composite score that ranges from -1 to 1. Therefore, we define the polarity of the comment to be judged based on the score computed by the review at different value intervals. Specifically, when the score < -0.05, the polarity of the review is judged to be negative, when the score > 0.05, the polarity of the review is judged to be positive, and when -0.05<score<0.05, the polarity of the review is judged to be neural. We newly defined two features, sentiment_title and sentiment_content, as the storage variables for the results of vader lexicon runs. (As later I will compare the Rating variable with sentiment_title and sentiment_content to see which new feature is more matching and have a further analysis.

In [6]:
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
df=data[['Review_title','Review_content']]
analyzer = SentimentIntensityAnalyzer()
compound_title=[]
sentiment_title=[]
compound_content=[]
sentiment_content=[]
for index,row in df.iterrows():
    vs1=analyzer.polarity_scores(row['Review_title'])
    vs2=analyzer.polarity_scores(row['Review_content'])
    if vs1['compound']<=-0.05:
        sentiment_title.append('neg')
    elif vs1['compound']>=0.05:
        sentiment_title.append('pos')
    else:
        sentiment_title.append('neu')
    compound_title.append(vs1['compound'])
    if vs2['compound']<=-0.05:
        sentiment_content.append('neg')
    elif vs2['compound']>=0.05:
        sentiment_content.append('pos')
    else:
        sentiment_content.append('neu')
    compound_content.append(vs2['compound'])

data['compound_title']=compound_title
data['sentiment_title']=sentiment_title
data['compound_content']=compound_content
data['sentiment_content']=sentiment_content
In [7]:
data
Out[7]:
Passanger_Name Flying_month Route Rating Verified Review_title Review_content Traveller_type Class compound_title sentiment_title compound_content sentiment_content
0 Paige Boet June 2023 New Orleans to London 1.0 Trip Verified The airline lost my luggage The airline lost my luggage and was absolutely... Solo Leisure Economy Class -0.3182 neg -0.7990 neg
1 S Layne March 2023 London to Amman 1.0 Trip Verified fully refunded by our travel insurance We booked on the BA website, round trip flight... Couple Leisure Business Class 0.0000 neu 0.0219 neu
2 E Lanewoski Heathrow to Bodrum Business Class 2.0 Trip Verified no boarding drinks provided First time flying with BA business class, neve... A321 neo Solo Leisure -0.2960 neg -0.7096 neg
3 Joel Burman June 2023 Amman to London 4.0 Not Verified WiFi didn't work You can buy sandwiches and crisps but don't ex... Solo Leisure Economy Class 0.0000 neu -0.5849 neg
4 R Vines London City to Ibiza Business Class 7.0 Trip Verified stick with economy This is a two-for-one review covering economy ... Embraer 190 Family Leisure 0.0000 neu 0.9789 pos
... ... ... ... ... ... ... ... ... ... ... ... ... ...
3575 W Benson NaN NaN 4.0 NaN British Airways customer review LHR-HKG on Boeing 747 - 23/08/12. Much has bee... Economy Class no 0.0000 neu 0.9129 pos
3576 S Luqman NaN NaN 4.0 NaN British Airways customer review Just got back from Bridgetown Barbados flying ... Economy Class no 0.0000 neu -0.8763 neg
3577 D Smith NaN NaN 4.0 NaN British Airways customer review LHR-JFK-LAX-LHR. Check in was ok apart from be... Economy Class no 0.0000 neu -0.2003 neg
3578 W Benson NaN NaN 6.0 NaN British Airways customer review HKG-LHR in New Club World on Boeing 777-300 - ... Business Class yes 0.0000 neu 0.9864 pos
3579 Michael Dielissen NaN NaN 8.0 NaN British Airways customer review YYZ to LHR - July 2012 - I flew overnight in p... Premium Economy yes 0.0000 neu 0.0933 pos

3580 rows × 13 columns

Sentiment Analysis Evaluation (Compare with Rating Score)¶

I explore their alignment with the corresponding ratings. Typically, negative comments should correlate with lower ratings, and positive comments should relate to higher ratings. so, we categorized ratings into three groups: 1, 2, and 3 as negative; 4 and 5 as neutral; and any rating above 5 as positive.

In [8]:
# Define the Rating into three emotion category
df2 = data['Rating']
Rating_type = []

for index, rating in df2.items():
    if rating in [1.0, 2.0, 3.0]:
        Rating_type.append('neg')
    elif rating in [4.0, 5.0]:
        Rating_type.append('neu')
    else:
        Rating_type.append('pos')

data['Rating_type'] = Rating_type
In [9]:
data
Out[9]:
Passanger_Name Flying_month Route Rating Verified Review_title Review_content Traveller_type Class compound_title sentiment_title compound_content sentiment_content Rating_type
0 Paige Boet June 2023 New Orleans to London 1.0 Trip Verified The airline lost my luggage The airline lost my luggage and was absolutely... Solo Leisure Economy Class -0.3182 neg -0.7990 neg neg
1 S Layne March 2023 London to Amman 1.0 Trip Verified fully refunded by our travel insurance We booked on the BA website, round trip flight... Couple Leisure Business Class 0.0000 neu 0.0219 neu neg
2 E Lanewoski Heathrow to Bodrum Business Class 2.0 Trip Verified no boarding drinks provided First time flying with BA business class, neve... A321 neo Solo Leisure -0.2960 neg -0.7096 neg neg
3 Joel Burman June 2023 Amman to London 4.0 Not Verified WiFi didn't work You can buy sandwiches and crisps but don't ex... Solo Leisure Economy Class 0.0000 neu -0.5849 neg neu
4 R Vines London City to Ibiza Business Class 7.0 Trip Verified stick with economy This is a two-for-one review covering economy ... Embraer 190 Family Leisure 0.0000 neu 0.9789 pos pos
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
3575 W Benson NaN NaN 4.0 NaN British Airways customer review LHR-HKG on Boeing 747 - 23/08/12. Much has bee... Economy Class no 0.0000 neu 0.9129 pos neu
3576 S Luqman NaN NaN 4.0 NaN British Airways customer review Just got back from Bridgetown Barbados flying ... Economy Class no 0.0000 neu -0.8763 neg neu
3577 D Smith NaN NaN 4.0 NaN British Airways customer review LHR-JFK-LAX-LHR. Check in was ok apart from be... Economy Class no 0.0000 neu -0.2003 neg neu
3578 W Benson NaN NaN 6.0 NaN British Airways customer review HKG-LHR in New Club World on Boeing 777-300 - ... Business Class yes 0.0000 neu 0.9864 pos pos
3579 Michael Dielissen NaN NaN 8.0 NaN British Airways customer review YYZ to LHR - July 2012 - I flew overnight in p... Premium Economy yes 0.0000 neu 0.0933 pos pos

3580 rows × 14 columns

Performance of the Sentiment_content and the Sentiment_title¶

Then I use a histogram chart to show how the grouped ratings compare to with the sentiment content and sentiment title. We observed that using the Sentiment Content for analysis have a greater accuracy compared to using the Sentiment Title. This suggests that the actual content of customer comments provides more reliable insights into sentiment.

In [10]:
import matplotlib.pyplot as plt

emocont = data['sentiment_content']
rattype = data['Rating_type']
#emotion_content
plt.subplot(1, 2, 1) 
plt.hist(emocont, bins=3, edgecolor='black')
plt.xlabel('Sentiment Content')
plt.ylabel('Frequency')
plt.title('Sentiment Content Distribution')
#Rating_type
plt.subplot(1, 2, 2) 
plt.hist(rattype, bins=3, edgecolor='black')
plt.xlabel('Rating Type')
plt.ylabel('Frequency')
plt.title('Rating Type Distribution')

plt.tight_layout()
plt.show()
In [11]:
import matplotlib.pyplot as plt

emocont = data['sentiment_title']
rattype = data['Rating_type']
#emotion_title
plt.subplot(1, 2, 1) 
plt.hist(emocont, bins=3, edgecolor='black')
plt.xlabel('Sentiment Title')
plt.ylabel('Frequency')
plt.title('Sentiment Title Distribution')
#Rating_type
plt.subplot(1, 2, 2) 
plt.hist(rattype, bins=3, edgecolor='black')
plt.xlabel('Rating Type')
plt.ylabel('Frequency')
plt.title('Rating Type Distribution')

plt.tight_layout()
plt.show()

Then to evaluate the consistency between ratings and sentiment results, we can calculated an overall accuracy rate.

In [12]:
# Define the Accuracy
data['Accuracy'] = 0
data.loc[data['sentiment_content'] == data['Rating_type'], 'Accuracy'] = 1
print(data)
         Passanger_Name          Flying_month                  Route  Rating  \
0            Paige Boet             June 2023  New Orleans to London     1.0   
1               S Layne            March 2023        London to Amman     1.0   
2           E Lanewoski    Heathrow to Bodrum         Business Class     2.0   
3           Joel Burman             June 2023        Amman to London     4.0   
4               R Vines  London City to Ibiza         Business Class     7.0   
...                 ...                   ...                    ...     ...   
3575           W Benson                   NaN                    NaN     4.0   
3576           S Luqman                   NaN                    NaN     4.0   
3577            D Smith                   NaN                    NaN     4.0   
3578           W Benson                   NaN                    NaN     6.0   
3579  Michael Dielissen                   NaN                    NaN     8.0   

           Verified                            Review_title  \
0     Trip Verified             The airline lost my luggage   
1     Trip Verified  fully refunded by our travel insurance   
2     Trip Verified             no boarding drinks provided   
3      Not Verified                       WiFi didn't work    
4     Trip Verified                      stick with economy   
...             ...                                     ...   
3575            NaN         British Airways customer review   
3576            NaN         British Airways customer review   
3577            NaN         British Airways customer review   
3578            NaN         British Airways customer review   
3579            NaN         British Airways customer review   

                                         Review_content   Traveller_type  \
0     The airline lost my luggage and was absolutely...     Solo Leisure   
1     We booked on the BA website, round trip flight...   Couple Leisure   
2     First time flying with BA business class, neve...         A321 neo   
3     You can buy sandwiches and crisps but don't ex...     Solo Leisure   
4     This is a two-for-one review covering economy ...      Embraer 190   
...                                                 ...              ...   
3575  LHR-HKG on Boeing 747 - 23/08/12. Much has bee...    Economy Class   
3576  Just got back from Bridgetown Barbados flying ...    Economy Class   
3577  LHR-JFK-LAX-LHR. Check in was ok apart from be...    Economy Class   
3578  HKG-LHR in New Club World on Boeing 777-300 - ...   Business Class   
3579  YYZ to LHR - July 2012 - I flew overnight in p...  Premium Economy   

               Class  compound_title sentiment_title  compound_content  \
0      Economy Class         -0.3182             neg           -0.7990   
1     Business Class          0.0000             neu            0.0219   
2       Solo Leisure         -0.2960             neg           -0.7096   
3      Economy Class          0.0000             neu           -0.5849   
4     Family Leisure          0.0000             neu            0.9789   
...              ...             ...             ...               ...   
3575              no          0.0000             neu            0.9129   
3576              no          0.0000             neu           -0.8763   
3577              no          0.0000             neu           -0.2003   
3578             yes          0.0000             neu            0.9864   
3579             yes          0.0000             neu            0.0933   

     sentiment_content Rating_type  Accuracy  
0                  neg         neg         1  
1                  neu         neg         0  
2                  neg         neg         1  
3                  neg         neu         0  
4                  pos         pos         1  
...                ...         ...       ...  
3575               pos         neu         0  
3576               neg         neu         0  
3577               neg         neu         0  
3578               pos         pos         1  
3579               pos         pos         1  

[3580 rows x 15 columns]
In [13]:
data.head(10)
Out[13]:
Passanger_Name Flying_month Route Rating Verified Review_title Review_content Traveller_type Class compound_title sentiment_title compound_content sentiment_content Rating_type Accuracy
0 Paige Boet June 2023 New Orleans to London 1.0 Trip Verified The airline lost my luggage The airline lost my luggage and was absolutely... Solo Leisure Economy Class -0.3182 neg -0.7990 neg neg 1
1 S Layne March 2023 London to Amman 1.0 Trip Verified fully refunded by our travel insurance We booked on the BA website, round trip flight... Couple Leisure Business Class 0.0000 neu 0.0219 neu neg 0
2 E Lanewoski Heathrow to Bodrum Business Class 2.0 Trip Verified no boarding drinks provided First time flying with BA business class, neve... A321 neo Solo Leisure -0.2960 neg -0.7096 neg neg 1
3 Joel Burman June 2023 Amman to London 4.0 Not Verified WiFi didn't work You can buy sandwiches and crisps but don't ex... Solo Leisure Economy Class 0.0000 neu -0.5849 neg neu 0
4 R Vines London City to Ibiza Business Class 7.0 Trip Verified stick with economy This is a two-for-one review covering economy ... Embraer 190 Family Leisure 0.0000 neu 0.9789 pos pos 1
5 M King June 2023 London to Washington 1.0 Trip Verified Communication is terrible Absolutely horrible airline. Communication is ... Couple Leisure Economy Class -0.4767 neg -0.8932 neg neg 1
6 C Dean Amsterdam to London Business Class 1.0 Trip Verified delays and cancellations Having experienced delays and cancellations de... A350 Business 0.0000 neu 0.0516 pos neg 0
7 Richard Hodges London Heathrow to Kalamata Economy Class 7.0 Trip Verified Economy class seating was truly dreadful Travelled to Heathrow to Kalamata and return j... Boeing 737 Couple Leisure 0.0000 neu 0.7900 pos pos 1
8 Carol Willmore June 2023 London to Funchal 1.0 Not Verified flight failed at every level This flight failed at every level. We were del... Couple Leisure Economy Class -0.5106 neg 0.8693 pos neg 0
9 Nadine Kohler June 2023 Marseille to Seattle via London Heathrow 1.0 Not Verified BA refuses to make it right Beware of British Airways and their marketing ... Family Leisure Economy Class 0.0000 neu -0.5519 neg neg 1
In [14]:
total_rows = len(data)
num_ones = data['Accuracy'].sum()
overall_accuracy = num_ones / total_rows
print(f"Overall accuracy: {overall_accuracy:.2%}")
Overall accuracy: 70.08%

We can see the output accuracy is about 70%. It is enough high, as sentiment anlysis may can not detect the special comments such as rating the high points on purpose but express the negative comments or using the sarcastic comments and so on to express their feeling.

Prediction for Sentiment Result (Models Comparison)¶

Then use CountVectorizer to convert the text into a word frequency matrix. Among them, stop_words='english' means to remove common English stop words, max_df=0.5 means to ignore words with document frequency more than 50%, and decode_error='ignore' means to ignore decoding errors. This step generates the word frequency matrix counts_train for the training set.

The word frequency matrix is transformed into a TF-IDF (Word Frequency-Inverse Document Frequency) matrix using TfidfTransformer. This step generates the TF-IDF matrix tfidf_train for the training set.

Using the same parameter configuration, construct the TF-IDF matrix tfidf_valid on the test set. note that the vocabulary of the training set should be used to construct the TF-IDF matrix for the test set to ensure word consistency.

In [15]:
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.metrics import ConfusionMatrixDisplay
from sklearn.model_selection import train_test_split
import random
import numpy as np
X=data['Review_content']
Y=data['sentiment_content']
X_train,X_test,Y_train,Y_test=train_test_split(X,Y,test_size=0.3,random_state=random.randint(0,1000))

RandomForest¶

In [16]:
# max_df=0.5: ingnore terms with document frequency higher than the given threshold
count_vec1 = CountVectorizer(stop_words = 'english', max_df = 0.5, decode_error = 'ignore')
counts_train = count_vec1.fit_transform(X_train)
tfidftransformer = TfidfTransformer()
tfidf_train = tfidftransformer.fit(counts_train).transform(counts_train)
    
# Build TFIDF vectors based on testing tweets
count_vec2 = CountVectorizer(vocabulary = count_vec1.vocabulary_, stop_words = 'english', max_df = 0.5, decode_error = 'ignore')
counts_valid = count_vec2.fit_transform(X_test)
tfidf_valid = tfidftransformer.fit(counts_valid).transform(counts_valid)
 

# Use Random Forest to classify the test set
# n_estimatores controls the no. of trees used, random_state=0: no randomness in feature/instance sampling
clf = RandomForestClassifier(criterion='entropy', n_estimators=300, random_state=0)
clf.fit(tfidf_train, Y_train)
predict_results = clf.predict(tfidf_valid)
   
#F1-score
'''TP, TN, FN, FP = 0, 0, 0, 0
for i in range(data.shape[0]):
        if Y_test[i] == 'pos' and predict_results[i] == 'pos':
            TP += 1
        elif Y_test[i] == 'neg' and predict_results[i] == 'neg':
            TN += 1
        elif Y_test[i] == 'pos' and predict_results[i] == 'neg':
            FN += 1
        else:
            FP += 1'''
TP = sum((a == 'pos' and b == 'pos') for a, b in zip(Y_test, predict_results))
TN = sum((a == 'neg' and b == 'neg') for a, b in zip(Y_test, predict_results))
FP = sum((a == 'neg' and b == 'pos') for a, b in zip(Y_test, predict_results))
FN = sum((a == 'pos' and b == 'neg') for a, b in zip(Y_test, predict_results))
# print(TP, TN, FN, FP)
precision = TP / (TP + FP)
recall = TP / (TP + FN)
F1 = 2 * recall * precision / (recall + precision)

print("The F-score is:")
print(F1)
The F-score is:
0.8438690022848437

DecisionTree¶

In [17]:
clf1= DecisionTreeClassifier(criterion='entropy', random_state=random.randint(0,1000))
clf1.fit(tfidf_train, Y_train)
predict_results = clf1.predict(tfidf_valid)
TP = sum((a == 'pos' and b == 'pos') for a, b in zip(Y_test, predict_results))
TN = sum((a == 'neg' and b == 'neg') for a, b in zip(Y_test, predict_results))
FP = sum((a == 'neg' and b == 'pos') for a, b in zip(Y_test, predict_results))
FN = sum((a == 'pos' and b == 'neg') for a, b in zip(Y_test, predict_results))
# print(TP, TN, FN, FP)
precision = TP / (TP + FP)
recall = TP / (TP + FN)
F1 = 2 * recall * precision / (recall + precision)
print("The F-score is:")
print(F1)
The F-score is:
0.7457351746547521

Bagging¶

In [18]:
clf2=BaggingClassifier(DecisionTreeClassifier(max_depth=3),n_estimators=50)
clf2.fit(tfidf_train, Y_train)
predict_results = clf2.predict(tfidf_valid)
TP = sum((a == 'pos' and b == 'pos') for a, b in zip(Y_test, predict_results))
TN = sum((a == 'neg' and b == 'neg') for a, b in zip(Y_test, predict_results))
FP = sum((a == 'neg' and b == 'pos') for a, b in zip(Y_test, predict_results))
FN = sum((a == 'pos' and b == 'neg') for a, b in zip(Y_test, predict_results))
# print(TP, TN, FN, FP)
precision = TP / (TP + FP)
recall = TP / (TP + FN)
F1 = 2 * recall * precision / (recall + precision)
print("The F-score is:")
print(F1)
The F-score is:
0.7299006323396567

Adaboost¶

In [19]:
np.random.seed(1)
clf3 = AdaBoostClassifier(DecisionTreeClassifier(max_depth=3),n_estimators=5) # number estimators is the key parameter
clf3.fit(tfidf_train, Y_train)
predict_results = clf3.predict(tfidf_valid)
TP = sum((a == 'pos' and b == 'pos') for a, b in zip(Y_test, predict_results))
TN = sum((a == 'neg' and b == 'neg') for a, b in zip(Y_test, predict_results))
FP = sum((a == 'neg' and b == 'pos') for a, b in zip(Y_test, predict_results))
FN = sum((a == 'pos' and b == 'neg') for a, b in zip(Y_test, predict_results))
# print(TP, TN, FN, FP)
precision = TP / (TP + FP)
recall = TP / (TP + FN)
F1 = 2 * recall * precision / (recall + precision)
print("The F-score is:")
print(F1)
The F-score is:
0.6872727272727274

We can see the Random Forest have the best performance with an F1 score of 0.8479, later on the airlines company can apply this prediction models to quickly identify the direction of public opinion in the market, so that the company's public relations department can keep an eye on the company's overall reputation and public opinion in the market and take remedial measures when negative public opinion is detected in a timely manner.

Then I use emotion analysis to categorize the comments into the eight dimensions of emotion. Based on the score, we can see the comment belong to which emotion category. As emotion analysis helps to understand consumer emotions, improve customer experiences, and then make us have a further understand which features have an impact on their experience so that we can draw a suggestion to our business object and enables us to build better marketing strategies.

Emotion Analysis¶

Using the well-known NRC emotion lexicons to do the Emotion Analysis the lexicon can be found from: https://saifmohammad.com/WebPages/NRC-Emotion-Lexicon.htm created by Dr. Saif M. Mohammad and Dr. Peter Turney

In [20]:
#!/usr/bin/env python
# coding: utf-8
import pandas as pd
import re
import nltk

nltk.download('wordnet')
from nltk.stem import WordNetLemmatizer
#negation.csv including the words of not, don't, but, doesn't, except, no
NRC_lexicon_path = 'NRC-Emotion-Lexicon-Wordlevel-v0.92.txt'
negation_path = 'Negation.csv'
negation_words = [word.strip() for word in open(negation_path, 'r')]

# Read lexicon data
lexicon_df = pd.read_csv(NRC_lexicon_path, names=["word", "emotion", "label"], sep='\t', keep_default_na=False)
lexicon_df = lexicon_df.pivot(index='word', columns='emotion', values='label').reset_index()
dataset_path = 'AirlineScrappedReview.csv'

# Read dataset
dataset_df = pd.read_csv(dataset_path)

# Extract the 'Review_title' column as a list of text
review_list = dataset_df['Review_content'].tolist()

token_list = []

for line in review_list:
    tokens = re.split('[ ;.?|:]', line.strip())
    for token in tokens:
        if token != '':
            # Lemmatize the words to better map the lexicon keyword
            lemmatizer = WordNetLemmatizer()
            token = lemmatizer.lemmatize(token)
            token_list.append(token.lower())

mapping_df = lexicon_df.loc[lexicon_df['word'].isin(token_list)]

# Change the polarity of words based on negation
mapping_words = mapping_df['word'].tolist()
for word in mapping_words:
    word_index = token_list.index(word)
    if word_index >= 3:
        for i in range(word_index - 3, word_index):
            if token_list[i] in negation_words:
                negative = mapping_df.loc[mapping_df['word'] == word, 'negative'].copy().item()
                positive = mapping_df.loc[mapping_df['word'] == word, 'positive'].copy().item()

                mapping_df.loc[mapping_df['word'] == word, 'negative'] = positive
                mapping_df.loc[mapping_df['word'] == word, 'positive'] = negative
    else:
        for i in range(0, word_index):
            if token_list[i] in negation_words:
                negative = mapping_df.loc[mapping_df['word'] == word, 'negative'].copy().item()
                positive = mapping_df.loc[mapping_df['word'] == word, 'positive'].copy().item()

                mapping_df.loc[mapping_df['word'] == word, 'negative'] = positive
                mapping_df.loc[mapping_df['word'] == word, 'positive'] = negative

# Calculate emotion scores for each review title
emotions = ['anger', 'anticipation', 'disgust', 'fear', 'joy', 'sadness', 'surprise', 'trust']
emotion_scores = []

for line in review_list:
    tokens = re.split('[ ;.?|:]', line.strip())
    emotion_count = {}
    for emotion in emotions:
        emotion_count[emotion] = 0

    for token in tokens:
        if token != '':
            lemmatizer = WordNetLemmatizer()
            token = lemmatizer.lemmatize(token)
            token = token.lower()
            if token in mapping_df['word'].tolist():
                token_emotion_scores = mapping_df.loc[mapping_df['word'] == token, emotions].values[0]
                for i, emotion in enumerate(emotions):
                    emotion_count[emotion] += token_emotion_scores[i]

    total_count = sum(emotion_count.values())
    if total_count > 0:
        emotion_score = {emotion: count / total_count for emotion, count in emotion_count.items()}
    else:
        emotion_score = {emotion: 0.0 for emotion in emotions}

    emotion_scores.append(emotion_score)

# Create new columns for each emotion score
for emotion in emotions:
    data[emotion] = [score[emotion] for score in emotion_scores]
[nltk_data] Downloading package wordnet to /Users/liam/nltk_data...
[nltk_data]   Package wordnet is already up-to-date!
In [21]:
data['MAX_EMOTION'] = data[emotions].idxmax(axis=1)
In [22]:
data.head(10)
Out[22]:
Passanger_Name Flying_month Route Rating Verified Review_title Review_content Traveller_type Class compound_title ... Accuracy anger anticipation disgust fear joy sadness surprise trust MAX_EMOTION
0 Paige Boet June 2023 New Orleans to London 1.0 Trip Verified The airline lost my luggage The airline lost my luggage and was absolutely... Solo Leisure Economy Class -0.3182 ... 1 0.133333 0.133333 0.133333 0.133333 0.066667 0.200000 0.133333 0.066667 sadness
1 S Layne March 2023 London to Amman 1.0 Trip Verified fully refunded by our travel insurance We booked on the BA website, round trip flight... Couple Leisure Business Class 0.0000 ... 0 0.057143 0.085714 0.000000 0.228571 0.028571 0.171429 0.057143 0.371429 trust
2 E Lanewoski Heathrow to Bodrum Business Class 2.0 Trip Verified no boarding drinks provided First time flying with BA business class, neve... A321 neo Solo Leisure -0.2960 ... 1 0.166667 0.333333 0.000000 0.333333 0.000000 0.166667 0.000000 0.000000 anticipation
3 Joel Burman June 2023 Amman to London 4.0 Not Verified WiFi didn't work You can buy sandwiches and crisps but don't ex... Solo Leisure Economy Class 0.0000 ... 0 0.125000 0.125000 0.000000 0.000000 0.125000 0.000000 0.125000 0.500000 trust
4 R Vines London City to Ibiza Business Class 7.0 Trip Verified stick with economy This is a two-for-one review covering economy ... Embraer 190 Family Leisure 0.0000 ... 1 0.000000 0.130435 0.000000 0.000000 0.173913 0.043478 0.043478 0.608696 trust
5 M King June 2023 London to Washington 1.0 Trip Verified Communication is terrible Absolutely horrible airline. Communication is ... Couple Leisure Economy Class -0.4767 ... 1 0.230769 0.000000 0.230769 0.230769 0.000000 0.153846 0.000000 0.153846 anger
6 C Dean Amsterdam to London Business Class 1.0 Trip Verified delays and cancellations Having experienced delays and cancellations de... A350 Business 0.0000 ... 0 0.142857 0.071429 0.142857 0.214286 0.000000 0.142857 0.071429 0.214286 fear
7 Richard Hodges London Heathrow to Kalamata Economy Class 7.0 Trip Verified Economy class seating was truly dreadful Travelled to Heathrow to Kalamata and return j... Boeing 737 Couple Leisure 0.0000 ... 1 0.074074 0.259259 0.074074 0.148148 0.148148 0.148148 0.037037 0.111111 anticipation
8 Carol Willmore June 2023 London to Funchal 1.0 Not Verified flight failed at every level This flight failed at every level. We were del... Couple Leisure Economy Class -0.5106 ... 0 0.030303 0.303030 0.030303 0.030303 0.212121 0.090909 0.060606 0.242424 anticipation
9 Nadine Kohler June 2023 Marseille to Seattle via London Heathrow 1.0 Not Verified BA refuses to make it right Beware of British Airways and their marketing ... Family Leisure Economy Class 0.0000 ... 1 0.026316 0.289474 0.026316 0.078947 0.105263 0.105263 0.131579 0.236842 anticipation

10 rows × 24 columns

In [23]:
data['MAX_EMOTION'].value_counts()
Out[23]:
trust           1947
anticipation     905
sadness          185
joy              171
anger            164
fear             144
disgust           45
surprise          19
Name: MAX_EMOTION, dtype: int64

Draw a histogram to see the whole Emotion content distribution.

In [24]:
emotion = data['MAX_EMOTION']
plt.hist(emotion, bins=8, edgecolor='black')
plt.xlabel('Emotion Content')
plt.ylabel('Frequency')
plt.title('Emotion Content Distribution')
Out[24]:
Text(0.5, 1.0, 'Emotion Content Distribution')
In [25]:
output_file = 'emotion analysis.csv'
data.to_csv(output_file, index=False)
print(f"Data saved to {output_file}")
Data saved to emotion analysis.csv
In [26]:
X=data['Review_content']
Y=data['MAX_EMOTION']
X_train,X_test,Y_train,Y_test=train_test_split(X,Y,test_size=0.3,random_state=random.randint(0,1000))

Prediction for Emotional Analysis Result (Models Comparison)¶

Similar as the sentiment analysis, we do the models comparison, to provide the best model for the further analysis if the company want to predict the comments, and can keep an eye on the company's overall reputation and public opinion in the market.

RandomForest¶

In [27]:
# max_df=0.5: ingnore terms with document frequency higher than the given threshold
count_vec1 = CountVectorizer(stop_words = 'english', max_df = 0.5, decode_error = 'ignore')
counts_train = count_vec1.fit_transform(X_train)
tfidftransformer = TfidfTransformer()
tfidf_train = tfidftransformer.fit(counts_train).transform(counts_train)
    
# Build TFIDF vectors based on testing tweets
#validation_labels = validation_set['Label'].values
#validation_features = validation_set['Text'].values
count_vec2 = CountVectorizer(vocabulary = count_vec1.vocabulary_, stop_words = 'english', max_df = 0.5, decode_error = 'ignore')
counts_valid = count_vec2.fit_transform(X_test)
tfidf_valid = tfidftransformer.fit(counts_valid).transform(counts_valid)
 

# Use Random Forest to classify the test set
# n_estimatores controls the no. of trees used, random_state=0: no randomness in feature/instance sampling
clf = RandomForestClassifier(criterion='entropy', n_estimators=300, random_state=0)
clf.fit(tfidf_train, Y_train)
predict_results = clf.predict(tfidf_valid)
In [28]:
from sklearn.metrics import classification_report
report = classification_report(Y_test, predict_results)
print("\nClassification Report:\n", report)
Classification Report:
               precision    recall  f1-score   support

       anger       1.00      0.05      0.09        42
anticipation       0.66      0.22      0.33       264
     disgust       1.00      0.06      0.11        17
        fear       1.00      0.03      0.05        36
         joy       1.00      0.08      0.14        53
     sadness       0.00      0.00      0.00        67
    surprise       0.00      0.00      0.00         8
       trust       0.59      0.98      0.73       587

    accuracy                           0.60      1074
   macro avg       0.66      0.18      0.18      1074
weighted avg       0.62      0.60      0.50      1074

/Users/liam/anaconda3/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, msg_start, len(result))
/Users/liam/anaconda3/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, msg_start, len(result))
/Users/liam/anaconda3/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, msg_start, len(result))

DecisionTree¶

In [29]:
clf1= DecisionTreeClassifier(criterion='entropy', random_state=random.randint(0,1000))
clf1.fit(tfidf_train, Y_train)
predict_results = clf1.predict(tfidf_valid)

report = classification_report(Y_test, predict_results)
print("\nClassification Report:\n", report)
Classification Report:
               precision    recall  f1-score   support

       anger       0.09      0.12      0.10        42
anticipation       0.41      0.49      0.45       264
     disgust       0.14      0.06      0.08        17
        fear       0.15      0.17      0.16        36
         joy       0.23      0.19      0.21        53
     sadness       0.18      0.09      0.12        67
    surprise       0.00      0.00      0.00         8
       trust       0.67      0.66      0.66       587

    accuracy                           0.51      1074
   macro avg       0.23      0.22      0.22      1074
weighted avg       0.50      0.51      0.50      1074

Bagging¶

In [30]:
clf2=BaggingClassifier(DecisionTreeClassifier(max_depth=3),n_estimators=50)
clf2.fit(tfidf_train, Y_train)
predict_results = clf2.predict(tfidf_valid)

report = classification_report(Y_test, predict_results)
print("\nClassification Report:\n", report)
Classification Report:
               precision    recall  f1-score   support

       anger       0.00      0.00      0.00        42
anticipation       0.63      0.32      0.43       264
     disgust       0.00      0.00      0.00        17
        fear       0.00      0.00      0.00        36
         joy       0.00      0.00      0.00        53
     sadness       0.00      0.00      0.00        67
    surprise       0.00      0.00      0.00         8
       trust       0.58      0.93      0.71       587

    accuracy                           0.59      1074
   macro avg       0.15      0.16      0.14      1074
weighted avg       0.47      0.59      0.50      1074

/Users/liam/anaconda3/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, msg_start, len(result))
/Users/liam/anaconda3/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, msg_start, len(result))
/Users/liam/anaconda3/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, msg_start, len(result))

AdaBoost¶

In [31]:
np.random.seed(1)
clf3 = AdaBoostClassifier(DecisionTreeClassifier(max_depth=3),n_estimators=5) # number estimators is the key parameter
clf3.fit(tfidf_train, Y_train)

report = classification_report(Y_test, predict_results)
print("\nClassification Report:\n", report)
Classification Report:
               precision    recall  f1-score   support

       anger       0.00      0.00      0.00        42
anticipation       0.63      0.32      0.43       264
     disgust       0.00      0.00      0.00        17
        fear       0.00      0.00      0.00        36
         joy       0.00      0.00      0.00        53
     sadness       0.00      0.00      0.00        67
    surprise       0.00      0.00      0.00         8
       trust       0.58      0.93      0.71       587

    accuracy                           0.59      1074
   macro avg       0.15      0.16      0.14      1074
weighted avg       0.47      0.59      0.50      1074

/Users/liam/anaconda3/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, msg_start, len(result))
/Users/liam/anaconda3/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, msg_start, len(result))
/Users/liam/anaconda3/lib/python3.11/site-packages/sklearn/metrics/_classification.py:1469: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, msg_start, len(result))

Word Cloud¶

After did the Sentiment Analysis and the Emotion Analysis, we can base on the Review_content and use the Word Cloud to find out the aspects that customers focus on from the comments, as the accuracy of the sentiment analysis align with the rating is enough high (70% accuracy). To improve our service, I focus on comments from customers who are marked as negative, extract sentiment words and nouns, and then use these word pairs for high-frequency word statistics and word clouds for visualization.

In [32]:
import pandas as pd
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.tag import pos_tag
from wordcloud import WordCloud
import matplotlib.pyplot as plt
import re
In [33]:
data = pd.read_csv('emotion analysis.csv')
reviews = data['Review_content']
In [34]:
def extract_keywords(text):
    # divide the words
    tokens = word_tokenize(text)
    
    # delete the stop words
    stop_words = set(stopwords.words('english'))
    tokens = [token.lower() for token in tokens if token.lower() not in stop_words]
    
    # extract the emotion word and noun
    tagged_tokens = pos_tag(tokens)
    keywords = [word for word, pos in tagged_tokens if pos.startswith('NN') or pos == 'JJ']
    
    return keywords
In [35]:
reviews_neg = [review for review,sentimentkind in zip(data['Review_content'], data['sentiment_content']) if sentimentkind == 'neg']
In [36]:
all_keywords_neg = []
for review in reviews_neg:
    keywords = extract_keywords(review)
    all_keywords_neg.extend(keywords)

word_freq_neg = nltk.FreqDist(all_keywords_neg)
In [37]:
wordcloud = WordCloud(width=800, height=400, background_color='white').generate_from_frequencies(word_freq_neg)
plt.figure(figsize=(10, 5))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
plt.title('The Wordcloud of Neg-Sentiment ',fontsize=16)
plt.show()
In [38]:
top_keywords_neg = word_freq_neg.most_common(20) 
top_keywords_neg
Out[38]:
[('flight', 2775),
 ('ba', 1651),
 ('service', 1097),
 ('british', 970),
 ('airways', 956),
 ('seat', 899),
 ('london', 831),
 ('food', 823),
 ('class', 817),
 ('seats', 792),
 ('business', 706),
 ('time', 683),
 ('staff', 666),
 ('cabin', 623),
 ('hours', 585),
 ('heathrow', 548),
 ('airline', 543),
 ('economy', 508),
 ('plane', 466),
 ('passengers', 458)]

From the word cloud map, we can see that seven meaningful words, service, seat, food, time, staff, cabin, and passengers, appear most frequently in the comments of negative customers. Other less meaningful words, such as the word: BA is abbreviations for the companies we analyze. These words have little to do with the sentiment score we're going to return to, so we'll ignore them. Then we do the regression between the keywords and the sentiment scope to see which features are more important.

Analyse the Relationship between Keywords and Sentiment Score¶

In [39]:
keywords = ['service', 'seat', 'food', 'time', 'staff', 'cabin', 'passengers']
In [40]:
data_dummy = data[['compound_content','Review_content']]
data_dummy
Out[40]:
compound_content Review_content
0 -0.7990 The airline lost my luggage and was absolutely...
1 0.0219 We booked on the BA website, round trip flight...
2 -0.7096 First time flying with BA business class, neve...
3 -0.5849 You can buy sandwiches and crisps but don't ex...
4 0.9789 This is a two-for-one review covering economy ...
... ... ...
3575 0.9129 LHR-HKG on Boeing 747 - 23/08/12. Much has bee...
3576 -0.8763 Just got back from Bridgetown Barbados flying ...
3577 -0.2003 LHR-JFK-LAX-LHR. Check in was ok apart from be...
3578 0.9864 HKG-LHR in New Club World on Boeing 777-300 - ...
3579 0.0933 YYZ to LHR - July 2012 - I flew overnight in p...

3580 rows × 2 columns

In [41]:
for keyword in keywords:
    data_dummy[keyword] = data_dummy['Review_content'].apply(lambda x: 1 if re.search(keyword, str(x), re.IGNORECASE) else 0)
data_dummy
Out[41]:
compound_content Review_content service seat food time staff cabin passengers
0 -0.7990 The airline lost my luggage and was absolutely... 0 0 0 1 0 0 0
1 0.0219 We booked on the BA website, round trip flight... 1 1 0 1 0 0 0
2 -0.7096 First time flying with BA business class, neve... 0 0 0 1 0 0 0
3 -0.5849 You can buy sandwiches and crisps but don't ex... 0 0 1 0 0 0 0
4 0.9789 This is a two-for-one review covering economy ... 0 1 0 0 0 1 0
... ... ... ... ... ... ... ... ... ...
3575 0.9129 LHR-HKG on Boeing 747 - 23/08/12. Much has bee... 0 0 1 1 0 1 0
3576 -0.8763 Just got back from Bridgetown Barbados flying ... 0 0 1 0 1 0 0
3577 -0.2003 LHR-JFK-LAX-LHR. Check in was ok apart from be... 1 1 1 0 0 0 0
3578 0.9864 HKG-LHR in New Club World on Boeing 777-300 - ... 0 1 1 1 0 1 0
3579 0.0933 YYZ to LHR - July 2012 - I flew overnight in p... 0 1 1 0 0 0 0

3580 rows × 9 columns

In [42]:
dummy_feature = ['service', 'seat', 'food', 'time', 'staff', 'cabin', 'passengers']
target = ['compound_content']
x = data_dummy[dummy_feature]
y = data_dummy[target]

Apply sklearn.linear¶

In [43]:
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, 
test_size=0.30, random_state=0)

lin_reg = LinearRegression() 
lin_reg.fit(x_train, y_train)

print(lin_reg.coef_)
print(lin_reg.intercept_)
[[-0.00298762  0.056533    0.15886395  0.1438536  -0.04885124  0.11787597
  -0.1755481 ]]
[-0.00527394]
In [44]:
from sklearn.metrics import mean_absolute_error
y_pred=lin_reg.predict(x_test)
MAE = mean_absolute_error(y_test,y_pred)
print(MAE)
0.7038536713096997

Apply OLS linear regression¶

In [45]:
import statsmodels.api as sm

x = sm.add_constant(x)
model = sm.OLS(y, x).fit()

print(model.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:       compound_content   R-squared:                       0.043
Model:                            OLS   Adj. R-squared:                  0.041
Method:                 Least Squares   F-statistic:                     23.08
Date:                Sat, 20 Apr 2024   Prob (F-statistic):           7.91e-31
Time:                        17:44:29   Log-Likelihood:                -4094.2
No. Observations:                3580   AIC:                             8204.
Df Residuals:                    3572   BIC:                             8254.
Df Model:                           7                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const         -0.0367      0.028     -1.294      0.196      -0.092       0.019
service        0.0044      0.026      0.173      0.863      -0.046       0.055
seat           0.0657      0.027      2.478      0.013       0.014       0.118
food           0.1631      0.026      6.261      0.000       0.112       0.214
time           0.1510      0.026      5.866      0.000       0.101       0.201
staff         -0.0372      0.028     -1.338      0.181      -0.092       0.017
cabin          0.1667      0.027      6.109      0.000       0.113       0.220
passengers    -0.1379      0.032     -4.245      0.000      -0.202      -0.074
==============================================================================
Omnibus:                    23623.046   Durbin-Watson:                   1.916
Prob(Omnibus):                  0.000   Jarque-Bera (JB):              395.271
Skew:                          -0.333   Prob(JB):                     1.47e-86
Kurtosis:                       1.514   Cond. No.                         4.45
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

We can see that "service" has a large p-value, which means it is not significant in our regression. This may be because the information contained is multifaceted and multi-directional, leading to uninterpretable results. "staff" is significant at relatively low confidence levels. And we can see the seat, the food, the time, the cabin and the passengers are p<0.05, so using this method, the BA company can base on these keywords to improve their service and enhance customer satisfaction.

EXtract High TF-IDF words (Other ways to select the keywords)¶

Other way to check about the keywords: we can calculate the TF-IDF of the words in each document, then average them over the entire document and arrange them according to their mean. In this way, we can obtain keywords that retain both importance and representativeness. Comparing this result with our keywords, we can see that our keywords still maintain sufficient importance.

In [46]:
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer(stop_words='english', max_df=0.5, decode_error='ignore')
X = vectorizer.fit_transform(data['Review_content'])

# Get the list of feature words and corresponding TF-IDF weights
feature_names = vectorizer.get_feature_names_out()
tfidf_matrix = X.toarray()

# Calculates the average TF-IDF weights for the entire document collection, packaging the feature words and corresponding average TF-IDF
avg_tfidf_weights = tfidf_matrix.mean(axis=0)
word_weights = list(zip(feature_names, avg_tfidf_weights))

# Sorted according to TF-IDF weights
sorted_word_weights = sorted(word_weights, key=lambda x: x[1], reverse=True)
sorted_word_weights
Out[46]:
[('service', 0.03141408030037682),
 ('seat', 0.030134057941641357),
 ('good', 0.029552353466484247),
 ('food', 0.028507840241092684),
 ('crew', 0.027757565890326873),
 ('british', 0.02720972676779846),
 ('airways', 0.027079747098601392),
 ('seats', 0.026836456593829713),
 ('london', 0.02644483581466497),
 ('class', 0.026147105853638504),
 ('cabin', 0.025796976897096716),
 ('time', 0.025564322455035954),
 ('business', 0.02331081803710214),
 ('staff', 0.02237291443310823),
 ('heathrow', 0.022275203762611988),
 ('economy', 0.021921772173147964),
 ('check', 0.01888127415195583),
 ('flights', 0.018276535290586492),
 ('club', 0.01798800885023037),
 ('lhr', 0.017854161144403843),
 ('return', 0.01778046243256447),
 ('plane', 0.017021261189970497),
 ('lounge', 0.01646477632629716),
 ('airline', 0.016178203315889345),
 ('just', 0.015997730722572837),
 ('boarding', 0.01582837709922282),
 ('hours', 0.015821426086518004),
 ('aircraft', 0.015476306238580663),
 ('new', 0.01535194089443002),
 ('experience', 0.015212040232109834),
 ('passengers', 0.015211286583107293),
 ('fly', 0.01452813988284171),
 ('meal', 0.01443236421461217),
 ('review', 0.01421443542275791),
 ('did', 0.014171448321454127),
 ('great', 0.014151124484501974),
 ('comfortable', 0.013790388828870442),
 ('hour', 0.013380863483002494),
 ('verified', 0.013345331714584352),
 ('drinks', 0.013141140407915065),
 ('really', 0.013013690881139241),
 ('airlines', 0.01288633952922331),
 ('premium', 0.012878721655544339),
 ('friendly', 0.0128459092967959),
 ('excellent', 0.012714015365357441),
 ('flew', 0.012702058306601325),
 ('better', 0.012553063658866024),
 ('nice', 0.01254930084954301),
 ('long', 0.012538247394037609),
 ('like', 0.012535643718100755),
 ('entertainment', 0.012441136479093794),
 ('customer', 0.012337537649817173),
 ('world', 0.012280157400373883),
 ('poor', 0.01218447208661124),
 ('served', 0.012135322467134764),
 ('old', 0.011952841685736779),
 ('told', 0.011836540405854088),
 ('minutes', 0.011684397478368278),
 ('got', 0.011437983574685272),
 ('gatwick', 0.01143540364496345),
 ('flying', 0.0112694482041782),
 ('airport', 0.011125719551753246),
 ('luggage', 0.010989946913004383),
 ('offered', 0.010854936059802655),
 ('way', 0.010684527777423292),
 ('breakfast', 0.01058338021436217),
 ('leg', 0.010446022848987921),
 ('delayed', 0.010377924879916662),
 ('late', 0.01033340112019457),
 ('board', 0.010331362762191964),
 ('drink', 0.010322893996658141),
 ('trip', 0.010297204055067136),
 ('arrived', 0.010277357388464053),
 ('bag', 0.010161878222071907),
 ('ok', 0.010137531958600232),
 ('short', 0.010083119781873892),
 ('choice', 0.010006205138886018),
 ('small', 0.009887593862958786),
 ('booked', 0.009676816520866575),
 ('haul', 0.009655893892671847),
 ('don', 0.009553482637121732),
 ('room', 0.009470422419921347),
 ('pay', 0.009419401314579015),
 ('extra', 0.009377233636012188),
 ('people', 0.009208802724493599),
 ('quite', 0.009201196519138331),
 ('didn', 0.009157785018535666),
 ('europe', 0.009126238461856994),
 ('quality', 0.009106672570883512),
 ('left', 0.009051874262311996),
 ('overall', 0.00899842427056922),
 ('asked', 0.008968556933302839),
 ('took', 0.008939184980673118),
 ('given', 0.008894845465340319),
 ('used', 0.00886033305418265),
 ('little', 0.008816427749201026),
 ('day', 0.008600899923383771),
 ('best', 0.008599009643511112),
 ('paid', 0.008512888786631295),
 ('use', 0.008501543652601728),
 ('selection', 0.008394598228902645),
 ('money', 0.00836667064444121),
 ('cost', 0.008344733381916342),
 ('years', 0.008317633319352322),
 ('gate', 0.008282512342664385),
 ('make', 0.00825867933780793),
 ('baggage', 0.00824921615490955),
 ('bags', 0.008222380057962983),
 ('having', 0.008173111484764057),
 ('terminal', 0.008167511381716456),
 ('a380', 0.008034292238133843),
 ('travel', 0.008030500861060897),
 ('ife', 0.007931235601661422),
 ('space', 0.007883686469558109),
 ('water', 0.00782009796726164),
 ('efficient', 0.007813876308974208),
 ('screen', 0.0077455266542034345),
 ('bit', 0.007740117345243494),
 ('route', 0.0077199498098105045),
 ('available', 0.007691068758545875),
 ('said', 0.007652008530950837),
 ('fine', 0.007630656274315731),
 ('clean', 0.007608702798826096),
 ('delay', 0.007516336264289903),
 ('departure', 0.00751385000191698),
 ('10', 0.007504365348667091),
 ('bad', 0.007443366748874955),
 ('cancelled', 0.007294446380898254),
 ('seating', 0.007289570272451927),
 ('free', 0.007264974902452209),
 ('ve', 0.007225103767079369),
 ('times', 0.007200508436709048),
 ('row', 0.007148566359958617),
 ('product', 0.007054967764097379),
 ('days', 0.007037517202336778),
 ('flown', 0.007024168671977105),
 ('air', 0.00701840095940868),
 ('helpful', 0.007013124363072395),
 ('pleasant', 0.006992659663976874),
 ('747', 0.006981504929452598),
 ('arrival', 0.006970837091466087),
 ('meals', 0.00696259775253772),
 ('30', 0.006944927927526201),
 ('inflight', 0.006862533694576915),
 ('say', 0.006859146626314762),
 ('journey', 0.006844315437211947),
 ('work', 0.0068330218930670065),
 ('security', 0.006832632687364054),
 ('price', 0.00681543336303187),
 ('outbound', 0.006757321946412722),
 ('uncomfortable', 0.0067519986364960845),
 ('worst', 0.006712185145837325),
 ('dinner', 0.0066944320699965365),
 ('early', 0.00667688872568111),
 ('t5', 0.006641612790955921),
 ('far', 0.006612032369322372),
 ('need', 0.00658454069516495),
 ('professional', 0.006527485464698208),
 ('offer', 0.006519355421548713),
 ('going', 0.006497656503165181),
 ('legroom', 0.006469501984484881),
 ('middle', 0.006466270103720828),
 ('wine', 0.00643173329525799),
 ('ticket', 0.006413400112913947),
 ('attentive', 0.006372621203333916),
 ('quick', 0.0063513975466035115),
 ('low', 0.0063474983395693865),
 ('landing', 0.006344734517143622),
 ('went', 0.006311751977176637),
 ('ground', 0.006292190075775899),
 ('tea', 0.006228876985157434),
 ('passenger', 0.006193437047716806),
 ('wife', 0.006183626253595796),
 ('sleep', 0.006183031781668997),
 ('fast', 0.006138097075187108),
 ('checked', 0.006121665674661547),
 ('boeing', 0.0060931623137525665),
 ('snack', 0.006084612049538566),
 ('despite', 0.006080174510328678),
 ('think', 0.006075647714485089),
 ('online', 0.0060736348394352414),
 ('help', 0.0060348204451980934),
 ('hot', 0.006034020909678463),
 ('different', 0.005927001212512418),
 ('777', 0.005922790260783306),
 ('wasn', 0.005881609101441295),
 ('onboard', 0.005843527713506656),
 ('standard', 0.0058391936462475974),
 ('pretty', 0.005839171829528421),
 ('home', 0.005834773909624076),
 ('aisle', 0.005809832725105523),
 ('couldn', 0.0057924238527545845),
 ('singapore', 0.005769016918132379),
 ('refund', 0.005700462533802118),
 ('problem', 0.005697903055363171),
 ('booking', 0.005685368916630872),
 ('came', 0.0056612978446849),
 ('wait', 0.005648931391165194),
 ('plus', 0.005634114100297468),
 ('later', 0.00562682227236251),
 ('change', 0.005610521669528152),
 ('usual', 0.005511618294962092),
 ('main', 0.005483024486702716),
 ('half', 0.0054817869056538755),
 ('provided', 0.0054812590470998245),
 ('window', 0.005476510559420249),
 ('deck', 0.005471695369653829),
 ('priority', 0.005463191061446681),
 ('worth', 0.0054566686474527),
 ('want', 0.005448006401028654),
 ('know', 0.005390104354086859),
 ('decent', 0.005352674333278796),
 ('year', 0.0053203547679054625),
 ('option', 0.005291575786693025),
 ('lot', 0.0052735390936852235),
 ('awful', 0.005268679111758815),
 ('member', 0.00520762267942921),
 ('20', 0.005206318439314563),
 ('customers', 0.005194565817537545),
 ('waiting', 0.005182582193392432),
 ('chicken', 0.005149525374275982),
 ('terrible', 0.005144713783454893),
 ('cramped', 0.005138687441408003),
 ('gave', 0.005130948086455156),
 ('feel', 0.005119244931941199),
 ('number', 0.005104479481626352),
 ('looking', 0.005076536651517841),
 ('called', 0.005047655465111778),
 ('disappointed', 0.005034832989016493),
 ('able', 0.004998041310729954),
 ('15', 0.004980544729721614),
 ('kept', 0.004972929112471886),
 ('limited', 0.004944937933349368),
 ('felt', 0.004935781195372582),
 ('budget', 0.004934219339652479),
 ('coffee', 0.004907118493988522),
 ('book', 0.004899873920986772),
 ('queue', 0.004884781250244404),
 ('champagne', 0.004872766924284712),
 ('phone', 0.0048357676385990915),
 ('snacks', 0.004821160266375789),
 ('second', 0.004820449569872997),
 ('choose', 0.004807557755969092),
 ('attendants', 0.00478848037176489),
 ('pre', 0.004787853422364758),
 ('person', 0.004768236739963093),
 ('smooth', 0.004749740220164722),
 ('hard', 0.004734961138789877),
 ('plenty', 0.004720875649777102),
 ('travelling', 0.0046934717912254995),
 ('expect', 0.004693194775455643),
 ('offering', 0.004687817147272),
 ('night', 0.004684324454456585),
 ('line', 0.004684250808345121),
 ('mins', 0.00466816886223191),
 ('hand', 0.0046602929392147545),
 ('lost', 0.00465831848636571),
 ('especially', 0.004657981584917993),
 ('sandwich', 0.004655067315394962),
 ('care', 0.004647443874771583),
 ('compared', 0.0046448697047933325),
 ('carrier', 0.0046251291396134575),
 ('looked', 0.0046198361616919025),
 ('happy', 0.0046083665468087),
 ('landed', 0.004602518365226297),
 ('connecting', 0.004601230451666611),
 ('getting', 0.004591324337076977),
 ('definitely', 0.004587157187911987),
 ('travelled', 0.004581874195366442),
 ('upgrade', 0.004574260621897941),
 ('tried', 0.004573184261633877),
 ('options', 0.004571308969505443),
 ('tv', 0.00456635869416729),
 ('jfk', 0.004561523917874441),
 ('using', 0.004534051126275513),
 ('try', 0.004511053883238002),
 ('issue', 0.0045061631927835),
 ('extremely', 0.0045060898391699545),
 ('legs', 0.004490906849290014),
 ('rude', 0.0044836243797390886),
 ('received', 0.00448099097323424),
 ('does', 0.004479060069982475),
 ('okay', 0.004461528119128273),
 ('trying', 0.004457713480591631),
 ('recommend', 0.004443684837737616),
 ('average', 0.004439436862931997),
 ('slow', 0.004435458945590703),
 ('traveller', 0.004418494599282551),
 ('lgw', 0.004405122109944315),
 ('high', 0.004399015575633152),
 ('thought', 0.004390209701420273),
 ('value', 0.0043867869122236796),
 ('finally', 0.004380687647763514),
 ('away', 0.004375784832868762),
 ('tired', 0.004370502568745795),
 ('working', 0.004369204630820543),
 ('ask', 0.00435276485799691),
 ('managed', 0.004352131533816693),
 ('sure', 0.004321436387764449),
 ('actually', 0.0043209109471688285),
 ('let', 0.004309777187201492),
 ('toilets', 0.004305576089421726),
 ('upper', 0.004282021467272368),
 ('fact', 0.004266389692057662),
 ('disappointing', 0.004258817067138985),
 ('busy', 0.004249166033217835),
 ('paying', 0.004243253467423697),
 ('point', 0.004228636568703601),
 ('charge', 0.004227687520721839),
 ('easy', 0.004211673779620002),
 ('big', 0.004209255231587573),
 ('missed', 0.004186055972415823),
 ('course', 0.0041849822160650064),
 ('morning', 0.004183518107065315),
 ('serve', 0.004171887071578104),
 ('quickly', 0.004163595681206578),
 ('informed', 0.004135205654779988),
 ('menu', 0.004102798133394099),
 ('come', 0.004093484068294386),
 ('attendant', 0.004087008018554912),
 ('end', 0.004078939295476261),
 ('thing', 0.004072023105595894),
 ('rest', 0.00406853616142147),
 ('avoid', 0.004044860724629509),
 ('drop', 0.004031817040834639),
 ('polite', 0.004025725588164695),
 ('card', 0.004023793469057234),
 ('avios', 0.004015432759259419),
 ('boarded', 0.004011195890339829),
 ('easyjet', 0.004001724771276649),
 ('bus', 0.004000156641658514),
 ('a320', 0.003999969967253959),
 ('difference', 0.003981314583332558),
 ('12', 0.003975384590568824),
 ('past', 0.0039607894253012805),
 ('screens', 0.003951794906112785),
 ('cold', 0.003950859762983131),
 ('planes', 0.003947490885515621),
 ('tickets', 0.003941181628590455),
 ('forward', 0.003934506003290292),
 ('bar', 0.003922762039015367),
 ('reasonable', 0.003915512666862109),
 ('special', 0.003911693734039988),
 ('longer', 0.003904854000657546),
 ('worse', 0.0039040309287844514),
 ('cheese', 0.0038936185180779475),
 ('eat', 0.003867101489197484),
 ('changed', 0.0038621028025232736),
 ('start', 0.003861685075188762),
 ('sat', 0.0038572676416020056),
 ('ryanair', 0.0038570636546875077),
 ('desk', 0.003847103729067643),
 ('things', 0.003839714111289725),
 ('narrow', 0.003814189726129629),
 ('comfort', 0.003811804358967546),
 ('process', 0.0038081140163556512),
 ('decided', 0.00380395091898504),
 ('buy', 0.0038003478009404606),
 ('fare', 0.0037960393532474415),
 ('fault', 0.0037956796214148117),
 ('hotel', 0.0037921083286395706),
 ('months', 0.0037814649431352813),
 ('choices', 0.0037733799252822908),
 ('tasty', 0.00376418110059481),
 ('lack', 0.0037637223908007144),
 ('voucher', 0.0037607067511886633),
 ('expected', 0.0037352889213406767),
 ('departed', 0.003734506566393651),
 ('40', 0.003712419911869959),
 ('instead', 0.0037120679120221556),
 ('toilet', 0.003702672723602445),
 ('look', 0.0036860706889330014),
 ('standards', 0.0036747531486728286),
 ('couple', 0.003665271371377551),
 ('wanted', 0.003654572573314743),
 ('emirates', 0.0036498516530055003),
 ('seated', 0.003635957068207793),
 ('possible', 0.0036304396708971435),
 ('lunch', 0.003626161226486636),
 ('sitting', 0.0036138944166167883),
 ('sit', 0.0036133006045541683),
 ('needed', 0.0036082669228290135),
 ('started', 0.0036039273069750784),
 ('reason', 0.0036008319277635805),
 ('future', 0.0035979522255868263),
 ('information', 0.003589623205843693),
 ('area', 0.003582897702376623),
 ('meant', 0.00357564147423098),
 ('movies', 0.003570054070822447),
 ('expensive', 0.0035683932643118758),
 ('absolutely', 0.0035633373800117445),
 ('run', 0.00356012059741018),
 ('holiday', 0.0035347219043046422),
 ('right', 0.003520736595672947),
 ('problems', 0.0035126367367802436),
 ('complaint', 0.003503500234086293),
 ('uk', 0.003502629447734747),
 ('company', 0.003496810439331943),
 ('schedule', 0.0034871540026602376),
 ('allowed', 0.0034801083749931246),
 ('carriers', 0.0034627557380039887),
 ('website', 0.003453615663174293),
 ('dirty', 0.0034511715370685314),
 ('slightly', 0.0034342668496566823),
 ('email', 0.0034278431522135307),
 ('ago', 0.003419739697729714),
 ('wrong', 0.003412410866345198),
 ('apart', 0.003409196370905069),
 ('round', 0.0034076138091709363),
 ('unfortunately', 0.0034004563364172175),
 ('galley', 0.0033973966101075686),
 ('connection', 0.0033875258955426393),
 ('services', 0.003385397228454182),
 ('catering', 0.003382951301091524),
 ('ahead', 0.0033739068963814104),
 ('city', 0.0033642575568252997),
 ('previous', 0.003362480585446495),
 ('level', 0.003350836106136417),
 ('issues', 0.00334793292067057),
 ('shame', 0.003344398864820064),
 ('quiet', 0.0033415284657131628),
 ('prices', 0.0033348200076363907),
 ('virgin', 0.0033283233735625995),
 ('minute', 0.003327750602381977),
 ('taken', 0.003326111062724763),
 ('rows', 0.0033239779148813786),
 ('particularly', 0.0033231898870061606),
 ('45', 0.0033180893444155197),
 ('san', 0.003315364987027023),
 ('configuration', 0.003313656722863934),
 ('bed', 0.003309336657980065),
 ('clearly', 0.0033085205808398442),
 ('tray', 0.0033083681737178525),
 ('recently', 0.0033065832022148504),
 ('cheap', 0.0033056984574013314),
 ('captain', 0.0033014720416177407),
 ('agent', 0.00329696561585399),
 ('weeks', 0.00328856377588668),
 ('787', 0.003286654572512974),
 ('track', 0.003274956068557278),
 ('access', 0.0032668692140871446),
 ('experienced', 0.003266222628153828),
 ('welcome', 0.0032592840914427964),
 ('york', 0.003258162447485144),
 ('european', 0.0032453589205974297),
 ('earlier', 0.0032437183928802015),
 ('provide', 0.0032410016750422346),
 ('appalling', 0.0032409798278172346),
 ('delays', 0.0032382517959725628),
 ('including', 0.0032327505473877622),
 ('pass', 0.0032317482413097023),
 ('gone', 0.0032186755125037337),
 ('400', 0.0032179484326912327),
 ('rome', 0.003209169440681309),
 ('recline', 0.003205270094726233),
 ('glasgow', 0.0031960937547804574),
 ('flat', 0.0031897122283127155),
 ('bangkok', 0.0031895302710402344),
 ('total', 0.003179913289194463),
 ('case', 0.0031745801480068977),
 ('miami', 0.0031455826773355657),
 ('surprised', 0.003141414987798952),
 ('wouldn', 0.0031382873759763785),
 ('worked', 0.003136668444438815),
 ('broken', 0.003126864543316856),
 ('certainly', 0.003118108446738853),
 ('warm', 0.00311720979926166),
 ('week', 0.003116961489594301),
 ('non', 0.003115421227901986),
 ('understand', 0.0031029641851367707),
 ('difficult', 0.003098674693469968),
 ('upgraded', 0.0030973875182833464),
 ('tiny', 0.0030971278222021986),
 ('spent', 0.0030944874663794237),
 ('salad', 0.0030916094156135964),
 ('bottle', 0.0030843873467151874),
 ('glass', 0.003084363634749772),
 ('acceptable', 0.0030776412424347956),
 ('additional', 0.0030652056252336306),
 ('american', 0.003054648573208942),
 ('family', 0.003054268941889952),
 ('probably', 0.0030542055465690447),
 ('sandwiches', 0.003047059768098026),
 ('arrive', 0.0030470168901651907),
 ('coming', 0.003043014753369553),
 ('11', 0.003032445441418205),
 ('large', 0.0030302176389253757),
 ('manchester', 0.0030291754781543428),
 ('ran', 0.003028082661404287),
 ('fairly', 0.0030260056924748375),
 ('serving', 0.0030107698387434752),
 ('taking', 0.003003413057979965),
 ('needs', 0.0029955444480321354),
 ('husband', 0.002988245593252312),
 ('unless', 0.002982134766116776),
 ('storage', 0.0029739200589667934),
 ('trolley', 0.0029639434065285575),
 ('doing', 0.002951507633118333),
 ('eventually', 0.0029470569597312302),
 ('order', 0.0029465610581860713),
 ('ways', 0.002942305618852338),
 ('older', 0.002939341272416857),
 ('whilst', 0.002937654279160666),
 ('leaving', 0.002929001448557324),
 ('adequate', 0.0029184129445906527),
 ('regular', 0.002913727652530607),
 ('attitude', 0.0029121356218715657),
 ('fantastic', 0.002909406335504451),
 ('town', 0.00290735310246486),
 ('job', 0.0028987744737156173),
 ('impressed', 0.002882526337807783),
 ('turned', 0.0028804112636770865),
 ('outward', 0.002873646537073958),
 ('toronto', 0.0028693667001863823),
 ('compensation', 0.0028655155720849106),
 ('won', 0.002857050953892551),
 ('real', 0.0028543327971613203),
 ('films', 0.00284206785309997),
 ('afternoon', 0.002841949490867488),
 ('huge', 0.002839307984098676),
 ('fa', 0.0028350998740956025),
 ('lady', 0.0028339385740801105),
 ('madrid', 0.0028333318600609492),
 ('johannesburg', 0.0028249244483466765),
 ('gold', 0.0028184508082237793),
 ('recent', 0.002811076495293975),
 ('place', 0.0028052320611609382),
 ('waited', 0.002803574065615764),
 ('reviews', 0.002791564393881063),
 ('simply', 0.002784465573675658),
 ('cape', 0.0027792328523859842),
 ('light', 0.002773174231494327),
 ('24', 0.0027703894715245926),
 ('enjoyed', 0.002758632213469482),
 ('seen', 0.0027524771401969315),
 ('exit', 0.0027520630729252894),
 ('clear', 0.0027507523573288327),
 ('stand', 0.002746533107190814),
 ('response', 0.002743893449555566),
 ('complimentary', 0.002742052730969571),
 ('inedible', 0.002732806793151405),
 ('complaints', 0.0027232296034143807),
 ('amsterdam', 0.002720997778298003),
 ('table', 0.0027185668696473385),
 ('soon', 0.0027146531449010634),
 ('beef', 0.0027058806691361826),
 ('evening', 0.0027012287860452375),
 ('points', 0.0026969734752265904),
 ('carry', 0.002694474842411173),
 ('appeared', 0.002689726592727319),
 ('children', 0.0026865235871138095),
 ('cup', 0.0026777469169386448),
 ('amazing', 0.0026770675599558265),
 ('dated', 0.0026743273538764115),
 ('totally', 0.002665586249247944),
 ('layout', 0.0026655350377161264),
 ('hong', 0.002665473731687061),
 ('kong', 0.002665473731687061),
 ('generally', 0.002664211619970934),
 ('contact', 0.0026629475529580363),
 ('alternative', 0.002662685196747262),
 ('prior', 0.0026598056588789045),
 ('sydney', 0.0026580653180594063),
 ('control', 0.0026519415347068245),
 ('waste', 0.002645779907768158),
 ('edinburgh', 0.0026412275738029196),
 ('improved', 0.002627412271235632),
 ('joke', 0.00262741104783606),
 ('refused', 0.002626344053523945),
 ('overhead', 0.002623917891282974),
 ('basic', 0.002615306271111622),
 ('paris', 0.0026144901334947254),
 ('50', 0.002613023617444453),
 ('partner', 0.002612419163804676),
 ('overnight', 0.00258270091545451),
 ('making', 0.0025816631536033905),
 ('section', 0.0025810929835658955),
 ('14', 0.0025804242649867717),
 ('advance', 0.002574985324974163),
 ('cut', 0.002566793577813709),
 ('roll', 0.0025603257980777094),
 ('makes', 0.0025526800157687993),
 ('situation', 0.0025526020169076356),
 ('horrible', 0.002550307364468054),
 ('claim', 0.0025485472257302405),
 ('vancouver', 0.002545116656930205),
 ('lax', 0.002544417344202393),
 ('immigration', 0.0025411620836132773),
 ('nearly', 0.0025368283278851237),
 ('impossible', 0.002529657653301663),
 ('galleries', 0.0025249388203229155),
 ('hold', 0.002519340993906682),
 ('open', 0.0025124546038244955),
 ('closed', 0.0025084582504204464),
 ('ended', 0.0025076872425711097),
 ('lovely', 0.002501664060771136),
 ('month', 0.0024995001662239565),
 ('happened', 0.002495472482232245),
 ('chose', 0.0024947833794110243),
 ('sent', 0.002488931822625187),
 ('positive', 0.0024860127057055002),
 ('close', 0.0024826107174452067),
 ('near', 0.002478361014496922),
 ('inbound', 0.0024746897499347913),
 ('athens', 0.0024705474658222393),
 ('spacious', 0.002467922110747831),
 ('sorry', 0.0024656535324049703),
 ('frequent', 0.002463733864039751),
 ('direct', 0.0024515996236640645),
 ('means', 0.0024507725093280493),
 ('completely', 0.0024494966643031466),
 ('cutting', 0.0024472338732373115),
 ('lounges', 0.0024458590299514453),
 ('juice', 0.0024451445935304923),
 ('thank', 0.0024414662868499203),
 ('reasonably', 0.0024396363927343084),
 ('items', 0.0024316182937159643),
 ('outstanding', 0.0024258372728874914),
 ('walk', 0.002424047569650959),
 ('facing', 0.002418919070601048),
 ('personal', 0.0024177482644335627),
 ('destination', 0.002416161595223483),
 ('ll', 0.0024099958593232274),
 ('counter', 0.0024057904166016088),
 ('covid', 0.0024048986692041248),
 ('credit', 0.0024040173530885276),
 ('delivered', 0.0024030788691900467),
 ('crisps', 0.0024021010195545145),
 ('a319', 0.0023976801418994754),
 ('767', 0.002396449187837933),
 ('sort', 0.0023897182137840654),
 ('watch', 0.002376990893125312),
 ('sleeping', 0.0023757367350037023),
 ('queues', 0.002375430486623616),
 ('2014', 0.00237359170985955),
 ('pasta', 0.002370700644589601),
 ('face', 0.002369441002004117),
 ('tell', 0.002366937385713082),
 ('south', 0.0023637336010379176),
 ('chaotic', 0.0023605572131242462),
 ('vegas', 0.0023585387798084854),
 ('leave', 0.0023556964082376438),
 ('silver', 0.002353575461010013),
 ('moved', 0.0023527176788778474),
 ('pitch', 0.0023520864117414324),
 ('today', 0.0023509412114164413),
 ('age', 0.002343616711620494),
 ('centre', 0.002341762281904938),
 ('wifi', 0.0023412453729091294),
 ('complete', 0.002335021320501182),
 ('200', 0.0023265524943083976),
 ('miss', 0.0023243242656856844),
 ('transfer', 0.002319855007120586),
 ('kind', 0.0023196414033929084),
 ('worn', 0.002309821454281799),
 ('vegetarian', 0.0023075489491535687),
 ('young', 0.0023047905033259405),
 ('app', 0.00230372035438904),
 ('doesn', 0.002291138623401926),
 ('entire', 0.0022907018166845932),
 ('dublin', 0.002287269674594408),
 ('members', 0.002286662068377414),
 ('believe', 0.0022802083098145904),
 ('boston', 0.002279578180616201),
 ('qatar', 0.0022788667253191516),
 ('mediocre', 0.002278511698068202),
 ('dubai', 0.002277013545329059),
 ('similar', 0.0022769198973557865),
 ('chicago', 0.002276596611538031),
 ('asking', 0.0022755674977781347),
 ('barcelona', 0.0022681499688491894),
 ('domestic', 0.002257180742295676),
 ('missing', 0.0022568623358058975),
 ('arriving', 0.002255162492081152),
 ('wonderful', 0.0022541692157741033),
 ('wines', 0.002254096994550871),
 ('policy', 0.002250195427607886),
 ('yes', 0.0022454083596294442),
 ('ordered', 0.002244945551167495),
 ('usually', 0.0022422614484612735),
 ('enjoyable', 0.002233939240756676),
 ('superb', 0.002225306326918055),
 ('returned', 0.0022243288612672483),
 ('weather', 0.0022183505605840896),
 ('las', 0.0021990521388396585),
 ('rebooked', 0.0021833052461136555),
 ('add', 0.0021830788487887808),
 ('mixed', 0.002182867618553315),
 ('twice', 0.0021814925509669827),
 ('saying', 0.0021798309397208517),
 ('00', 0.002175913457396238),
 ('assistance', 0.0021726529159338194),
 ('bread', 0.0021670983156176844),
 ('rear', 0.0021636167532633915),
 ('english', 0.002155023159814284),
 ('fully', 0.0021512329253234124),
 ('style', 0.0021417097889799806),
 ('crews', 0.002131946509548295),
 ('interior', 0.002129038548206684),
 ('date', 0.0021244509567341814),
 ('man', 0.0021239124131649688),
 ('costs', 0.0021219882878571994),
 ('fleet', 0.0021146188607825255),
 ('june', 0.002105161621314081),
 ('1st', 0.0021033049656750294),
 ('lisbon', 0.002103131468734123),
 ('lots', 0.002099678434554874),
 ('min', 0.002095253733070379),
 ('complain', 0.0020903880690945416),
 ('dreadful', 0.0020888553428890953),
 ('packed', 0.0020864271835799777),
 ('tight', 0.002083544844261987),
 ('iberia', 0.0020798755772173947),
 ('supposed', 0.002079425727904743),
 ('cabins', 0.002076208823592284),
 ('pilot', 0.002076032829600339),
 ('belfast', 0.002070299312023832),
 ('cancel', 0.00206653981606423),
 ('cheaper', 0.0020644253133214226),
 ('technical', 0.002049263935618396),
 ('sadly', 0.0020489314029427243),
 ('crowded', 0.0020469457001835293),
 ('sad', 0.0020453156809099042),
 ('deal', 0.002045178563930457),
 ('explanation', 0.0020403496192104714),
 ('read', 0.002038818352532053),
 ('request', 0.002037207897359963),
 ('weren', 0.0020364607313696275),
 ('speak', 0.0020314812001718478),
 ('concorde', 0.0020307046610088804),
 ('stay', 0.002030134528213192),
 ('apology', 0.0020273915899154176),
 ('status', 0.0020239191888057604),
 ('privacy', 0.002021190253069571),
 ('normal', 0.0020205066123428956),
 ('25', 0.0020196404893132423),
 ('showing', 0.002018557934247016),
 ('hope', 0.0020170152928007),
 ('checking', 0.002016723111291145),
 ('cathay', 0.0020164572009057084),
 ('b777', 0.002012097961152138),
 ('size', 0.0020087268554933977),
 ('box', 0.0020063106809977147),
 ('scheduled', 0.0020057154380300757),
 ('requested', 0.002004463580662682),
 ('separate', 0.0020028101782816445),
 ('manager', 0.0020010413664186327),
 ('obviously', 0.0020007035460186807),
 ('routes', 0.0019948157556573527),
 ('flyer', 0.0019939727127317123),
 ('unable', 0.0019928625440121146),
 ('hadn', 0.001991034466163476),
 ('national', 0.0019906305145185907),
 ('kids', 0.0019894499720073317),
 ('executive', 0.0019804254326369415),
 ('dry', 0.0019741808357744608),
 ('touch', 0.001973902076173633),
 ('miles', 0.0019661203366408793),
 ('plastic', 0.0019606069262892408),
 ('movie', 0.001955892656790372),
 ('passport', 0.0019506638626761706),
 ('perfect', 0.0019503069106942704),
 ('croissant', 0.0019500453078311335),
 ('receive', 0.0019467022565015344),
 ('guess', 0.0019455112953616748),
 ('advised', 0.0019434330300126842),
 ('exactly', 0.0019402505040200487),
 ('disgusting', 0.0019396806521339125),
 ('position', 0.0019376267893153544),
 ('set', 0.0019375945468363283),
 ('francisco', 0.001935612302109922),
 ('disappointment', 0.0019339626926394272),
 ('purchase', 0.0019313308401481333),
 ('seattle', 0.0019237885495418552),
 ('steward', 0.0019219060520700298),
 ('apparently', 0.001920767795012234),
 ('hardly', 0.0019180345759262194),
 ('showed', 0.0019131482315865238),
 ('answer', 0.0019123276536295556),
 ('group', 0.0019093134570516814),
 ('100', 0.0019089229930314602),
 ('followed', 0.0019056808756605995),
 ('13', 0.0019047499998317676),
 ('beverages', 0.0019029824537841293),
 ('refreshments', 0.0018963523057581677),
 ('curry', 0.0018960000296222425),
 ('istanbul', 0.00189546655639924),
 ('march', 0.0018953883799326555),
 ('inside', 0.0018921250893455994),
 ('bring', 0.0018909856848490386),
 ('allow', 0.0018904762378522718),
 ('included', 0.0018893286075346463),
 ('outdated', 0.0018891855845997634),
 ('child', 0.0018888030617360905),
 ('improve', 0.001881011413500901),
 ('angeles', 0.0018755861409396727),
 ('los', 0.0018755861409396727),
 ('video', 0.001873782576625645),
 ('following', 0.0018726484438599791),
 ('single', 0.0018719645708622718),
 ('maybe', 0.0018688737295262583),
 ('explained', 0.0018599763688642338),
 ('type', 0.001859630829374853),
 ('original', 0.001857792118681048),
 ('hrs', 0.001852521549200652),
 ('isn', 0.0018524619672020954),
 ('airbus', 0.0018462733612123372),
 ('stop', 0.0018456673464793646),
 ('mistake', 0.0018449292054503292),
 ('barely', 0.0018349495167457013),
 ('expectations', 0.00183420901495582),
 ('shows', 0.0018331542684543124),
 ('hungry', 0.0018290546531420158),
 ('stuck', 0.0018284208057620331),
 ('passed', 0.0018263050278677553),
 ('considering', 0.0018204414548288615),
 ('competitors', 0.0018173202545181983),
 ('management', 0.001817029466768329),
 ('aa', 0.0018136499986897952),
 ('notice', 0.001807377500424515),
 ('particular', 0.001805377430171306),
 ('stewardess', 0.0018052508221981735),
 ('competition', 0.0018029471307510828),
 ('tampa', 0.0017994860144868322),
 ('enjoy', 0.0017993507093956077),
 ('rubbish', 0.0017986544671956515),
 ('vouchers', 0.0017963558714731312),
 ('treated', 0.0017943556764380198),
 ('superior', 0.0017939147727166968),
 ('berlin', 0.0017923506558580862),
 ('2015', 0.0017914026044410793),
 ('mind', 0.001791233695299414),
 ('attention', 0.0017896200613750286),
 ('form', 0.00178955654740213),
 ('geneva', 0.0017865648045462003),
 ('suitcase', 0.0017817604437398275),
 ('continue', 0.0017784910276813348),
 ('slept', 0.0017766032404375799),
 ('thanks', 0.0017752298854683715),
 ('complained', 0.001774360247076777),
 ('munich', 0.0017710267946897762),
 ('allowance', 0.0017674575173474705),
 ('brussels', 0.0017636345550595218),
 ('caused', 0.0017575395059654369),
 ('improvement', 0.001755618688955733),
 ('90', 0.0017525291369389767),
 ('charging', 0.0017493537979990763),
 ('select', 0.001748685139648066),
 ('dessert', 0.0017482125317782701),
 ('matter', 0.0017473965626226156),
 ('negative', 0.0017464909427288818),
 ('headphones', 0.0017449983548395733),
 ('bother', 0.001744712979915603),
 ('anymore', 0.0017435621789761264),
 ('music', 0.0017427498872936123),
 ('major', 0.0017417265904038119),
 ('washington', 0.001740992895121134),
 ('lie', 0.0017406434466193117),
 ('step', 0.0017340519626994478),
 ('smaller', 0.0017312579627745649),
 ('january', 0.0017228240780035426),
 ('refurbished', 0.0017218569641941),
 ('baby', 0.0017214918883834416),
 ('miserable', 0.001719527226043257),
 ('t3', 0.001717543690268026),
 ('fee', 0.0017150398383451106),
 ('truly', 0.001714836110667833),
 ('door', 0.0017140115064784639),
 ('interested', 0.0017129242181209503),
 ('rebook', 0.0017119881181418485),
 ('spend', 0.001711525701025155),
 ('charged', 0.001709746413746669),
 ('delicious', 0.0017087608180731122),
 ('saw', 0.0017075720456337451),
 ('operated', 0.001707306127232322),
 ('brought', 0.001706880926274596),
 ('lcy', 0.0017066515468169355),
 ('fruit', 0.0017046305497220573),
 ('idea', 0.0017026588309496556),
 ('ridiculous', 0.001702360643720938),
 ('treat', 0.0017023332001648937),
 ('result', 0.001700874673714042),
 ('traveling', 0.0016995891896623373),
 ('ready', 0.001696646514568016),
 ('final', 0.0016961476561156358),
 ('december', 0.0016956937673777337),
 ('support', 0.0016950215899540094),
 ('b747', 0.00169430648087281),
 ('minimal', 0.001692564089602034),
 ('foot', 0.0016914812074044893),
 ('giving', 0.0016901197599232735),
 ('starter', 0.0016879436965902767),
 ('april', 0.0016875505586803157),
 ('relatively', 0.0016833660473856748),
 ('experiences', 0.001679702576214261),
 ('fit', 0.0016794410429392768),
 ('fas', 0.0016757605213853946),
 ('announced', 0.001675421977459092),
 ('st', 0.0016745947338159416),
 ('feet', 0.0016705934380159165),
 ('badly', 0.0016686829595924221),
 ('jnb', 0.0016681707652333076),
 ('bought', 0.0016663275502369322),
 ('floor', 0.001665700948071271),
 ('impression', 0.0016652698983670516),
 ('previously', 0.0016645783919384692),
 ('a321', 0.0016608986361004536),
 ('feels', 0.001658498274213673),
 ('lower', 0.001656998080732555),
 ('announcements', 0.0016547818942206113),
 ('running', 0.0016545698652728818),
 ('reserve', 0.0016543889853337756),
 ('surprise', 0.0016532277190147977),
 ('returning', 0.0016522172998120772),
 ('daughter', 0.0016521362464465906),
 ('offers', 0.0016521095275038103),
 ('tall', 0.001651962094153678),
 ('fair', 0.0016516603626846808),
 ('communication', 0.0016485005638898174),
 ('pleasantly', 0.00164762302454001),
 ('october', 0.001645380021919746),
 ('unhelpful', 0.0016447685797701312),
 ('manage', 0.0016435598872784857),
 ('unacceptable', 0.0016424570781705917),
 ('chaos', 0.00164104197640601),
 ('organised', 0.0016402809147840118),
 ('pick', 0.0016337383404272181),
 ('modern', 0.001632798795977636),
 ('reduced', 0.0016324474709526174),
 ('cooked', 0.001631918194844701),
 ('pillow', 0.001630873322375283),
 ('expecting', 0.0016288967108989715),
 ('wide', 0.001628005486503751),
 ('reasons', 0.0016271954682048721),
 ('head', 0.001626204749708423),
 ('reclined', 0.0016238728320273493),
 ('chosen', 0.001614949867897909),
 ('terms', 0.0016145257161574757),
 ('turn', 0.001614177351352402),
 ('range', 0.0016141252009381256),
 ('dish', 0.0016130300786398033),
 ('calls', 0.001612867584442039),
 ('properly', 0.0016114774744005176),
 ('updated', 0.0016114540017778641),
 ('charges', 0.0016052076950315217),
 ('regularly', 0.0016047536387655734),
 ('helped', 0.0016037620405756812),
 ('airplane', 0.0016024539735017774),
 ('looks', 0.0016014877876036665),
 ('absolute', 0.0016012604656898332),
 ('allocated', 0.0016006087913650099),
 ('loyal', 0.0016005562097331236),
 ('opinion', 0.0015925764707286807),
 ('safety', 0.001592082796780421),
 ('spoke', 0.0015907346699573098),
 ('met', 0.0015889195206918118),
 ('chocolate', 0.001585385946711413),
 ('honest', 0.001584992880543722),
 ('feeling', 0.0015848686190524494),
 ('multiple', 0.0015809958398442442),
 ('lights', 0.0015796799085504671),
 ('normally', 0.0015788914332420868),
 ('filthy', 0.0015749911596585672),
 ('international', 0.001573840690103607),
 ('presented', 0.0015732396729273364),
 ('star', 0.0015716516785139099),
 ('based', 0.0015709707329023912),
 ('lucky', 0.0015696838920758443),
 ('qantas', 0.0015695743559388231),
 ('calling', 0.0015682377976400919),
 ('general', 0.0015659575497393755),
 ('nightmare', 0.0015629009632789506),
 ('straight', 0.0015626455815217861),
 ('surprisingly', 0.0015625635821267683),
 ('remote', 0.0015611323874203922),
 ('north', 0.001559411096718031),
 ('barbados', 0.0015589923203419456),
 ('liked', 0.001557913680145628),
 ('moment', 0.0015567905044910682),
 ('august', 0.0015557623831064108),
 ('required', 0.0015541039757714814),
 ('effort', 0.0015484698997161861),
 ('handled', 0.0015483574188140873),
 ('wt', 0.0015456654988415216),
 ('write', 0.0015422348796047302),
 ('ignored', 0.0015417810725860575),
 ('newer', 0.001541778393760271),
 ('message', 0.0015412628152803184),
 ('oneworld', 0.0015386851524834884),
 ('son', 0.0015365965839697628),
 ('takes', 0.0015322554669147707),
 ('noticed', 0.0015317281480619071),
 ('view', 0.0015290483464364874),
 ('changes', 0.0015281659283538356),
 ('loaded', 0.0015257584448932237),
 ('appreciated', 0.0015255933838487351),
 ('prefer', 0.0015236593276771245),
 ('asian', 0.0015220247223680652),
 ('18', 0.0015164730863916193),
 ('rate', 0.0015154477439527636),
 ...]
In [ ]: