суббота, 2 декабря 2023 г.

Машинное обучение в розничной торговле : выбросы и заполнение пропущенных значений

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

Начнем с подготовки данных, на которых будем отрабатывать приемы выявления выбросов и заполнения пропусков. Набор данных представляет собой дневные показатели продажи в магазине одежды в сентябре - октябре 2023 года, всего 48 строк и 8 столбцов.

df=pd.read_excel('data/shday.xlsx')
df.head()

daterevvisitchsaleslchpriceconv
02023-09-0122579541325883.5202565.8520.061
12023-09-02612489678641882.9383257.9200.094
22023-09-03609786856631792.8413406.6260.074
32023-09-0426659638131782.5163417.8970.081
42023-09-0527703032128712.5363901.8310.087

Обозначения :

  • date - дата
  • rev - выручка
  • visit - количество посетителей
  • sales - количество покупок
  • ch - количество чеков
  • conv - конверсия = количество чеков / количество посетителей
  • lch - длина чека = количество покупок / количество чеков
  • price - средняя цена покупки = выручка / количество покупок

Интересную информацию может дать корреляционная матрица

plt.figure(figsize=(12, 7))
sns.heatmap(df.corr(numeric_only=False),vmin=-0.3,vmax=0.6,
center=0,annot=True,fmt='.2f',
mask=~np.tri(df.corr(numeric_only=False).shape[1], k=-1, dtype=bool),
linewidth=2,cbar=False)


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

df0=df.loc[:,df.columns[1:]].copy()
df0.head()

revvisitchsaleslchpriceconv
022579541325883.5202565.8520.061
1612489678641882.9383257.9200.094
2609786856631792.8413406.6260.074
326659638131782.5163417.8970.081
427703032128712.5363901.8310.087

Сгенерируем номера строк для вставки выбросов

random.seed(1000)
random_list = random.sample(range(1, df.shape[0]), k=6)
random_list

[28, 43, 7, 26, 23, 5]

Для того, чтобы получить выбросы по посетителям, используем "нереальные" значения конверсии, слишком большие и слишком маленькие и по ним рассчитаем
выбросы посетителей

conv=np.array([0.85,0.9,0.95,0.007,0.008,0.009])
df0.loc[random_list,'visit']=round(df0.loc[random_list,'ch']/conv,0)
df0.loc[random_list,'conv']=df0.loc[random_list,'ch']/df0.loc[random_list,'visit']
df0.loc[random_list]

revvisitchsaleslchpriceconv
2815891721.018482.6673310.7710.857
4352184964.0581462.5173574.3080.906
717474524.023753.2612329.9330.958
26559271143.08263.2502151.0380.007
233648484125.0331133.4243228.7430.008
51958202556.023954.1302061.2630.009

Для вывода сводной статистике используем метод pandas describe(). Сводная статистика дает нам представление о распределении данных и может просигнализировать наличие выбросов в наборе данных. Например, мы можем видеть большой разрыв между минимальным и максимальные значения. В этом наборе мы видим большой разрыв между средним и максимальным и минимальным значениями посетителей. Этот большой разрыв является указателем на наличие выбросов, как с  очень высокими значениями, так и с очень низкими значениями.

df0.describe()

revvisitchsaleslchpriceconv
count48.00048.00048.00048.00048.00048.00048.000
mean262182.500445.66727.95880.3542.8873302.3410.135
std137015.629663.44613.93740.4270.518622.3060.203
min55927.00021.0008.00026.0001.7922061.2630.007
25%174748.750207.00019.75049.7502.5312922.7290.074
50%215608.000241.50023.00071.0002.8413261.9920.090
75%304316.750436.25033.00095.0003.1703618.5500.105
max612489.0004125.00064.000188.0004.2505218.8000.958

Далее для выявления выбросов используем метод коробочной диаграммы в Seaborn. Выбросы — это значения за пределами усов. Они представлены в виде черных кружков.

