В этой статье я рассмотрю набор данных Airbnb Istanbul. Первый набор данных — это набор данных отзывов. Столбцы: listing_id (для свойства), id (идентификатор комментария), date (дата комментария), Reviewer_id, Reviewer_name, комментарии. Второй — набор данных списков. В этом наборе данных намного больше столбцов, однако я буду использовать только id и review_scores_rating (средний рейтинг этого свойства). Оба набора данных можно найти ниже.
http://data.insideairbnb.com/turkey/marmara/istanbul/2021-02-26/data/reviews.csv.gz
http://data.insideairbnb.com/turkey/marmara/istanbul/2021-02-26/data/listings.csv.gz
Анализируя эти два набора данных, я попытаюсь ответить на три вопроса.
1. Можно ли предсказать мнение клиентов по комментариям?
Да, их можно предсказать. Для этой цели я использовал Vader Sentiment Analyzer. Во-первых, необходимо импортировать библиотеку nltk. Затем необходимо загрузить «vader_lexicon» и оттуда импортировать SentimentInsensityAnalyzer.
import nltk nltk.download('vader_lexicon') from nltk.sentiment.vader import SentimentIntensityAnalyzer
После чтения набора данных отзывов как df_com комментарии должны быть предварительно обработаны для задачи НЛП.
def preprocess(text): # transform all to the lowercase text=text.lower() # remove hyperlinks if any text = re.sub(r'https?:\/\/.*[\r\n]*', '', text) text = re.sub(r'http?:\/\/.*[\r\n]*', '', text) #Replace &, <, > with &,<,> respectively text=text.replace(r'&?',r'and') text=text.replace(r'<',r'<') text=text.replace(r'>',r'>') #remove non ascii chars text=text.encode("ascii",errors="ignore").decode() #remove some puncts text=re.sub(r'[:"#$%&\*+,-/:;<=>@\\^_`!?.{|}~]+','',text) text=re.sub(r"'","",text) text=re.sub(r"\(","",text) text=re.sub(r"\)","",text) text=" ".join(text.split()) return text df_com['comments'] =df_com['comments'].apply(preprocess)
Стандартизация текстов была выполнена с использованием вышеуказанной функции. После стандартизации давайте сделаем что-нибудь со стоп-словами. Помимо стоп-слов по умолчанию, некоторые общеупотребительные слова также могут использоваться для интеллектуального анализа мнений — поэтому давайте посмотрим на облако слов.
wordcloud_text = " ".join(desc for desc in df_com.comments) print ("There are {} words in the combination of all descriptions.".format(len(wordcloud_text)))
В комбинации всех комментариев 47204197 слов.
stopwords = set(STOPWORDS) wordcloud = WordCloud(stopwords=stopwords, background_color="white").generate(wordcloud_text) plt.imshow(wordcloud, interpolation='bilinear') plt.axis("off") plt.show()
Как видно, слова «пребывание», «Стамбул», «квартира», «место» являются общеупотребительными и используются очень часто. Я предпочитаю удалять их в первую очередь, потому что они не являются отличительными для мнений. Итак, давайте обновим стоп-слова этими словами, чтобы потом их исключить, и снова посмотрим на облако слов.
stopwords.update(['stay','istanbul','apartment','place']) wordcloud = WordCloud(stopwords=stopwords, background_color="white").generate(wordcloud_text) plt.imshow(wordcloud, interpolation='bilinear') plt.axis("off") plt.show()
Вроде нормально. Теперь у нас всего 32 150 465 слов вместо 47 204 197. Кроме того, кажется, что нет ничего общего с манипулятивными общеупотребительными словами — то есть словами, которые очень часто используются и не имеют ничего общего с мнением. Итак, давайте удалим стоп-слова из текста.
df_com['comments'] = df_com['comments'].apply(lambda x: ' '.join([item for item in x.split() if item not in stopwords]))
Теперь мы готовы начать анализ мнений. Давайте определим анализатор и создадим столбец «оценки», применив анализ мнений к комментариям.
sid = SentimentIntensityAnalyzer() df_com['scores'] = df_com['comments'].apply(lambda review: sid.polarity_scores(review))
Теперь у нас есть такая колонка:
{«отрицательный»: 0,026, «ней»: 0,671, «положительный»: 0,303, «составной»: 0,9913}
Итак, как видно, в основном есть отрицательные, нейтральные и положительные веса. Кроме того, существует «составное» значение от -1 до +1, которое можно определить, используя три основных. Следовательно, соединения будет достаточно для анализа. Итак, давайте определим столбец «составной» для составной оценки мнений.
df_com['compound'] = df_com['scores'].apply(lambda score_dict: score_dict['compound'])
Давайте посмотрим на составной столбец.
Как видно, большинство мнений больше нуля. Это означает, что подавляющее большинство комментариев положительные.
2. Что можно сказать о распределении оценок?
Мы классифицировали все комментарии. Теперь давайте посмотрим рейтинги. Во-первых, давайте возьмем среднее значение всех составных оценок мнений и сгруппируем по listing_id (идентификатору свойства). Затем создайте фрейм данных.
df_comments_avg = df_com.groupby(['listing_id'])['compound'].mean() df_comments_avg = pd.DataFrame(df_comments_avg)
Теперь из второго набора данных, как было сказано ранее, нам нужны только «id» и «review_scores_rating». Набор данных листинга был прочитан как «df_list».
df_ovr_ratings = df_list[['id','review_scores_rating']]
Теперь давайте посмотрим на нулевые значения.
print('There is total of',len(df_ovr_ratings),'rows with', (df_ovr_ratings['id'].nunique()/len(df_ovr_ratings))*100,'% of unique id numbers and', 100*df_ovr_ratings['review_scores_rating'].isna().sum()/len(df_ovr_ratings),'% of null values')
Всего имеется 23650 строк со 100,0 % уникальных идентификаторов и 54,00845665961945 % нулевых значений. Конечно, нулевые рейтинги могут быть интерполированы другими столбцами из набора данных списков с помощью другого алгоритма машинного обучения. Однако это не входит в предмет данной статьи. Следовательно, давайте отбросим нулевые значения для простоты.
df_ovr_ratings.dropna(inplace=True) print('There is total of',len(df_ovr_ratings),'rows with', (df_ovr_ratings['id'].nunique()/len(df_ovr_ratings))*100,'% of unique id numbers')
Всего имеется 10877 строк со 100,0 % уникальных идентификаторов. Убрано более половины строк. Конечно, это нежелательно в первую очередь, однако, как было сказано ранее, давайте оставим это так для простоты. Теперь посмотрим на распределение оценок.
plt.hist(df_ovr_ratings['review_scores_rating']) plt.title('Histogram of overall ratings') plt.xlabel('Ratings') plt.show()
Как видно, подавляющее большинство оценок выше 90%. Распределение смещено вправо и не имеет ничего общего с нормальным распределением. На самом деле, кажется, существует четкая положительная корреляция между оценками и мнениями. Давайте немного углубимся в отношения.
3. Какая связь между оценками и мнениями?
Мы проанализировали мнения и рейтинги отдельно. Во-первых, давайте сгруппируем соединения, взяв средние значения.
df_comments_avg = df_com.groupby(['listing_id'])['compound'].mean() df_comments_avg = pd.DataFrame(df_comments_avg)
Теперь соберите их вместе. У нас будут идентификаторы, составные оценки мнений и общие рейтинги в одном и том же наборе данных. Итак, давайте сделаем внутреннее соединение.
merged_df = df_ovr_ratings.join(df_comments_avg,how='inner',on='id') merged_df.head()
Давайте посмотрим на взаимосвязь между рейтингами и оценками мнений.
plt.scatter(merged_df['review_scores_rating'],merged_df['compound']) plt.xlabel('Rating') plt.ylabel('Opinion Score') plt.show()
Как видно, существует четкая положительная корреляция между высокими оценками и более высокими оценками положительного мнения. Однако при более низких рейтингах (например, ниже 60) мы не видим корреляции.