воскресенье, 25 сентября 2022 г.

Статистика c Python в розничной торговле : описательная статистика и визуализация

Начинаю серию статей по статистике в розничной торговле с использованием Python. В нашем первом модуле мы рассмотрим описательную статистику. То есть способы представления данных с помощью чисел и графиков. В частности, рассмотрим некоторые важные принципы визуализации информации.

Это важно, потому что большинство людей предпочитают смотреть на графики, а не на числа. Таким образом, лучше всего сообщать информацию графически, когда это возможно.
Сначала рассмотрим пример, показывающий, почему описательная статистика так полезна. Предположим, что нам необходимо проанализировать данные по длине чека 128 магазинов розничной торговли в июле 20.. года. Загружаем и смотрим на данные

df=pd.read_excel("shops_lch_07.xlsx")
df.head()
shoplch
0Sh13.617
1Sh23.513
2Sh33.657
3Sh43.422
4Sh52.760

И если вы посмотрите на этот длинный список цифр, как-то сложно увидеть, что происходит. Однако, если мы сделаем более простую графику, подобную этой,
мы сразу видим, что распределение близко к нормальному и при этом есть несколько выбросов, которые скорее всего связаны с ошибкой при загрузке данных.

plt.figure(figsize=(10,6))
sns.histplot(df)


Сейчас существует множество способов визуализации данных. Какой из них выбрать, зависит от характер данных и цели визуализации. И мы сейчас рассмотрим несколько примеров. Во-первых, рассмотрим случай, когда данные качественные. То есть данные представляют собой не числа, а категории, такие например как товарные категории. В этом случае мы будем использовать круговую диаграмму.
Сначала загрузим новые данные, представляющие годовые продажи розничной сети одежды, разделенные по годам и товарным категориям (под продажами понимаем выручки в некоторых условных единицах).

df=pd.read_excel("clothing_cat.xlsx")
df

yearhoodyjacketpantsshirtshortssweatert-shirt
0201417437113792441539107278161115055370718548600
1201518354815324931810714319725122399421271583617
...........................
4201826379616481231569927249077101788369063550383
5201932097315048141465151251654101938290987486544

6 rows × 8 columns

Построим круговые диаграммы по годам

