BillSplitterMDS

build codecov Documentation Python License: MIT

A Python package to help groups split trip bills fairly among participants.

Summary

When a group of people travel together, different people often pay for different expenses throughout the trip. At the end, it can be complicated to figure out how much each person actually owes and how money should be transferred to settle all debts fairly. BillSplitterMDS simplifies this process by reading expense data from a CSV file and calculating the optimal transfers needed to balance everyone’s contributions.

Create the project environment

Please run the following command in bash terminal:

conda env create -f environment.yml

After the environment is created, activate it through the following:

conda activate billsplittermds

Installation

Please change the directory to the root directory and run:

pip install -i https://test.pypi.org/simple/ billsplittermds

If the above does not work, use the following (recommended):

pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ billsplittermds

Running the function tests

To verify that each of the functions work appropriately, function tests are written in python scripts. To run these tests go to the root project directory in the terminal and write the following command:

pytest tests/

Build documentation

Please go to the root directory first and run:

quartodoc build

Following that, run another command:

quarto preview

Then there will be a window popped up in your browser showing the rendered documentation webpage.

Deploy documentation

GitHub pages have been set up to automate deployment. Please go to this URL to see the rendered homepage of our project:

https://ubc-mds.github.io/BillSplitterMDS/

To see the documentation page of our 4 functions, please visit the following, or click the Reference tab at the top of the homepage:

https://ubc-mds.github.io/BillSplitterMDS/reference/

Functions

The package provides four main functions:

  • load_validate_data(csv_path): Reads a CSV file containing trip expense data and validates that tax and tip percentages are within reasonable ranges. Returns a validated pandas DataFrame.

  • split_by_item(valid_df): Calculates how much each person should pay based on the items they shared. Computes individual costs by dividing item prices (with tax and tip) among sharers, then aggregates totals per person.

  • individual_total_payments(valid_df): Calculates the total amount each person actually paid during the trip by summing up all bills paid by each person.

  • amount_to_transfer(should_pay_df, actually_paid_df): Determines the money transfers needed to settle all debts. Takes the outputs of split_by_item() and individual_total_payments() to compute who owes money to whom.

Input CSV Format

The CSV file should have the following columns:

Column Description Example
payer Name of person who paid Amy
item_name Description of expense Pasta
item_price Price before tax/tip 18
shared_by Names separated by semicolons Amy;Ben
tax_pct Tax as decimal 0.05
tip_pct Tip as decimal 0.12

Example CSV:

payer,item_name,item_price,shared_by,tax_pct,tip_pct
Amy,Pasta,18,Amy,0.05,0.12
Sam,Taxi,33,Amy;Ben;Sam;Joe,0.05,0.00
Ben,double-room,230,Amy;Ben,0.12,0.04

Usage

from billsplittermds import (
    load_validate_data,
    split_by_item,
    individual_total_payments,
    amount_to_transfer
)

# Load and validate the expense data
df = load_validate_data("trip_expenses.csv")

# Calculate what each person should pay
should_pay = split_by_item(df)

# Calculate what each person actually paid
actually_paid = individual_total_payments(df)

# Get the transfers needed to settle up
transfers = amount_to_transfer(should_pay, actually_paid)
print(transfers)

Python Ecosystem

There are several expense-splitting apps and packages available:

  • Splitwise - A popular mobile/web app for splitting bills (not a Python package)
  • split-bill - A simple Python package for equal bill splitting

BillSplitterMDS differs from these by focusing specifically on trip expenses where items can be shared by different subsets of people, with configurable tax and tip percentages per item.

Contributors

  • Quan Hoang
  • Jessie Liang
  • Norton Yu
  • Omar Ramos

License

MIT License

Full Usage Demonstration

Here’s a complete runnable example showing how to use all four functions in the package.

Step 1: Prepare Sample Data

First, let’s create a sample CSV file with trip expenses:

import pandas as pd

