Домашнее задание. NumPy, pandas, CSV и JSON¶
Задание к первой лекции и практике: повторяем работу с массивами и таблицами, читаем данные из CSV и JSON и считаем метрики качества из лекции.
Как решать
- В каждой задаче уже есть заготовка функции. Замените
raise NotImplementedErrorсвоим решением. - Не меняйте названия функций и их аргументы: решения будут проверяться автоматически.
- Функция должна возвращать ответ, а не печатать его. Входные массивы и таблицы не изменяйте.
- После каждой задачи есть ячейка с проверкой на примерах. Если она печатает «OK», решение верно хотя бы на этих примерах. Скрытые тесты будут другими, поэтому учитывайте все условия.
- В задачах на NumPy старайтесь обходиться без циклов
for: почти всё решается одной-двумя строками.
Баллы
| Часть | Задачи | Баллы |
|---|---|---|
| NumPy | 1–10 | по 0.5, всего 5 |
| pandas, CSV и JSON | 11–14 | по 1, всего 4 |
| Итого | 9 |
Как сдать
Решения сдаются в Яндекс.Контесте.
- Сохраните ноутбук и отправьте весь файл
.ipynbцеликом в задачу «1. NumPy» и тот же файл — в задачу «2. Pandas». Компилятор — (make) yandexdataschool. - Проверка занимает несколько минут. В отчёте видно, какие задачи засчитаны, а для незасчитанных указана причина. Можно сдать часть задач, а потом отправить ноутбук ещё раз.
- При проверке выполняются только импорты и определения функций: остальные ячейки (примеры, проверки,
print) не запускаются, поэтому незаконченная задача или упавшийassertне мешают проверить остальные. Но функция не должна использовать переменные из других ячеек — всё нужное передаётся ей в аргументах. - Задачи 3–8 по математике (векторы и матрицы) решаются на бумаге, ответ вводится прямо в Контесте. Формат ответа описан в условии; на каждую задачу 5 попыток.
Автор: Алексей Чернуха. CC BY 4.0.
import json
import numpy as np
import pandas as pd
print(np.arange(1, 10, 3)) # от 1 до 10 (не включая) с шагом 3
print(np.zeros(4), np.ones(3, dtype=int))
print(np.full(3, 7)) # массив из одинаковых чисел
m = np.arange(12).reshape(3, 4) # матрица 3 × 4
print(m)
print("строка 1: ", m[1])
print("столбец 2: ", m[:, 2])
print("каждая вторая: ", m[0, ::2]) # срез с шагом
print("форма:", m.shape, "| тип:", m.dtype)
[1 4 7] [0. 0. 0. 0.] [1 1 1] [7 7 7] [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] строка 1: [4 5 6 7] столбец 2: [ 2 6 10] каждая вторая: [0 2] форма: (3, 4) | тип: int64
NumPy: операции, маски и агрегаты по осям¶
Арифметика и сравнения работают поэлементно. Массив из True/False (маска) выбирает элементы; True при сложении считается единицей.
a = np.array([4, -1, 7, 2, -5])
print(a * 10, a ** 2, np.abs(a))
print("маска a > 0: ", a > 0)
print("элементы > 0: ", a[a > 0])
print("сколько > 0: ", (a > 0).sum())
print("и / или: ", a[(a > 0) & (a < 5)], a[(a < 0) | (a > 5)])
m = np.array([[1, 2, 3],
[4, 5, 6]])
print("сумма всего: ", m.sum())
print("среднее по столбцам:", m.mean(axis=0)) # axis=0 — сворачиваем строки
print("среднее по строкам: ", m.mean(axis=1)) # axis=1 — сворачиваем столбцы
print("с keepdims:\n", m.mean(axis=1, keepdims=True))
[ 40 -10 70 20 -50] [16 1 49 4 25] [4 1 7 2 5] маска a > 0: [ True False True True False] элементы > 0: [4 7 2] сколько > 0: 3 и / или: [4 2] [-1 7 -5] сумма всего: 21 среднее по столбцам: [2.5 3.5 4.5] среднее по строкам: [2. 5.] с keepdims: [[2.] [5.]]
NumPy: broadcasting¶
Массивы разной формы растягиваются друг под друга: строка прибавляется к каждой строке матрицы, столбец — к каждому столбцу.
m = np.array([[1, 2, 3],
[4, 5, 6]])
print(m + np.array([10, 20, 30])) # строка формы (3,)
print(m + np.array([[100], [200]])) # столбец формы (2, 1)
print(np.arange(3).reshape(-1, 1) + np.arange(4)) # (3, 1) + (4,) -> (3, 4)
[[11 22 33] [14 25 36]] [[101 102 103] [204 205 206]] [[0 1 2 3] [1 2 3 4] [2 3 4 5]]
NumPy: индексы минимума, сортировка, пропуски, выбор по условию¶
a = np.array([30, 10, 20, 10])
print("индекс минимума:", a.argmin(), "| индекс максимума:", a.argmax()) # при равенстве — первый
print("порядок сортировки:", np.argsort(a, kind="stable"))
print("отсортированный: ", a[np.argsort(a, kind="stable")])
print("по списку индексов:", a[[3, 0, 0]])
b = np.array([1.0, np.nan, 3.0])
print("где пропуск:", np.isnan(b))
print("mean / nanmean:", b.mean(), np.nanmean(b))
print("np.where:", np.where(a > 15, a, -1)) # где условие верно — a, иначе -1
print("единичная матрица:\n", np.eye(3, dtype=int))
индекс минимума: 1 | индекс максимума: 0 порядок сортировки: [1 3 2 0] отсортированный: [10 10 20 30] по списку индексов: [10 30 30] где пропуск: [False True False] mean / nanmean: nan 2.0 np.where: [30 -1 20 -1] единичная матрица: [[1 0 0] [0 1 0] [0 0 1]]
pandas: чтение и осмотр таблицы¶
pd.read_csv(path) читает CSV-файл. Для примера соберём небольшую таблицу прямо в коде.
students = pd.DataFrame({
"name": ["Ася", "Боря", "Вика", "Гена", "Даша"],
"grade": [10, 11, None, 10, 11],
"school": ["№1", "№2", "№1", "№1", "№2"],
"score": [85, 92, 78, 64, 99],
})
print(students.dtypes) # grade стал float из-за пропуска
students.head()
name object grade float64 school object score int64 dtype: object
| name | grade | school | score | |
|---|---|---|---|---|
| 0 | Ася | 10.0 | №1 | 85 |
| 1 | Боря | 11.0 | №2 | 92 |
| 2 | Вика | NaN | №1 | 78 |
| 3 | Гена | 10.0 | №1 | 64 |
| 4 | Даша | 11.0 | №2 | 99 |
pandas: фильтрация, пропуски, типы¶
print(students[students["score"] >= 80]["name"].tolist())
print(students[students["score"].between(70, 90)]["name"].tolist()) # границы включаются
print("пропусков:", students["grade"].isna().sum())
known = students.dropna(subset=["grade"]).reset_index(drop=True) # индекс заново 0, 1, 2, …
known["grade"] = known["grade"].astype(int)
known
['Ася', 'Боря', 'Даша'] ['Ася', 'Вика'] пропусков: 1
| name | grade | school | score | |
|---|---|---|---|---|
| 0 | Ася | 10 | №1 | 85 |
| 1 | Боря | 11 | №2 | 92 |
| 2 | Гена | 10 | №1 | 64 |
| 3 | Даша | 11 | №2 | 99 |
pandas: группировка и сортировка¶
В agg можно сразу задать имена новых столбцов: имя=("столбец", "функция").
by_school = students.groupby("school").agg(
students=("name", "count"),
best=("score", "max"),
avg=("score", "mean"),
)
print(by_school, "\n")
# сначала по убыванию school, при равенстве — по возрастанию score
students.sort_values(["school", "score"], ascending=[False, True])
students best avg school №1 3 85 75.666667 №2 2 99 95.500000
| name | grade | school | score | |
|---|---|---|---|---|
| 1 | Боря | 11.0 | №2 | 92 |
| 4 | Даша | 11.0 | №2 | 99 |
| 3 | Гена | 10.0 | №1 | 64 |
| 2 | Вика | NaN | №1 | 78 |
| 0 | Ася | 10.0 | №1 | 85 |
JSON и объединение таблиц¶
Модуль json читает файл в обычные списки и словари. pd.json_normalize разворачивает вложенные списки в таблицу, а pd.merge объединяет таблицы по общему столбцу.
teams = [
{"team": "Альфа", "city": "Минск", "members": [{"name": "Ася", "age": 16}, {"name": "Боря", "age": 17}]},
{"team": "Бета", "city": "Брест", "members": [{"name": "Вика", "age": 15}]},
]
with open("teams.json", "w", encoding="utf-8") as f:
json.dump(teams, f, ensure_ascii=False)
with open("teams.json", encoding="utf-8") as f:
data = json.load(f)
print(type(data), data[0]["team"], data[0]["members"][1]["name"])
members = pd.json_normalize(data, record_path="members", meta=["team"])
print(members, "\n")
pd.merge(members, students[["name", "score"]], on="name")
<class 'list'> Альфа Боря name age team 0 Ася 16 Альфа 1 Боря 17 Альфа 2 Вика 15 Бета
| name | age | team | score | |
|---|---|---|---|---|
| 0 | Ася | 16 | Альфа | 85 |
| 1 | Боря | 17 | Альфа | 92 |
| 2 | Вика | 15 | Бета | 78 |
Часть 1. NumPy¶
Задача 1. Квадраты чётных чисел (0.5 балла)¶
Верните массив квадратов всех чётных чисел от 0 до n включительно, по возрастанию.
even_squares(7) -> [0, 4, 16, 36]
even_squares(0) -> [0]
def even_squares(n: int) -> np.ndarray:
# ваш код
raise NotImplementedError
assert np.array_equal(even_squares(7), [0, 4, 16, 36])
assert np.array_equal(even_squares(8), [0, 4, 16, 36, 64])
assert np.array_equal(even_squares(0), [0])
print("Задача 1: OK")
Задача 2. Шахматная доска (0.5 балла)¶
Верните матрицу n × n из нулей и единиц, раскрашенную как шахматная доска. В левом верхнем углу стоит 0.
checkerboard(3) -> [[0, 1, 0],
[1, 0, 1],
[0, 1, 0]]
def checkerboard(n: int) -> np.ndarray:
# ваш код
raise NotImplementedError
assert np.array_equal(checkerboard(3), [[0, 1, 0], [1, 0, 1], [0, 1, 0]])
assert np.array_equal(checkerboard(2), [[0, 1], [1, 0]])
assert np.array_equal(checkerboard(1), [[0]])
assert checkerboard(8).shape == (8, 8) and checkerboard(8).sum() == 32
print("Задача 2: OK")
Задача 3. Нормировка в отрезок [0, 1] (0.5 балла)¶
Признаки часто приводят к одному масштабу. Отобразите значения массива x в отрезок $[0, 1]$: минимум переходит в 0, максимум в 1:
$$x' = \frac{x - \min x}{\max x - \min x}.$$
Если все элементы равны, верните массив из нулей той же длины.
minmax_normalize([2, 4, 6, 10]) -> [0.0, 0.25, 0.5, 1.0]
minmax_normalize([5, 5, 5]) -> [0.0, 0.0, 0.0]
def minmax_normalize(x: np.ndarray) -> np.ndarray:
# ваш код
raise NotImplementedError
assert np.allclose(minmax_normalize(np.array([2, 4, 6, 10])), [0, 0.25, 0.5, 1])
assert np.allclose(minmax_normalize(np.array([-1.0, 1.0])), [0, 1])
assert np.allclose(minmax_normalize(np.array([5, 5, 5])), [0, 0, 0])
print("Задача 3: OK")
Задача 4. Ближайшее значение (0.5 балла)¶
Найдите в массиве x элемент, ближайший к числу v. Если таких несколько, верните тот, что стоит в массиве раньше.
nearest_value([1, 8, 3, 10], 7) -> 8
nearest_value([4, 6], 5) -> 4
def nearest_value(x: np.ndarray, v: float) -> float:
# ваш код
raise NotImplementedError
assert nearest_value(np.array([1, 8, 3, 10]), 7) == 8
assert nearest_value(np.array([4, 6]), 5) == 4
assert nearest_value(np.array([6, 4]), 5) == 6
assert nearest_value(np.array([0.5, -2.0, 3.0]), -10) == -2.0
print("Задача 4: OK")
Задача 5. Сортировка строк по столбцу (0.5 балла)¶
Дана матрица m: строки — объекты, столбцы — признаки. Отсортируйте строки по возрастанию значения в столбце с номером j. Строки с одинаковым значением должны остаться в исходном порядке.
m = [[3, 10],
[1, 20],
[2, 30]]
sort_rows_by_column(m, 0) -> [[1, 20], [2, 30], [3, 10]]
Подсказка: np.argsort(..., kind="stable").
def sort_rows_by_column(m: np.ndarray, j: int) -> np.ndarray:
# ваш код
raise NotImplementedError
m = np.array([[3, 10], [1, 20], [2, 30]])
assert np.array_equal(sort_rows_by_column(m, 0), [[1, 20], [2, 30], [3, 10]])
assert np.array_equal(sort_rows_by_column(m, 1), m)
m = np.array([[1, 5], [0, 7], [1, 2], [0, 1]])
assert np.array_equal(sort_rows_by_column(m, 0), [[0, 7], [0, 1], [1, 5], [1, 2]])
print("Задача 5: OK")
Задача 6. Центрирование строк (0.5 балла)¶
Вычтите из каждой строки матрицы m среднее значение этой строки. После этого среднее каждой строки станет равно нулю.
center_rows([[1, 2, 3],
[10, 20, 60]]) -> [[-1, 0, 1],
[-20, -10, 30]]
Подсказка: mean(axis=1, keepdims=True).
def center_rows(m: np.ndarray) -> np.ndarray:
# ваш код
raise NotImplementedError
m = np.array([[1, 2, 3], [10, 20, 60]])
assert np.allclose(center_rows(m), [[-1, 0, 1], [-20, -10, 30]])
assert np.allclose(center_rows(np.array([[5.0, 5.0]])), [[0, 0]])
print("Задача 6: OK")
Задача 7. Заполнение пропусков (0.5 балла)¶
Как говорилось на лекции, в реальных данных бывают пропуски. В матрице m они обозначены как np.nan. Замените каждый пропуск средним значением его столбца, посчитанным по известным значениям. Гарантируется, что в каждом столбце есть хотя бы одно число. Исходную матрицу не изменяйте.
m = [[1, nan],
[3, 4 ],
[nan, 8 ]]
fill_nan_with_column_mean(m) -> [[1, 6],
[3, 4],
[2, 8]]
Подсказка: np.nanmean, np.isnan, np.where.
def fill_nan_with_column_mean(m: np.ndarray) -> np.ndarray:
# ваш код
raise NotImplementedError
m = np.array([[1, np.nan], [3, 4], [np.nan, 8]])
original = m.copy()
assert np.allclose(fill_nan_with_column_mean(m), [[1, 6], [3, 4], [2, 8]])
assert np.array_equal(m, original, equal_nan=True), "исходная матрица изменилась"
assert np.allclose(fill_nan_with_column_mean(np.array([[1.0, 2.0]])), [[1, 2]])
print("Задача 7: OK")
Задача 8. One-hot кодирование (0.5 балла)¶
Категориальный признак (например, город или профессию) часто превращают в набор бинарных признаков. Дан массив labels с номерами категорий от 0 до n_classes - 1. Верните матрицу размера len(labels) × n_classes из нулей и единиц: в строке i единица стоит только в столбце labels[i].
one_hot([2, 0, 1, 2], 3) -> [[0, 0, 1],
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]]
def one_hot(labels: np.ndarray, n_classes: int) -> np.ndarray:
# ваш код
raise NotImplementedError
assert np.array_equal(one_hot(np.array([2, 0, 1, 2]), 3),
[[0, 0, 1], [1, 0, 0], [0, 1, 0], [0, 0, 1]])
assert np.array_equal(one_hot(np.array([0, 0]), 4), [[1, 0, 0, 0], [1, 0, 0, 0]])
print("Задача 8: OK")
Задача 9. Ошибки регрессии (0.5 балла)¶
Посчитайте две метрики из лекции для истинных ответов y_true и предсказаний y_pred:
$$\mathrm{MAE}=\frac1N\sum_{i=1}^{N}\lvert y_{\text{pred},i}-y_{\text{true},i}\rvert, \qquad \mathrm{MSE}=\frac1N\sum_{i=1}^{N}\bigl(y_{\text{pred},i}-y_{\text{true},i}\bigr)^2.$$
Верните пару (mae, mse).
Пример из лекции: цены квартир 5, 6, 7, 8, 24 млн, всем предсказываем медиану 7.
regression_errors([5, 6, 7, 8, 24], [7, 7, 7, 7, 7]) -> (4.2, 59.0)
def regression_errors(y_true: np.ndarray, y_pred: np.ndarray) -> tuple[float, float]:
# ваш код
raise NotImplementedError
mae, mse = regression_errors(np.array([5, 6, 7, 8, 24]), np.array([7, 7, 7, 7, 7]))
assert np.isclose(mae, 4.2) and np.isclose(mse, 59.0)
mae, mse = regression_errors(np.array([5, 6, 7, 8, 24]), np.full(5, 10))
assert np.isclose(mae, 5.6) and np.isclose(mse, 50.0)
print("Задача 9: OK")
Задача 10. Accuracy, precision, recall (0.5 балла)¶
Модель отвечает «да» (1) или «нет» (0). По истинным ответам y_true и предсказаниям y_pred посчитайте метрики из лекции:
- accuracy — доля верных ответов среди всех;
- precision — доля верных среди тех, кому модель сказала «да»;
- recall — доля найденных моделью среди всех настоящих «да».
Если модель ни разу не сказала «да», считайте precision равным 0. Если настоящих «да» нет, считайте recall равным 0.
Верните тройку (accuracy, precision, recall).
y_true = [1, 0, 1, 1, 0, 0, 1, 0]
y_pred = [1, 1, 0, 1, 1, 0, 1, 0]
binary_metrics(y_true, y_pred) -> (0.625, 0.6, 0.75)
def binary_metrics(y_true: np.ndarray, y_pred: np.ndarray) -> tuple[float, float, float]:
# ваш код
raise NotImplementedError
y_true = np.array([1, 0, 1, 1, 0, 0, 1, 0])
y_pred = np.array([1, 1, 0, 1, 1, 0, 1, 0])
assert np.allclose(binary_metrics(y_true, y_pred), (0.625, 0.6, 0.75))
assert np.allclose(binary_metrics(np.array([1, 0, 1]), np.array([0, 0, 0])), (1 / 3, 0, 0))
assert np.allclose(binary_metrics(np.array([0, 0]), np.array([0, 1])), (0.5, 0, 0))
print("Задача 10: OK")
Часть 2. pandas, CSV и JSON¶
Работаем с данными банка о клиентах и интернет-магазина о заказах. Ячейка ниже создаёт два файла с примерами:
clients.csv— клиенты банка: возраст, город, профессия, доход в рублях и вернул ли клиент кредит (returned: 1 — да, 0 — нет). Как и в таблице из лекции, здесь есть пропуски и ошибки в данных.orders.json— заказы этих клиентов в интернет-магазине: у каждого заказа есть список товаров с ценой и количеством.
Функции получают путь к файлу: проверка будет запускать их на других файлах той же структуры.
clients_csv = """client_id,name,age,city,profession,income,returned
1,Анна,34,Минск,врач,2100,1
2,Борис,,Гомель,инженер,1650,0
3,Виктор,140,Минск,учитель,1900,1
4,Галина,29,Брест,программист,52000,1
5,Дмитрий,41,Гомель,инженер,1500,1
6,Елена,23,Минск,программист,2600,0
7,Жанна,57,Брест,врач,2300,1
8,Захар,15,Минск,школьник,0,1
9,Ирина,36,Гомель,учитель,1400,0
10,Кирилл,48,Минск,инженер,2000,1
"""
with open("clients.csv", "w", encoding="utf-8") as f:
f.write(clients_csv)
orders = [
{"order_id": 101, "client_id": 1, "date": "2026-09-01",
"items": [{"product": "тетрадь", "price": 2.5, "qty": 4},
{"product": "ручка", "price": 1.2, "qty": 10}]},
{"order_id": 102, "client_id": 4, "date": "2026-09-02",
"items": [{"product": "ноутбук", "price": 2400.0, "qty": 1}]},
{"order_id": 103, "client_id": 1, "date": "2026-09-05",
"items": [{"product": "рюкзак", "price": 85.0, "qty": 1},
{"product": "тетрадь", "price": 2.5, "qty": 2}]},
{"order_id": 104, "client_id": 6, "date": "2026-09-07", "items": []},
{"order_id": 105, "client_id": 9, "date": "2026-09-08",
"items": [{"product": "книга", "price": 30.0, "qty": 3}]},
{"order_id": 106, "client_id": 6, "date": "2026-09-10",
"items": [{"product": "наушники", "price": 120.0, "qty": 1},
{"product": "ручка", "price": 1.2, "qty": 5}]},
]
with open("orders.json", "w", encoding="utf-8") as f:
json.dump(orders, f, ensure_ascii=False, indent=2)
pd.read_csv("clients.csv")
| client_id | name | age | city | profession | income | returned | |
|---|---|---|---|---|---|---|---|
| 0 | 1 | Анна | 34.0 | Минск | врач | 2100 | 1 |
| 1 | 2 | Борис | NaN | Гомель | инженер | 1650 | 0 |
| 2 | 3 | Виктор | 140.0 | Минск | учитель | 1900 | 1 |
| 3 | 4 | Галина | 29.0 | Брест | программист | 52000 | 1 |
| 4 | 5 | Дмитрий | 41.0 | Гомель | инженер | 1500 | 1 |
| 5 | 6 | Елена | 23.0 | Минск | программист | 2600 | 0 |
| 6 | 7 | Жанна | 57.0 | Брест | врач | 2300 | 1 |
| 7 | 8 | Захар | 15.0 | Минск | школьник | 0 | 1 |
| 8 | 9 | Ирина | 36.0 | Гомель | учитель | 1400 | 0 |
| 9 | 10 | Кирилл | 48.0 | Минск | инженер | 2000 | 1 |
Задача 11. Чистка данных (1 балл)¶
Прочитайте таблицу клиентов из CSV-файла по пути path и удалите строки с некорректным возрастом:
- возраст не указан (пропуск);
- возраст меньше
18или больше100.
Верните таблицу с теми же столбцами в исходном порядке строк. Индекс должен идти заново: 0, 1, 2, … (reset_index(drop=True)). Столбец age должен иметь целочисленный тип (int): после пропусков pandas читает его как дробный.
На примере остаются клиенты с client_id 1, 4, 5, 6, 7, 9, 10.
def clean_clients(path: str) -> pd.DataFrame:
# ваш код
raise NotImplementedError
clean = clean_clients("clients.csv")
assert list(clean.columns) == ["client_id", "name", "age", "city", "profession", "income", "returned"]
assert clean["client_id"].tolist() == [1, 4, 5, 6, 7, 9, 10]
assert clean.index.tolist() == list(range(7))
assert pd.api.types.is_integer_dtype(clean["age"]), "age должен быть целым"
assert clean["age"].tolist() == [34, 29, 41, 23, 57, 36, 48]
print("Задача 11: OK")
Задача 12. Отчёт по городам (1 балл)¶
По очищенной таблице клиентов (используйте clean_clients(path)) постройте отчёт по городам. Верните таблицу, у которой индекс — название города (city), а столбцы такие:
| Столбец | Что в нём |
|---|---|
clients |
число клиентов из города |
mean_income |
средний доход |
return_rate |
доля клиентов, вернувших кредит |
Строки отсортируйте по убыванию clients, а при равенстве — по названию города в алфавитном порядке.
На примере:
| city | clients | mean_income | return_rate |
|---|---|---|---|
| Минск | 3 | 2233.33… | 0.666… |
| Брест | 2 | 27150.0 | 1.0 |
| Гомель | 2 | 1450.0 | 0.5 |
Подсказка: groupby(...).agg(clients=("client_id", "count"), ...).
def city_report(path: str) -> pd.DataFrame:
# ваш код
raise NotImplementedError
report = city_report("clients.csv")
expected = pd.DataFrame(
{"clients": [3, 2, 2],
"mean_income": [6700 / 3, 27150.0, 1450.0],
"return_rate": [2 / 3, 1.0, 0.5]},
index=pd.Index(["Минск", "Брест", "Гомель"], name="city"),
)
pd.testing.assert_frame_equal(report, expected, check_dtype=False, check_index_type=False)
print("Задача 12: OK")
Задача 13. Суммы заказов из JSON (1 балл)¶
Прочитайте заказы из JSON-файла по пути path (структура как в orders.json). Посчитайте сумму каждого заказа: сумму price × qty по всем его товарам. У заказа без товаров сумма равна 0.
Верните pd.Series с именем total, у которой индекс — order_id (с именем order_id), отсортированный по возрастанию.
order_id
101 22.0
102 2400.0
103 90.0
104 0.0
105 90.0
106 126.0
Name: total, dtype: float64
Подсказка: файл можно прочитать модулем json, а вложенный список товаров развернуть через pd.json_normalize(data, record_path="items", meta=[...]). Посмотрите, что при этом происходит с заказом без товаров.
def order_totals(path: str) -> pd.Series:
# ваш код
raise NotImplementedError
totals = order_totals("orders.json")
expected = pd.Series([22.0, 2400.0, 90.0, 0.0, 90.0, 126.0], name="total",
index=pd.Index([101, 102, 103, 104, 105, 106], name="order_id"))
pd.testing.assert_series_equal(totals, expected, check_dtype=False, check_index_type=False)
print("Задача 13: OK")
Задача 14. Лучшие покупатели (1 балл)¶
Объедините клиентов из CSV (clients_path, без очистки) и заказы из JSON (orders_path). Для каждого клиента, сделавшего хотя бы один заказ, посчитайте общую сумму всех его заказов. Верните k клиентов с наибольшей суммой.
Результат — таблица со столбцами name и total, отсортированная по убыванию total, а при равенстве — по имени в алфавитном порядке. Индекс: 0, 1, …, k - 1. Если клиентов с заказами меньше k, верните всех. Гарантируется, что у каждого заказа client_id есть в таблице клиентов.
На примере top_clients("clients.csv", "orders.json", 3):
| name | total | |
|---|---|---|
| 0 | Галина | 2400.0 |
| 1 | Елена | 126.0 |
| 2 | Анна | 112.0 |
Подсказка: используйте order_totals и pd.merge.
def top_clients(clients_path: str, orders_path: str, k: int) -> pd.DataFrame:
# ваш код
raise NotImplementedError
top = top_clients("clients.csv", "orders.json", 3)
expected = pd.DataFrame({"name": ["Галина", "Елена", "Анна"], "total": [2400.0, 126.0, 112.0]})
pd.testing.assert_frame_equal(top, expected, check_dtype=False, check_index_type=False)
top = top_clients("clients.csv", "orders.json", 10)
assert top["name"].tolist() == ["Галина", "Елена", "Анна", "Ирина"]
print("Задача 14: OK")