fig, ax = plt.subplots(1, 2,figsize=(40,18))
sns.set(font_scale = 3)
ax1 = sns.boxplot(data= df0, x= 'visit', ax = ax[0])
ax1.set_xlabel('visit', fontsize = 30)
ax1.set_title('Outlier visit', fontsize = 40)
ax2 = sns.boxplot(data= df0, x= 'conv', ax=ax[1])
ax2.set_xlabel('conv', fontsize = 30)
ax2.set_title('Outlier conv', fontsize = 40)



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

Начнем с IQR отдельно для посетителей и конверсии

Рассчитаем IQR для посетителей, используя метод квантилей в pandas:

Q1 = df0['visit'].quantile(0.25)
Q3 = df0['visit'].quantile(0.75)
IQR = Q3 - Q1
print(IQR)

229.25


Определим выбросы посетителей с помощью IQR:

df0.loc[(df0['visit'] < (Q1 - 1.5 *IQR)) |
(df0['visit'] > (Q3 + 1.5 * IQR)),['visit','conv']]


visitconv
2856.00.074
52556.00.009
234125.00.008
261143.00.007

Как видно этот метод выявил четыре выброса, причем один неверный, т.е. из шести выбросов выявились только три. Посмотрим, как он сработает на конверсии.

Q1 = df0['conv'].quantile(0.25)
Q3 = df0['conv'].quantile(0.75)
IQR = Q3 - Q1
print(IQR)

0.031164399035558107

df0.loc[(df0['conv'] < (Q1 - 1.5 *IQR)) |
(df0['conv'] > (Q3 + 1.5 * IQR)),['visit','conv']]

visitconv
52556.00.009
724.00.958
234125.00.008
261143.00.007
2821.00.857
4364.00.906

По конверсии выявились все шесть выбросов. Конечно, конверсия - это нормированная величина, заранее известен диапазон ее изменения - от 0 до 1 и по ее значениям гораздо проще выделить выброс.

Теперь посмотрим, как нам покажут выбросы гистограммы, построенные по посетителям и конверсии.

По посетителям

plt.figure(figsize = (40,18))
ax = sns.histplot(data= df0,x= 'visit',bins=60)
ax.set_xlabel('visit', fontsize = 30)
ax.set_ylabel('Count', fontsize = 30)
ax.set_yticks(range(1,20))
ax.set_title('Outlier visit', fontsize = 40)
plt.xticks(fontsize = 30)
plt.yticks(fontsize = 30)


Все шесть выбросов проявились как отдельно стоящие столбики, наглядно, но не очевидно, за выброс можно принять еще одно значение.
Посмотрим что покажет конверсия

plt.figure(figsize = (40,18))
ax = sns.histplot(data= df0, x= 'conv',bins=60)
ax.set_xlabel('conv', fontsize = 30)
ax.set_ylabel('Count', fontsize = 30)
ax.set_title('Outlier conv', fontsize = 40)
ax.set_yticks(range(1,15))
plt.xticks(fontsize = 30)
plt.yticks(fontsize = 30)



Все шесть выбросов на гистограмме отразились.

Поиск двумерных выбросов

Двумерные выбросы обычно представляют собой большие или малые значения, которые встречаются в двух переменных одновременно. Проще говоря, эти значения отличаются от других наблюдений, когда мы рассматриваем две переменные вместе. По отдельности значения каждой переменной могут быть выбросами, а могут и не быть; однако в совокупности они являются выбросами. Чтобы обнаружить двумерные выбросы, нам обычно необходимо проверить взаимосвязь между двумя переменными. Основной метод — визуализировать взаимосвязь с помощью диаграммы рассеяния. Также для выявления выбросов можно использовать  коробчатую диаграмму. Используя коробчатую диаграмму, мы можем легко определить контекстуальные выбросы, которые обычно представляют собой наблюдения, считающиеся аномальными в конкретном контексте. Контекстуальный выброс значительно отличается от остальных точек данных в конкретном контексте. Например, при анализе посетителей мы можем анализировать такие показатели как выручку, количество чеков и конверсию. Контекстные выбросы вряд ли проявятся как выбросы, если проводится только одномерный анализ посетителей. Они часто выявляются, когда мы рассматриваем посетителей в пределах конкретного контекста, например вместе с конверсией. Опять же, важно понимать контекст и цель анализа, прежде чем выбирать соответствующий метод для обработки двумерных выбросов. Мы рассмотрим, как идентифицировать двумерные выбросы, используя методы точечной диаграммы и коробчатой ​​диаграммы. 

