В этой заметке я хочу разобрать несколько «парадоксов» в данных, о которых полезно знать как начинающему аналитику данных, так и любому человеку, кто не хочет быть введенным в заблуждение некорректными статистическими выводами.
За рассматриваемыми примерами не кроется сложной математики помимо базовых свойств выборки (таких, как среднее арифметическое и дисперсия), зато такие кейсы могут встретиться и на собеседовании, и в жизни.
«Новозеландцы, эмигрирующие в Австралию, повышают IQ обеих стран»
Эту цитату приписывают сэру Роберту Малдуну, премьер-министру Новой Зеландии. А может ли такое быть с точки зрения математики?
Итак, феноменом Уилла Роджерса называют кажущийся парадокс, заключающийся в том, что перемещение (численного) элемента из одного множества в другое может увеличить среднее значение обоих множеств. Начнем с очевидного примера: рассмотрим множества и . У среднее арифметическое элементов составляет 1, а у среднее арифметическое элементов составляет 550. Если взять число 100 и переместить его из второго множества в первое, то получатся множества со средним арифметическим 25.75 > 1 и со средним арифметическим 1000 > 550.
Тем не менее, совсем не обязательно, чтобы множества располагались так далеко друг от друга на числовой прямой, и не обязательно, чтобы перемещаемый элемент был минимальным во втором множестве и/или стал максимальным в первом.
Например, пусть . Среднее элементов множества составляет 5, а элементов множества — 7. Теперь переместим 6 из второго множества в первое, и получим множества со средним значением 5.1(6) и со средним значением 7.(3).
На самом деле такое увеличение обоих средних происходит при следующих условиях:
перемещаемый элемент строго меньше среднего значения элементов второго множества до его удаления;
перемещаемый элемент строго больше среднего значения первого множества до его добавления;
как следствие, изначально среднее значение элементов первого множества (куда перемещают) должно быть строго меньше, чем у второго (откуда перемещают).
Вывод: подобная ситуация может встретиться в различных областях. Например, при улучшении диагностики заболеваний на ранней стадии ожидаемая продолжительность жизни среди здоровых и среди больных может увеличиться в одной и той же выборке, если некоторые из «здоровых» (а на самом деле плохо обследованных) перейдут в категорию «больных», и многие из них успешно вылечатся благодаря раннему выявлению болезни.
И да, внимательный читатель скажет, что никакого парадокса тут нет, и будет абсолютно прав. Это явление звучит немного контринтуитивно на словах, но примеры выше делают его очевидным.
Парадокс Симпсона
Представьте, что вы работаете в фирме, которая продает два типа продуктов, допустим, сепульки и бирюльки. (Допустим для простоты модели, что сепульки и бирюльки всегда учитываются в чеках по отдельности.) Утром к вам в кабинет забегает радостный стажёр-аналитик и сообщает, что за последний месяц средний чек в категории сепулек вырос на 5%, средний чек в категории бирюлек — на 7%. Общий средний чек он не проверял, но логично предположить, что он тоже вырос на некоторую величину в интервале между 5 и 7 процентов. Что могло пойти не так?
Февраль |
Март |
|
Сепульки, средний чек |
200 рублей |
210 рублей (+5%) |
Бирюльки, средний чек |
100 рублей |
107 рублей (+7%) |
Вы открываете систему аналитики, смотрите подробнее и понимаете, что общий средний чек фирмы уменьшился, хотя цены на товары не менялись (то есть средний чек пропорционален количеству товаров в чеке), и скидок не было. Добавим в нашу оптимистичную таблицу дополнительные данные, без которых нельзя вычислить общий средний чек:
Февраль |
Март |
|
Сепульки, средний чек |
200 рублей |
210 рублей (+5%) |
Бирюльки, средний чек |
100 рублей |
107 рублей (+7%) |
Доля покупок сепулек |
50% |
35% |
Доля покупок бирюлек |
50% |
65% |
Общий средний чек |
150 рублей |
145,05 рублей (-4,63%) |
Средний чек в марте вычислен так: .
Вывод: не стоит делать выводы по отдельным показателям, если ключевую метрику задают не только они (в данном случае стажёр не учел, что в среднем чеке по всем покупкам категории фактически взвешиваются пропорционально доле покупок в них). Понимание парадокса Симпсона может уберечь от неверных выводов в том числе и при AB-тестировании.
Квартет Энскомба
А теперь история о том, почему визуализация данных бывает буквально необходима. Представьте, что вам рассказывают о четырёх множествах точек , про которые известно следующее: среднее значение переменной , дисперсия переменной , среднее значение переменной , дисперсия переменной и корреляция между переменными у них совпадают* для каждого из множеств. А также совпадают* коэффициенты, задающие прямую линейной регрессии.
*с точностью до двух-трёх знаков после запятой
Казалось бы, выборки должны быть очень похожи между собой. Но здесь подвох кроется в том, что по умолчанию многие представляют себе что-то вроде нормального распределения (или другой из основных типов), хотя об этом изначально ничего не сказано. Воспользуемся датасетом из библиотеки seaborn и визуализируем эти данные:
import seaborn as sns
sns.set_theme(style="ticks")
# Load the example dataset for Anscombe's quartet
df = sns.load_dataset("anscombe")
# Show the results of a linear regression within each dataset
sns.lmplot(
data=df, x="x", y="y", col="dataset", hue="dataset",
col_wrap=2, palette="muted", ci=None,
height=4, scatter_kws={"s": 50, "alpha": 1}
)
Посчитаем характеристики этих наборов точек:
mean_1 = df[df["dataset"] == "I"].mean()
mean_2 = df[df["dataset"] == "II"].mean()
mean_3 = df[df["dataset"] == "III"].mean()
mean_4 = df[df["dataset"] == "IV"].mean()
mean_1, mean_2, mean_3, mean_4
Этот код для подсчета средних по координатам и выводит следующий результат:
(x 9.000000
y 7.500909
dtype: float64,
x 9.000000
y 7.500909
dtype: float64,
x 9.0
y 7.5
dtype: float64,
x 9.000000
y 7.500909
dtype: float64)
Код для остальных характеристик кладу под кат, чтобы не делать статью слишком длинной:
Hidden text
Дисперсия
std_1 = df[df["dataset"] == "I"].std()
std_2 = df[df["dataset"] == "II"].std()
std_3 = df[df["dataset"] == "III"].std()
std_4 = df[df["dataset"] == "IV"].std()
std_1, std_2, std_3, std_4
(x 3.316625
y 2.031568
dtype: float64,
x 3.316625
y 2.031657
dtype: float64,
x 3.316625
y 2.030424
dtype: float64,
x 3.316625
y 2.030579
dtype: float64)
Коэффициент корреляции и
import numpy as np
corr_1 = np.corrcoef(df[df["dataset"] == "I"]["x"], df[df["dataset"] == "I"]["y"])[0, 1]
corr_2 = np.corrcoef(df[df["dataset"] == "II"]["x"], df[df["dataset"] == "II"]["y"])[0, 1]
corr_3 = np.corrcoef(df[df["dataset"] == "III"]["x"], df[df["dataset"] == "III"]["y"])[0, 1]
corr_4 = np.corrcoef(df[df["dataset"] == "IV"]["x"], df[df["dataset"] == "IV"]["y"])[0, 1]
corr_1, corr_2, corr_3, corr_4
(0.81642051634484, 0.8162365060002428, 0.8162867394895984, 0.8165214368885028)
Прямая линейной регрессии
k1, b1 = np.polyfit(df[df["dataset"] == "I"]["x"], df[df["dataset"] == "I"]["y"], 1)
k2, b2 = np.polyfit(df[df["dataset"] == "II"]["x"], df[df["dataset"] == "II"]["y"], 1)
k3, b3 = np.polyfit(df[df["dataset"] == "III"]["x"], df[df["dataset"] == "III"]["y"], 1)
k4, b4 = np.polyfit(df[df["dataset"] == "IV"]["x"], df[df["dataset"] == "IV"]["y"], 1)
k1, k2, k3, k4, b1, b2, b3, b4
(0.5000909090909095,
0.5000000000000002,
0.499727272727273,
0.4999090909090908,
3.0000909090909076,
3.0009090909090905,
3.0024545454545457,
3.0017272727272712)
Вывод: иногда и взгляда на все основные статистики не будет достаточно, и тогда необходима визуализация. При этом зачастую даже самого простого scatter plot (графика с точками на плоскости) хватит, чтобы заметить различия в выборках.
The Datasaurus Dozen
Квартет Энскомба наглядно демонстрирует, почему визуализация данных важна, но подобрать 4 множества по 11 точек — ничего особенного, не так ли? Более интересным примером может быть «датазаврова дюжина», состоящая из 13 наборов точек, которые складываются в различные фигуры.
Подход авторов заключался в том, что изначально был взят «динозавр» из точек, а затем данные итеративно незначительно менялись так, чтобы значения средних, дисперсий и коэффициента корреляции оставались такими же с точностью до двух знаков после запятой, до тех пор, пока не получалась очередная фигура (овал, звезда и так далее). Для каждой из полученных фигур потребовалось около 200 тысяч итераций алгоритма.
Псевдокод алгоритма для генерации таких наборов точек имеет следующий вид:
current_ds ← initial_ds
for x iterations, do:
test_ds ← perturb(current_ds, temp)
if similar_enough(test_ds, initial_ds):
current_ds ← test_ds
function perturb(ds, temp):
loop:
test ← move_random_points(ds)
if fit(test) > fit(ds) or temp > random():
return test
initial_ds
— изначальный набор точекcurrent_ds
— набор точек на данный моментfit()
— функция, проверяющая, насколько набор точек на данный момент напоминает нужную фигуруsimilar_enough()
— функция, проверяющая, что значения статистик достаточно близкиmove_random_points()
— функция, случайным образом сдвигающая точки
Заключение
Все эти примеры подводят нас к важности применения разведочного анализа данных (exploratory data analysis) — это выражение обозначает подход к работе с данными через анализ всех основных показателей и (практически всегда) их визуализацию. Критический взгляд на выводы, сделанные по паре показателей — важная черта как аналитика данных, так и любого человека, который не хочет позволить себя обмануть.
Спасибо, что дочитали! Буду рада дополнениям и вопросам в комментариях.
ksbes
Главная загадка любого статистика: "что, блин, вообще эти люди хотят посчитать на самом деле?"
NechkaP Автор
Вот-вот) Так же как и загадка любого статистика, который видит в жизни чей-то вывод из данных: "что, блин, вообще эти люди посчитали на самом деле, чтобы сделать очередной заголовок про среднюю
температурузарплату по больнице 100 тысяч? Медиану, хитрое среднее с весами?"Вспоминается книга "Как лгать с помощью статистики", но это уже другая история)
Ivan22
Да, верно. Потому что когда знаешь какой результат ты хочешь получить, подогнать нужные данные под него -раз плюнуть