Skip to content

Глава 13. Итоговый проект: отчёт по успеваемости

Задача главы: собрать сквозной скрипт «выгрузка → расчёты → готовый отчёт», в котором работают инструменты всех предыдущих глав вместе, а не по одному в изолированном примере — и получить свой первый завершённый инструмент аналитика.


13.1 Постановка задачи и формат входной выгрузки

Деканат выгружает из системы один файл journal_dirty.csv со всеми предметами и всеми потоками сразу — одна строка выгрузки описывает одну сдачу одного предмета одним учеником:

student_namesubjectscoresubmitted_at
Иванов ИванPython7816.05.2024
Ким ЮрийPythonн/д20.05.2024
Петров ПётрPython15019.05.2024
Сидорова АннаАналитика9015.05.2024

Выгрузка почти никогда не приходит идеальной: в именах бывают случайные пробелы, в баллах — текстовые заглушки вместо чисел и опечатки, из-за которых балл выходит за пределы 0–100. Задача — не «написать код для чистых данных», а собрать отчёт, который выдержит именно такую выгрузку целиком, ни разу не упав и не потеряв ни одной строки без следа.

На выходе нужны три вещи:

  1. Файл report.csv — по одной строке на ученика: средний балл, статус, просрочка.
  2. Консольная сводка — рейтинг, группа риска, разбивка по предметам, медиана, разброс, распределение статусов.
  3. Журнал отбракованных строк — что не попало в отчёт и почему.

Это ровно то же самое разделение, что и в главе Файлы и CSV («чтение → расчёты → запись»), только теперь на входе — реалистично грязная выгрузка, а расчётов — на порядок больше.

// PYTHON_EXECUTOR (Pyodide Wasm)

13.2 Слой чтения и нормализации: главы 3, 10, 11

Зачем это нужно. Прежде чем что-либо считать, выгрузку нужно превратить из «текста в файле» в чистые, однотипные значения — и сделать это так, чтобы одна кривая строка не обрушила чтение всех остальных. Это уже знакомая связка трёх глав: csv.DictReader открывает файл (Файлы и CSV), .strip() убирает случайные пробелы по краям ФИО и предмета (Строки и f-строки), а try / except перехватывает как нечисловой балл, так и балл вне диапазона, откладывая проблемную строку в отдельный список вместо падения (Ошибки и исключения).

// PYTHON_EXECUTOR (Pyodide Wasm)

Функция отдаёт два списка сразу: valid_rows — то, с чем можно дальше считать, и rejected_rows — то, что попало под нож, с понятной причиной для каждой строки. Ни одна строка выгрузки не пропадает без объяснения — тот же принцип, что закрывал мини-кейс главы 11.


13.3 Слой расчётов: функции из глав 4, 5, 9, 12

Зачем это нужно. У valid_rows — плоская структура: одна строка на пару «ученик–предмет». Чтобы получить средний балл и статус по ученику, а не по отдельной сдаче, данные сначала группируют (Словари), затем считают через собственные функции (Функции), уточняют медианой и разбросом (Стандартная библиотека) и, наконец, выстраивают в рейтинг (Списки).

// PYTHON_EXECUTOR (Pyodide Wasm)

get_average_score, переданная в sorted(..., key=get_average_score) без круглых скобок, — не новый трюк, а прямое применение ловушки из 9.1: имя функции без () — это ссылка на сам объект-функцию, а не её результат. Обычно эта ссылка мешает (переменной по ошибке достаётся функция вместо числа); здесь она работает как задумано — sorted() сам вызовет эту функцию на каждом элементе, чтобы понять, по чему сравнивать. Это тот самый приём с собственной функцией в key=, который был обещан ещё в главе Списки.

// PYTHON_EXECUTOR (Pyodide Wasm)

13.4 Слой вывода: f-строки, файл отчёта, сводка по группам

Зачем это нужно. Расчёты бесполезны, пока они не превратились в две конкретные вещи: файл, который можно переслать коллеге, и текст на экране, который читается за десять секунд. Запись — тот же csv.writer в режиме 'w' с newline='' из главы Файлы и CSV; вывод на экран — f-строки из главы Переменные и ввод данных и спецификаторы :.1f из главы Строки и f-строки.

// PYTHON_EXECUTOR (Pyodide Wasm)

Сводка по группам (предметам) и рейтинг по ученикам — это один и тот же приём «сгруппировать → посчитать → напечатать», примененный на двух разных уровнях одной и той же выгрузки: по строкам предмета и по строкам ученика.


13.5 Разбор итогов и куда двигаться дальше

Всё, что написано в этой главе, делает вручную то, что уже умеет специализированная библиотека pandas — но написанное вручную не потрачено впустую: без него pandas выглядел бы чёрным ящиком, а его ошибки — необъяснимой магией.

// PYTHON_EXECUTOR (Pyodide Wasm)

Две строки pandas заменяют десятки строк ручной группировки — но groupby() — это тот же setdefault() из главы 5 под капотом, а .mean() — тот же statistics.mean() из главы 12, просто с другим синтаксисом сверху. Ошибка KeyError в pandas — тот же KeyError, что уже разобран в главе Словари. Библиотеки для визуализации (matplotlib, seaborn) превращают subject_averages и ranking из этой главы в столбчатые диаграммы и графики — следующий логичный шаг после того, как текстовый отчёт уже научился строиться надёжно.