Рассмотрим три точечные диаграммы посетителей с конверсией, выручкой и количеством чеков.

sns.set_style("darkgrid")
plt.figure(figsize = (40,18))
ax = sns.scatterplot(data= df0, x= 'visit', y ='conv', s = 200)
ax.set_xlabel('visit', fontsize = 30)
ax.set_ylabel('conv', fontsize = 30)
plt.xticks(fontsize=30)
plt.yticks(fontsize=30)
ax.set_title('Bivariate Outlier Analysis visit and conv', fontsize = 40)

plt.figure(figsize = (40,18))
ax = sns.scatterplot(data= df0, x= 'visit', y ='rev', s = 200)
ax.set_xlabel('visit', fontsize = 30)
ax.set_ylabel('rev', fontsize = 30)
plt.xticks(fontsize=30)
plt.yticks(fontsize=30)
ax.set_title('Bivariate Outlier Analysis visit and rev', fontsize = 40)
plt.figure(figsize = (40,18))
ax = sns.scatterplot(data= df0, x= 'visit', y ='ch', s = 200)
ax.set_xlabel('visit', fontsize = 30)
ax.set_ylabel('ch', fontsize = 30)
plt.xticks(fontsize=30)
plt.yticks(fontsize=30)
ax.set_title('Bivariate Outlier Analysis visit and ch', fontsize = 40)

Как видно, лучше всего выбросы видны на диаграмме посетители - конверсия, с этими параметрами будем работать дальше и для определение выбросов будем использовать расстояние Махаланобиса. 

Расстояние Махаланобиса — это расстояние между заданной точкой и центром масс, делённое на ширину эллипсоида в направлении заданной точки. Оно отличается от расстояния Евклида тем, что учитывает корреляции между переменными и инвариантно к масштабу.

 Оно часто используется для поиска выбросов в статистическом анализе, включающем несколько переменных. Для расчета этого расстояния используем функцию mahalanobis из библиотеки scipy.


Для начала отмасштабируем данные, чтобы все переменные имели одинаковый масштаб, с помощью класса StandardScaler.в scikit-learn

from sklearn.preprocessing import StandardScaler
from scipy.spatial.distance import mahalanobis

scaler = StandardScaler()
df0_scaled = scaler.fit_transform(df0.loc[:,['visit','conv']])
df0_scaled = pd.DataFrame(df0_scaled,columns=['visit','conv'])
df0_scaled.head()

visitconv
0-0.050-0.371
10.354-0.203
20.625-0.306
3-0.099-0.268
4-0.190-0.239

А теперь рассчитаем расстояние Махаланобиса в нашем двумерном пространстве посетители - конверсия.

mean = df0_scaled.mean()
cov = df0_scaled.cov()

inv_cov = np.linalg.inv(cov)
distances = []
for _, x in df0_scaled.iterrows():
    d = mahalanobis(x, mean, inv_cov)
    distances.append(d)
df0['mahalanobis_distances'] = distances
df0.loc[:,['visit','conv','mahalanobis_distances']].head(10)

visitconvmahalanobis_distances
04130.0610.394
16780.0940.370
28560.0740.638
33810.0810.313
43210.0870.346
525560.0093.185
62370.0970.416
7240.9584.066
83790.0920.263
95960.0940.273

Чтобы идентифицировать выбросы построим блочную диаграмму

plt.figure(figsize = (40,18))
ax = sns.boxplot(data= df0, x= 'mahalanobis_distances')
ax.set_xlabel('Mahalanobis Distances', fontsize = 30)
plt.xticks(fontsize=30)
ax.set_title('Outlier Analysis of Mahalanobis Distances',fontsize = 40)


