Monday, October 31, 2011

How to create a cost sharing app using Google Spreadsheets

I want to document a solution to a practical problem: a few people may want to share the expenses of various kind in a fair fashion.

The straightforward way is for everyone to just announce their expenses, and collect 1/n parts of them from everyone else. However, it is obvious that this is a very tedious way of doing things: say, 5 people spend 10 euros each on some things, thus each of them will need to give 4 times 2 euro - and receive 4 times 2 euro - the result of all this activity being exactly the same when it has started!

Thus, it's a clear area of optimization. At first I thought about a web app. Google apps could do that. However, I decided to try to be even lazier and to implement the whole thing using Google spreadsheets. The result is a successful "app" which in the main "screen" (the main sheet of the spreadsheet) displays for every person whether they owe money to someone or whether they need to collect money. A person who needs to collect money can chase some of the debtors, and then, once collected, put a note about that. The system will recalculate the debts.

First, the "theory" (if I may call this so:).

We start with a per-person value of how much expense do they incur. The "fairness" means that all of them would have equal expenses - so the target value is the average of the expenses. As one person gives the money to the other one in order to "balance" - this event needs to be recorded in order to decrease the imbalance for both - in different directions.

That's all that is there, really. The rest is the mechanics.

First, let's assume that the users are "normal" people and they do not want to enter the data into the spreadsheet manually (not only it is boring but is also very error prone).

So, we create so called "web form". Pitifully only one instance of this seems to be possible. Anyway, we make a web form with 5 fields:
"My name is": dropdown list with all the names.
"I have made an": dropdown with 2 choices: "expense" and "repayment"
"How many euros": the text input to put the $$$ value in.
"What for?": freeform text field to document the purpose of transaction.
"If you repaid to someone, to whom?": dropdown with the default choice of "---" and then the same names as in the first field.

Having to manually enter the names is somewhat annoying, I haven't figured out the way around it. Anyway, as you try entering the data into this form, you notice it starts to fill the sheet in. You will see the first line of the sheet being: "A>Timestamp |B> I am: | C>I have made a: | D>How many euros ? | E>What for ? | F>If you repaid someone, to whom did you repay ?" (the "A>" is the name of the column, for reference).

Starting from row 2 you will see the actual data. We need to shift the data to row 12 - the rows 2..11 we will need for various aggregate calculations.

The column "K" will hold the labels for them - just for us to remember. The contents of this column, starting from "K2":

Who
Got/Made
Type
Total
DeltaAvg(+/-)
Repaid
Received
Balance

Now, in L2, M2, N2... we need to put in the names - exactly the same way as we have typed them earlier in the web form. Let's say we have four people: Amy, Bob, Carol, Dave. L2 will read Amy, M2 - Bob, N2 - Carol, O2 - Dave; Repeat two more times in the same order. So P2 and T2 will read Amy, etc.

Why the names are repeated three times ? This will become obvious shortly. Go to the third line, and leave L3..O3 empty, then P3..S3 will become "made" and T3..W3 will become "got". Now go to the line 4, and the cells L4..O4 will need to have "expense", and the cells P4..W3 will need to have "repayment".

Now this clarifies the purpose of the three groups - this is a mechanism to untangle the data from the dense web form: each column will only get a value if that particular person was involved in that particular activity.

To calculate the total expenses per person, enter the following formula into L5: =SUM(L$12:L), and copypaste it into M5..W5 - notice that the column will auto-change. This is precisely as we need it. The line 5 will hold the calculated totals per activity per person.

Now let's calculate the average of total expenses per person. Enter the formula into the J6: =average(L5:O5), and put the label into J5 saying "Average total expense" or something similar.

L6 will hold the signed delta between the average and the expenses for Amy: =$J$6-L$5; copy-paste this same formula to M6..O6.

L7 will hold the amount of how much Amy has repaid to others, so the formula will be naturally the sum of her repayments: =SUM(P12:P); similarly to previous value - copy-paste this formula into M7..O7.

L8 will hold the amount of how much Amy has received from others in repayments. So, the formula is: =SUM(T12:T); again copy-paste this formula into M8..O8 - the column will auto-update to correct value.

L9 will hold the final "balance" for Amy - if she needs to pay, the value is positive, if she needs to get money from others, it will be negative. The formula is: =L6-L7+L8
and similarly needs to be copy-pasted into M9..O9.

We are almost there - now we need to actually populate the rows with the monetary values... One problem: the rows with the actual data are being dynamically added, how do we add the formulae there ? The answer is with the "arrayformula" - it autoexpands down as needed.

So, put the following formula into L12: =ARRAYFORMULA(IF($B12:$B=L$2,IF($C12:$C=L$4,$D12:$D,0),0))

This says that if the name of the participant matches the name for this column and the name of the activity matches - then the number entered will appear here, else it will be zero.

Copy this formula to M12..S12. This way we make sparse tables for two activities - entering expenses, and entering the repayments to others. If you move to L13, you will see the following formula autopopulated: =CONTINUE(L12, 2, 1). Neat, huh ?

Now we need to populate the third group - the synthetic one that captures the amounts the person *receives* in repayments. For this we need just to slightly modify the above arrayformula, and put it into T12: =ARRAYFORMULA(IF($F12:$F=T$2, IF($C12:$C=T$4,$D12:$D,0), 0))

You see that the only thing changed is the column from the form entry with which we compare.

Copy-paste this formula to U12..W12. We are almost there.

This completes the "brains" of the application, and the line 9 is already usable,
but we can make it more user friendly. Rename the current only sheet into "Expenses" and lock it. Create a new sheet called "Dashboard".

Go to cell B3 in this spreadsheet, and enter the formula: =Expenses!L2 - this will autopopulate the first name. Copy-paste this formula to C3..E3 - this will populate the other names.

Now we need to tell the status of the balances of these people.

Go to B5, and enter this formula:

=IF(Expenses!L9>0;concatenate("Needs to pay " , Expenses!L9, " euros"); IF(Expenses!L9<0; Concatenate("Needs to take ", -Expenses!L9, " euros"); ""))

This will translate the sign into the appropriate action for the person.

Last touch: we can liven the actions up, if we color the "needs to pay" actions with one color, and "needs to take" with another color.

Right-click the B5, and select "Conditional formatting".

Add a new rule with "text contains" "pay" and select red background and green color, and another rule with "text contains" "take" and select green background and red color.

Copy-paste the cell from B5 to C5..E5.

Lock this sheet too. You are done and now have an expense tracking application fully embedded into google spreadsheets.

Use the Web form to enter the expenses and the notes of repayment - and use the "Dashboard" as an indicator of ongoing balances - you can set your own rules when you need to repay to others, e.g. when the amount of money reaches over certain threshold.

There is also in theory a possible loss of precision that might happen as the total expense grows. If you think this is a problem - write your solution. I think I know how to solve it, but I don't want to spoil the fun for you!

Oh - and if you find this writeup useful - please leave a comment!