Data preparation
The S&P 500 numbers are pre-tax whereas the Berkshire numbers are after-tax. Data source: Warren Buffett’s 2021 annual letter.
year = 1965:2021
berkshire = c(49.5, -3.4, 13.3, 77.8, 19.4, -4.6, 80.5, 8.1, -2.5, -48.7,
2.5, 129.3, 46.8, 14.5, 102.5, 32.8, 31.8, 38.4, 69, -2.7,
93.7, 14.2, 4.6, 59.3, 84.6, -23.1, 35.6, 29.8, 38.9, 25, 57.4,
6.2, 34.9, 52.2, -19.9, 26.6, 6.5, -3.8, 15.8, 4.3, 0.8, 24.1,
28.7, -31.8, 2.7, 21.4, -4.7, 16.8, 32.7, 27, -12.5, 23.4,
21.9, 2.8, 11.0, 2.4, 29.6)
sp500 = c(10, -11.7, 30.9, 11, -8.4, 3.9, 14.6, 18.9, -14.8, -26.4, 37.2, 23.6,
-7.4, 6.4, 18.2, 32.3, -5, 21.4, 22.4, 6.1, 31.6, 18.6, 5.1, 16.6,
31.7, -3.1, 30.5, 7.6, 10.1, 1.3, 37.6, 23, 33.4, 28.6, 21, -9.1, -11.9,
-22.1, 28.7, 10.9, 4.9, 15.8, 5.5, -37, 26.5, 15.1, 2.1, 16, 32.4, 13.7,
1.4, 12, 21.8, -4.4, 31.5, 18.4, 28.7)
# performance analytics
performance.func = function(df){
out = df %>%
mutate(
sp500_divd_incl=cumprod(c(1, 1 + lead(df$sp500/100)[1:length(df$sp500)-1])),
berkshire_per_share_mv=cumprod(c(1, 1 + lead(df$berkshire/100)[1:length(df$berkshire)-1]))
)
list(
out,
c(# Compounded Annual Gain
SP500=round((out$sp500_divd_incl[n+1] ^ (1/n)-1)*100, 1),
Berkshire=round((out$berkshire_per_share_mv[n+1] ^ (1/n)-1)*100, 1)
),
c(# Performance_Check
SP500=all.equal(
out$sp500[-1],
100*((out$sp500_divd_incl/lag(out$sp500_divd_incl))^(1/(out$year-lag(out$year))) - 1)[-1]
),
Berkshire=all.equal(
out$berkshire[-1],
100*((out$berkshire_per_share_mv/lag(out$berkshire_per_share_mv))^(1/(out$year-lag(out$year))) - 1)[-1]
)
)
)
}
Compounded Annual Gain
2012-2021
df = data.frame(year, sp500, berkshire)
df = df %>% filter(between(year, 2011, 2021))
n = nrow(df)-1
out = performance.func(df)
# data.frame(start=out[[1]]$year[2], end=out[[1]]$year[n+1])
data.frame(CAGR=out[[2]]) %>%
knitr::kable()
SP500 |
16.6 |
Berkshire |
14.6 |
out[[1]] %>%
select(year, sp500_divd_incl, berkshire_per_share_mv) %>%
tidyr::gather("type", "growth", sp500_divd_incl, berkshire_per_share_mv) %>%
ggplot(aes(x = year, y = growth)) +
geom_line(aes(color = type), size = 1) +
scale_color_manual(values = c("#00AFBB", "#E7B800")) +
theme_minimal()