fig, axes = plt.subplots(2, 3, figsize=(10, 6))
for i, (idx, row) in enumerate(df.set_index('year').iterrows()):
    ax = axes[i // 3, i % 3]
    row = row[row.gt(row.sum()*0.01)]
    ax.pie(row, labels=row.index,autopct="%.1f", startangle=30)
    ax.set_title(idx)
fig.subplots_adjust(wspace=.2)


Идея круговой диаграммы состоит в том, что каждый кусок "пирога" нарезается в соответствии с соответствующим процентом товарной группы в годовой выручке.
Круговая диаграмма позволяет легче увидеть, какой процент от общего количества представляет категория. Например, если мы посмотрим на толстовки (hoody),то мы видим, что их доля в выручке начиная с 2017 года стабильно растет.
Другим удобным способом представления таких данных является блочная диаграмма.
df.set_index('year').div(df.set_index('year').sum(1), axis=0).plot.bar(figsize=(14, 8))

По этой диаграмме легко заметить "борьбу" за лидерство двух основных товарных групп - джинсы (pants) и куртки (jacket) и начиная с 2017 года куртки обошли джинсы по доле в годовой выручке.
Стандартным графиком для одной непрерывной переменной является гистограмма. Принцип ее построения прост - горизонтальная шкала разбивается на диапазоны исследуемой величины, а вертикальная показывает количество значений, попавших в этот диапазон. Такую гистограмму мы рассмотрели с самого начала. Другой вариант гистограммы - вместо количества значений показывает долю этих значений.

С гистограммой тесно связан график плотности, который строится на основе оценки непрерывного распределения вероятности по результатам измерений. Обычно стремятся аппроксимировать это распределение комбинацией ядер, т. е. комбинацией более простых распределений, например нормального (гауссова). Поэтому графики плотности еще называют графиками ядерной оценки плотности (KDE – kernel density estimate). 

plt.figure(figsize=(10,6))
sns.histplot(df,stat="percent",kde=True)
plt.axvline(df.mean()[0],c="r", linestyle="--")


На этом графике выведена гистограмма длин чека вместе с графиком плотности, вертикальная черта показывает среднее значение. Очевидно, что если исключить выбросы, то распределение нормальное.

Следующий вариант представления информации называется "бохплот", его также называют "ящик с усами"

plt.figure(figsize=(10,6))
sns.boxplot(x=df['lch'])
plt.axvline(df.lch.median(),c="r", linestyle="--")
plt.axvline(df.lch.min(),c="b", linestyle="--")
plt.axvline(df.lch.max(),c="b", linestyle="--")
plt.axvline(df.lch.quantile(0.25),c="r", linestyle="dotted")
plt.axvline(df.lch.quantile(0.75),c="r", linestyle="dotted")



Идея состоит в том, что он визуализирует пять ключевых показателей выборки. В этом примере мы смотрим те же данные, что и в гистограмме - распределение длин чека по магазинам розничной сети одежды. Нижний ус показывает наименьшее значение, верхний ус показывает наибольшее значение. И затем есть еще три числа - это медиана, это число, где половина данных меньше, а половина больше, также есть первый квартиль, это число, при котором четверть данных меньше, а три четверти больше. И, наконец, третий квартиль — это число,  где три четверти данных меньше, а одна четверть больше. Коробчатая диаграмма содержит меньше информации, чем гистограмма, но занимает меньше места, и поэтому его можно использовать для сравнения несколько наборов данных, поместив их на один и тот же график. Рассмотрим пример - дневная длина чека в четырех магазинах с июня по август 2019 года

df=pd.read_excel("shops_lch_06_08.xlsx",index_col="date", parse_dates=True)
df


shoplch
date
2019-06-01Sh13.19
2019-06-01Sh23.31
.........
2019-08-31Sh32.40
2019-08-31Sh42.31

368 rows × 2 columns


sns.boxplot(x='shop',y='lch',data=df)


Наконец, давайте посмотрим на данные, состоящие не из одного числа, а из двух, то есть данные поступают парами. Эти данные обычно отображаются в системе координат, которая называется точечной диаграммой.  Рассмотрим следующий набор данных : вместе с длиной чека выведены дневные показатели посетителей и выручки для тех же четырех магазинов. 

df=pd.read_excel("shops_visit_rev_lch_06_08.xlsx",index_col="date", parse_dates=True)
df

Выведем точечную диаграмму для двух показателей - по горизонтальной оси - дневной показатель посетителей, по вертикальной - дневная выручка.

plt.figure(figsize=(10,6))
sns.scatterplot(x='visit', y='rev',hue="shop",data=df)
Точечная диаграмма очень полезна для визуализации взаимосвязи между двумя переменными. Например, мы видим что между количеством посетителей и выручкой существует прямо пропорциональная зависимость (конечно, было бы удивительно, если бы ее не было). Данные разделены цветом по магазинам. Такое разделение помогает понять, что угол наклона этой зависимости магазинов заметно отличается.
Если же вывести такой график в координатах посетители - длина чека, то можно увидеть, что такой зависимости нет (хотя можно было бы предположить, что она будет скорее отрицательной).

plt.figure(figsize=(10,6))
sns.scatterplot(x='visit', y='lch',hue="shop",data=df)


Цель статистического анализа обычно состоит в сравнении наблюдаемых данных с каким-либо эталоном. Поэтому очень полезно предоставлять контекст в графическом отображении. В книге «Визуальное отображение количественной информации» Эдварда Тафти есть хорошее объяснение этому. Один из способов обеспечить контекст в графике — использовать принцип малых кратностей (Small Multiples). Термин Small Multiples был предложен и популяризован Эдвардом Тафти, который описал этот вид визуализации как графические изображения, которые находятся в одном контексте, но имеют разное содержание.

Мы уже видели, что boxplot очень полезен для этого. На следующем графике показаны длины чека по месяца. Этот позволяет сравнить, как длина чека меняется в течение года.

plt.figure(figsize=(10,6))
sns.boxplot(x='num_month',y='value',data=df0)
plt.ylabel('lch')






Вот еще один пример, показывающий маленькие кратные. Он показывает несколько временных рядов, это месячные выручки восьми магазинов с января 2015 по декабрь 2019.

axs = df.plot(color='0.25', figsize=(11, 15),subplots=True, sharex=True,
      title=['Sh01','Sh02','Sh03','Sh04','Sh05','Sh06','Sh07','Sh08',])




Просматривая такой график легко можно сделать некоторые выводы : во-первых временные ряды имеют ярко выраженную сезонность, причем скорее всего они расположены в одном регионе и во-вторых, что большая часть магазинов имеет падающий тренд и требует более глубоко анализа причин с этим связанных.

Числовые сводные показатели

Среднее и медиана

Итак, давайте поговорим об основных числовых показателях выборок. Если мы хотим характеризовать данные, используя только одно число, мы используем среднее значение или медиану. Среднее (mean) это сумма всех значений, деленная на число значений. Медиана (median) - это такое значение, что половина сортированных данных находится выше и ниже данного значения. 
Среднее и медиана — это всего одна из размерностей в обобщении признака.
Вторая размерность, вариабельность, именуемая также дисперсностью, показывает,
сгруппированы ли значения данных плотно, или же они разбросаны. В основе
статистики лежит вариабельность: ее измерение, уменьшение, различение случайной
вариабельности от реальной, идентификация различных источников реальной
вариабельности и принятие решений в условиях ее присутствия.

Если мы хотим охарактеризовать наши данные одним числом, то используем среднее или медиану. Среднее (mean) это сумма всех значений, деленная на число значений. Медиана (median) - это такое значение, что половина сортированных данных находится выше и ниже данного значения. 
Разбираем гистограмму длины чека, на ней площадь соответствует процентам.  Медиана составляет 3,1 при этом примерно половина площади гистограммы ниже 3,1 и половине выше этого значения. 
Медиана и среднее наиболее часто используемы показатели распределения, если гистограмма симметрична, то они совпадают, на наше гистограмме длины чека это хорошо видно.

plt.figure(figsize=(10,6))
mean = df.lch.mean()
median =df.lch.median()
sns.histplot(df,stat="percent",kde=True)
plt.axvline(x=median, c='b', label='Median')
plt.axvline(x=mean, c='red', label='Mean')
plt.xlabel("lsh")
plt.legend()

Если же построить гистограмму месячных выручек 128 магазинов, то будет видно, что распределение скошенное, правая часть длиннее, чем левая сторона, называется cкошенной вправо. В этом случае среднее значение может быть больше медианы.
Если гистограмма сильно перекошена, как в случае размера месячных выручек магазинов, знание среднего значения на самом деле мало что нам говорит. В таких случаях лучше использовать медиану.





Вернемся к гистограмме длины чека и нанесем на нее еще несколько интересных значений, характеризующих это распределение. Речь идет о перцентилях, квантилях и межквартильном размахе. Если отсортировать нашу выборку и идти с левой стороны, то первые 25% - это 25-й перцентиль или 1-й квартиль, 50% - 50-й перцентиль и 2-й квартиль или медиана, 75% - 75-й перцентиль или 3-й квартиль и 100% - 100-й перцентиль или 4-й квартиль или максимальное число. Если из значения 3-го квартиля вычесть значение 1-го, то получим межквартильный размах. Один из обычных способов определений выбросов использует межквартильный размах (МКР) и состоит в том, что «слабые» выбросы - это те значения, которые меньше 25% перцентили (1-й квартиль) минус 1,5*МКР или больше 75% перцентили (3-й квартиль) плюс 1,5*МКР. 
Все эти значения нанесены на нашей гистограмме. 



Подводя итоги обозначим основные определения касающиеся описательной статистики 

Оценки центрального положения

Переменные с измеряемыми или количественными данными могут иметь тысячи
четко различимых значений. Базовый шаг в разведывании данных состоит в получении
’’типичного значения” для каждого признака (переменной): оценки того, где
расположено большинство данных (т. е. их центральной тенденции).

Ключевые термины оценок центрального положения

Среднее (mean)
    Сумма всех значений, деленная на число значений.
Среднее взвешенное (weighted mean)
    Сумма произведений всех значений на их веса, деленная на сумму весов.
Медиана (median)
    Такое значение, что половина сортированных данных находится выше и ниже
    данного значения.
Медиана взвешенная (weighted median)
    Такое значение, что половина суммы весов находится выше и ниже сортированных
    данных.
Среднее усеченное (trimmed mean)
    Среднее число всех значений после отбрасывания фиксированного числа предельных
    значений.
Робастный (robust)
    Не чувствительный к предельным значениям.
Выброс (outlier)
    Значение данных, которое сильно отличается от большинства данных

• Базовой метрикой центрального положения является среднее, но оно может
    быть чувствительным к предельным значениям (выбросам).
• Другие метрики (медиана, среднее усеченное) являются более робастными.

Оценки вариабельности

Центральное положение — это всего одна из размерностей в обобщении признака.
Вторая размерность, вариабельность, именуемая также дисперсностью, показывает,
сгруппированы ли значения данных плотно, или же они разбросаны. В основе
статистики лежит вариабельность: ее измерение, уменьшение, различение случайной
вариабельности от реальной, идентификация различных источников реальной
вариабельности и принятие решений в условиях ее присутствия.

Ключевые термины для метрик вариабельности

Отклонения (deviations)
     Разница между наблюдаемыми значениями и оценкой центрального положения.
Дисперсия (variance)
    Сумма квадратичных отклонений от среднего, деленная на п -1, где п — это
    число значений данных.
Стандартное отклонение (standard deviation)
    Квадратный корень из дисперсии.
Среднее абсолютное отклонение (mean absolute deviation)
    Среднее абсолютных значений отклонений от среднего
Медианное абсолютное отклонение от медианы (median absolute deviation
    from the median)
Размах (range)
    Разница между самым крупным и самым малым значениями в наборе данных.
Порядковые статистики (order statistics)
    Метрики на основе значений данных, отсортированных от самых малых до
    самых крупных.
Перцентиль (percentile)
    Такое значение, что Р процент значений принимает данное значение или
    меньшее и (100 - Р) процент значений принимает данное значение или большее.
Межквартильный размах (interquartile range)
    Разница между 75-м и 25-м перцентилями.

• Дисперсия и стандартное отклонение — наиболее широко распространенные
и в рутинном порядке регистрируемые статистики вариабельности.
• Обе статистики чувствительны к выбросам.
• Более робастные метрики включают среднее абсолютное отклонение, медианное
абсолютное отклонение от медианы и перцентили (квантили).

Как эти показатели можно получить в Python :

Для примеров используем следующие данные продаж 128 розничных магазинов одежды в июле : sale - количество сделанных покупок, cheks - количество чеков, lch - длина чека lsh = sale / cheks

df=pd.read_excel("shops_sale_07.xlsx",index_col="shop")
df.head()


salechekslch
shop
Sh115884393.617
Sh215354373.513
Sh335559723.657
Sh432589523.422
Sh515215512.760


Для вычисления среднего и медианы длины чека на Python мы можем использовать методы DataFrame пакета pandas. Для усеченного среднего значения требуется функция
trim_mean из библиотеки scipy.stats:

from scipy import stats
print(f'mean = {df.lch.mean():.4f}')
print(f'trim mean = {stats.trim_mean(df.lch, 0.1):.4f}')
print(f'median = {df.lch.median():.4f}')

mean = 3.0340
trim mean = 3.0266
median = 3.0258

Взвешенное среднее значение доступно с помощью пакета NumPy. Для взвешенной
медианы мы можем использовать специализированный пакет weightedstats, в качестве  веса используется количество чеков

import weightedstats as ws
print(f'weighted mean (np) = {np.average(df.lch, weights=df.cheks):.4f}')
print(f'weighted mean (ws) = {ws.weighted_mean(df.lch, weights=df.cheks):.4f}')
print(f'weighted median = {ws.weighted_median(df.lch, weights=df.cheks):.4f}')

weighted mean (np) = 3.0485
weighted mean (ws) = 3.0485
weighted median = 3.0449

DataFrame пакета pandas предоставляет методы для вычисления стандартного
отклонения и квантилей. Используя квантили, мы можем легко определить 
межквартильный размах (IQR). Для робастной оценки медианного абсолютного
отклонения (MAD)  используем функцию robust.scale.mad из пакета statsmodels:

#robust является подпакетом statsmodels, и импорт пакета, как правило, 
#не приводит к автоматическому импорту подпакетов (если пакет не написан #для этого явным образом)
import statsmodels as sm
import statsmodels.robust
print(f'standard deviation = {df.lch.std():.4f}')
print(f'interquartile range = {df.lch.quantile(0.75) - df.lch.quantile(0.25):.4f}')
print(f'median absolute deviation from the median = {sm.robust.scale.mad(df.lch):.4f}')

standard deviation = 0.4759
interquartile range = 0.3443
median absolute deviation from the median = 0.2658









Комментариев нет:

Отправить комментарий