value_counts отвечает на вопрос «сколько раз встречается каждое значение одного столбца». Когда столбцов
два — «сколько покупок сделано в каждом городе через каждый канал» — нужна таблица: значения одного столбца
в строках, другого — в столбцах, на пересечении — количество. Её называют таблицей частот или
таблицей сопряжённости.
crosstab
Функции pd.crosstab передают два столбца: первый ляжет в строки, второй — в столбцы.
import pandas as pd
orders = pd.read_csv("data/shop_orders.csv")
pd.crosstab(orders["city"], orders["channel"])| channel | маркетплейс | приложение | сайт |
|---|---|---|---|
| city | |||
| Екатеринбург | 64 | 99 | 149 |
| Казань | 87 | 85 | 169 |
| Москва | 239 | 260 | 449 |
| Новосибирск | 68 | 82 | 108 |
| Санкт-Петербург | 169 | 158 | 262 |
То же самое сделала бы pivot_table с aggfunc="count", но crosstab короче: ей не нужен столбец
со значениями — она считает сами строки. Итоги добавляют знакомые margins и margins_name:
pd.crosstab(orders["city"], orders["channel"], margins=True, margins_name="Всего")| channel | маркетплейс | приложение | сайт | Всего |
|---|---|---|---|---|
| city | ||||
| Екатеринбург | 64 | 99 | 149 | 312 |
| Казань | 87 | 85 | 169 | 341 |
| Москва | 239 | 260 | 449 | 948 |
| Новосибирск | 68 | 82 | 108 | 258 |
| Санкт-Петербург | 169 | 158 | 262 | 589 |
| Всего | 627 | 684 | 1137 | 2448 |
Сохраните в cat_channel таблицу частот: категории в строках, каналы в столбцах, без итогов. В tea_app
сохраните число строк с чаем, купленным через приложение.
pd.crosstab(orders["category"], orders["channel"]).cat_channel.loc["Чай", "приложение"].Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
cat_channel = ...
tea_app = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
cat_channel = pd.crosstab(orders["category"], orders["channel"])
tea_app = cat_channel.loc["Чай", "приложение"]Доли: по строкам, по столбцам, от всего
Количества трудно сравнивать: в Москве всего больше. Параметр normalize превращает их в доли — и у него три
режима. От выбора режима зависит, на какой вопрос отвечает таблица.
normalize="index" — доли внутри строки: каждая строка в сумме даёт 1.
pd.crosstab(orders["city"], orders["channel"], normalize="index").round(3)| channel | маркетплейс | приложение | сайт |
|---|---|---|---|
| city | |||
| Екатеринбург | 0.205 | 0.317 | 0.478 |
| Казань | 0.255 | 0.249 | 0.496 |
| Москва | 0.252 | 0.274 | 0.474 |
| Новосибирск | 0.264 | 0.318 | 0.419 |
| Санкт-Петербург | 0.287 | 0.268 | 0.445 |
Читается по строке: «в Казани 49.6 % покупок сделано на сайте».
normalize="columns" — доли внутри столбца: в сумме 1 даёт каждый столбец.
pd.crosstab(orders["city"], orders["channel"], normalize="columns").round(3)| channel | маркетплейс | приложение | сайт |
|---|---|---|---|
| city | |||
| Екатеринбург | 0.102 | 0.145 | 0.131 |
| Казань | 0.139 | 0.124 | 0.149 |
| Москва | 0.381 | 0.380 | 0.395 |
| Новосибирск | 0.108 | 0.120 | 0.095 |
| Санкт-Петербург | 0.270 | 0.231 | 0.230 |
Читается по столбцу: «14.9 % покупок на сайте приходится на Казань».
normalize="all" — доли от всей таблицы: единицу дают все ячейки вместе.
pd.crosstab(orders["city"], orders["channel"], normalize="all").round(3)| channel | маркетплейс | приложение | сайт |
|---|---|---|---|
| city | |||
| Екатеринбург | 0.026 | 0.040 | 0.061 |
| Казань | 0.036 | 0.035 | 0.069 |
| Москва | 0.098 | 0.106 | 0.183 |
| Новосибирск | 0.028 | 0.033 | 0.044 |
| Санкт-Петербург | 0.069 | 0.065 | 0.107 |
«6.9 % всех покупок — Казань, сайт».
Три числа для одной и той же ячейки — 0.496, 0.149 и 0.069 — и все верные. Различается знаменатель: покупки Казани, покупки сайта, все покупки.
t = pd.DataFrame({"a": ["x", "x", "y", "y"], "b": ["p", "q", "p", "p"]})
print(pd.crosstab(t["a"], t["b"], normalize="index").loc["x", "p"])Что выведет этот код?
normalize="index" считает доли внутри строки. В строке x две записи: одна с p, одна с q — доля p равна 0.5. Вариант 0.33 — доля внутри столбца p (одна из трёх), 0.25 — доля от всей таблицы (одна из четырёх).Сохраните в city_mix доли категорий в покупках каждого города: города в строках, категории в столбцах,
каждая строка в сумме даёт 1; округление до трёх знаков. В tea_city сохраните город, где доля чая самая
высокая.
pd.crosstab(orders["city"], orders["category"], normalize="index").round(3).city_mix["Чай"].idxmax().Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
city_mix = ...
tea_city = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
city_mix = pd.crosstab(orders["city"], orders["category"], normalize="index").round(3)
tea_city = city_mix["Чай"].idxmax()Связь двух признаков
Таблица частот — самый простой способ увидеть, связаны ли два признака. Возьмём доставку: зависит ли оценка от скорости? Оставим заказы, где известны и срок, и оценка, и разобьём срок на три группы:
delivery = pd.read_csv("data/delivery_h1.csv").dropna()
delivery["speed"] = pd.cut(delivery["days"], bins=[0, 3, 5, 14], labels=["быстро", "нормально", "долго"])
delivery["rating"] = delivery["rating"].astype("int64")
pd.crosstab(delivery["speed"], delivery["rating"])| rating | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| speed | |||||
| быстро | 0 | 3 | 31 | 92 | 76 |
| нормально | 0 | 10 | 67 | 102 | 20 |
| долго | 7 | 33 | 47 | 17 | 0 |
Количества в строках разные, поэтому сравнивать нужно доли внутри строки:
pd.crosstab(delivery["speed"], delivery["rating"], normalize="index").round(2)| rating | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| speed | |||||
| быстро | 0.00 | 0.01 | 0.15 | 0.46 | 0.38 |
| нормально | 0.00 | 0.05 | 0.34 | 0.51 | 0.10 |
| долго | 0.07 | 0.32 | 0.45 | 0.16 | 0.00 |
Картина ясная: при быстрой доставке больше 80 % оценок — четвёрки и пятёрки, при долгой пятёрок нет вовсе, а три четверти оценок — двойки и тройки.
Добавьте в delivery столбец happy: True, если оценка 4 или 5. Сохраните в happy_table таблицу долей:
скорость в строках, happy в столбцах, доли внутри строки (не округляйте их). В fast_happy и slow_happy сохраните долю
довольных при быстрой и при долгой доставке.
pd.crosstab(delivery["speed"], delivery["happy"], normalize="index").False и True без кавычек: happy_table.loc["быстро", True].Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
# добавьте в delivery столбец happy
happy_table = ...
fast_happy = ...
slow_happy = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
delivery["happy"] = delivery["rating"] >= 4
happy_table = pd.crosstab(delivery["speed"], delivery["happy"], normalize="index")
fast_happy = happy_table.loc["быстро", True]
slow_happy = happy_table.loc["долго", True]83 % довольных при быстрой доставке и 16 % при долгой. Оценка сильно связана со скоростью доставки.
Итоги
pd.crosstab(столбец для строк, столбец для столбцов)— таблица частот двух столбцов.normalize="index"— доли внутри строки,"columns"— внутри столбца,"all"— от всей таблицы.- Читая долю, уточняйте знаменатель: что в сумме даёт единицу.
margins=Trueдобавляет итоги, как уpivot_table.