Во второй части итогового проекта посмотрим на год во времени: когда магазин зарабатывал больше, когда меньше, и как менялась роль каналов продаж. Начнём с того, на чём закончили: выручка каждой строки файла.
import numpy as np
path = "data/shop_orders.csv"
nums = np.loadtxt(path, delimiter=",", skiprows=1, usecols=(7, 8))
revenue = nums[:, 0] * nums[:, 1]
total = revenue.sum()
print(revenue.shape, total)(2448,) 3301420.0
Шаг 1. Выручка по месяцам
Дата в файле — текст вида 2025-03-14. Месяц — символы с 5-го по 6-й, считая с нуля: срез строки d[5:7], а int делает из
него число. Для 2448 строк это обычный генератор списка из Python — NumPy не обязан уметь всё. (Полноценную
работу с датами вы освоите в курсе pandas.)
dates = np.loadtxt(path, delimiter=",", skiprows=1, usecols=1, dtype=str)
month = np.array([int(d[5:7]) for d in dates])
print(dates[:3], dates[-3:])
print(month[:3], month[-3:])['2025-01-01' '2025-01-01' '2025-01-01'] ['2025-12-31' '2025-12-31' '2025-12-31'] [1 1 1] [12 12 12]
Месяц — целое от 1 до 12, то есть готовый код группы: суммы по месяцам даёт np.bincount с весами, как в
прошлой части. Код 0 не встречается — его сумма будет нулём, как у нулевой грани кубика.
Сохраните в monthly выручку каждого месяца — 12 чисел, январь первым. В best_month и worst_month —
номера (1–12) месяцев с наибольшей и наименьшей выручкой.
np.bincount(month, weights=revenue) — 13 чисел; нулевое лишнее — срез [1:].monthly.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
monthly = ...
best_month = ...
worst_month = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
monthly = np.bincount(month, weights=revenue)[1:]
best_month = monthly.argmax() + 1
worst_month = monthly.argmin() + 1Шаг 2. Рост к прошлому месяцу
Изменение к прошлому месяцу — разность соседей: monthly[1:] - monthly[:-1], два среза со сдвигом на один, как
в парадоксе дней рождения. Первый элемент разностей — февраль минус январь.
Сохраните в change изменение выручки каждого месяца к предыдущему (11 чисел, начиная с февраля), а в
rise_month — номер месяца (1–12), в котором выручка выросла сильнее всего.
change[0] — это февраль, месяц номер 2.change.argmax() + 2.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
change = ...
rise_month = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
change = monthly[1:] - monthly[:-1]
rise_month = change.argmax() + 2Шаг 3. Накопленный итог и кварталы
Сохраните в running накопленную выручку с начала года по месяцам, а в half_month — номер месяца, к концу
которого магазин заработал хотя бы половину годовой выручки.
monthly.cumsum(); годовая выручка — последний элемент.True в маске running >= половина — argmax маски; номер месяца на единицу больше.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
running = ...
half_month = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
running = monthly.cumsum()
half_month = (running >= running[-1] / 2).argmax() + 1Сохраните в quarters выручку каждого квартала (4 числа), а в weak_quarter — номер (1–4) самого слабого
квартала.
monthly.reshape(4, 3), строка — квартал.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
quarters = ...
weak_quarter = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
quarters = monthly.reshape(4, 3).sum(axis=1)
weak_quarter = quarters.argmin() + 1Лето — самый слабый сезон: в жару горячий кофе и чай покупают меньше, а в декабре выручку поднимают подарки к Новому году.
Шаг 4. Каналы продаж по месяцам
Теперь хотим таблицу 12 × 3: месяц × канал (маркетплейс, приложение, сайт). Это группы по двум признакам
сразу. Приём: склеить два кода в один. Если у канала коды 0–2, то (month - 1) * 3 + канал даёт каждой паре
«месяц, канал» свой номер от 0 до 35: январь — 0, 1, 2, февраль — 3, 4, 5 и так далее. bincount по такому
коду даёт 36 сумм, а reshape(12, 3) раскладывает их в таблицу:
m_demo = np.array([1, 1, 2, 2, 2]) # месяц
c_demo = np.array([0, 2, 1, 2, 2]) # канал
amount = np.array([10, 20, 30, 40, 50])
pair = (m_demo - 1) * 3 + c_demo
print(pair)
print(np.bincount(pair, weights=amount).reshape(2, 3))[0 2 4 5 5] [[10. 0. 20.] [ 0. 30. 90.]]
Загрузите столбец каналов (4) и сохраните в by_channel таблицу 12 × 3 — выручку каждого канала в каждом месяце,
в shares — долю каждого канала в выручке своего месяца (строки в сумме дают 1), а в site_peak — номер месяца
(1–12), когда доля сайта была наибольшей.
np.unique(…, return_inverse=True); парный код — (month - 1) * 3 + коды.by_channel.sum(axis=1, keepdims=True); сайт — столбец 2.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
by_channel = ...
shares = ...
site_peak = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
channel = np.loadtxt(path, delimiter=",", skiprows=1, usecols=4, dtype=str)
ch_names, ch_codes = np.unique(channel, return_inverse=True)
by_channel = np.bincount((month - 1) * 3 + ch_codes, weights=revenue).reshape(12, 3)
shares = by_channel / by_channel.sum(axis=1, keepdims=True)
site_peak = shares[:, 2].argmax() + 1print("лучший месяц:", best_month, "худший:", worst_month)
print("кварталы, тыс. ₽:", np.round(quarters / 1000))
year_share = by_channel.sum(axis=0) / total
for name, share in zip(["маркетплейс", "приложение", "сайт"], year_share * 100):
print(f" {name}: {share:.0f} % за год")лучший месяц: 12 худший: 7 кварталы, тыс. ₽: [867. 864. 652. 918.] маркетплейс: 27 % за год приложение: 27 % за год сайт: 46 % за год
Выводы
Выручка «Кофейной лавки» — сезонная: лучший месяц — декабрь, худший — июль, третий квартал заметно слабее остальных, а половину годовой выручки магазин набрал к концу июня. Сайт — главный канал (46 % за год), а в октябре его доля доходила до двух третей.
Это конец курса NumPy. Вы прошли путь от первого массива до проекта, в котором без единого цикла по данным загрузили файл, сгруппировали, нашли тренды и сезонность. Те же вопросы — и много новых — в курсе pandas решаются короче, а понимание массивов, осей, масок и транслирования пригодится там на каждом шаге.