forked from bergant/finstr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
README.Rmd
390 lines (280 loc) · 10.9 KB
/
README.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
---
title: "finstr - Financial Statements in R"
output:
md_document:
variant: markdown_github
---
```{r, echo = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "img/README-"
)
```
![finstr](img/logo.png)
The purpose of **finstr** package is to create an environment for
reproducible financial statement analysis.
The package will not cover specific types of
analysis (except in examples and package vignettes) but will provide
a domain language to write them.
With other functions in basic R and existing R packages it anables users
to store, share, reuse and reproduce the results of their analitic work.
For now it is offering:
**1. Data structure for financial statements**
- Reading from data parsed with XBRL package
- Statements in tidy format with accounting taxonomy concepts as columns
- Encapsulates calculation hierarchy of variables
- Default printing in transposed format and with visible hierarchy
**2. Statement calculation validation**
- Calculation of higher order elements
- Check if calculated values match original values
**3. Merge statements**
- Merge different periods of equal statement type
- Merge statements of a different type
**4. Calculate and reveal**
- Custom financial ratio calculations definitions
- Exposing data by rearranging the statament hierarchy
- Time lagged difference
## Installation
To install finstr from github use `install_github` from devtools package:
```{r, eval=FALSE}
library(devtools)
install_github("bergant/finstr")
```
```{r, echo=FALSE, results='hide', message=FALSE }
library(dplyr)
library(tidyr)
library(finstr)
data(xbrl_data_aapl2013)
data(xbrl_data_aapl2014)
```
## Get data
Use XBRL package to parse XBRL files. For example:
```{r xbrl_parse_min, eval=FALSE, echo=TRUE}
library(XBRL)
# parse XBRL (Apple 10-K report)
xbrl_url2014 <- "https://www.sec.gov/Archives/edgar/data/320193/000119312514383437/aapl-20140927.xml"
xbrl_url2013 <-
"https://www.sec.gov/Archives/edgar/data/320193/000119312513416534/aapl-20130928.xml"
old_o <- options(stringsAsFactors = FALSE)
xbrl_data_aapl2014 <- xbrlDoAll(xbrl_url2014)
xbrl_data_aapl2013 <- xbrlDoAll(xbrl_url2013)
options(old_o)
```
## Prepare statements
With `xbrl_get_statements` convert XBRL data to *statements* object.
```{r xbrl_get_statements}
library(finstr)
st2013 <- xbrl_get_statements(xbrl_data_aapl2013)
st2014 <- xbrl_get_statements(xbrl_data_aapl2014)
st2014
```
Statements object is a list of
several statement objects (ballance sheets, income and cash
flow statements).
To get a single *statement* use *statements* object as a regular R list:
```{r statement}
balance_sheet2013 <- st2013$StatementOfFinancialPositionClassified
balance_sheet2014 <- st2014$StatementOfFinancialPositionClassified
income2013 <- st2013$StatementOfIncome
income2014 <- st2014$StatementOfIncome
balance_sheet2014
tail(income2014, 2)
```
## Validate statement calculation hierarchy
Recalculate higher order concepts from basic values and check for errors.
```{r check}
check <- check_statement(balance_sheet2014)
check
```
In case of error the numbers with errors will be presented along with elements:
```{r check_error}
check_statement(
within(balance_sheet2014, InventoryNet <- InventoryNet * 2)
)
```
Validation returns all calculation results in a readable data frame.
Lets check only operating income from income statement:
```{r check_results check}
check <- check_statement(income2014, element_id = "OperatingIncomeLoss")
check
check$expression[1]
check$calculated / 10^6
```
## Merge statements from different periods
Use `merge` function to create single financial statement data from two
statements.
```{r merge_statement}
balance_sheet <- merge( balance_sheet2013, balance_sheet2014 )
```
The structure of merged balance sheets may differ if XBRL
taxonomy changes.
Function `merge` takes care of it by expanding the elements
hierarchy to fit both statements.
The values of any missing elements in different periods is set to 0.
To merge all statements from *statements* object use merge on statements objects:
```{r merge_statements}
# merge all statements
st_all <- merge( st2013, st2014 )
# check if balance sheets are merged:
balance_sheet <- st_all$StatementOfFinancialPositionClassified
balance_sheet$endDate
```
## Merge different types of statements
If there are no matching elements between the two statements
`merge` joins statements by matching their periods.
For some financial ratio calculations the combined statement may be
a better starting point.
```{r merge_types, eval=FALSE}
merge.statement(
st_all$StatementOfFinancialPositionClassified,
st_all$StatementOfIncome )
```
## Calculate financial ratios
Statement object (in our case `balance_sheet`) is also a data frame object
with statement elements as columns and time periods as rows.
It is possible then to use statement as a data frame.
Lets calculate current ratio which is defined by
$$ Current Ratio = \frac{Current Assets}{Current Liabilities} $$
With dplyr package we can use `mutate`, `select` or `transmute` functions:
```{r dplyr}
library(dplyr)
balance_sheet %>% transmute(
date = endDate,
CurrentRatio = AssetsCurrent / LiabilitiesCurrent
)
```
By using `finstr::calculate` function we can achieve the same result but
don't have to handle the date field and there is a rounding parameter.
Lets calculate for example two ratios:
```{r calculate}
balance_sheet %>% calculate( digits = 2,
Current_Ratio = AssetsCurrent / LiabilitiesCurrent,
Quick_Ratio =
( CashAndCashEquivalentsAtCarryingValue +
AvailableForSaleSecuritiesCurrent +
AccountsReceivableNetCurrent
) / LiabilitiesCurrent
)
```
If we need a period average value we can use a `lag` function.
For example, to calculate *DSO* (days sales outstanding) over longer periods
the average of account receivable is compared to net sales.
We will use the formula for yearly preiods:
$$ DSO = \frac{Average Accounts Receivable}{Sales Revenue} \times 365 $$
In this case we need to connect two type of statements: balance sheets and
income statements. With matching reporting periods it can be accomplished
with joining two data frames:
```{r DaysSalesOutstanding}
merge(balance_sheet, st_all$StatementOfIncome ) %>% calculate( digits = 2,
.AccountReceivableLast = lag(AccountsReceivableNetCurrent),
.AccountReceivableAvg = (.AccountReceivableLast + AccountsReceivableNetCurrent)/2,
DaysSalesOutstanding = .AccountReceivableAvg / SalesRevenueNet * 365
)
```
The leading dot instructs the calculate function to hide the value. In our case
only DaysSalesOutstanding is selected in final result. Use `digits` parameter to control rounding.
## Reusing calculations
When running same calculation for different statements, define the
calculation with `calculation` and call `calculate` with argument
`calculations`:
```{r calculation}
# define calculation
profit_margins <- calculation(
Gross_Margin =
(SalesRevenueNet - CostOfGoodsAndServicesSold) / SalesRevenueNet,
Operating_Margin =
OperatingIncomeLoss / SalesRevenueNet,
Net_Margin =
NetIncomeLoss / SalesRevenueNet
)
# run profit margins for two different statements
income2013 %>% calculate(calculations = profit_margins, digits = 2)
income2014 %>% calculate(calculations = profit_margins, digits = 2)
```
## Rearranging statement hierarchy
Calculations gives us freedom to use any formula with any data from
financial statements.
Most of the time this is not necessary as we can get useful information just by
regrouping calculation hierarchy.
There are many additional reasons why is rearranging statements useful step before
actual calculations:
* We can avoid errors in formulas with many variables
* Accounting taxonomies do change and using many formulas on original statement is
harder to support than using custom hierarchy for analysis starting point
* When sharing analysis it is much easier to print 6 values instead of 30
To rearrange the statement to simple 2-level hierarchy use `expose` function.
```{r expose}
expose( balance_sheet,
# Assets
`Current Assets` = "AssetsCurrent",
`Noncurrent Assets` = other("Assets"),
# Liabilites and equity
`Current Liabilities` = "LiabilitiesCurrent",
`Noncurrent Liabilities` = other(c("Liabilities", "CommitmentsAndContingencies")),
`Stockholders Equity` = "StockholdersEquity"
)
```
Balance sheet stays divided by *Assets* and *Liabilities and Equity*.
For the second level we are exposing *current assets* from *noncurrent* and
similar is done for the *liabilities*. We choose to separate *equity*.
Function `expose` expects a list of vectors with element names.
Function `other` helps us identify elements without enumerating every single element.
Using `other` reduces a lot of potential errors as the function "knows" which
elements are not specified and keeps the balance sheet complete.
Sometimes it is easier to define a complement than a list of elements.
In this case we can use the `%without%` operator. Lets expose for example
*tangible* and then *intangible* assets:
```{r expose_without}
expose( balance_sheet,
# Assets
`Tangible Assets` =
"Assets" %without% c("Goodwill", "IntangibleAssetsNetExcludingGoodwill"),
`Intangible Assets` = other("Assets"),
# Liabilites and equity
`Liabilities` = c("Liabilities", "CommitmentsAndContingencies"),
`Stockholders Equity` = "StockholdersEquity"
)
```
##Lagged difference
To calculate lagged difference for entire statement use `diff` function.
The result is statement of changes between successive years:
```{r diff}
diff(balance_sheet)
```
# Balance sheet visualization
## Prepare custom hierarchy
The only way to visualize a balance sheet is by exposing a limited number of values.
The first step is then to aggregate a balance sheet by selected concepts.
We can use `expose` to specify these groups of elements. For example:
```{r expose_graph}
bs_simple <- expose( balance_sheet,
# Assets
`Current Assets` = "AssetsCurrent",
`Noncurrent Assets` = other("Assets"),
# Liabilites and equity
`Current Liabilities` = "LiabilitiesCurrent",
`Noncurrent Liabilities` = other(c("Liabilities", "CommitmentsAndContingencies")),
`Stockholders Equity` = "StockholdersEquity"
)
```
## Print as a table
```{r htmlTable, warning=FALSE}
library(htmlTable)
print(bs_simple, html = TRUE, big.mark = ",", dateFormat = "%Y")
```
## Double stacked graph
Using ggplot2 package we can plot a simplified balance sheet:
```{r graph_byside, fig.width=7.0}
library(ggplot2)
plot_double_stacked_bar(bs_simple)
```
Another option is to group by faceting balance sheet side instead of date:
```{r graph_bydate, fig.width=7.0}
plot_double_stacked_bar(bs_simple, by_date = FALSE)
```
Using **proportional** form we reveal the changes in balance sheet structure:
```{r graph_prop, fig.width=7.0}
bs_simple_prop <- proportional(bs_simple)
plot_double_stacked_bar(bs_simple_prop)
```