Abstract
Learn how to construct and visualize business plans in R. This package provides some tools that, although sometimes rather simple, can be used to build an interactive model of your business activity.
The businessPlanR
package is the result of the Cultural Commons Collecting Society’s
(C3S) need for a comprehensive business plan in order to apply for
admission at the DPMA. Business plans can be a bit like looking into a
crystal ball, especially when you’re trying something completely new
with your business. The more we got into the interrelated details, the
more we wanted a tool that would allow us to model very specific aspects
of our business case, using data we had gathered from surveys of our
members, and to be able to change any aspect of the whole calculation
and see how it affected everything else. We also wanted to see what it
would look like if, for example, things went on like this for the next
five or ten years, rather than just the three years we were supposed to
deliver.
That way we could easily look at which factors had the most impact on our plans and which were less critical. Flexibility was key, so this package doesn’t try to impose too many constraints on a business plan. On the other hand, this probably means that a bit more work is needed to get any results at all.
We hope that the package has remained generic enough for others to find it useful.
The package provides some S4 classes, methods and a bunch of functions for repeating tasks. Things have been kept quite abstract to be versatile. For example, when it comes to the flow of money, we basically only distinguish between money coming in (revenue) and money going out (expense). A this level we don’t distinguish between, e. g., costs, investments or interest. This is because these classifications can change between contexts, while the amount of money remains the same. So it’s up to us to treat a particular expense as an investment in our depreciation plan. This will become clearer as you work through the example.
The main workflow is
These objects are used for the calculations, the lists define where the results of the calculations are visible, and the types simply ensure that we always know what specific transaction we are dealing with.
For this vignette, let’s take an example case we want to model, a small greengrocer’s in Germany called Saftladen. It is planned to open its doors in 2024 and we would like to plan its first three years of business. The example is set in Germany because we don’t know about specific for business plan practices in other countries. Adapt it to your needs.
Our new Saftladen will sell fruit (»Obst«) and vegetables (»Gemüse«). It will also offer T-shirts with its logo (»Merchandise«) and it plans to take out a loan (»Kreditaufnahme«). These are all sources of revenue for our business, so our plan starts by defining them as types of revenue:
set_types(
types=list(
"Obst"=rgb(0.1,0.9,0.2,0.8),
"Gemüse"=rgb(0.2,0.9,0.4,0.8),
"Merchandise"=rgb(0.3,0.8,0.3,0.8),
"Kreditaufnahme"=rgb(0.3,0.7,0.2,0.8)
),class="revenue",
name="Saftladen"
)
»What about those rgb()
colors?« you might ask.
Transaction types are defined as a named list of colors. These colors
are used by some of the package’s plotting methods to provide a quick
visual overview. If you don’t use them, just set them all to white or
whatever. The really important thing here is that each income
source must have a unique named entry as a revenue here. This
also forces you to think about these sources early on.
This set of revenue types is named "Saftladen"
. The name
allows you to define several of these sets in parallel, e. g. if you’re
modeling two businesses at the same time.
Note that this is not assigned to an object. Instead,
set_types()
is actually a wrapper for
options()
and adds this information to the options of the
current session, as a named list called businessPlanR
:
options("businessPlanR")
## $businessPlanR
## $businessPlanR$Saftladen
## $businessPlanR$Saftladen$types
## $businessPlanR$Saftladen$types$revenue
## $businessPlanR$Saftladen$types$revenue$Obst
## [1] "#1AE633CC"
##
## $businessPlanR$Saftladen$types$revenue$Gemüse
## [1] "#33E666CC"
##
## $businessPlanR$Saftladen$types$revenue$Merchandise
## [1] "#4DCC4DCC"
##
## $businessPlanR$Saftladen$types$revenue$Kreditaufnahme
## [1] "#4DB333CC"
As for our expenses, we’re expecting to pay our employees (»Löhne und Gehälter«), social security contributions (»Sozialabgaben«), purchase goods (»Waren«), infrastructure (»Infrastruktur«), depreciation (»Abschreibung«), loan repayment (»Kredittilgung«), interest (»Zinsen«), and taxes (»Steuern«):
set_types(
types=list(
"Löhne und Gehälter"=rgb(0.7,0.2,0.4,0.8),
"Sozialabgaben"=rgb(0.7,0.2,0.4,0.8),
"Waren"=rgb(0.7,0.3,0.5,0.8),
"Infrastruktur"=rgb(0.75,0.2,0.4,0.8),
"Abschreibung"=rgb(0.9,0.2,0.4,0.8),
"Kredittilgung"=rgb(0.9,0.2,0.4,0.8),
"Zinsen"=rgb(0.9,0.2,0.4,0.8),
"Steuern"=rgb(0.9,0.2,0.4,0.8)
),class="expense",
name="Saftladen"
)
Consequently, each source of cash outflow must have a uniquely named entry as an expense here.
Before we look at individual transactions, we create an object of
class operations
. You might think of this as the closest
thing you have to the tabs in Excel spreadsheets you may have worked
with in the past, as it collects all sorts of financial movements in a
structured way.
This operations
object is like your complete business
case, and it’s what most of the methods in this package expect as input:
You throw them everything you’ve got so they can pick out the relevant
bits.
This object also defines the time period your business plan will cover:
<- operations(
saftladen_2024_2026 period=c("2024.01", "2026.12")
)
businessPlanR
uses months as the smallest time
resolution.
With all revenue and expense types already set, we can now define the
corresponding financial transactions. The methods we’ll use first are
revenue()
and expense()
:
# By default, revenues are repeated every month until
# the value changes.
<- revenue(
rev_merch_2024_2026 type="Merchandise",
category="Merch",
name="T-Shirts",
valid_types="Saftladen",
"2024.03"=30,
"2024.08"=40,
"2025.03"=50,
"2025.09"=70,
"2026.02"=90,
"2026.07"=100,
"2026.10"=110
)
# We plan to produce our T-shirts in January each year.
# Since these expenses should not be repeated every month,
# we can set missing="0"; alternatives are "rep" for repeat
# (default) or "interpol" for automatic interpolation.
<- expense(
exp_merch_2024_2026 type="Waren",
category="Merch",
name="T-Shirts",
missing="0",
valid_types="Saftladen",
"2024.01"=200,
"2025.01"=400,
"2026.01"=600
)
Let’s look at the financial values of these objects. This can be done
using get_value()
:
get_value(rev_merch_2024_2026)
## type category name 2024.03 2024.04 2024.05 2024.06 2024.07 2024.08
## 1 Merchandise Merch T-Shirts 30 30 30 30 30 40
## 2024.09 2024.10 2024.11 2024.12 2025.01 2025.02 2025.03 2025.04 2025.05 2025.06
## 1 40 40 40 40 40 40 50 50 50 50
## 2025.07 2025.08 2025.09 2025.10 2025.11 2025.12 2026.01 2026.02 2026.03 2026.04
## 1 50 50 70 70 70 70 70 90 90 90
## 2026.05 2026.06 2026.07 2026.08 2026.09 2026.10
## 1 90 90 100 100 100 110
get_value(exp_merch_2024_2026)
## type category name 2024.01 2024.02 2024.03 2024.04 2024.05 2024.06 2024.07
## 1 Waren Merch T-Shirts 200 0 0 0 0 0 0
## 2024.08 2024.09 2024.10 2024.11 2024.12 2025.01 2025.02 2025.03 2025.04 2025.05
## 1 0 0 0 0 0 400 0 0 0 0
## 2025.06 2025.07 2025.08 2025.09 2025.10 2025.11 2025.12 2026.01
## 1 0 0 0 0 0 0 0 600
As you can see, revenue()
and expense()
always calculate one entry per month. You can control these calculations
directly via the missing
argument (see the comment in the
example). It is also possible to set a value with per_use
,
which will not use the numbers as actual financial values but, the
number of times that value has been used. So if our shirts sell for 10
bucks and we start selling three shirts a month, this will give us the
same numbers:
# By default, the sales are repeated each month until
# the value changes.
<- revenue(
rev_merch_2024_2026 type="Merchandise",
category="Merch",
name="T-Shirts",
per_use=10,
valid_types="Saftladen",
"2024.03"=3,
"2024.08"=4,
"2025.03"=5,
"2025.09"=7,
"2026.02"=9,
"2026.07"=10,
"2026.10"=11
)get_value(rev_merch_2024_2026)
## type category name 2024.03 2024.04 2024.05 2024.06 2024.07 2024.08
## 1 Merchandise Merch T-Shirts 30 30 30 30 30 40
## 2024.09 2024.10 2024.11 2024.12 2025.01 2025.02 2025.03 2025.04 2025.05 2025.06
## 1 40 40 40 40 40 40 50 50 50 50
## 2025.07 2025.08 2025.09 2025.10 2025.11 2025.12 2026.01 2026.02 2026.03 2026.04
## 1 50 50 70 70 70 70 70 90 90 90
## 2026.05 2026.06 2026.07 2026.08 2026.09 2026.10
## 1 90 90 100 100 100 110
The get_value()
method can also show quarterly and
yearly totals ("month"
is just the default):
get_value(rev_merch_2024_2026, resolution="quarter")
## type category name 2024.Q1 2024.Q2 2024.Q3 2024.Q4 2025.Q1 2025.Q2
## 1 Merchandise Merch T-Shirts 30 90 110 120 130 150
## 2025.Q3 2025.Q4 2026.Q1 2026.Q2 2026.Q3 2026.Q4
## 1 170 210 250 270 300 110
get_value(exp_merch_2024_2026, resolution="year")
## type category name 2024 2025 2026
## 1 Waren Merch T-Shirts 200 400 600
operations
objectAt the moment, our two transactions are not yet part of our business
case. We need to add them to our operations
object:
update_operations(saftladen_2024_2026) <- rev_merch_2024_2026
update_operations(saftladen_2024_2026) <- exp_merch_2024_2026
Note that both transactions do not cover the full period we have
defined: Missing months are assumed to have a value of zero. Also, we
don’t really need individual transaction objects, we can assign the
output of both revenue()
and expense()
directly to update_operations()<-
or even
operations(...)
.
With these few functions, you should already be able to write your
own wrapper functions to implement many of the financial movements you
need to cover. Because you can assign financial values to each month
individually, you are free to write your own algorithms for whatever
complex cash flows you assume, and combine all the results in an
operations
object.
There are also some additional functions to help you with very common tasks (or at least we’ve come across them so often that we’ve written functions for them):
first_last()
creates a list of two elements from
January of the first given year to December of the last, both with the
same amount specified.growth()
calculates the differences between successive
values in a numerical vector.regularly()
creates lists of recurring financial
transactions for given years (useful, for example, to define quarterly
transactions, which may even have different amounts).regularly_delayed()
extends regularly()
to
cover cases where you know the annual total of transactions, but they
don’t start at the beginning of the year.calc_staff()
calculates the number of staff needed to
complete a given task.fin_needs()
tries to estimate your capital needs from
the cash flow.There are also some additional object classes that cover additional types of transactions:
loan()
defines loans with interest and repayment
schedule.depreciation()
defines the depreciation schedule for
defined items, i. e. your inventory.transaction_plan()
calculates complete repayment and
depreciation schedules from single or multiple loan
or
depreciation
objects according to the specifics defined
with these objects.You can compare the transaction_plan
class to
operations
, as both are structured collections of multiple
objects. There’s also an update_plan()<-
method to add
or replace objects in existing transaction_plan
collections, similar to update_operations()<-
.
Our goal, of course, it to have nice tables for our income statement
or liquidity plan. Obviously, we can’t just turn the
operations
object into a table for this, but have to decide
which of the statements are relevant for a particular type of table.
What’s more, these are usually not just single large tables, but are
structured into (probably even nested) sections.
So we need to define the structure for the tables we need, including
the relevant transactions and their place in a table. This is done with
the functions table_model()
(which can do some validity
checking) and model_node()
(which is used »inside«
table_model()
for nested models. Don’t wonder too much
about the term model here, we could have called it
template or something.
Let’s sketch a very simple model that subtracts expenses from revenues to get an income statement:
<- table_model(
saftladen_inc_stamt # Gross income from various sources
"Bruttoeinnahmen"=model_node(
revenue=c(
"Obst",
"Gemüse",
"Merchandise"
)
),# Gross revenue, i.e. minus costs of goods
"Bruttoertrag"=model_node(
carry="Bruttoeinnahmen",
expense=c(
"Waren"
)
),# Gross profit, i.e. minus operating and depreciation expenses
"Betriebsergebnis"=model_node(
carry="Bruttoertrag",
expense=c(
"Löhne und Gehälter",
"Sozialabgaben",
"Infrastruktur",
"Abschreibung"
)
),# Operating profit, i.e. minus interest expenses
"Gewinn vor Steuern"=model_node(
carry="Betriebsergebnis",
expense=c(
"Zinsen"
)
),# Profit after taxes
"Nettogewinn"=model_node(
carry="Gewinn vor Steuern",
expense=c(
"Steuern"
)
),valid_types="Saftladen"
)
As you can see, we’re reusing some of the type names we defined for
revenue
and expense
. Each type listed is used
to calculate the sum for the node (as we call a named list in this
context) in which it appears, with revenues added and expenses
subtracted.
All entries must be named, here »Bruttoeinnahmen« (gross
revenue) and »Bruttoertrag« (gross profit), as these names will
be used in the actual table. Each node could contain child nodes (just
add named objects with model_node()
), so more complex
hierarchical structures can be designed, but let’s keep it simple for
now.
Providing valid_types="Saftladen"
allows
table_model()
to check that all revenues and expenses have
actually been defined, to avoid typos and missing values in our tables.
Also note the use of carry="Bruttoeinnahmen"
in the second
node, which references the previous one. The effect of this is that the
calculated sum of the referenced node is used as the initial value of
the referencing node when calculating subtotals for each table section,
before any revenues are added or expenses subtracted.
With an initial model defined, we can now condense our
operations
object into the tables we need:
condense(
saftladen_2024_2026,model=saftladen_inc_stamt
)
## Position Type 2024 2025 2026
## 1 Bruttoeinnahmen Merchandise 350 660 930
## 2 Bruttoeinnahmen Sum 350 660 930
## 3 Bruttoertrag Waren -200 -400 -600
## 4 Bruttoertrag Sum 150 260 330
## 5 Betriebsergebnis Sum 150 260 330
## 6 Gewinn vor Steuern Sum 150 260 330
## 7 Nettogewinn Sum 150 260 330
Even from this very primitive example, you can see how
condense()
calculates subtotals (where Type is
Sum) for each section (Position) of our model. Many
methods in this package support the resolution
argument, as
we’ve already seen with get_value()
, and so does
condense()
:
condense(
saftladen_2024_2026,model=saftladen_inc_stamt,
resolution="quarter"
)
## Position Type 2024.Q1 2024.Q2 2024.Q3 2024.Q4 2025.Q1 2025.Q2
## 1 Bruttoeinnahmen Merchandise 30 90 110 120 130 150
## 2 Bruttoeinnahmen Sum 30 90 110 120 130 150
## 3 Bruttoertrag Waren -200 0 0 0 -400 0
## 4 Bruttoertrag Sum -170 90 110 120 -270 150
## 5 Betriebsergebnis Sum -170 90 110 120 -270 150
## 6 Gewinn vor Steuern Sum -170 90 110 120 -270 150
## 7 Nettogewinn Sum -170 90 110 120 -270 150
## 2025.Q3 2025.Q4 2026.Q1 2026.Q2 2026.Q3 2026.Q4
## 1 170 210 250 270 300 110
## 2 170 210 250 270 300 110
## 3 0 0 -600 0 0 0
## 4 170 210 -350 270 300 110
## 5 170 210 -350 270 300 110
## 6 170 210 -350 270 300 110
## 7 170 210 -350 270 300 110
This is just a raw data frame. But we can also use the
kable_bpR()
method on operations
objects along
with a table model to get nicely formatted tables in RMarkdown. The
methods make heavy use of the kableExtra
package:
kable_bpR(
saftladen_2024_2026,model=saftladen_inc_stamt,
resolution="year"
)
2024 | 2025 | 2026 | |
---|---|---|---|
Bruttoeinnahmen | |||
Merchandise | 350 € | 660 € | 930 € |
350 € | 660 € | 930 € | |
Bruttoertrag | |||
Waren | -200 € | -400 € | -600 € |
150 € | 260 € | 330 € | |
Betriebsergebnis | |||
150 € | 260 € | 330 € | |
Gewinn vor Steuern | |||
150 € | 260 € | 330 € | |
Nettogewinn | |||
150 € | 260 € | 330 € |
With only one revenue and expense stream, our plan is still not very meaningful. So let’s add some more transactions:
# Apples
update_operations(saftladen_2024_2026) <- revenue(
type="Obst",
category="Obst",
name="Äpfel",
valid_types="Saftladen",
"2024.01"=2200,
"2024.08"=3000,
"2024.09"=3400,
"2024.12"=2600,
"2025.01"=2300,
"2025.08"=3100,
"2025.09"=3500,
"2025.12"=2700,
"2026.01"=2400,
"2026.08"=3200,
"2026.09"=3600,
"2026.12"=2800
)update_operations(saftladen_2024_2026) <- expense(
type="Waren",
category="Obst",
name="Äpfel",
valid_types="Saftladen",
"2024.01"=1650,
"2024.08"=2250,
"2024.09"=2550,
"2024.12"=1950,
"2025.01"=1725,
"2025.08"=2325,
"2025.09"=2625,
"2025.12"=2025,
"2026.01"=1800,
"2026.08"=2400,
"2026.09"=2700,
"2026.12"=2100
)# Grapes
update_operations(saftladen_2024_2026) <- revenue(
type="Obst",
category="Obst",
name="Trauben",
missing="interpol",
valid_types="Saftladen",
"2024.01"=480,
"2025.01"=590,
"2026.01"=730,
"2026.12"=820
)update_operations(saftladen_2024_2026) <- expense(
type="Waren",
category="Obst",
name="Trauben",
missing="interpol",
valid_types="Saftladen",
"2024.01"=340,
"2025.01"=410,
"2026.01"=510,
"2026.12"=580
)# Potatoes
update_operations(saftladen_2024_2026) <- revenue(
type="Gemüse",
category="Gemüse",
name="Kartoffeln",
missing="interpol",
valid_types="Saftladen",
"2024.01"=2440,
"2025.01"=2670,
"2026.01"=2800,
"2026.12"=2980
)update_operations(saftladen_2024_2026) <- expense(
type="Waren",
category="Gemüse",
name="Kartoffeln",
missing="interpol",
valid_types="Saftladen",
"2024.01"=1950,
"2025.01"=2130,
"2026.01"=2240,
"2026.12"=2400
)
Let’s look at the updated table:
kable_bpR(
saftladen_2024_2026,model=saftladen_inc_stamt,
resolution="year"
)
2024 | 2025 | 2026 | |
---|---|---|---|
Bruttoeinnahmen | |||
Obst | 37.565 € | 40.250 € | 42.900 € |
Gemüse | 30.545 € | 32.755 € | 34.680 € |
Merchandise | 350 € | 660 € | 930 € |
68.460 € | 73.665 € | 78.510 € | |
Bruttoertrag | |||
Waren | -52.455 € | -56.335 € | -60.180 € |
16.005 € | 17.330 € | 18.330 € | |
Betriebsergebnis | |||
16.005 € | 17.330 € | 18.330 € | |
Gewinn vor Steuern | |||
16.005 € | 17.330 € | 18.330 € | |
Nettogewinn | |||
16.005 € | 17.330 € | 18.330 € |
It now summarises all the goods we have defined so far, grouped by
type
and placed according to our model. If you want to
examine all the data in more detail, you can use the
detailed
view of the table:
kable_bpR(
saftladen_2024_2026,model=saftladen_inc_stamt,
resolution="year",
detailed=TRUE
)
2024 | 2025 | 2026 | |||
---|---|---|---|---|---|
Revenue | |||||
Merchandise | Merch | T-Shirts | 350 € | 660 € | 930 € |
Obst | Obst | Äpfel | 31.200 € | 32.400 € | 33.600 € |
Obst | Obst | Trauben | 6.365 € | 7.850 € | 9.300 € |
Gemüse | Gemüse | Kartoffeln | 30.545 € | 32.755 € | 34.680 € |
Exepense | |||||
Waren | Merch | T-Shirts | -200 € | -400 € | -600 € |
Waren | Obst | Äpfel | -23.400 € | -24.300 € | -25.200 € |
Waren | Obst | Trauben | -4.465 € | -5.470 € | -6.540 € |
Waren | Gemüse | Kartoffeln | -24.390 € | -26.165 € | -27.840 € |
As mentioned earlier, the package supports some common special cases of transaction plans. The idea here is to define a complete investment or loan repayment plan and let the methods of this package select relevant aspects of these plans for different tables, such as the profit and loss statement or the liquidity plan.
Let’s say our shop needs two new cash registers. We’ll buy one for 450 € in the first month of our plan, and the second one a year and a half later. Let the amortisation period for this type of investment be 72 months:
<- depreciation(
dep_cashreg1 type="Abschreibung",
category="Laden",
name="Kasse 1",
amount=450,
obsolete=72,
invest_month="2024.01",
valid_types="Saftladen"
)<- depreciation(
dep_cashreg2 type="Abschreibung",
category="Laden",
name="Kasse 2",
amount=450,
obsolete=72,
invest_month="2025.07",
valid_types="Saftladen"
)
The objects now contain the full investment and depreciation plans
for both cash registers. We can add both aspects to our operations
object separately, but we need to tell update_operations()
how to handle them:
update_operations(
saftladen_2024_2026,as_transaction=list(
c(
to="expense",
aspect="investment",
valid_types="Saftladen",
type="Infrastruktur"
),c(
to="expense",
aspect="depreciation",
valid_types="Saftladen",
type="Abschreibung"
)
)<- dep_cashreg1
) update_operations(
saftladen_2024_2026,as_transaction=list(
c(
to="expense",
aspect="investment",
valid_types="Saftladen",
type="Infrastruktur"
),c(
to="expense",
aspect="depreciation",
valid_types="Saftladen",
type="Abschreibung"
)
)<- dep_cashreg2
)
kable_bpR(
saftladen_2024_2026,model=saftladen_inc_stamt,
resolution="year"
)
2024 | 2025 | 2026 | |
---|---|---|---|
Bruttoeinnahmen | |||
Obst | 37.565 € | 40.250 € | 42.900 € |
Gemüse | 30.545 € | 32.755 € | 34.680 € |
Merchandise | 350 € | 660 € | 930 € |
68.460 € | 73.665 € | 78.510 € | |
Bruttoertrag | |||
Waren | -52.455 € | -56.335 € | -60.180 € |
16.005 € | 17.330 € | 18.330 € | |
Betriebsergebnis | |||
Infrastruktur | -450 € | -450 € | 0 € |
Abschreibung | -75 € | -112 € | -150 € |
15.480 € | 16.768 € | 18.180 € | |
Gewinn vor Steuern | |||
15.480 € | 16.768 € | 18.180 € | |
Nettogewinn | |||
15.480 € | 16.768 € | 18.180 € |
We can also combine both objects into one transaction plan to get one comprehensive table:
<- transaction_plan(plan_type="depreciation")
dep_plan update_plan(dep_plan) <- dep_cashreg1
update_plan(dep_plan) <- dep_cashreg2
kable_bpR(
dep_plan,dep_names=c(
investment="Investition",
depreciation="Abschreibung",
value="Wert",
sum="Summe"
),resolution="year",
years=2024:2029
)
2024 | 2025 | 2026 | 2027 | 2028 | 2029 | |||
---|---|---|---|---|---|---|---|---|
Laden | Kasse 1 | Investition | 450,00 € | 0,00 € | 0,00 € | 0,00 € | 0,00 € | 0,00 € |
Laden | Kasse 1 | Abschreibung | -75,00 € | -75,00 € | -75,00 € | -75,00 € | -75,00 € | -75,00 € |
Laden | Kasse 1 | Wert | 375,00 € | 300,00 € | 225,00 € | 150,00 € | 75,00 € | 0,00 € |
Laden | Kasse 2 | Investition | 0,00 € | 450,00 € | 0,00 € | 0,00 € | 0,00 € | 0,00 € |
Laden | Kasse 2 | Abschreibung | 0,00 € | -37,50 € | -75,00 € | -75,00 € | -75,00 € | -75,00 € |
Laden | Kasse 2 | Wert | 0,00 € | 412,50 € | 337,50 € | 262,50 € | 187,50 € | 112,50 € |
Summe | Investition | 450,00 € | 450,00 € | 0,00 € | 0,00 € | 0,00 € | 0,00 € | |
Summe | Abschreibung | -75,00 € | -112,50 € | -150,00 € | -150,00 € | -150,00 € | -150,00 € | |
Summe | Wert | 375,00 € | 712,50 € | 562,50 € | 412,50 € | 262,50 € | 112,50 € |
Similar to investments and depreciation, loans are easy to calculate. The profit and loss statement only looks at the interest, but we’ll also add the principal rates which we’ll need later for our liquidity plan:
<- loan(
loan_2024 type="Kreditaufnahme",
category="Bank",
name="Anschubfinanzierung",
amount=5000,
period=60,
interest=0.075,
first_month="2024.01",
schedule=c("amortization"),
valid_types="Saftladen"
)update_operations(
saftladen_2024_2026,as_transaction=list(
c(
to="expense",
aspect="principal",
valid_types="Saftladen",
type="Kredittilgung"
),c(
to="expense",
aspect="interest",
valid_types="Saftladen",
type="Zinsen"
),c(
to="revenue",
aspect="balance_start",
valid_types="Saftladen",
type="Kreditaufnahme"
)
)<- loan_2024
)
kable_bpR(
saftladen_2024_2026,model=saftladen_inc_stamt,
resolution="year"
)
2024 | 2025 | 2026 | |
---|---|---|---|
Bruttoeinnahmen | |||
Obst | 37.565 € | 40.250 € | 42.900 € |
Gemüse | 30.545 € | 32.755 € | 34.680 € |
Merchandise | 350 € | 660 € | 930 € |
68.460 € | 73.665 € | 78.510 € | |
Bruttoertrag | |||
Waren | -52.455 € | -56.335 € | -60.180 € |
16.005 € | 17.330 € | 18.330 € | |
Betriebsergebnis | |||
Infrastruktur | -450 € | -450 € | 0 € |
Abschreibung | -75 € | -112 € | -150 € |
15.480 € | 16.768 € | 18.180 € | |
Gewinn vor Steuern | |||
Zinsen | -341 € | -266 € | -191 € |
15.139 € | 16.502 € | 17.989 € | |
Nettogewinn | |||
15.139 € | 16.502 € | 17.989 € |
Similar to the investment and depreciation plans, you could also combine several loans into one transaction plan to include them as a table. But in our example there is only one loan:
<- transaction_plan(plan_type="loan")
loan_plan update_plan(loan_plan) <- loan_2024
kable_bpR(
loan_plan,loan_names=c(
balance_start="Restschuld Beginn",
interest="Zinsen",
principal="Tilgung",
total="Zahlung",
cumsum="Kumuliert",
balance_remain="Restschuld Ende",
sum="Summe"
),resolution="year"
)
2024 | 2025 | 2026 | 2027 | 2028 | |||
---|---|---|---|---|---|---|---|
Bank | Anschubfinanzierung | Restschuld Beginn | 5.000,00 € | 4.000,00 € | 3.000,00 € | 2.000,00 € | 1.000,00 € |
Bank | Anschubfinanzierung | Zinsen | 340,51 € | 265,54 € | 190,56 € | 115,58 € | 40,61 € |
Bank | Anschubfinanzierung | Tilgung | 999,96 € | 999,96 € | 999,96 € | 999,96 € | 999,96 € |
Bank | Anschubfinanzierung | Zahlung | 1.340,47 € | 1.265,50 € | 1.190,52 € | 1.115,54 € | 1.040,57 € |
Bank | Anschubfinanzierung | Kumuliert | 1.340,47 € | 2.605,97 € | 3.796,49 € | 4.912,03 € | 5.952,60 € |
Bank | Anschubfinanzierung | Restschuld Ende | 4.000,00 € | 3.000,00 € | 2.000,00 € | 1.000,00 € | 0,00 € |
Summe | Restschuld Beginn | 5.000,00 € | 4.000,00 € | 3.000,00 € | 2.000,00 € | 1.000,00 € | |
Summe | Zinsen | 340,51 € | 265,54 € | 190,56 € | 115,58 € | 40,61 € | |
Summe | Tilgung | 999,96 € | 999,96 € | 999,96 € | 999,96 € | 999,96 € | |
Summe | Zahlung | 1.340,47 € | 1.265,50 € | 1.190,52 € | 1.115,54 € | 1.040,57 € | |
Summe | Kumuliert | 1.340,47 € | 2.605,97 € | 3.796,49 € | 4.912,03 € | 5.952,60 € | |
Summe | Restschuld Ende | 4.000,00 € | 3.000,00 € | 2.000,00 € | 1.000,00 € | 0,00 € |
The steps to create a cash flow plan are simple: Define another model
to include all the relevant types of revenue and expense, and then call
kable_bpR(cashflow=TRUE)
using that model:
<- table_model(
saftladen_cashflow "Einzahlungen"=model_node(
revenue=c(
"Obst",
"Gemüse",
"Merchandise",
"Kreditaufnahme"
)
),"Auszahlungen"=model_node(
expense=c(
"Löhne und Gehälter",
"Sozialabgaben",
"Infrastruktur",
"Waren",
"Zinsen",
"Kredittilgung",
"Steuern"
)
)
)
kable_bpR(
saftladen_2024_2026,model=saftladen_cashflow,
resolution="year",
cashflow=TRUE
)
2024 | 2025 | 2026 | |
---|---|---|---|
Begin | |||
0 € | 19.215 € | 34.829 € | |
Einzahlungen | |||
Obst | 37.565 € | 40.250 € | 42.900 € |
Gemüse | 30.545 € | 32.755 € | 34.680 € |
Merchandise | 350 € | 660 € | 930 € |
Kreditaufnahme | 5.000 € | 0 € | 0 € |
73.460 € | 73.665 € | 78.510 € | |
Auszahlungen | |||
Infrastruktur | -450 € | -450 € | 0 € |
Waren | -52.455 € | -56.335 € | -60.180 € |
Zinsen | -341 € | -266 € | -191 € |
Kredittilgung | -1.000 € | -1.000 € | -1.000 € |
-54.245 € | -58.050 € | -61.371 € | |
End | |||
19.215 € | 34.829 € | 51.969 € |
So far we have used fixed numbers in our objects. Sure, that’s just an example and it doesn’t really matter if those numbers make sense. But if this were your actual business plan, these numbers would represent of how you think your business will develop. Nobody expects your calculations to predict the future to three decimal places, they should be as plausible as possible. Otherwise, your plan may not convince readers to invest in your idea.
Readers might ask themselves: Why do you think you will sell this amount of potatoes in a particular month? How did you arrive at these figures? And you will often find that when things change (like laws affecting your business or interest rates) or you learn new information that causes you to adjust your assumptions, you will need to update some numbers, which in turn will change your calculations right through to the end result. We have written this package to deal with all this intuitively.
Here is how we did it, and it helped us a lot: We used RMarkdown to write our business plan. In that document, we explained how we expected different aspects of our undertaking to interact, and what initial values we knew, or why we thought it was plausible to propose a certain value. Since these initial assumptions could change at any time, all actual numbers should be defined as a vector or list at the beginning of the document and referenced in the text, so that by changing the values in that object, you not only update your calculations, but also the numbers that appear in the documentation. Finally, we translated these expectations into simple functions to calculate the actual numbers that would then be used as revenues and expenses.
This separates the numbers we assume (vector/list) from the interactions between the basic aspects of our business we assume (functions), i. e. both can be adjusted separately.
Let’s go back to our example and try to do this for our apple sales. You have talked to local farmers and studied both the neighbourhood and consumer analyses. You have come up with the following figures:
<- list(
assumed total_customers=1860, # number of potential customers?
pct_year_1st=25, # how many of them will buy at our store?
pct_year_last=35, # how will the number progress?
pct_demand=30, # how much will they buy from us?
kg_per_customer=c( # how many Kg were cosumed in recent years?
apples=23
),exp_per_kg=c( # how much will it cost to buy goods?
apples=2.3
),rev_per_kg=c( # at what price will we sell?
apples=3.49
) )
In our business plan, we can now use this list of numbers to explain
our expectations. The package has a function called
nice_numbers()
to format numbers, round them and add a
prefix or suffix. So
nice_numbers(assumed[["total_customers"]])
prints as 1.860,
nice_numbers(assumed[["pct_year_1st"]], suffix="%")
as
25 %, and
nice_numbers(assumed[["exp_per_kg"]][["apples"]], suffix="€", digits=2)
as 2,30 €:
According to the demographic analysis, there are a total of 1.860 potential customers for the Saftladen. We assume that we will reach 25 % of this market in the first year, increasing to 35 % in the third year as a result of our marketing efforts. We also assume that our average customer will buy 30 % of their annual apple requirements in our store. From consumer research in recent years, we know that the total annual consumption of apples per person is around 23 Kg on average. We have an agreement with local farmers to buy apples at 2,30 € per Kg, and we calculate a selling price of 3,49 € per Kg.
Great! So how many customers can we expect if these assumptions hold?
<- function(data, years){
n_customers <- seq(
customer_pct from=data[["pct_year_1st"]],
to=data[["pct_year_last"]],
length.out=length(years)
/ 100
) <- round(data[["total_customers"]] * customer_pct)
result names(result) <- years
return(result)
}
n_customers(data=assumed, years=2024:2026)
## 2024 2025 2026
## 465 558 651
How many Kg of apples will we sell?
<- function(what, data, years){
kg_sale <- data[["kg_per_customer"]][[what]]
avg_amount_total <- round(
result *
avg_amount_total "pct_demand"]] / 100) *
(data[[n_customers(data=data, years=years)
)return(result)
}
kg_sale(what="apples", data=assumed, years=2024:2026)
## 2024 2025 2026
## 3208 3850 4492
Apple season usually is from August to November, so let’s not spread this equally across the year:
<- function(what, distribution, data, years){
spread_sales <- kg_sale(what=what, data=data, years=years)
yearly_sales <- round(as.vector(sapply(
distrib
yearly_sales,function(kg){
* distribution
kg
}
)))names(distrib) <- paste0(rep(years, each=length(distribution)), names(distribution))
return(distrib)
}
<- spread_sales(
sales_apples_kg what="apples",
distribution=c(
".01"=0.20,
".08"=0.27,
".09"=0.30,
".12"=0.23
),data=assumed,
years=2024:2026
)
# multiply by prices
<- round(sales_apples_kg * assumed[["exp_per_kg"]][["apples"]])) (sales_apples_expense
## 2024.01 2024.08 2024.09 2024.12 2025.01 2025.08 2025.09 2025.12 2026.01 2026.08
## 1477 1992 2213 1697 1771 2392 2656 2038 2065 2790
## 2026.09 2026.12
## 3100 2376
<- round(sales_apples_kg * assumed[["rev_per_kg"]][["apples"]])) (sales_apples_revenue
## 2024.01 2024.08 2024.09 2024.12 2025.01 2025.08 2025.09 2025.12 2026.01 2026.08
## 2241 3022 3357 2576 2687 3630 4031 3092 3134 4233
## 2026.09 2026.12
## 4705 3605
Now we apply these figures to our operations object:
update_operations(saftladen_2024_2026) <- revenue(
type="Obst",
category="Obst",
name="Äpfel",
valid_types="Saftladen",
.list=as.list(sales_apples_revenue)
)update_operations(saftladen_2024_2026) <- expense(
type="Waren",
category="Obst",
name="Äpfel",
valid_types="Saftladen",
.list=as.list(sales_apples_expense)
)
kable_bpR(
saftladen_2024_2026,model=saftladen_inc_stamt,
resolution="year"
)
2024 | 2025 | 2026 | |
---|---|---|---|
Bruttoeinnahmen | |||
Obst | 37.721 € | 45.474 € | 53.191 € |
Gemüse | 30.545 € | 32.755 € | 34.680 € |
Merchandise | 350 € | 660 € | 930 € |
68.616 € | 78.889 € | 88.801 € | |
Bruttoertrag | |||
Waren | -49.722 € | -56.830 € | -63.901 € |
18.894 € | 22.059 € | 24.900 € | |
Betriebsergebnis | |||
Infrastruktur | -450 € | -450 € | 0 € |
Abschreibung | -75 € | -112 € | -150 € |
18.369 € | 21.496 € | 24.750 € | |
Gewinn vor Steuern | |||
Zinsen | -341 € | -266 € | -191 € |
18.028 € | 21.231 € | 24.559 € | |
Nettogewinn | |||
18.028 € | 21.231 € | 24.559 € |
Our previous revenues and expenses for apples have been replaced by
update_operations()
. Instead of a bunch of numbers that
were hard to understand, these sales are now transparently derived from
data that we have explained.
Finally, you can use functions like the ones we just wrote in a Shiny
application. More specifically, if you don’t call methods like
revenue()
or loan()
directly, but inside a
custom function, you can use the same functions in a Shiny app and in
your RMarkdown document, making sure that all calculations stay in sync.
That is, you write your calculations in separate R script files that can
be sourced by both Shiny and RMarkdown.
It can be very revealing to use such a Shiny app to examine the
overall impact of changes to the numerical assumptions in your business
plan. For example, you could use slider controls for each value defined
in assumed
and explore how your business would be affected
if there were fewer customers than expected or if selling prices
fell.
We added a permalink to the Shiny app we used to discuss our business
model as a team, which included all the configurable parameters. Such a
Shiny permalink can be turned into a named list using
businessPlanR
’s permalink2list()
function,
which in turn makes it very easy to copy a custom configuration from
Shiny into an RMarkdown document, so that all calculations and tables
can be replicated.
The relevant functions of the businessPlanR
package have
been designed to produce output that should be usable in HTML and LaTeX
contexts, without the need to adapt the calls.