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:
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:
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.
from billsplittermds import ( load_validate_data, split_by_item, individual_total_payments, amount_to_transfer)# Load and validate the expense datadf = load_validate_data("trip_expenses.csv")# Calculate what each person should payshould_pay = split_by_item(df)# Calculate what each person actually paidactually_paid = individual_total_payments(df)# Get the transfers needed to settle uptransfers = 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:
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 datavalid_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 payshould_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 paidactually_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 uptransfers = amount_to_transfer(should_pay, actually_paid)print("\nTransfers needed to settle all debts:")iflen(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 fileimport osos.remove('trip_expenses.csv')print("\nDemo complete! Sample file cleaned up.")