Найти в Дзене
Аналитика данных

Доверительный интервал оценок клиентов

Допустим ваша компания проводит опросы своих клиентов каждый месяц. И в итоге получает среднюю оценку своей работы — MOS (Mean Opinion Score). Наша задача: узнать доверительный интервал полученной средней оценки в каждом месяце. Дано: файлик с оценками клиентов. Колонки: Далее открываем Jupyter notebook, пишем: # Импорт библиотек
import numpy as np
import pandas as pd
import plotly.graph_objects as go # Читаем данные
df = pd.read_excel('mos_data.xlsx')
df.info()
# Удаляем ячейки без оценки
df = df[df['points'].notna()]
# Переводим дату опроса в месяц и год
df['month'] = df['date'].dt.to_period('M')
# Порядок в колонках
df = df[['date', 'month', 'id_client', 'clients_month', 'points']] df_cnt_pnt = df.groupby(by='month', as_index=False).agg(pnt_cnt=('points', 'count'))
df = df.merge(df_cnt_pnt, how='left', on='month') df_std = df.groupby(by='month', as_index=False).agg(pnt_std=('points', 'std'))
df = df.merge(df_std, how='left', on='month') df_avg = df.groupby(by='month', as_index=Fa
Оглавление

Описание

Допустим ваша компания проводит опросы своих клиентов каждый месяц. И в итоге получает среднюю оценку своей работы — MOS (Mean Opinion Score).

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

Дано: файлик с оценками клиентов. Колонки:

  • 'date' — дата оценки клиента,
  • 'clients_month' — количество всех клиентов за месяц,
  • 'id_client' — идентификатор клиента,
  • 'points' — проставленный балл клиента.

Далее открываем Jupyter notebook, пишем:

# Импорт библиотек
import numpy as np
import pandas as pd
import plotly.graph_objects as go

1. Исходные данные

# Читаем данные
df = pd.read_excel('mos_data.xlsx')
df.info()

# Удаляем ячейки без оценки
df = df[df['points'].notna()]

# Переводим дату опроса в месяц и год
df['month'] = df['date'].dt.to_period('M')

# Порядок в колонках
df = df[['date', 'month', 'id_client', 'clients_month', 'points']]

2. Количество оценок в месяце опроса

df_cnt_pnt = df.groupby(by='month', as_index=False).agg(pnt_cnt=('points', 'count'))
df = df.merge(df_cnt_pnt, how='left', on='month')

3. Стандартное отклонение (STD или σ) 'points' в текущем месяце

df_std = df.groupby(by='month', as_index=False).agg(pnt_std=('points', 'std'))
df = df.merge(df_std, how='left', on='month')

4. Среднее 'points' в текущем месяце

df_avg = df.groupby(by='month', as_index=False).agg(pnt_avg=('points', 'mean'))
df = df.merge(df_avg, how='left', on='month')

5. Стандартная ошибка (SE) 'points' в текущем месяце

Стандартная ошибка среднего с поправкой на конечную популяцию вычисляется по формуле:

Формула вычисления стандартная ошибка среднего с поправкой на конечную популяцию
Формула вычисления стандартная ошибка среднего с поправкой на конечную популяцию

SE — стандартная ошибка среднего (standard error), показатель, который измеряет, насколько среднее значение выборки может отклоняться от истинного среднего генеральной совокупности.

  • s — стандартное отклонение выборки, показывает, насколько сильно значения в выборке разбросаны относительно её среднего.
  • n — размер выборки, количество наблюдений или ответов в выборке.
  • N — общий размер популяции (генеральной совокупности), то есть общее количество клиентов или единиц, из которых берётся выборка.
# Стандартная ошибка (SE)
df['se'] = (df['pnt_std'] / np.sqrt(df['pnt_cnt']))*(np.sqrt((df['clients_month'] - df['pnt_cnt'])/(df['clients_month'] - 1)))

6. Доверительный интервал 'points' в текущем месяце

Число z (z-оценка) — это значение стандартизованной нормально распределённой случайной величины, соответствующее выбранному уровню доверительной вероятности (95%).

value_z = 1.96
df['down_interval'] = df['pnt_avg'] - (value_z * df['se'])
df['up_interval'] = df['pnt_avg'] + (value_z * df['se'])

7. Дисперсия (степень отклонения от среднего) 'points' в текущем месяце

df_variance = df.groupby('month', as_index=False).agg(pnt_var=('points', 'var'))
df = df.merge(df_variance, how='left', on='month')

# Смотрим, что получилось
df.info()

8. Визуализация среднего 'points' и доверительного интервала

fig = go.Figure()
# Линия со средним значением 'points'
fig.add_trace(go.Scatter(
····x=df['date'], y=df['pnt_avg'],
····mode='lines+markers',
····name='Среднее points'))
# Заливка доверительного интервала
fig.add_trace(go.Scatter(
····# сначала по основному порядку
····x=np.concatenate([df['date'], df['date'][::-1]]),
····# потом в обратном
····y=np.concatenate([df['up_interval'], df['down_interval'][::-1]]),
····fill='toself',
····fillcolor='rgba(0,100,80,0.2)',
····line=dict(color='rgba(255,255,255,0)'),
····hoverinfo='skip', showlegend=True,
····name='Доверительный интервал 95%'))
fig.update_layout(
····title='points с 95% доверительным интервалом по месяцам',
····xaxis_title='Месяц', yaxis_title='points',
····template='plotly_white',
····height=700, width=1050)

fig.show()
Визуализация среднего 'points' и доверительного интервала
Визуализация среднего 'points' и доверительного интервала

9. Допущенная ошибка 'points'

value_z = 1.96 # для 95% доверия
def error_from_sample_size(N, s, n, z=value_z):
····# Обработка NaN и нулевых значений s и n
····if np.isnan(s) or s == 0 or n == 0 or np.isnan(n):
········return np.nan
····# Вычисление ошибки по формуле
····E = z * s * np.sqrt((N - n) / (n * (N - 1)))
····return E
# Добавим столбец 'sample_error' с ошибкой для каждой строки в df
df['sample_error'] = df.apply(
····lambda row: error_from_sample_size(
········N=row['clients_month'],
········s=row['pnt_std'],
········n=row['pnt_cnt']),
····axis=1)
# Смотрим что получилось
display(df[['month', 'clients_month', 'pnt_std', 'pnt_cnt', 'sample_error']].drop_duplicates(ignore_index=True))
-4

10. Необходимый размер выборки

Размер выборки рассчитывается по формуле:

-5
  • n — необходимый размер выборки;
  • N — общий размер генеральной совокупности (общее число клиентов для данного месяца)
  • z — z-значение, соответствующее заданному уровню доверия (например, для 95% доверия z ≈ 1.96)
  • s — стандартное отклонение оценок в популяции (можно рассчитать по данным выборки)
  • E — максимально допустимая ошибка выборки (то есть допустимая погрешность оценки среднего значения, например, 1 означает ошибку ±0.1)
# Константы
confidence_level = 0.95
alpha = 1 - confidence_level
value_z = 1.96 # для 95% доверия.
# Либо считаем: value_z = stats.norm.ppf(1 - alpha / 2)
# Максимально допустимая ошибка выборки
E = 0.1
def required_sample_size(N, s, z=value_z, E=E):
····numerator = N * (z**2) * (s**2)
····denominator = (E**2) * (N - 1) + (z**2) * (s**2)
····n = numerator / denominator
····return np.ceil(n) # Округляем в большую сторону
# Добавим новый столбец в df с расчетом необходимого размера выборки
df['sample_size'] = df.apply(
····lambda row: required_sample_size(
········N=row['clients_month'],
········s=row['pnt_std'] if not np.isnan(row['pnt_std']) else 1.0 # подставить 1 при отсутствии std),
····axis=1)
display(df[['month', 'clients_month', 'sample_error', 'sample_size', 'pnt_cnt']].drop_duplicates(ignore_index=True))
Таблица с необходимым размером выборки для E±0.1
Таблица с необходимым размером выборки для E±0.1
# Округление до сотых долей
df['se'] = round(df['se'], 2)
df['down_interval'] = round(df['down_interval'], 2)
df['pnt_std'] = round(df['pnt_std'], 2)
df['pnt_avg'] = round(df['pnt_avg'], 2)
df['down_interval'] = round(df['down_interval'], 2)
df['up_interval'] = round(df['up_interval'], 2)
df['pnt_var'] = round(df['pnt_var'], 2)
df['sample_error'] = round(df['sample_error'], 2)

# Берём для показа только нужные колонки
df_show = df[['month', 'pnt_cnt', 'sample_error', 'sample_size', 'clients_month', 'pnt_std', 'pnt_avg', 'se', 'down_interval', 'up_interval', 'pnt_var']]

df_show = df_show.drop_duplicates(ignore_index=True)
display(df_show)
Итоговая таблица
Итоговая таблица
  • month — месяц опроса.
  • pnt_cnt — количество полученных ответов в месяце опроса.
  • sample_error — предел допустимой неопределенности (точности) с вероятностью 95%.
  • sample_size — рекомендованная выборка по клиентам с точностью 'points' ±0.1.
  • clients_month — всего клиентов в месяце опроса.
  • pnt_std — стандартное отклонение 'points' по выборке в месяце опроса.
  • pnt_avg — среднее значение 'points' по выборке в месяце опроса.
  • se — стандартная ошибка, отклонение среднего 'points' от истинного среднего всей совокупности в месяце опроса.
  • down_interval — нижнее значение диапазона, в котором с 95% вероятностью находится истинное значение измеряемого параметра.
  • up_interval — верхнее значение диапазона, в котором с 95% вероятностью находится истинное значение измеряемого параметра.
  • pnt_var — дисперсия, это показатель степени разброса данных вокруг их среднего значения в месяце опроса.

Итоги

  • С помощью данного кода можно узнать необходимый размер выборки для заданной точности исследования.
  • Можно рассчитать доверительный интервал (ДИ) уже проведённого исследования и узнать погрешность для данной выборки.
  • Также можно визуализировать и наглядно показать результаты исследования с помощью библиотеки plotly express.