import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
Загрузка датасета¶
import os
!pip install -q kaggle
#https://www.kaggle.com/general/51898#520673
os.environ['KAGGLE_USERNAME'] = "onemadokafan" # username from the json file
os.environ['KAGGLE_KEY'] = "8b92294df626f5723ef03bf8f6a758b4" # key from the json file
!kaggle datasets download -d nicapotato/womens-ecommerce-clothing-reviews --unzip
Downloading womens-ecommerce-clothing-reviews.zip to /content 100% 2.79M/2.79M [00:00<00:00, 5.24MB/s] 100% 2.79M/2.79M [00:00<00:00, 4.46MB/s]
Обработка датасета¶
В данном датасете представлены отзывы покупателей женской одежды в онлайн-магазине. Мы выбрали следующие колонки для анализа: Age (возраст покупателей), Rating (рейтинг), Recommended IND (рекомендует ли покупатель данную вещь, 1 - да, 0 - нет), Positive Feedback Count (Количество пользователей, которые нашли данный отзыв полезным) и Class Name. Данные анонимизированы. Удалены строки, в которых отзывы оставлены на вещи из категорий "Casual bottoms" и "Chemises" (сорочки), поскольку они содержали малое количество отзывов (1 и 2, соответственно). Для начала посмотрим на саму таблицу:
old_data = pd.read_csv('Womens Clothing E-Commerce Reviews.csv')
data = old_data.drop(index=old_data.query("`Class Name` == 'Casual bottoms' | `Class Name` == 'Chemises'").index)
data.head()
Unnamed: 0 | Clothing ID | Age | Title | Review Text | Rating | Recommended IND | Positive Feedback Count | Division Name | Department Name | Class Name | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 767 | 33 | NaN | Absolutely wonderful - silky and sexy and comf... | 4 | 1 | 0 | Initmates | Intimate | Intimates |
1 | 1 | 1080 | 34 | NaN | Love this dress! it's sooo pretty. i happene... | 5 | 1 | 4 | General | Dresses | Dresses |
2 | 2 | 1077 | 60 | Some major design flaws | I had such high hopes for this dress and reall... | 3 | 0 | 0 | General | Dresses | Dresses |
3 | 3 | 1049 | 50 | My favorite buy! | I love, love, love this jumpsuit. it's fun, fl... | 5 | 1 | 0 | General Petite | Bottoms | Pants |
4 | 4 | 847 | 47 | Flattering shirt | This shirt is very flattering to all due to th... | 5 | 1 | 6 | General | Tops | Blouses |
И количество отзывов на различные категории товаров:
chart = sns.countplot(y='Class Name', data=data, color='maroon', order = data['Class Name'].value_counts().index)
chart.set_title('Distribution of clothing items by clothes type')
chart.set_xlabel('Number of reviews')
Text(0.5, 0, 'Number of reviews')
#@title Возраст покупателей
print(f'''Средний возраст покупателей, оставивших отзывы составил {np.mean(data.Age):0.1f} лет.
Минимальный возраст — {np.min(data.Age)} лет, что, вероятно, соответствует минимальному
возрасту для регистрации. Интересно, нужна ли там верификация возраста, т.е. не могут ли
зарегистрироваться несовершеннолетние люди под видом совершеннолетних. Максимальный возраст
— {np.max(data.Age)} лет. ''')
Средний возраст покупателей, оставивших отзывы составил 43.2 лет. Минимальный возраст — 18 лет, что, вероятно, соответствует минимальному возрасту для регистрации. Интересно, нужна ли там верификация возраста, т.е. не могут ли зарегистрироваться несовершеннолетние люди под видом совершеннолетних. Максимальный возраст — 99 лет.
Всего категорий с учетом удаленных 18, самые популярные — платья, вязаные вещи и блузки. Далее посмотрим, как распределен возраст людей, оставивших отзывы на эти категории. Видим, что наименьший медианный возраст у оставивших отзывы на купальные костюмы (36 лет), наибольший сразу у нескольких категорий: свитеров (sweaters), блуз (blouses), изделий мелкой вязки (fine gauge) и трендовой одежды (trend) — 43 года. Также при взгляде на график замечаем наибольшее количество выбросов у категории "Платья". Она самая многочисленная, так что это неудивительно :)
sns.set_theme(rc={'figure.figsize':(8,5)})
sns.boxplot(y='Class Name', x='Age', data=data, saturation=1, palette='BrBG', fliersize=1,
order=data[['Age', 'Class Name']].groupby(by='Class Name').median().sort_values(by='Age').index).set_title('Dictribution of customers age by type of clothes')
Text(0.5, 1.0, 'Dictribution of customers age by type of clothes')
В датасете есть 2 вида оценок товара: в бинарной (1 - рекомендую, 0 - нет) и в пятибальной шкалах. Интересно, являются ли они взаимозаменяемыми (иными словами — связана ли бинарная оценка и оценка по пятибальной шкале)? Для этого будем исходить из предположения, что рейтинг 4 и 5 соответствует рекомендации, ниже — ее отсутствию. Для начала взглянем на распределение оценок:
sns.set()
fig, ax = plt.subplots(1, 2, figsize=(15, 3))
sns.countplot(x='Rating', data=data, saturation=1, palette='Spectral', ax=ax[0])
sns.countplot(x='Recommended IND', data=data, saturation=1, palette='Spectral', ax=ax[1])
<Axes: xlabel='Recommended IND', ylabel='count'>
Посчитаем, какое количество пользователей поставило соответствующие оценки:
copy_data = data.copy()
copy_data.loc[copy_data['Rating'] <= 3, 'Rating'] = 0
copy_data.loc[copy_data['Rating'] > 3, 'Rating'] = 1
pseudo_vs_true = pd.crosstab(copy_data['Rating'], copy_data['Recommended IND'])
pseudo_vs_true
Recommended IND | 0 | 1 |
---|---|---|
Rating | ||
0 | 3979 | 1299 |
1 | 193 | 18012 |
Объединим результаты согласно критериям. Будем тестировать выборки с помощью χ2-теста (на независимость -- выборка большая, все частоты тоже) с помощью пакета scipy.stats.
from scipy.stats import chi2_contingency
chi2_contingency(pseudo_vs_true)
Chi2ContingencyResult(statistic=15467.839733914268, pvalue=0.0, dof=1, expected_freq=array([[ 937.69177703, 4340.30822297], [ 3234.30822297, 14970.69177703]]))
p-value очень маленькое, поэтому отклоняем нулевую гипотезу о независимости. Таким образом, бинарная оценку и оценку по пятибальной шкале действительно можно приравнять: большинство пользователей, ставя 4 или 5, станут рекомендовать ее другим.
Если бы отображалась цена покупки, было бы гораздо интереснее :(
Перейдем к другим вещам. Еще было бы неплохо посмотреть на самые популярные и при этом рекомендуемые товары. Мы решили рассмотреть 10 подобных вещей:
indices = list(data[['Recommended IND', 'Clothing ID']].groupby(by='Clothing ID').sum().sort_values(by='Recommended IND', ascending=False)[:11].index)
filtered_data = pd.DataFrame()
for index in indices:
filtered_data = pd.concat([data[data['Clothing ID'] == index], filtered_data])
sns.countplot(y='Class Name', data=filtered_data)
<Axes: xlabel='count', ylabel='Class Name'>
10 популярных товаров принадлежат к 5 категориям: платья (самая популярная на сайте), вязаные изделия, блузки, свитеры и изделия тонкой вязки. Они являются одними из самым популярных товаров, так что это неудивительно. Отличается ли возраст у покупателей таких товаров от всех покупателей сайта?
sns.set_theme(rc={'figure.figsize':(5,3)})
sns.violinplot(data=filtered_data, x='Age', y='Class Name', palette='RdGy', saturation=1, cut=0)
sns.violinplot(cut=0, data=data.query("(`Class Name` == 'Dresses') | (`Class Name` == 'Fine gauge') | (`Class Name` == 'Knits') | (`Class Name` == 'Blouses' | (`Class Name` == 'Sweaters'))"), x='Age', y='Class Name', palette='RdGy', saturation=1)
<Axes: xlabel='Age', ylabel='Class Name'>
При рассмотрении графиков меня заинтересовала категория изделий тонкой вязки (fine gauge), хоть там и отображаются медианы. Проведем z-тест на равенство средних, так как одна из выборок -- генеральная совокупность (=> знаем ее параметры -- стандартное отклонение).
from statsmodels.stats.weightstats import ztest
mean_top = filtered_data[filtered_data['Class Name'] == 'Fine gauge']['Age']
mean_all = data[data['Class Name'] == 'Fine gauge']['Age']
ztest(mean_top, mean_all)
(-0.6301897848132324, 0.5285704219130565)
p-value не позволяет отвергнуть гипотезу о равенстве средних. Остальные выборки даже не будем рассматривать.
На этом все!