out[[1]] %>%
knitr::kable()
2011 |
2.1 |
-4.7 |
1.000000 |
1.000000 |
2012 |
16.0 |
16.8 |
1.160000 |
1.168000 |
2013 |
32.4 |
32.7 |
1.535840 |
1.549936 |
2014 |
13.7 |
27.0 |
1.746250 |
1.968419 |
2015 |
1.4 |
-12.5 |
1.770698 |
1.722366 |
2016 |
12.0 |
23.4 |
1.983181 |
2.125400 |
2017 |
21.8 |
21.9 |
2.415515 |
2.590863 |
2018 |
-4.4 |
2.8 |
2.309232 |
2.663407 |
2019 |
31.5 |
11.0 |
3.036640 |
2.956382 |
2020 |
18.4 |
2.4 |
3.595382 |
3.027335 |
2021 |
28.7 |
29.6 |
4.627257 |
3.923426 |
data.frame(Performance_Check=out[[3]]) %>%
knitr::kable()
SP500 |
TRUE |
Berkshire |
TRUE |
2002-2021
df = data.frame(year, sp500, berkshire)
df = df %>% filter(between(year, 2001, 2021))
n = nrow(df)-1
out = performance.func(df)
# data.frame(start=out[[1]]$year[2], end=out[[1]]$year[n+1])
data.frame(CAGR=out[[2]]) %>%
knitr::kable()
out[[1]] %>%
select(year, sp500_divd_incl, berkshire_per_share_mv) %>%
tidyr::gather("type", "growth", sp500_divd_incl, berkshire_per_share_mv) %>%
ggplot(aes(x = year, y = growth)) +
geom_line(aes(color = type), size = 1) +
scale_color_manual(values = c("#00AFBB", "#E7B800")) +
theme_minimal()

out[[1]] %>%
knitr::kable()
2001 |
-11.9 |
6.5 |
1.0000000 |
1.000000 |
2002 |
-22.1 |
-3.8 |
0.7790000 |
0.962000 |
2003 |
28.7 |
15.8 |
1.0025730 |
1.113996 |
2004 |
10.9 |
4.3 |
1.1118535 |
1.161898 |
2005 |
4.9 |
0.8 |
1.1663343 |
1.171193 |
2006 |
15.8 |
24.1 |
1.3506151 |
1.453450 |
2007 |
5.5 |
28.7 |
1.4248989 |
1.870591 |
2008 |
-37.0 |
-31.8 |
0.8976863 |
1.275743 |
2009 |
26.5 |
2.7 |
1.1355732 |
1.310188 |
2010 |
15.1 |
21.4 |
1.3070447 |
1.590568 |
2011 |
2.1 |
-4.7 |
1.3344927 |
1.515812 |
2012 |
16.0 |
16.8 |
1.5480115 |
1.770468 |
2013 |
32.4 |
32.7 |
2.0495673 |
2.349411 |
2014 |
13.7 |
27.0 |
2.3303580 |
2.983752 |
2015 |
1.4 |
-12.5 |
2.3629830 |
2.610783 |
2016 |
12.0 |
23.4 |
2.6465409 |
3.221706 |
2017 |
21.8 |
21.9 |
3.2234869 |
3.927260 |
2018 |
-4.4 |
2.8 |
3.0816534 |
4.037223 |
2019 |
31.5 |
11.0 |
4.0523743 |
4.481317 |
2020 |
18.4 |
2.4 |
4.7980111 |
4.588869 |
2021 |
28.7 |
29.6 |
6.1750403 |
5.947174 |
data.frame(Performance_Check=out[[3]]) %>%
knitr::kable()
SP500 |
TRUE |
Berkshire |
TRUE |
1966-2021
df = data.frame(year, sp500, berkshire)
# df = df %>% filter(between(year, 2001, 2021))
n = nrow(df)-1
out = performance.func(df)
# data.frame(start=out[[1]]$year[2], end=out[[1]]$year[n+1])
data.frame(CAGR=out[[2]]) %>%
knitr::kable()
SP500 |
10.5 |
Berkshire |
19.8 |
out[[1]] %>%
select(year, sp500_divd_incl, berkshire_per_share_mv) %>%
tidyr::gather("type", "growth", sp500_divd_incl, berkshire_per_share_mv) %>%
ggplot(aes(x = year, y = growth)) +
geom_line(aes(color = type), size = 1) +
scale_color_manual(values = c("#00AFBB", "#E7B800")) +
theme_minimal()