На блочной диаграмме определились все шесть выбросов. Выделим их с помощью IQR-метода.


Q1 = df0['mahalanobis_distances'].quantile(0.25)
Q3 = df0['mahalanobis_distances'].quantile(0.75)
IQR = Q3 - Q1

df0.loc[(df0['mahalanobis_distances'] < (Q1 - 1.5 *IQR)) |
(df0['mahalanobis_distances'] > (Q3 + 1.5 * IQR))]
revvisitchsaleslchpriceconvmahalanobis_distances
5195820255623954.1302061.2630.0093.185
71747452423753.2612329.9330.9584.066
233648484125331133.4243228.7430.0085.597
265592711438263.2502151.0380.0071.119
281589172118482.6673310.7710.8573.559
4352184964581462.5173574.3080.9063.810

Обозначим выбросы посетителей как пропущенные значения

df0.loc[(df0['mahalanobis_distances'] < (Q1 - 1.5 * IQR))
|(df0['mahalanobis_distances'] > (Q3 + 1.5 * IQR)),'visit']=np.nan

Дальше наша задача заменить пропущенные в посетителях значения. Будем работать с новым набором данных

df1=df0.loc[:,['rev','visit','ch','sales','lch']].copy()
df1.isnull().sum()

rev      0
visit    6
ch       0
sales    0
lch      0
dtype: int64

Рассмотрим замену пропущенных значений методом к-ближайших соседей из библиотеки sklearn. Подготовим наши данные.

Набор данных с пропусками

dfnan=df1.iloc[random_list,:]
dfnan

revvisitchsaleslch
28158917NaN18482.667
43521849NaN581462.517
7174745NaN23753.261
2655927NaN8263.250
23364848NaN331133.424
5195820NaN23954.130

Набор данных без пропусков

dfnonan=df1.drop(random_list)
dfnonan.head()

revvisitchsaleslch
0225795413.025883.520
1612489678.0641882.938
2609786856.0631792.841
3266596381.031782.516
4277030321.028712.536

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

X = pd.DataFrame(dfnonan.ch)
y = dfnonan['visit']

X_with_nan=pd.DataFrame(dfnan['ch'])

Метод к-ближайших соседей можно использовать для классификации и для регрессии, Рассмотрим оба метода для решения нашей задачи.

Вариант 1 - используем метод KNeighborsClassifier 

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

Выведем метки классов нашей задачи

print(sorted(list(dfnonan.visit.unique())))

[181.0, 191.0, 193.0, 195.0, 200.0, 201.0, 207.0, 209.0, 210.0, 214.0, 215.0, 218.0, 219.0, 223.0, 226.0, 230.0, 236.0, 237.0, 246.0, 247.0, 257.0, 261.0, 265.0, 296.0, 321.0, 379.0, 381.0, 413.0, 420.0, 433.0, 446.0, 450.0, 474.0, 525.0, 527.0, 596.0, 614.0, 678.0, 856.0] 

Также выведем значения количества чеков

print(sorted(list(dfnonan.ch.unique())))

[13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 28, 29, 31, 32, 33, 34, 35, 39,
48, 52, 56, 63, 64]

Выведем также значения, по которым надо предсказать количество посетителей

print(sorted(list(X_with_nan.ch.unique())))

[8, 18, 23, 33, 58]

Загружаем библиотеку

from sklearn.neighbors import KNeighborsClassifier

Создаем модель и обучаем ее на наших данных. Для построения модели на обучающем
наборе вызываем метод fit объекта knn, который принимает в качестве аргумента 
массив NumPy  "X", содержащий обучающие данные и массив "y", соответствующий
обучающим меткам, в нашем случае это количество чеков и количество посетителей, 
им соответствующих. Самым важным параметром в этом методе является количество
соседей, которое мы установим равным 1 :


clf = KNeighborsClassifier(n_neighbors=1, weights='distance')
trained_model = clf.fit(X, y)

Прогнозировать класс (посетителей) пропущенных значений, чтобы сделать прогноз,
мы вызываем метод predict объекта knn