# Create sample trip expense data
data = {
    'payer': ['Amy', 'Sam', 'Ben', 'Amy'],
    'item_name': ['Pasta Dinner', 'Taxi to Airport', 'Hotel Room', 'Drinks'],
    'item_price': [18.0, 40.0, 200.0, 30.0],
    'shared_by': ['Amy', 'Amy;Sam;Ben', 'Amy;Ben', 'Amy;Sam;Ben'],
    'tax_pct': [0.08, 0.08, 0.10, 0.08],
    'tip_pct': [0.15, 0.10, 0.00, 0.18]
}

# Save to CSV
df = pd.DataFrame(data)
df.to_csv('trip_expenses.csv', index=False)
print("Sample CSV created:")
print(df.to_string(index=False))
Sample CSV created:
payer       item_name  item_price   shared_by  tax_pct  tip_pct
  Amy    Pasta Dinner        18.0         Amy     0.08     0.15
  Sam Taxi to Airport        40.0 Amy;Sam;Ben     0.08     0.10
  Ben      Hotel Room       200.0     Amy;Ben     0.10     0.00
  Amy          Drinks        30.0 Amy;Sam;Ben     0.08     0.18

Step 2: Load and Validate Data

Use load_validate_data() to read the CSV and validate that all values are within acceptable ranges:

from billsplittermds import load_validate_data

# Load and validate the expense data
valid_df = load_validate_data('trip_expenses.csv')
print("Data loaded and validated successfully!")
print(f"  - {len(valid_df)} expense records")
print(f"  - Tax range: {valid_df['tax_pct'].min():.0%} to {valid_df['tax_pct'].max():.0%}")
print(f"  - Tip range: {valid_df['tip_pct'].min():.0%} to {valid_df['tip_pct'].max():.0%}")
Data loaded and validated successfully!
  - 4 expense records
  - Tax range: 8% to 10%
  - Tip range: 0% to 18%

Step 3: Calculate What Each Person Should Pay

Use split_by_item() to calculate each person’s fair share based on which items they consumed:

from billsplittermds import split_by_item

# Calculate what each person should pay
should_pay = split_by_item(valid_df)
print("\nWhat each person SHOULD pay (based on items consumed):")
print(should_pay.to_string(index=False))
print(f"\nTotal: ${should_pay['should_pay'].sum():.2f}")

What each person SHOULD pay (based on items consumed):
name  should_pay
 Amy  160.473333
 Sam   28.333333
 Ben  138.333333

Total: $327.14

Step 4: Calculate What Each Person Actually Paid

Use individual_total_payments() to sum up what each person actually spent:

from billsplittermds import individual_total_payments

# Calculate what each person actually paid
actually_paid = individual_total_payments(valid_df)
print("\nWhat each person ACTUALLY paid (who paid the bills):")
print(actually_paid.to_string(index=False))
print(f"\nTotal: ${actually_paid['actually_paid'].sum():.2f}")

What each person ACTUALLY paid (who paid the bills):
name  actually_paid
 Amy          59.94
 Ben         220.00
 Sam          47.20

Total: $327.14

Step 5: Calculate Transfers Needed

Use amount_to_transfer() to determine who owes money to whom:

from billsplittermds import amount_to_transfer

# Calculate the transfers needed to settle up
transfers = amount_to_transfer(should_pay, actually_paid)
print("\nTransfers needed to settle all debts:")
if len(transfers) == 0:
    print("  No transfers needed - everyone is settled!")
else:
    for _, row in transfers.iterrows():
        print(f"  {row['sender']} -> {row['receiver']}: ${row['amount']:.2f}")

Transfers needed to settle all debts:
  Amy -> Ben: $81.67
  Amy -> Sam: $18.87

Cleanup

# Clean up the sample file
import os
os.remove('trip_expenses.csv')
print("\nDemo complete! Sample file cleaned up.")

Demo complete! Sample file cleaned up.