Медведь бухгалтер или эксперимент над личными финансами на python и polars
Личные финансы: важная тема в области финансовой грамотности. Хорошо бы понимать, сколько приходит, сколько уходит, откуда и куда, чтобы планировать долгосрочные финансовые цели. Для этого можно использовать базовую аналитику банка, приложения для учета финансов, но у них есть минусы.
Но если вы знаете python и хотя бы раз сталкивались с pandas, для вас финансовая аналитика может показаться куда более интересной.
Подготовим рабочую поверхность
Для анализа данных нам потребуются данные, python и polars. Чтобы получить данные о финансах, можно воспользоваться банковским сайтом. У Т-Банка это можно сделать во вкладке операции, выбрать «за весь период» и нажать кнопку «скачать в формате csv», хотя я все-таки рекомендую формат экселя, потому что csv в polars у меня почему-то не завелся, хотя это вроде бы самый простой формат данных.
Также нам понадобится python и polars, чтобы скачать python достаточно зайти на python.org и проследовать инструкции по загрузке или найти гайд. Чтобы скачать polars, достаточно создать папку с проектом, добавить туда наш датасет, создать виртуальное окружение командой python -m venv .venv
и написать pip install polars
, чтобы установить библиотеку polars.
Немнго про нашего медведя
Polars это новая библиотека для обработки данных на python, она созвучна с pandas и pyspark по области применения. Polars написан на rust, что в сравнении с pandas дает ощутимый прирост скорости. Об этом писали тут.
Начнем с простого
Чтобы немного вкатиться в инструмент, можно сделать что-то супер простое. Поэтому давайте для начала сделаем разбивку трат по категориям. Я буду использовать функциональный подход. Все манипуляции с данными я буду оформлять в виде функций. Загрузка данных описывается через функцию load_data()
, вот ее код:
import polars as pl
load_data():
return pl.read_excel('money.xls')
Здесь я работаю именно с экселем, потому что в моем случае csv файл неккоректно читался.
Дальше я использовал функции, чтобы выбрать нужные колонки, в polars для этого используется DataFrame.select()
, обратите внимание, название колонки нужно передавать, используя pl.col('Название колонки')
. Также я добавил фильтр, чтобы получить только отрицательные значения, так как Тинькофф хранит расходы именно со знаком минус. Итоговая функция выглядит вот так:
def get_valid_columns():
return load_data() \
.select(pl.col('Дата операции', ... , "Описание")) \
.filter(pl.col('Сумма операции') < 0)
Дальше мы пишем функцию для агрегации суммы расходов по категориям. Для этого нужно выполнить несколько шагов:
Сгруппируем данные по категориям
data.group_by('Категория')
, здесь можно использовать название колонки безpl.col()
Суммируем по категориям и сумме операции.
data.agg(pl.sum('Сумма операции'))
Сортируем данные
data.sort()
Итоговая функция выглядит вот так:
def get_categories_agg():
return get_valid_columns() \
.group_by('Категория') \
.agg(pl.sum('Сумма операции')) \
.sort('Сумма операции')
Дорогой день
Когда мы узнали в какие категории у нас уходит больше всего денег, можно продолжить баловство и посмотреть какие дни недели для вашего кошелька самые дорогие. Практическая польза от этого небольшая, но ради интереса и рефлексии думаю неплохое упражнение.
Чтобы разбить средние траты по дням недели нам нужно выполнить несколько шагов:
Получить нужные колонки с уже написанной функцией
get_valid_columns()
Написать функцию для конвертации строки в день недели с помощью модуля
datetime
from datetime import datetime
def date_converter(date: str):
format = "%d.%m.%Y %H:%M:%S"
return datetime.strptime(date, format).strftime("%A")
Создать новую колонку с днями недели используя функцию with_columns:
df = get_valid_columns().with_columns(pl.col('Дата операции').map_elements(lambda x: date_converter(x), return_dtype=pl.String).alias('День недели'))
Тут мы с помощью маппинга применяем к каждой строчке функцию
date_converter()
, указываем возвращаемое значение и элиасГруппируем по дню недели и выводим медиану для суммы операции:
df.group_by('День недели').agg(pl.col('Сумма операции').median())
Итоговые преобразования выглядят так:
def date_converter(date: str):
format = "%d.%m.%Y %H:%M:%S"
return datetime.strptime(date, format).strftime("%A")
def extract_week():
return get_valid_columns() \
.with_columns(pl.col('Дата операции') \
.map_elements(lambda x: date_converter(x), return_dtype=pl.String) \
.alias('День недели'))
def get_median_week_exp():
return extract_week()\
.group_by('День недели') \
.agg(pl.col('Сумма операции') \
.median()) \
.sort('Сумма операции')
get_median_week_exp()
Медианные расходы по годам
Теперь посмотрим на сколько изменились медианные траты в месяц от года к году. Для этого сначала создадим отдельные колонки с указанием месяца и года.
format = "%d.%m.%Y %H:%M:%S"
def create_month_and_year_col():
return get_valid_columns() \
.with_columns(
pl.col('Дата операции').map_elements(lambda x: datetime.strptime(x,format).month, return_dtype=pl.Int64).alias('Месяц'),
pl.col('Дата операции').map_elements(lambda x: datetime.strptime(x,format).year, return_dtype=pl.Int64).alias('Год')
)
Потом сгруппируем данные по Месяцу и Году
def group_by_month_and_year():
return create_month_and_year_col() \
.group_by('Месяц','Год') \
.agg(pl.all().sum()) \
.sort(pl.col('Год'), pl.col('Месяц')) \
.select(pl.col('Месяц'), pl.col('Год'), pl.col('Сумма операции'))
И посчитаем все вместе. Добавим сортировку по году
def aggregate_by_year():
return group_by_month_and_year() \
.select(pl.col('Год'),pl.col('Сумма операции')) \
.group_by('Год') \
.agg(pl.all() \
.median()) \
.sort('Год')
И вот как в итоге у нас получилось:
format = "%d.%m.%Y %H:%M:%S"
def create_month_and_year_col():
return get_valid_columns() \
.with_columns(
pl.col('Дата операции').map_elements(lambda x: datetime.strptime(x,format).month, return_dtype=pl.Int64).alias('Месяц'),
pl.col('Дата операции').map_elements(lambda x: datetime.strptime(x,format).year, return_dtype=pl.Int64).alias('Год')
)
def group_by_month_and_year():
return create_month_and_year_col() \
.group_by('Месяц','Год') \
.agg(pl.all().sum()) \
.sort(pl.col('Год'), pl.col('Месяц')) \
.select(pl.col('Месяц'), pl.col('Год'), pl.col('Сумма операции'))
def aggregate_by_year():
return group_by_month_and_year() \
.select(pl.col('Год'),pl.col('Сумма операции')) \
.group_by('Год') \
.agg(pl.all() \
.median()) \
.sort('Год')
aggregate_by_year()
Выводы
Итого, мы получили небольшую финансовую сводку нашей финансовой ситуации. Исходя из этих данных можно сделать следующие выводы:
По каким категориям распределяется наш доход и возможно стоит принять решение о сокращении или увеличении расходов по некоторым из них.
В какой день выходить из дома без карточки.
Насколько растет мое потребление от года к году. Составить прогноз.
Если вы любите считать деньги и хотите расширить представленный материал, то обязательно пишите в комментарии.