imputed_values = trained_model.predict(X_with_nan)
imputed_values

array([226., 596., 237., 200., 433., 237.])

Объединим факт и прогноз 

df_1=pd.DataFrame({'ch':dfnan['ch'],
    'visit_act':df.loc[random_list,'visit'],'visit_clf':imputed_values})
df_1

chvisit_actvisit_clf
2818196226.0
4358644596.0
723220237.0
268167200.0
2333413433.0
523258237.0

И оценим ошибку прогноза по суммарному показателю

df_1.visit_clf.sum()/df_1.visit_act.sum()

1.016

Прогноз завысил количество посетителей на 1.6%

Вариант 2 - используем метод KNeighborsRegressor


В этом случае нашу задачу заполнения пропущенных значений можно рассматривать
как задачу регрессии. В качестве независимой переменной принимаем количество
чеков, в качестве зависимой - количество посетителей. 
Алгоритм регрессии k ближайших соседей реализован в классе KNeighborsRegressor.
Он используется точно так же, как KNeighborsClassifier.

from sklearn.neighbors import KNeighborsRegressor

reg = KNeighborsRegressor(n_neighbors=3)
trained_model_reg = reg.fit(X, y)
imputed_values = trained_model_reg.predict(X_with_nan)
df_1['visit_reg']=imputed_values df_1

chvisit_actvisit_clfvisit_reg
2818196226.0214.000
4358644596.0642.000
723220237.0219.667
268167200.0209.000
2333413433.0434.333
523258237.0219.667






Ошибка прогноза в этом случае

df_1.visit_reg.sum()/df_1.visit_act.sum()

1.021

Прогноз завысил количество посетителей на 2.1%.

Рассмотрим еще один метод заполнения пропущенных значений. Класс IterativeImputer библиотеки sklearn реализует многомерные алгоритмы восстановления пропущенных
значений, оценивая другие значения в наборе. Данный класс моделирует каждый
признак пропущенного значения как функцию от других признаков и использует оценку
для замены значений. IterativeImputer фактически итеративно строит модель регрессии, используя подмножества столбцов для прогнозирования отсутствующих значений. 
Создадим для этого новый набор данных, с "урезанным" количеством признаков.

df11=df1.loc[:,['visit','ch','sales']].copy()
df11.isnull().sum()

visit    6
ch       0
sales    0
dtype: int64

Загружаем библиотеку и создаем экземпляр модели

from sklearn.impute import IterativeImputer
imputer=IterativeImputer()

Получаем восстановленные данные

imputer_data = imputer.transform(df11)

полученные данные преобразовываем в DataFrame

df011 = pd.DataFrame(imputer_data,columns = df11.columns)

Добавляем восстановленные данные в наш проверочный набор

df_1['visit_iimp']=df011.loc[random_list,'visit']
df_1

chvisit_actvisit_clfvisit_regvisit_impvisit_iimp
2818196226.0214.000204.968209.444
4358644596.0642.000601.604610.654
723220237.0219.667222.417280.110
268167200.0209.00092.579112.619
2333413433.0434.333430.058399.189
523258237.0219.667245.493307.926

И оценим точность

df_1.visit_iimp.sum()/df_1.visit_act.sum()

1.012

В этом варианте получили самую низкую ошибку прогноза 1,2%. Этот метод и выберем для дальнейшей работы. Пройдемся еще раз по алгоритму :

На входе имеем набор данных - дневные показатели торговли за определенный
 период

df=pd.read_excel('data/sh001.xlsx')
df.head()

revvisitchsaleslchpriceconv
022579541325883.5202565.8520.061
1612489678641882.9383257.9200.094
2609786856631792.8413406.6260.074
326659638131782.5163417.8970.081
427703032128712.5363901.8310.087

Проверяем на наличие пропущенных значений

df.isnull().sum()

rev      0
visit    2
ch       0
sales    0
lch      0
price    0
conv     0

Определили два пропуска, теперь выявим выбросы с помощью  расстояние Махаланобиса

Отмасштабируем данные

