Таблицу с продажами по месяцам можно записать двумя способами. В широком формате каждый месяц — отдельный столбец. В длинном — месяц записан значением в столбце, и на каждую пару «город, месяц» приходится своя строка.
| city | jan | feb |
|---|---|---|
| Омск | 120 | 130 |
| Тула | 95 | 105 |
| city | month | cups |
|---|---|---|
| Омск | jan | 120 |
| Тула | jan | 95 |
| Омск | feb | 130 |
| Тула | feb | 105 |
Данные одни и те же, форма разная. Широкая таблица компактна, её удобно читать. Длинная удобна для расчётов:
по столбцу month можно группировать и фильтровать — а по названиям столбцов нельзя. Поэтому данные
из отчётов и электронных таблиц, которые обычно приходят широкими, перед анализом часто «расплавляют».
melt: из широкой в длинную
import pandas as pd
wide = pd.DataFrame({
"city": ["Омск", "Тула"],
"jan": [120, 95],
"feb": [130, 105],
})
wide| city | jan | feb | |
|---|---|---|---|
| 0 | Омск | 120 | 130 |
| 1 | Тула | 95 | 105 |
Метод melt («расплавить») превращает столбцы в строки:
long = wide.melt(id_vars="city", var_name="month", value_name="cups")
long| city | month | cups | |
|---|---|---|---|
| 0 | Омск | jan | 120 |
| 1 | Тула | jan | 95 |
| 2 | Омск | feb | 130 |
| 3 | Тула | feb | 105 |
id_vars— столбцы, которые остаются как есть: они определяют наблюдение (здесь город);- все остальные столбцы «плавятся»: их названия уходят в один новый столбец, значения — в другой;
var_nameиvalue_name— названия этих двух новых столбцов.
Было 2 строки × 2 столбца с числами — стало 4 строки: по одной на каждую ячейку.
Если названия не задать, столбцы получат имена variable и value — лучше сразу дать им осмысленные:
wide.melt(id_vars="city")| city | variable | value | |
|---|---|---|---|
| 0 | Омск | jan | 120 |
| 1 | Тула | jan | 95 |
| 2 | Омск | feb | 130 |
| 3 | Тула | feb | 105 |
С длинной таблицей работают все знакомые приёмы:
print(long.groupby("month")["cups"].sum())
print(long[long["cups"] > 100])month feb 235 jan 215 Name: cups, dtype: int64 city month cups 0 Омск jan 120 2 Омск feb 130 3 Тула feb 105
Теперь настоящие данные — баллы 30 студентов за первый семестр, по столбцу на предмет:
scores = pd.read_csv("data/scores.csv")
scores.head(3)| student_id | math | physics | programming | history | english | |
|---|---|---|---|---|---|---|
| 0 | S01 | 68 | 49 | 68 | 82 | 81 |
| 1 | S02 | 70 | 66 | 68 | 77 | 87 |
| 2 | S03 | 61 | 58 | 72 | 60 | 70 |
Переведите таблицу scores в длинный формат и сохраните в marks: столбцы student_id, subject (название
предмета) и score (балл). В n_marks сохраните число строк.
scores.melt(id_vars="student_id", var_name="subject", value_name="score").Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
marks = ...
n_marks = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
marks = scores.melt(id_vars="student_id", var_name="subject", value_name="score")
n_marks = len(marks)По таблице marks сохраните: в subject_mean — средний балл по каждому предмету, округлённый до одного
знака; в weak — оценки ниже 50 баллов, отсортированные от самой низкой.
marks.groupby("subject")["score"].mean().round(1).marks[marks["score"] < 50].sort_values("score").Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
subject_mean = ...
weak = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
subject_mean = marks.groupby("subject")["score"].mean().round(1)
weak = marks[marks["score"] < 50].sort_values("score")В широкой таблице на вопрос «у кого и по какому предмету меньше 50» пришлось бы отвечать отдельно для каждого столбца. В длинной это один фильтр.
pivot: из длинной в широкую
Обратное превращение делает pivot. Ему нужно сказать, что будет в строках, что в столбцах и откуда
брать значения:
long.pivot(index="city", columns="month", values="cups")| month | feb | jan |
|---|---|---|
| city | ||
| Омск | 130 | 120 |
| Тула | 105 | 95 |
Города оказались в индексе, а над названиями столбцов появилась подпись month. Сами столбцы встали
по алфавиту: feb раньше jan. Чтобы получить совсем
обычную таблицу, индекс превращают в столбец, а подпись убирают:
back = long.pivot(index="city", columns="month", values="cups").reset_index()
back.columns.name = None
back| city | feb | jan | |
|---|---|---|---|
| 0 | Омск | 130 | 120 |
| 1 | Тула | 105 | 95 |
pivot и pivot_table
Названия похожи, и параметры те же — index, columns, values. Разница одна, но важная: pivot только
переставляет значения и ничего не считает. Поэтому каждой паре «строка, столбец» должно соответствовать
ровно одно значение. В полной ведомости у каждого студента по каждому предмету две оценки — за два семестра:
grades = pd.read_csv("data/grades.csv")
grades.head(4)| student | group | subject | term | score | |
|---|---|---|---|---|---|
| 0 | Анна А. | ИТ-21 | Математика | 1 | 68 |
| 1 | Анна А. | ИТ-21 | Математика | 2 | 71 |
| 2 | Анна А. | ИТ-21 | Физика | 1 | 49 |
| 3 | Анна А. | ИТ-21 | Физика | 2 | 60 |
grades.pivot(index="student", columns="subject", values="score")ValueError: Index contains duplicate entries, cannot reshape
Правило простое: переставить — pivot, посчитать — pivot_table.
t = pd.DataFrame({"who": ["a", "a", "b", "b"], "what": ["x", "y", "x", "y"], "n": [1, 2, 3, 4]})
print(t.pivot(index="who", columns="what", values="n").shape)Что выведет этот код?
who (a и b), в столбцах — разные значения what (x и y). Таблица 2 × 2; столбец who ушёл в индекс и в число столбцов не входит.Сохраните в first_term строки таблицы grades за первый семестр (term равен 1). Переведите их в широкий
формат и сохраните в sheet: студенты в строках, предметы в столбцах, в ячейках — баллы.
grades[grades["term"] == 1].first_term.pivot(index="student", columns="subject", values="score").Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
first_term = ...
sheet = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
first_term = grades[grades["term"] == 1]
sheet = first_term.pivot(index="student", columns="subject", values="score")Здесь значения нужно посчитать — значит, pivot_table. Сохраните в terms таблицу: студенты в строках,
семестры в столбцах, в ячейках — средний балл студента за семестр. Добавьте столбец growth — прирост
среднего балла от первого семестра ко второму. В best_growth сохраните имя студента с наибольшим приростом.
grades.pivot_table(values="score", index="student", columns="term", aggfunc="mean").terms[2] - terms[1].Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
terms = ...
# добавьте в terms столбец growth
best_growth = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
terms = grades.pivot_table(values="score", index="student", columns="term", aggfunc="mean")
terms["growth"] = terms[2] - terms[1]
best_growth = terms["growth"].idxmax()Итоги
- Широкий формат — значения признака в названиях столбцов; длинный — в отдельном столбце.
melt(id_vars=..., var_name=..., value_name=...)— из широкого в длинный.pivot(index=..., columns=..., values=...)— из длинного в широкий; требует одно значение на ячейку.pivot_tableделает то же, но сворачивает несколько значений функциейaggfunc.- Для расчётов удобнее длинная таблица, для показа — широкая.