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
data=pd.read_csv('AirlineScrappedReview.csv')
data.head()
| 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 |
data.describe()
| 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 |
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
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.
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.
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
data
| 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
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.
# 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
data
| 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
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.
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()
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.
# 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]
data.head(10)
| 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 |
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.
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.
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))
# 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
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
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
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.
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
#!/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!
data['MAX_EMOTION'] = data[emotions].idxmax(axis=1)
data.head(10)
| 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
data['MAX_EMOTION'].value_counts()
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.
emotion = data['MAX_EMOTION']
plt.hist(emotion, bins=8, edgecolor='black')
plt.xlabel('Emotion Content')
plt.ylabel('Frequency')
plt.title('Emotion Content Distribution')
Text(0.5, 1.0, 'Emotion Content Distribution')
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
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))
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.
# 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)
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))
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
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))
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))
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.
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
data = pd.read_csv('emotion analysis.csv')
reviews = data['Review_content']
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
reviews_neg = [review for review,sentimentkind in zip(data['Review_content'], data['sentiment_content']) if sentimentkind == 'neg']
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)
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()
top_keywords_neg = word_freq_neg.most_common(20)
top_keywords_neg
[('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.
keywords = ['service', 'seat', 'food', 'time', 'staff', 'cabin', 'passengers']
data_dummy = data[['compound_content','Review_content']]
data_dummy
| 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
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
| 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
dummy_feature = ['service', 'seat', 'food', 'time', 'staff', 'cabin', 'passengers']
target = ['compound_content']
x = data_dummy[dummy_feature]
y = data_dummy[target]
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]
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
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.
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.
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
[('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),
...]