Финальный кейс: полный скрипт «выгрузка → расчёты → отчёт»

Задача: собрать все слои этой главы в один запускаемый скрипт — от намеренно грязной выгрузки до сохранённого report.csv и полной консольной сводки по потоку.

// PYTHON_EXECUTOR (Pyodide Wasm)
--- ИТОГОВЫЙ ОТЧЁТ ПО ПОТОКУ ---
Отчёт сохранён в report.csv
Рейтинг (топ-3):
1. Смирнова Ольга — 90.0
2. Сидорова Анна — 89.0
3. Иванов Иван — 85.0
Группа риска (статус «незачёт»):
Ким Юрий — 55.0, просрочка 5 дн.
Петров Пётр — 15.0, просрочка 4 дн.
Средний балл по предметам:
Python: 86.0
Аналитика: 68.0
Медиана среднего балла по потоку: 85.0
Разброс (стандартное отклонение): 32.3
Распределение статусов: {'зачёт': 3, 'незачёт': 2}
Просрочили дедлайн: 3 из 5
Отбраковано строк при чтении: 2
Ким Юрий / Python: invalid literal for int() with base 10: 'н/д'
Петров Пётр / Python: балл вне диапазона 0-100: 150

Что получилось: из десяти строк грязной выгрузки в отчёт попали данные пяти учеников, обе испорченные строки отброшены не молча, а с точной причиной каждая. Рейтинг, группа риска, разбивка по предметам, медиана и распределение статусов — семь разных срезов одной и той же выгрузки, и ни один из них не потребовал нового синтаксиса: только csv, try/except, словари, свои функции и модули statistics/collections/datetime, изученные по одной главе за раз. report.csv на диске содержит только пять чистых строк ранжированного отчёта — то, за что можно ручаться перед деканатом.


Итоговый чек-лист аналитика (вместо чек-листа главы)

Эта глава последняя, поэтому чек-лист — не по одной теме, а по всем двенадцати:

  • Различаю + (склейка) и f-строку (f'{value}') при сборке текста — и не смешиваю str с числом напрямую (гл. 1)
  • Помню, что input() всегда возвращает str, и привожу к int()/float() до арифметики (гл. 1)
  • Использую // и % осознанно, различаю «сколько аудиторий» и «сколько сядет в последнюю» (гл. 2)
  • Расставляю скобки в and/or явно, не полагаясь на приоритет операторов по умолчанию (гл. 2)
  • Присваиваю результат строковых методов обратно — строки неизменяемы (гл. 3)
  • Проверяю правую границу срезов [start:stop] — она не включается ни у строк, ни у списков (гл. 3, 4)
  • Не присваиваю результат .append(), .sort(), .insert() — они меняют объект на месте и возвращают None (гл. 4)
  • Копирую списки срезом [:] или list(), когда нужна независимая копия, а не второе имя (гл. 4)
  • Читаю поля словаря через .get(key, default), если ключ может отсутствовать (гл. 5)
  • Группирую данные через setdefault(key, []).append(value) вместо ручной проверки «есть ли уже ключ» (гл. 5)
  • Нормализую регистр и пробелы перед сравнением множеств или строк из разных выгрузок (гл. 3, 6)
  • Ставлю узкие условия (elif) и узкие except выше широких — иначе широкая ветка перекрывает частные (гл. 7, 11)
  • Проверяю данные на пустоту (if not scores:) до расчётов, а не после падения (гл. 7, 12)
  • Инициализирую счётчики и сумматоры один раз, до цикла, а не внутри тела (гл. 8)
  • Отделяю логику расчёта в свою функцию (def), а не копирую формулу по разным местам скрипта (гл. 9)
  • Возвращаю результат через return, а не только печатаю его внутри функции, если результат нужен дальше (гл. 9)
  • Не использую изменяемый объект ([], {}) как значение по умолчанию в аргументах функции (гл. 9)
  • Открываю файлы через with open(..., encoding='utf-8') и указываю newline='' при записи CSV (гл. 10)
  • Выбираю 'w' для нового отчёта и 'a' для журнала, который копит историю запусков (гл. 10)
  • Оборачиваю в try/except конкретный код, который может упасть на кривых данных, а не весь скрипт (гл. 11)
  • Считаю и логирую отбракованные строки вместо except ... pass — итоговые цифры должны совпадать с числом строк на входе (гл. 11)
  • Беру медиану вместо среднего, когда в данных возможны выбросы (гл. 12)
  • Фиксирую формат даты в одной константе и сверяю его с документацией источника, а не угадываю (гл. 12)
  • Собираю выгрузку через слои — чтение и нормализация → расчёты → вывод — а не в одном нерасчленимом блоке кода (гл. 13)

Учебник на этом закончен — но собранные в нём функции, привычки читать traceback и разложенные по слоям скрипты теперь ваш личный набор инструментов, который переносится в любую новую выгрузку. Следующий шаг за пределами этой книги — pandas и визуализация, обозначенные в 13.5.

Возврат к началу: Оглавление