Всем привет, меня зовут Виталий, автор телеграмм канала Детектив данных, про мой путь в аналитике данных, мучаю питон и sql, строю графики и думаю как жить дальше.

По работе довольно много времени провожу в питоне и абсолютно всегда нужно контролировать чтобы в процессе работы с данными - эти данные не упустить, и если так произошло, то понять в какой момент нужно за ними вернуться.

Пример работы функции
Пример работы функции

Самый простой пример это merge который при неправильном использовании умножит наши данные и groupby который эти данные сократит (при наличии NaN). Помню на одном из самых первых занятий по питону лектор написал что-то вроде:

sum_df_sales = df['sales'].sum()
sum_df_sales 

Рабочий способ, но в повседневном использовании мне показался неудобным, когда число измеряется миллиардами, ничего кроме кучи цифр с "е06" на конце я не вижу. И сравнение работает только при повторном написании формулы с другой переменной и вычитания одной из другой, а для нормального форматирования числа еще нужно помучать print

А еще постепенно с опытом, я стал приходить к написанию функций под себя, написал, закинул себе конфиг, импортировал один раз и пользуешься своими функциями не засоряя общий код кучей лишнего кода. Этим мы сейчас и займёмся. Функция detective_sum - покажет нам сколько денег в столбце, сохранит результат в переменную и посчитает разницу с прошлой проверкой. А что бы не было скучно - обернём нашу функцию в детективную оболочку. Пусть у нас будет Аналитическая сводка, под наблюдением у нас как всегда будут данные, а вдохновением послужат хорошие и плохие российские сериалы про ментов. Функция получилась очень простой, но наглядной, разберётся даже начинающий аналитик.

Итак начнём:

Первое что мы сделаем это объявим глобальные переменные для хранения предыдущей суммы, номера дела, и словаря соответствия id таблицы с кодовым именем:

_previous_sum = None
_case_number = 0
_table_names = {} # Словарь для хранения соответствия между id таблицы и кодовым именем

Начинаем писать функцию:

def det_sum(table, column):

Аргументов у нас будет всего два. Это это имя таблицы и колонка, таблица у нас будет в формате "pd.DataFrame" а колонка в формате "str", то есть потребует заключения в кавычки

Далее инициализируем библиотеки и переменные (не забываем про отступы):

import pandas as pd
import random
global _previous_sum, _case_number, _table_names
_case_number += 1 # Каждый раз увеличиваем номер дела на единицу

Сразу же обращаемся к памяти всех миллиениалов, вспомнив слова Ларина из "Улицы разбитых фонарей". Первое что нужно сделать, это понять а вообще есть ли такой столбец:

  if column not in table.columns:
    print(f"\n🔍 Сводка №{_case_number}: Андрюха! Пропал столбец '{column}'! Возможен криминал! По коням!")
    return

Затем собственно говоря команда с первой лекции по питону - сохраним нашу сумму в переменную:

  current_sum = table[column].sum()

Дальше хочу чтобы мне выдавало не кучу цифр, а что-то более осязаемое для понимания, например миллиард, миллион с одним знаком после запятой. Ну и сразу форматируем наш результат.

# Функция для зашифровки чисел
  def format_number(num):
    abs_num = abs(num)
    if abs_num >= 1_000_000_000:
      return f"{num/1_000_000_000:.1f} млрд"
    elif abs_num >= 1_000_000:
      return f"{num/1_000_000:.1f} млн"
    elif abs_num >= 1_000:
      return f"{num/1_000:.1f} тыс"
    else:
      return f"{num:.1f}"
     # Форматируем текущую сумму
  formatted_current = format_number(current_sum)

Затем я пытался подтянуть имя таблицы в дальнейший текст, но мы не можем просто так вызвать это имя внутри нашей функции, и поэтому по правилам конспирации было решено каждой таблице присвоить своё уникальное условное наименование. Возьмём какое-нибудь случайное животное. Это позволит внутри кода проверять несколько разных таблиц, но сумма между вызовами функции будет сохраняться для удобства - ведь обычно редко имя до и после преобразования будет одним и тем же.

# Получаем уникальный идентификатор таблицы (по id в памяти)
  table_id = id(table)
   # Если для этой таблицы еще нет кодового имени - создаем новое
  if table_id not in _table_names:
    animals = ["Антилопа", "Бобр", "Барсук", "Волк", "Выдра", "Гепард", "Горилла", "Дикобраз", "Дельфин",
          "Енот", "Жираф", "Зебра", "Заяц", "Игуана", "Йеменский хамелеон", "Кот", "Кенгуру",
          "Лев", "Лама", "Медведь", "Морж", "Носорог", "Олень", "Панда", "Пума",
          "Рысь", "Слон", "Сурикат", "Тигр", "Тюлень", "Утконос", "Фламинго", "Хомяк",
          "Цапля", "Черепаха", "Шимпанзе", "Щука", "Эму", "Юрок", "Як"] 
    _table_names[table_id] = random.choice(animals)
     # Получаем кодовое имя для этой таблицы
  table_code = _table_names[table_id]

Ну и включаем фантазию, не забываем смайлики и поехали:

Первый и основной принт:

  print(f"\n🕵️ Сводка АН №{_case_number}: Наблюдение за объектом '{table_code}' по столбцу '{column}'")
  print("=" * 50)

Затем проверяем, что предыдущей сумма пуста и принимаем таблицу под наблюдение, а иначе фиксируем разницу:

  if _previous_sum is None:
    print(f"🔎 Объект '{table_animal}' был принят под наблюдение! Сумма: {formatted_current}")
    print("📋 Фиксируем в сводке...")
  else:
    # Выводим текущую сумму
    print(f"💼 Текущая сумма: {formatted_current}")
    # Вычисляем разницу
    difference = current_sum - _previous_sum

Далее идёт серия "if" и "else" - тут важно не напутать с отступами, так как всё это выражение это продолжение предыдущего "else" . Смотрим на разницу, и в зависимости от знака выводим соответствующее сообщение. Полный код в формате юпитер ноутбука будет в первом комментарии группы в телеграмме:

 if difference != 0:
      # Форматируем разницу
      formatted_diff = f"{abs(difference):,.0f}".replace(',', ' ')
      if difference > 0:
        print(f"⬆️ Хм... Сумма выросла на {formatted_diff}")
        print("🕵️ Похоже, мы пропустили новые связи! Отправляем экипаж для проверки!")
      else:
        print(f"⬇️ Ого! Сумма уменьшилась на {formatted_diff}")
        print("🔍 Кто-то пытается замести следы! Нужна проверка по камерам!")
    else:
      print("🤔 Сумма не изменилась...")
      print("📝 Заносим в сводку: Объект не выходил, встреч не зафиксировано")

Ну и выйдя из блока "if/else" завершаем функцию сохранением суммы по столбцу в переменную:

  print("=" * 50)
  _previous_sum = current_sum

Теперь нам нужно как нибудь встроить эту функцию в код, я использую свой конфиг файл, который лежит в проекте, вместе с кодами/паролями и другими функциями, он у меня лежит обычно в папке config с именем func.py как собственно говоря и видно из дальнейшего его вызова.

Вызываю я так, тут сразу с перегрузкой модуля, чтобы если вы внесли изменения в модуль, его можно было бы быстро перегрузить этой командой:

import importlib
import config.func     # импортируем модуль с функциями
importlib.reload(config.func) # перезагружаем модуль (если нужно)
from config.func import *  # импортируем все функции из config.func

Вот и всё. Один раз пишем и используем в работе удобную функцию. Еще увидимся.