out[[1]] %>%
knitr::kable()
1965 |
10.0 |
49.5 |
1.000000 |
1.000000 |
1966 |
-11.7 |
-3.4 |
0.883000 |
0.966000 |
1967 |
30.9 |
13.3 |
1.155847 |
1.094478 |
1968 |
11.0 |
77.8 |
1.282990 |
1.945982 |
1969 |
-8.4 |
19.4 |
1.175219 |
2.323502 |
1970 |
3.9 |
-4.6 |
1.221053 |
2.216621 |
1971 |
14.6 |
80.5 |
1.399326 |
4.001001 |
1972 |
18.9 |
8.1 |
1.663799 |
4.325082 |
1973 |
-14.8 |
-2.5 |
1.417557 |
4.216955 |
1974 |
-26.4 |
-48.7 |
1.043322 |
2.163298 |
1975 |
37.2 |
2.5 |
1.431437 |
2.217381 |
1976 |
23.6 |
129.3 |
1.769257 |
5.084454 |
1977 |
-7.4 |
46.8 |
1.638332 |
7.463978 |
1978 |
6.4 |
14.5 |
1.743185 |
8.546255 |
1979 |
18.2 |
102.5 |
2.060444 |
17.306166 |
1980 |
32.3 |
32.8 |
2.725968 |
22.982588 |
1981 |
-5.0 |
31.8 |
2.589670 |
30.291052 |
1982 |
21.4 |
38.4 |
3.143859 |
41.922815 |
1983 |
22.4 |
69.0 |
3.848083 |
70.849558 |
1984 |
6.1 |
-2.7 |
4.082816 |
68.936620 |
1985 |
31.6 |
93.7 |
5.372986 |
133.530233 |
1986 |
18.6 |
14.2 |
6.372362 |
152.491526 |
1987 |
5.1 |
4.6 |
6.697352 |
159.506136 |
1988 |
16.6 |
59.3 |
7.809113 |
254.093275 |
1989 |
31.7 |
84.6 |
10.284601 |
469.056185 |
1990 |
-3.1 |
-23.1 |
9.965779 |
360.704206 |
1991 |
30.5 |
35.6 |
13.005341 |
489.114904 |
1992 |
7.6 |
29.8 |
13.993747 |
634.871146 |
1993 |
10.1 |
38.9 |
15.407116 |
881.836021 |
1994 |
1.3 |
25.0 |
15.607408 |
1102.295026 |
1995 |
37.6 |
57.4 |
21.475794 |
1735.012371 |
1996 |
23.0 |
6.2 |
26.415226 |
1842.583138 |
1997 |
33.4 |
34.9 |
35.237912 |
2485.644654 |
1998 |
28.6 |
52.2 |
45.315954 |
3783.151163 |
1999 |
21.0 |
-19.9 |
54.832305 |
3030.304082 |
2000 |
-9.1 |
26.6 |
49.842565 |
3836.364967 |
2001 |
-11.9 |
6.5 |
43.911300 |
4085.728690 |
2002 |
-22.1 |
-3.8 |
34.206902 |
3930.471000 |
2003 |
28.7 |
15.8 |
44.024284 |
4551.485418 |
2004 |
10.9 |
4.3 |
48.822930 |
4747.199291 |
2005 |
4.9 |
0.8 |
51.215254 |
4785.176885 |
2006 |
15.8 |
24.1 |
59.307264 |
5938.404515 |
2007 |
5.5 |
28.7 |
62.569164 |
7642.726610 |
2008 |
-37.0 |
-31.8 |
39.418573 |
5212.339548 |
2009 |
26.5 |
2.7 |
49.864495 |
5353.072716 |
2010 |
15.1 |
21.4 |
57.394034 |
6498.630277 |
2011 |
2.1 |
-4.7 |
58.599308 |
6193.194654 |
2012 |
16.0 |
16.8 |
67.975198 |
7233.651356 |
2013 |
32.4 |
32.7 |
89.999162 |
9599.055350 |
2014 |
13.7 |
27.0 |
102.329047 |
12190.800294 |
2015 |
1.4 |
-12.5 |
103.761654 |
10666.950257 |
2016 |
12.0 |
23.4 |
116.213052 |
13163.016617 |
2017 |
21.8 |
21.9 |
141.547498 |
16045.717257 |
2018 |
-4.4 |
2.8 |
135.319408 |
16494.997340 |
2019 |
31.5 |
11.0 |
177.945021 |
18309.447047 |
2020 |
18.4 |
2.4 |
210.686905 |
18748.873776 |
2021 |
28.7 |
29.6 |
271.154047 |
24298.540414 |
data.frame(Performance_Check=out[[3]]) %>%
knitr::kable()
SP500 |
TRUE |
Berkshire |
TRUE |