scaler = StandardScaler()
df_scaled = scaler.fit_transform(df.loc[:,['visit','conv']])
df_scaled = pd.DataFrame(df_scaled,columns=['visit','conv'])
df_scaled.head()

Рассчитаем расстояние Махаланобиса и добавим его в наш набор


mean = df_scaled.mean()
cov = df_scaled.cov()

inv_cov = np.linalg.inv(cov)
distances = []
for _, x in df_scaled.iterrows():
    d = mahalanobis(x, mean, inv_cov)
    distances.append(d)
df['mahalanobis_distances'] = distances
df.loc[:,['visit','conv','mahalanobis_distances']].head(10)

visitconvmahalanobis_distances
0413.00.0610.398
1678.00.0940.357
2856.00.0740.619
3381.00.0810.318
4321.00.0870.351
52556.00.0093.114
6237.00.0970.419
724.00.9584.070
8379.00.0920.267
9596.00.0940.264


Выделим выбросы с помощью IQR-метода.

Q1 = df['mahalanobis_distances'].quantile(0.25)
Q3 = df['mahalanobis_distances'].quantile(0.75)
IQR = Q3 - Q1

df.loc[(df['mahalanobis_distances'] < (Q1 - 1.5 *IQR)) |
(df['mahalanobis_distances'] > (Q3 + 1.5 * IQR))]

revvisitchsaleslchpriceconvmahalanobis_distances
51958202556.023954.1302061.2630.0093.114
717474524.023753.2612329.9330.9584.070
233648484125.0331133.4243228.7430.0085.481
26559271143.08263.2502151.0380.0071.093
2815891721.018482.6673310.7710.8573.562
4352184964.0581462.5173574.3080.9063.814

Обозначим выбросы как пропущенные значения

df.loc[(df['mahalanobis_distances'] < (Q1 - 1.5 * IQR))
|(df['mahalanobis_distances'] > (Q3 + 1.5 * IQR)),'visit']=np.nan

Удалим ненужную колонку и посмотрим на количество пропусков

df.drop(['mahalanobis_distances'], axis = 1,inplace=True)
df.isnull().sum()

rev      0
visit    8
ch       0
sales    0
lch      0
price    0
conv     0
dtype: int64

Сохраним индексы строк с пропусками

ind_nan=df[df['visit'].isnull()].index
ind_nan

Int64Index([5, 7, 15, 23, 26, 28, 39, 43], dtype='int64')

Заполним пропущенные значения с помощью класса IterativeImputer библиотеки sklearn, который реализует многомерные алгоритмы восстановления пропущенных значений

imputer=IterativeImputer()
imputer.fit(df)
imputer_data = imputer.transform(df)
df0 = pd.DataFrame(imputer_data,columns = df.columns)
df0.iloc[ind_nan]

revvisitchsaleslchpriceconv
5195820.0243.30023.095.04.1302061.2630.009
7174745.0220.27123.075.03.2612329.9330.958
15310276.0367.91332.095.02.9693266.0630.071
23364848.0427.43233.0113.03.4243228.7430.008
2655927.090.7268.026.03.2502151.0380.007
28158917.0202.84218.048.02.6673310.7710.857
39156564.0199.95014.030.02.1435218.8000.064
43521849.0598.58958.0146.02.5173574.3080.906

Не забудем также пересчитать конверсия с заполненными значениями посетителей

df0['conv']=df0.ch/df0.visit
df0.iloc[ind_nan]

revvisitchsaleslchpriceconv
5195820.0243.30023.095.04.1302061.2630.095
7174745.0220.27123.075.03.2612329.9330.104
15310276.0367.91332.095.02.9693266.0630.087
23364848.0427.43233.0113.03.4243228.7430.077
2655927.090.7268.026.03.2502151.0380.088
28158917.0202.84218.048.02.6673310.7710.089
39156564.0199.95014.030.02.1435218.8000.070
43521849.0598.58958.0146.02.5173574.3080.097



На этом работу с выбросами и пропущенными значениями можно считать законченной. Теперь наши данные готовы для дальнейшего анализа.



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

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