Adds two new plot methods and health expenses

Two new graphs created, discrete, where all discretionary expenses are
plotted, and average, for the yearly average. This later one will be
moved to report form.
Health expenses separated from required. Commute moved from fixed to
required expenses.
New by_month_and_category functions added to transactions.py.
This commit is contained in:
Luís Murta 2021-02-02 21:57:07 +00:00
parent 9cdf626bdd
commit fe26bf8a6a
Signed by: satprog
GPG Key ID: DDF2EFC6179009DC
4 changed files with 229 additions and 26 deletions

23
main.py
View File

@ -2,7 +2,7 @@ from pathlib import Path
import argparse
import datetime as dt
from pfbudget.graph import monthly
from pfbudget.graph import average, discrete, monthly
from pfbudget.transactions import load_transactions, save_transactions
import pfbudget.tools as tools
@ -123,7 +123,6 @@ def vacation(state, args):
date(2019, 12, 23), date(2020, 1, 2)
date(2020, 7, 1), date(2020, 7, 30)
"""
print(args)
if args.option == "list":
print(state.vacations)
elif args.option == "remove":
@ -144,7 +143,10 @@ def status(state, args):
def graph(state, args):
monthly(state, start=dt.date(2020, 1, 1), end=dt.date(2020, 12, 31))
if args.option == "monthly":
monthly(state, args.start, args.end)
elif args.option == "discrete":
discrete(state, args.start, args.end)
if __name__ == "__main__":
@ -168,11 +170,9 @@ if __name__ == "__main__":
p_init.add_argument("raw", help="the raw data dir")
p_init.add_argument("data", help="the parsed data dir")
p_init.set_defaults(func=init)
p_restart.add_argument("--raw", help="new raw data dir")
p_restart.add_argument("--data", help="new parsed data dir")
p_restart.set_defaults(func=restart)
p_backup.add_argument(
"option",
@ -197,6 +197,19 @@ if __name__ == "__main__":
"pos", help="position of vacation to remove", type=int, nargs=1
)
p_graph.add_argument(
"option",
type=str,
choices=["monthly", "discrete"],
nargs="?",
default="monthly",
help="graph option help",
)
p_graph.add_argument("start", type=str, nargs="?", help="graph start date")
p_graph.add_argument("end", type=str, nargs="?", help="graph end date")
p_init.set_defaults(func=init)
p_restart.set_defaults(func=restart)
p_backup.set_defaults(func=backup)
p_parse.set_defaults(func=parse)
p_vacation.set_defaults(func=vacation)

View File

@ -48,11 +48,15 @@ def get_income_categories():
def get_fixed_expenses():
return [Utilities.name, Commute.name]
return [Utilities.name]
def get_required_expenses():
return [Groceries.name]
return [Groceries.name, Commute.name]
def get_health_expenses():
return [Medical.name]
def get_discretionary_expenses():
@ -64,6 +68,7 @@ def get_discretionary_expenses():
*get_income_categories(),
*get_fixed_expenses(),
*get_required_expenses(),
*get_health_expenses(),
Investment.name,
Null.name,
]
@ -212,3 +217,8 @@ class Investment(Categories):
name = "Investment"
regex = [c("subscrition")]
banks = ["BankC"]
class Medical(Categories):
name = "Medical"
regex = [c("hospital", "pharmacy")]

View File

@ -5,26 +5,33 @@ from .categories import (
get_income_categories,
get_fixed_expenses,
get_required_expenses,
get_health_expenses,
get_discretionary_expenses,
)
from .transactions import load_transactions, daterange, by_category, by_month
from .transactions import (
load_transactions,
daterange,
by_category,
by_month,
by_month_and_category,
)
def monthly(state, start, end):
transactions = load_transactions(state.data_dir)
if not start:
start = transactions[0].date
if not end:
end = transactions[-1].date
monthly_transactions = by_month(transactions, start, end)
monthly_transactions_by_categories = {}
income, fixed, required, discretionary = [], [], [], []
income, fixed, required, health, discretionary = [], [], [], [], []
monthly_transactions_by_categories = by_month_and_category(transactions, start, end)
for month, transactions in monthly_transactions.items():
monthly_transactions_by_categories[month] = by_category(transactions)
for _, transactions_by_category in monthly_transactions_by_categories.items():
income.append(
sum(
float(t.value)
for category, transactions in monthly_transactions_by_categories[
month
].items()
for category, transactions in transactions_by_category.items()
if transactions and category in get_income_categories()
for t in transactions
)
@ -32,9 +39,7 @@ def monthly(state, start, end):
fixed.append(
sum(
-float(t.value)
for category, transactions in monthly_transactions_by_categories[
month
].items()
for category, transactions in transactions_by_category.items()
if transactions and category in get_fixed_expenses()
for t in transactions
)
@ -42,19 +47,23 @@ def monthly(state, start, end):
required.append(
sum(
-float(t.value)
for category, transactions in monthly_transactions_by_categories[
month
].items()
for category, transactions in transactions_by_category.items()
if transactions and category in get_required_expenses()
for t in transactions
)
)
health.append(
sum(
-float(t.value)
for category, transactions in transactions_by_category.items()
if transactions and category in get_health_expenses()
for t in transactions
)
)
discretionary.append(
sum(
-float(t.value)
for category, transactions in monthly_transactions_by_categories[
month
].items()
for category, transactions in transactions_by_category.items()
if transactions and category in get_discretionary_expenses()
for t in transactions
)
@ -65,8 +74,166 @@ def monthly(state, start, end):
daterange(start, end, "month"),
fixed,
required,
health,
discretionary,
labels=["Fixed", "Required", "Discretionary"],
labels=["Fixed", "Required", "Health", "Discretionary"],
)
plt.legend(loc="upper left")
plt.show()
def discrete(state, start, end):
transactions = load_transactions(state.data_dir)
if not start:
start = transactions[0].date
if not end:
end = transactions[-1].date
income, fixed, required, health, discretionary = [], [], [], [], []
monthly_transactions_by_categories = by_month_and_category(transactions, start, end)
for _, transactions_by_category in monthly_transactions_by_categories.items():
income.append(
sum(
float(t.value)
for category, transactions in transactions_by_category.items()
if transactions and category in get_income_categories()
for t in transactions
)
)
fixed.append(
sum(
-float(t.value)
for category, transactions in transactions_by_category.items()
if transactions and category in get_fixed_expenses()
for t in transactions
)
)
required.append(
sum(
-float(t.value)
for category, transactions in transactions_by_category.items()
if transactions and category in get_required_expenses()
for t in transactions
)
)
health.append(
sum(
-float(t.value)
for category, transactions in transactions_by_category.items()
if transactions and category in get_health_expenses()
for t in transactions
)
)
d = []
for category, transactions in transactions_by_category.items():
if category in get_discretionary_expenses():
try:
d.append(sum(-float(t.value) for t in transactions))
except TypeError:
d.append(0)
discretionary.append(d)
# transposing discretionary
discretionary = list(map(list, zip(*discretionary)))
plt.plot(daterange(start, end, "month"), income, label="Income")
plt.stackplot(
daterange(start, end, "month"),
fixed,
required,
health,
*discretionary,
labels=["Fixed", "Required", "Health", *get_discretionary_expenses()],
)
plt.legend(loc="upper left")
plt.show()
def average(state, start, end):
transactions = load_transactions(state.data_dir)
income, fixed, required, health, discretionary = [], [], [], [], []
monthly_transactions_by_categories = by_month_and_category(transactions, start, end)
for _, transactions_by_category in monthly_transactions_by_categories.items():
income.append(
sum(
float(t.value)
for category, transactions in transactions_by_category.items()
if transactions and category in get_income_categories()
for t in transactions
)
)
fixed.append(
sum(
-float(t.value)
for category, transactions in transactions_by_category.items()
if transactions and category in get_fixed_expenses()
for t in transactions
)
)
required.append(
sum(
-float(t.value)
for category, transactions in transactions_by_category.items()
if transactions and category in get_required_expenses()
for t in transactions
)
)
health.append(
sum(
-float(t.value)
for category, transactions in transactions_by_category.items()
if transactions and category in get_health_expenses()
for t in transactions
)
)
d = []
for category, transactions in transactions_by_category.items():
if category in get_discretionary_expenses():
try:
d.append(sum(-float(t.value) for t in transactions))
except TypeError:
d.append(0)
discretionary.append(d)
# transposing discretionary
discretionary = list(map(list, zip(*discretionary)))
print(discretionary)
n = len(daterange(start, end, "month"))
avg_income = sum(income) / n
l_avg_income = [avg_income] * n
avg_fixed = [sum(fixed) / n] * n
avg_required = [sum(required) / n] * n
avg_health = [sum(health) / n] * n
avg_discretionary = [[sum(d) / n] * n for d in discretionary]
print(avg_discretionary)
plt.plot(daterange(start, end, "month"), l_avg_income, label=f"Income {avg_income}")
plt.stackplot(
daterange(start, end, "month"),
avg_fixed,
avg_required,
avg_health,
*avg_discretionary,
labels=[
f"Fixed {avg_fixed[0]/avg_income * 100}%",
f"Required {avg_required[0]/avg_income * 100}%",
f"Health {avg_health[0]/avg_income * 100}%",
*[
f"{e} {avg_discretionary[i][0]/avg_income * 100}%"
for i, e in enumerate(get_discretionary_expenses())
],
],
)
plt.legend(bbox_to_anchor=(1, 1), loc="upper left")
plt.show()

View File

@ -127,6 +127,9 @@ class Transactions(list):
def daterange(start, end, period):
if not start or not end:
raise TransactionError("daterange requires start and end")
if period == "year":
r = [d.strftime("%Y") for d in rrule(YEARLY, dtstart=start, until=end)]
elif period == "month":
@ -179,6 +182,16 @@ def by_category(transactions) -> dict:
return transactions_by_category
def by_month_and_category(transactions, start, end) -> dict:
monthly_transactions_by_categories = {}
monthly_transactions = by_month(transactions, start, end)
for month, transactions in monthly_transactions.items():
monthly_transactions_by_categories[month] = by_category(transactions)
return monthly_transactions_by_categories
def load_transactions(data_dir) -> Transactions:
transactions = Transactions()
for df in Path(data_dir).iterdir():