\[ % MathJax has no bm package; redefine \bm in terms of \boldsymbol, which it supports natively \newcommand{\bm}[1]{\boldsymbol{#1}} % --- Operators ----------------------------------------------------------- % Expectation operator \DeclareMathOperator{\E}{E} % Variance operator \DeclareMathOperator{\Var}{Var} % Covariance operator \DeclareMathOperator{\Cov}{Cov} % Correlation operator \DeclareMathOperator{\Corr}{Corr} % Rank operator (Spearman rank correlation) \DeclareMathOperator{\Rank}{Rank} % Skewness operator \DeclareMathOperator{\Skewness}{Skewness} % Kurtosis operator \DeclareMathOperator{\Kurtosis}{Kurtosis} % Difference/differential operator (upright d per ISO 80000-2) \newcommand{\Diff}{\mathrm{d}} % --- Risk Measures ------------------------------------------------------- % Value at Risk \DeclareMathOperator{\VaR}{VaR} % Expected Shortfall \DeclareMathOperator{\ES}{ES} % Marginal Value at Risk \DeclareMathOperator{\MVaR}{MVaR} % Component Value at Risk \DeclareMathOperator{\CompVaR}{CVaR} % Incremental Value at Risk \DeclareMathOperator{\IVaR}{IVaR} % Component Expected Shortfall \DeclareMathOperator{\CompES}{CES} % Weighted sensitivity for risk factor in FRTB SBM bucket \DeclareMathOperator{\WS}{WS} % Hedge-benefit ratio for FRTB DRC bucket \DeclareMathOperator{\HBR}{HBR} % Stress scenario risk measure for FRTB NMRF capital add-on \DeclareMathOperator{\SES}{SES} % Jump-to-default exposure for obligor (FRTB DRC) \DeclareMathOperator{\JTD}{JTD} % Loss given default for obligor (FRTB DRC) \DeclareMathOperator{\LGD}{LGD} % Default risk weight for obligor (FRTB DRC) \DeclareMathOperator{\RW}{RW} % ES ratio function \newcommand{\ESratio}{\lambda} % Arbitrary risk measure function (coherence axioms) \newcommand{\RiskMeasure}{\varphi} % Asset position (coherence axioms) \newcommand{\Asset}{A} % First named asset in examples \newcommand{\AssetA}{A} % Second named asset in examples \newcommand{\AssetB}{B} % Third named asset in examples \newcommand{\AssetC}{C} % Constant (risk measure axioms) \newcommand{\Constant}{c} % Bucket-level aggregate sensitivity in FRTB SBM \newcommand{\BucketAgg}{S} % Cross-bucket correlation in FRTB SBM \newcommand{\CrossBucketCorr}{\gamma} % Notional amount for an instrument (FRTB DRC and RRAO) \newcommand{\Notional}{\text{Notional}} % --- Distributions ------------------------------------------------------- % Binomial distribution \DeclareMathOperator{\Binomial}{Binomial} % Uniform distribution \DeclareMathOperator{\Uniform}{Uniform} % Normal distribution \newcommand{\NormalDist}{\mathcal{N}} % Student-t CDF \newcommand{\StudentCDF}{t} % Student-t PDF \newcommand{\StudentPDF}{f} % --- Returns ------------------------------------------------------------- % Price \newcommand{\Price}{P} % Simple (arithmetic) return \newcommand{\SimpleReturns}{R} % Log (compound) return \newcommand{\CompoundReturns}{y} % Matrix of compound returns \newcommand{\ReturnMatrix}{\bm{y}} % Dividend payment \newcommand{\dividend}{d} % --- Volatility ---------------------------------------------------------- % Realised variance \DeclareMathOperator{\RealisedVar}{RV} % Realised volatility (square root of realised variance) \DeclareMathOperator{\RealisedVol}{RVol} % Volatility \newcommand{\Vol}{\sigma} % GARCH intercept parameter \newcommand{\GARCHconst}{\omega} % ARCH coefficient (weight on past squared returns) \newcommand{\ARCHcoeff}{\alpha} % GARCH coefficient (weight on past variance) \newcommand{\GARCHcoeff}{\beta} % EWMA decay factor (typically 0.94) \newcommand{\EWMAdecay}{\lambda} % Standardized residual / error term \newcommand{\StdNormal}{\epsilon} % Leverage parameter in apARCH \newcommand{\APARCHleverage}{\zeta} % Power parameter in apARCH \newcommand{\APARCHpower}{\delta} % Degrees of freedom (Student-t) \newcommand{\DOF}{\nu} % GJR-GARCH leverage parameter \newcommand{\GJRleverage}{\gamma} % GARCH-X external regressor coefficient \newcommand{\RegressorCoeff}{\xi} % Mean (first moment) \newcommand{\Mean}{\mu} % Lag order in volatility models \newcommand{\Lag}{L} % Dummy variable (indicator in regressions) \newcommand{\DummyVar}{D} % --- Portfolio ----------------------------------------------------------- % Portfolio weight (scalar) \newcommand{\weight}{w} % Portfolio weight vector \newcommand{\weights}{\bm{w}} % Covariance matrix \newcommand{\CovMatrix}{\bm{\Sigma}} % Correlation matrix \newcommand{\CorrMatrix}{\bm{C}} % Correlation coefficient \newcommand{\correlation}{\rho} % Number of assets in portfolio \newcommand{\NumberAssets}{K} % Portfolio value \newcommand{\PortfolioValue}{\vartheta} % Asset beta (CAPM) \newcommand{\AssetBeta}{\beta} % Ledoit-Wolf shrinkage intensity \newcommand{\ShrinkageIntensity}{\delta} % Sample covariance matrix \newcommand{\SampleCov}{\bm{S}} % --- Time Series --------------------------------------------------------- % Sample size \newcommand{\SampleSize}{T} % Generic count (number of lags, payments, etc.) \newcommand{\Count}{N} % Estimation window length \newcommand{\EstWindow}{W_E} % Testing window length \newcommand{\TestWindow}{W_T} % Stress window length \newcommand{\StressWindow}{W_S} % AR coefficient \newcommand{\ARcoeff}{\phi} % MA coefficient \newcommand{\MAcoeff}{\psi} % --- Probability --------------------------------------------------------- % Probability level (e.g. 0.01 for 1% VaR) \newcommand{\probability}{p} % Cumulative sorted scenario weight \newcommand{\CumScenarioWeight}{\Omega} % p-quantile of the P and L distribution \newcommand{\Quantile}{q} % Quantile function \newcommand{\QuantileFunction}{Q} % Probability density function \newcommand{\PDF}{f} % Cumulative distribution function \newcommand{\CDF}{F} % Standard normal CDF \newcommand{\NormalCDF}{\Phi} % Inverse standard normal (quantile function) \newcommand{\NormalQuantile}{\Phi^{-1}} % Standard normal density function \newcommand{\NormalPDF}{\phi} % Likelihood function \newcommand{\lik}{\mathcal{L}} % Log-likelihood \newcommand{\LogLikelihood}{\ell} % Parameter vector (MLE estimation) \newcommand{\ParamSet}{\theta} % Parameter space \newcommand{\ParamSpace}{\Theta} % Fisher information matrix \newcommand{\FisherInfo}{\mathcal{I}} % Statistical power (Type II error rate) \newcommand{\StatPower}{\beta} % Significance level (CI coverage complement) \newcommand{\SignifLevel}{\gamma} % --- Options ------------------------------------------------------------- % Call option label \newcommand{\CallOption}{\text{call}} % Put option label \newcommand{\PutOption}{\text{put}} % Strike price \newcommand{\Strike}{X} % Risk-free interest rate \newcommand{\RiskFree}{r_f} % Option maturity (time to expiration) \newcommand{\OptionMaturity}{\tau} % Option delta \newcommand{\OptionDelta}{\Delta} % Option gamma \newcommand{\OptionGamma}{\Gamma} % Option vega \newcommand{\Vega}{\mathcal{V}} % Holding period \newcommand{\HoldingPeriod}{H} % Simulation profit/loss (signed; negative is a loss) \newcommand{\ProfitLoss}{\Pi} % Futures price \newcommand{\Futures}{F} % Hedge ratio \newcommand{\HedgeRatio}{h} % Units of basic asset held \newcommand{\StockHolding}{x^b} % Units of options held \newcommand{\OptionHolding}{x^o} % --- Interest Rates ------------------------------------------------------ % Bond convexity \newcommand{\Convexity}{C} % Interest rate / yield \newcommand{\Yields}{r} % Key rate change, basis points (scalar tenor component); used as \KeyRateChange_k or \KeyRateChange_{t,k}. EXCEPTION to the no-macro-to-macro-indirection rule, decided 2026-08-09: composed from \Yields on purpose, so a future change to the rate glyph propagates here without a follow-up edit. \Yields is defined earlier in generated output (same interest_rates category, TOML order), so expansion order is safe; verify this still holds if either entry is reordered. \newcommand{\KeyRateChange}{\Delta \Yields^{\mathrm{bp}}} % Key rate changes, basis points (vector); used bare or as \KeyRateChanges_t. EXCEPTION to the no-macro-to-macro-indirection rule, decided 2026-08-09: composed from \Yields on purpose, so a future change to the rate glyph propagates here without a follow-up edit. \bm{\Yields} depends on \bm/\boldsymbol correctly expanding a macro argument rather than a bare letter --- confirm this renders correctly the first time either consumer compiles it. \newcommand{\KeyRateChanges}{\Delta \bm{\Yields}^{\mathrm{bp}}} % Dollar value of a basis point \newcommand{\DV}{\text{DV01}} % Mean reversion speed (interest rate models) \newcommand{\MeanReversion}{\varkappa} % Modified duration \newcommand{\ModDur}{D} % Macaulay duration \newcommand{\MacDur}{D_{\text{Mac}}} % Effective duration \newcommand{\EffDur}{D_{\text{eff}}} % DV01 vector \newcommand{\DVvec}{\bm{d}} % Long-run mean rate (Vasicek/CIR/Hull-White) \newcommand{\LongRunRate}{\theta} % Hull-White time-varying drift \newcommand{\HullWhiteDrift}{\varphi} % Cash flow (coupon/principal payment) \newcommand{\CashFlow}{c} % --- Extreme Value ------------------------------------------------------- % Tail index (EVT, Pareto-type tails) \newcommand{\TailIndex}{\iota} % Shape parameter (xi = 1/iota) \newcommand{\ShapeParam}{\varsigma} % Threshold value (EVT) \newcommand{\Threshold}{u} % Extremal index (Leadbetter 1983) \newcommand{\ExtremalIndex}{\theta} % Multivariate extremal coefficient, theta in [1,d]. theta=1 complete tail dependence; theta=d asymptotic independence. Glyph collides with ExtremalIndex and ParamSet; disambiguated at source by macro name. \newcommand{\ExtremalCoef}{\theta} % GEV distribution function \newcommand{\GEV}{\mathcal{H}} % GPD distribution function \newcommand{\GPD}{\mathcal{G}} % GPD scale parameter \newcommand{\GPDscale}{\beta} % GPD scale parameter at a threshold u, distinct from the generic GPD scale \newcommand{\GPDscaleAtThreshold}{\beta_u} % Number of threshold exceedances (EVT) \newcommand{\TailCount}{C} % Sample maximum (block maxima, EVT) \newcommand{\SampleMax}{M} % Kurtosis value (realized, e.g. sample kurtosis) \newcommand{\KurtosisVal}{\kappa} % Pareto scaling constant (EVT) \newcommand{\ParetoConst}{\mathcal{A}} % Generic constant (EVT asymptotics) \newcommand{\RemainderConst}{\mathcal{C}} % Little-o asymptotic notation \newcommand{\LittleO}{o} % Moment order (EVT) \newcommand{\MomentOrder}{\mathscr{m}} % Doubled sample size (EVT block maxima) \newcommand{\DoubleSample}{D} % GEV normalizing location constant \newcommand{\GEVloc}{a} % GEV normalizing scale constant \newcommand{\GEVscale}{b} % Negative return (loss, EVT context) \newcommand{\NegativeReturn}{L} % --- Copulas ------------------------------------------------------------- % Copula function \newcommand{\Copula}{\mathcal{C}} % Copula density \newcommand{\CopulaDensity}{c} % Generator function (Archimedean copulas) \newcommand{\generator}{\varphi} % Copula dependence parameter \newcommand{\CopulaParam}{\theta} % Copula marginal parameter set \newcommand{\MarginalParamSet}{\eta} % Lower tail dependence coefficient \newcommand{\LowerTailDep}{\lambda_L} % Upper tail dependence coefficient \newcommand{\UpperTailDep}{\lambda_U} % Marginal CDF (second variable) \newcommand{\MarginalCDF}{G} % Joint density function \newcommand{\JointDensity}{h} % Marginal density function \newcommand{\MarginalDensity}{g} % Joint distribution function \newcommand{\JointCDF}{H} % Gaussian copula correlation parameter \newcommand{\CopulaCorr}{\rho} % --- Multivariate -------------------------------------------------------- % Diagonal matrix of conditional volatilities \newcommand{\DiagVolD}{\bm{D}} % Idiosyncratic variance matrix (diagonal, factor models) \newcommand{\IdioVarMatrix}{\bm{\Psi}} % Covariance matrix entry (two indices) \newcommand{\CovElement}{\sigma} % Auxiliary matrix in DCC dynamics \newcommand{\DCCauxQ}{\bm{Q}} % BEKK constant matrix \newcommand{\BEKKconst}{\bm{\Omega}} % BEKK ARCH parameter matrix \newcommand{\BEKKarch}{\bm{A}} % BEKK GARCH parameter matrix \newcommand{\BEKKgarch}{\bm{B}} % DCC rescaling diagonal matrix \newcommand{\DCCrescaleZ}{\bm{Z}} % DCC auxiliary matrix element \newcommand{\DCCelement}{q} % Factor loadings matrix (loadings/eigenvectors of the covariance matrix) \newcommand{\FactorLoadings}{\bm{\Lambda}} % Factor subscript label \newcommand{\factor}{\text{factor}} % PCA eigenvalue \newcommand{\Eigenvalue}{\lambda} % Number of factors in factor model \newcommand{\NumberFactors}{m} % Factor return vector \newcommand{\FactorReturn}{\bm{g}} % PCA factor score (scalar; not an eigenvector) \newcommand{\PCAFactor}{F} % BEKK cross-covariance coefficient \newcommand{\BEKKcross}{\delta} % Factor loading element \newcommand{\FactorLoading}{\Lambda} % DCC correlation persistence parameter \newcommand{\DCCxi}{\xi} % DCC news coefficient (weight on recent shocks) \newcommand{\DCCzeta}{\zeta} % --- Simulation ---------------------------------------------------------- % Number of simulation paths \newcommand{\NumberSims}{B} % Cholesky factor of covariance matrix \newcommand{\Cholesky}{\bm{L}} % Uniform random number \newcommand{\UniformDraw}{\tilde{u}} % --- Backtesting --------------------------------------------------------- % Violation ratio (observed/expected violations) \DeclareMathOperator{\ViolRatio}{VR} % Stressed Value at Risk \DeclareMathOperator{\StressedVaR}{SVaR} % QLIKE loss function for variance forecasts \DeclareMathOperator{\QLIKE}{QLIKE} % Mean squared error (forecast loss function) \DeclareMathOperator{\MSE}{MSE} % Mean absolute error (forecast loss function) \DeclareMathOperator{\MAE}{MAE} % Markov transition probability \newcommand{\TransProb}{\pi} % Exception indicator: 1 if y_t < -VaR_t \newcommand{\ExceptionInd}{\eta} % Exception count \newcommand{\ExceptionCount}{\upsilon} % Probability Integral Transform \newcommand{\ProbIntegralTransform}{\hat{u}} % Quantile score (pinball loss function) \newcommand{\QuantileScore}{\mathcal{S}} % Kolmogorov-Smirnov test statistic \newcommand{\KSstat}{D} % Test statistic (generic base letter) \newcommand{\TestStat}{J} % --- General ------------------------------------------------------------- % Regression intercept \newcommand{\RegressionIntercept}{a} % Regression slope \newcommand{\RegressionSlope}{\beta} % Risk factor \newcommand{\RiskFactor}{x} % Forecast horizon \newcommand{\ForecastHorizon}{h} % Block length (bootstrap) \newcommand{\BlockLength}{\ell} % Capital tau variant \newcommand{\DeliveryTime}{\Upsilon} % Today's calendar time in years (option pricing input; distinct from trading-date index t) \newcommand{\CalendarTime}{t^*} % Probability measure \newcommand{\ProbMeasure}{\mathbb{P}} % Risk-neutral measure \newcommand{\RiskNeutral}{\mathbb{Q}} % Pricing function (Black-Scholes, bond pricing, etc.) \newcommand{\PricingFn}{V} % Indicator function \newcommand{\Indicator}{\bm{1}} % Vector of ones \newcommand{\OnesVector}{\bm{1}} % Risk factor sensitivity (first-order P&L) \newcommand{\Sensitivity}{\delta} % Second-order sensitivity (convexity/gamma) \newcommand{\ConvexitySens}{\gamma} % Output floor percentage (Basel) \newcommand{\OutputFloor}{\alpha} % --- Subscript Labels ---------------------------------------------------- % Subscript label: annualized \newcommand{\Annual}{a} % Subscript label: implied \newcommand{\Implied}{I} % Subscript label: portfolio \newcommand{\Portfolio}{\pi} % --- Text Abbreviations -------------------------------------------- % S&P 500 index \newcommand{\SP}{\text{S\&P-500}} % Student-t distribution (text) \newcommand{\St}{\text{Student-t}} % Geopolitical Risk index (Caldara and Iacoviello) \newcommand{\GPR}{\text{GPR}} \]

17  Presentations and reports

A risk analyst submits a VaR report to the regulator on Friday. On Monday, the regulator asks for the same analysis with an updated dataset. If the original report was assembled by hand — copying numbers from a console, pasting charts into slides — recreating it takes hours and invites transcription errors. A reproducible report, where code and narrative live in a single source file, regenerates in seconds with the new data, keeping every number and chart consistent with it.

There are two main approaches to creating reports from data analysis. One is the traditional copy-and-paste method using familiar software. The other is an integrated reporting system that combines code and output directly.

Many integrated approaches are based on Markdown, a lightweight markup language that uses plain text formatting syntax. Unlike Word’s complex formatting system, Markdown files are readable as plain text and can be easily version-controlled. Quarto is a publishing system built around Markdown that works well with R, Python and Julia, allowing us to embed our analysis directly into reports. This ensures that calculations, plots and statistics are consistent with the underlying data, reducing errors from manual copying and making it easier to update reports when new data arrives.

17.1 Data and libraries

The data below is what the example reports are built from.

library(reshape2)
library(lubridate)
library(zoo)
library(xts)
library(tseries)
library(ggplot2)
library(moments)
source("common/functions.r", chdir = TRUE)
data = ProcessRawData()

Prices = tail(data$Price, 500)
Returns = tail(data$Return, 500)
tickers = names(Returns)[2:length(names(Returns))]
import pandas as pd
import numpy as np
from scipy import stats
from scipy.stats import skew, kurtosis, jarque_bera
from statsmodels.stats.diagnostic import acorr_ljungbox
from plotnine import ggplot, aes, geom_line, scale_y_log10, labs, theme_minimal

data = ProcessRawData()

Prices = data['Price'].tail(500).reset_index(drop=True)
Returns = data['Return'].tail(500).reset_index(drop=True)
tickers = [col for col in Returns.columns if col != 'date']
using DataFrames, Dates, Statistics, StatsBase, PrettyTables
using CSV, TidierPlots, CairoMakie, HypothesisTests, Distributions

data = ProcessRawData();
Prices = last(data["Price"], 500);
Returns = last(data["Return"], 500);
tickers = [name for name in names(Returns) if name != "date"];

17.2 Word and PowerPoint vs. Quarto

17.2.1 Word and PowerPoint

Most people use Microsoft Word or PowerPoint to make reports and presentations, copying the output from R into the document. This is the standard approach. It is also fragile.

The benefit of Word or PowerPoint is familiarity. You know the software and how to design the document you need. For tables, move the numbers into Excel first. For figures, export an SVG from RStudio and import that file.

The downside is that every number and figure has to be carried across by hand, and carried again on every update. That takes time and it is where transcription and version errors come from.

Never screenshot an image out of R into Word or PowerPoint. A screenshot is a fixed grid of pixels, so it blurs as soon as anyone resizes it. Export an SVG, which is stored as instructions for drawing the figure and stays sharp at any size, or copy the image directly.

17.2.2 Quarto

Quarto is a publishing system that allows you to mix Markdown and R, Python or Julia code in the same document. If you are familiar with Jupyter, it is quite similar.

When you write a document in Quarto, RStudio can directly export it to an HTML page or a PDF, Word or PowerPoint file. You can also export to other formats, including presentations, from the same source. If you export to a Word or PowerPoint file, you can edit the files there. Quarto is at its best when it produces the final PDF itself. That removes another editing step.

RStudio will need additional libraries for Quarto files and will give you an installation option when you start using Quarto.

17.3 Setting up Quarto

In RStudio, go to File → New File → Quarto Document or Quarto Presentation. This will create a new file with a .qmd extension that combines Markdown text with executable R code chunks.

For a single, simple document, save the file in the same directory as your data. This is the fastest way to get started. For a larger project, use the separate directories for raw data, code and output described in the best-practice chapter’s project-structuring guidance. Once created, you can write your analysis using a mix of narrative text and R code, then render the document to produce a report in your chosen format.

17.3.1 YAML headers for different output formats

Quarto files normally start with a YAML header that controls how the document is processed and formatted. YAML (YAML Ain’t Markup Language) is a human-readable data format that uses indentation and simple syntax to define configuration options.

The YAML header is enclosed between three dashes (---) at the very beginning of your document and three dashes at the end of the header. This section must come before any content and specifies information like the title, output format and author.

The headers below are minimal examples for six output formats.

17.3.1.1 HTML file

HTML renders quickly, which makes it useful for testing your document structure while you work.

---
title: "Title"
format: html
author: "Me"
---

17.3.1.2 PDF file

PDF output has a fixed page layout, which suits printing and formal submission.

---
title: "Title"
format: pdf
author: "Me"
---

17.3.1.3 Word file

Word output opens as an editable .docx file, so colleagues can revise it without Quarto installed.

---
title: "Title"
format: docx
author: "Me"
---

17.3.1.4 PDF presentation file

Beamer produces PDF slides using LaTeX, the same engine as the PDF document format.

---
title: "Title"
format: beamer
author: "Me"
---

17.3.1.5 PowerPoint file

PowerPoint output opens as an editable .pptx file, useful when a colleague needs to adjust slides in PowerPoint itself.

---
title: "Title"
format: pptx
author: "Me"
---

17.3.1.6 Interactive presentation file

RevealJS slides run in any web browser, with no separate viewer or software needed.

---
title: "Title"
format: revealjs
author: "Me"
---

17.3.2 Quarto and PDF files

If you want to make PDF files directly from Quarto files in RStudio, you need additional software. In particular, Quarto uses a typesetting system called LaTeX for that. On many university machines, LaTeX is already installed. If it is not, you have two options.

One option is to install the full LaTeX distribution.

Alternatively, use the tinytex package from Yihui Xie, the author of knitr. This has two advantages. The installation footprint is much smaller, and RStudio handles almost everything automatically. Go to the webpage for tinytex yihui.org/tinytex/ for more details.

Do the following in the R console:

install.packages('tinytex')
library(tinytex)
tinytex::install_tinytex()

17.4 Running code in Quarto

When you click Render, Quarto generates a document that includes both the prose and the output of the embedded code. You can embed code in chunks or inline.

17.4.1 Code chunks

Code chunks are marked with the language name in braces: {r} for R, {python} for Python and {julia} for Julia.

```{r}
a = 34
b = 0.4
result = a^b
cat(round(result, 4), "\n")
```

This renders as:

a = 34
b = 0.4
result = a^b
cat(round(result, 4), "\n")
4.0982
```{python}
a = 34
b = 0.4
result = a**b
print(f"{result:.4f}")
```

This renders as:

a = 34
b = 0.4
result = a**b
print(f"{result:.4f}")
4.0982
```{julia}
using Printf;
a = 34;
b = 0.4;
result = a^b;
@printf("%.4f\n", result)
```

This renders as:

using Printf;
a = 34;
b = 0.4;
result = a^b;
@printf("%.4f\n", result)
4.0982

You get the code and its output displayed in your report.

17.4.2 Hiding code

You can hide the code but show the output using #| echo: false:

```{r}
#| echo: false
cat("The firms we have are:", paste(tickers, collapse=", "), "\n")
```

This renders as:

The firms we have are: AAPL, DIS, GE, INTC, JPM, MCD
```{python}
#| echo: false
print("The firms we have are:", end=" ")
print(", ".join(tickers))
```

This renders as:

The firms we have are: AAPL, DIS, GE, INTC, JPM, MCD
```{julia}
#| echo: false
print("The firms we have are: ")
println(join(tickers, ", "))
```

This renders as:

The firms we have are: AAPL, DIS, GE, INTC, JPM, MCD

17.4.3 Suppressing library messages

When loading libraries in reports, startup messages can clutter the output. Each language has its own approach:

Use suppressPackageStartupMessages() to create cleaner documents:

{verbatim, lang="markdown"} ```jhf #| echo: false suppressPackageStartupMessages(library(lubridate)) suppressPackageStartupMessages(library(moments)) ```

Python libraries typically do not produce startup messages. Use #| output: false if needed:

{verbatim, lang="markdown"} ```augxlmcx #| echo: false #| output: false import pandas as pd import numpy as np ```

Julia package loading can produce precompilation messages. Use #| output: false:

{verbatim, lang="markdown"} ```oyanjtn #| echo: false #| output: false using DataFrames, Statistics ```

17.5 Creating plots in reports

Chapter 12 covers plotting fundamentals. Here we embed plots in reports. Plots are embedded directly in the output when placed in code chunks:

```{r}
df_plot = data.frame(date = ymd(Prices$date), price = Prices$AAPL)
ggplot(df_plot, aes(x = date, y = price)) +
  geom_line() +
  scale_y_log10() +
  labs(title = "AAPL Price", x = "", y = "Price (log scale)") +
  theme_minimal()
```

This renders as:

```{python}
df_plot = pd.DataFrame({
    'date': Prices['date'],
    'price': Prices['AAPL']
})

(ggplot(df_plot, aes(x='date', y='price'))
 + geom_line()
 + scale_y_log10()
 + labs(title="AAPL Price", x="", y="Price (log scale)")
 + theme_minimal())
```

This renders as:

```{julia}
df_plot = DataFrame(
    date = Prices[!, "date"],
    price = Prices[!, "AAPL"]
);

p = ggplot(df_plot, @aes(x = date, y = price)) +
    geom_line() +
    scale_y_log10() +
    labs(title = "AAPL Price", x = "", y = "Price (log scale)") +
    theme_minimal()
```

This renders as:

17.6 Creating tables

17.6.1 Basic text output

For multiple assets, we can create summary statistics programmatically:

```{r}
for(i in tickers){
  x = Returns[[i]]
  cat(sprintf("%s %.6f %.6f %.6f %.6f\n", i, mean(x), sd(x), min(x), max(x)))
}
```

This renders as:

AAPL 0.000076 0.019382 -0.060472 0.085236
DIS -0.001447 0.020029 -0.141139 0.061060
GE -0.000148 0.021152 -0.109101 0.068496
INTC -0.001189 0.022253 -0.124186 0.101278
JPM 0.000136 0.016246 -0.063434 0.060033
MCD 0.000534 0.011124 -0.049908 0.040062
```{python}
for ticker in tickers:
    x = Returns[ticker]
    print(f"{ticker} {x.mean():.6f} {x.std():.6f} {x.min():.6f} {x.max():.6f}")
```

This renders as:

AAPL 0.000076 0.019382 -0.060472 0.085236
DIS -0.001447 0.020029 -0.141139 0.061060
GE -0.000148 0.021152 -0.109101 0.068496
INTC -0.001189 0.022253 -0.124186 0.101278
JPM 0.000136 0.016246 -0.063434 0.060033
MCD 0.000534 0.011124 -0.049908 0.040062
```{julia}
using Printf
for ticker in tickers
    x = Returns[!, ticker]
    @printf("%s %.6f %.6f %.6f %.6f\n", ticker, mean(x), std(x), minimum(x), maximum(x))
end
```

This renders as:

AAPL 0.000076 0.019382 -0.060472 0.085236
DIS -0.001447 0.020029 -0.141139 0.061060
GE -0.000148 0.021152 -0.109101 0.068496
INTC -0.001189 0.022253 -0.124186 0.101278
JPM 0.000136 0.016246 -0.063434 0.060033
MCD 0.000534 0.011124 -0.049908 0.040062

17.6.2 Formatted tables

For better formatting, each language has table-rendering functions:

```{r}
library(knitr)

df = matrix(ncol = 5, nrow = length(tickers))
for(i in 1:length(tickers)){
  x = Returns[[tickers[i]]]
  lb = Box.test(x^2, lag = 10, type = "Ljung-Box")
  df[i,] = c(mean(x)*100, sd(x)*100, max(x)*100, lb$statistic, lb$p.value)
}

df = as.data.frame(df)
df = cbind(tickers, df)
names(df) = c("Asset", "Mean", "SD", "Max", "LB Stat", "LB p-val")

kable(df, digits = 3, caption = "Sample statistics")
```

This renders as:

Table: Sample statistics

|Asset |   Mean|    SD|    Max| LB Stat| LB p-val|
|:-----|------:|-----:|------:|-------:|--------:|
|AAPL  |  0.008| 1.938|  8.524|  55.333|    0.000|
|DIS   | -0.145| 2.003|  6.106|  13.945|    0.176|
|GE    | -0.015| 2.115|  6.850|  16.577|    0.084|
|INTC  | -0.119| 2.225| 10.128|   8.740|    0.557|
|JPM   |  0.014| 1.625|  6.003|  23.351|    0.010|
|MCD   |  0.053| 1.112|  4.006|  18.309|    0.050|
```{python}
#| output: asis
stats_data = []
for ticker in tickers:
    x = Returns[ticker]
    lb = acorr_ljungbox(x**2, lags=[10], return_df=True)
    stats_data.append({
        'Asset': ticker,
        'Mean': round(x.mean() * 100, 3),
        'SD': round(x.std() * 100, 3),
        'Max': round(x.max() * 100, 3),
        'LB Stat': round(lb['lb_stat'].values[0], 3),
        'LB p-val': round(lb['lb_pvalue'].values[0], 3)
    })

df_stats = pd.DataFrame(stats_data)
print(df_stats.to_html(index=False))
```

This renders as:

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th>Asset</th>
      <th>Mean</th>
      <th>SD</th>
      <th>Max</th>
      <th>LB Stat</th>
      <th>LB p-val</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>AAPL</td>
      <td>0.008</td>
      <td>1.938</td>
      <td>8.524</td>
      <td>55.333</td>
      <td>0.000</td>
    </tr>
    <tr>
      <td>DIS</td>
      <td>-0.145</td>
      <td>2.003</td>
      <td>6.106</td>
      <td>13.945</td>
      <td>0.176</td>
    </tr>
    <tr>
      <td>GE</td>
      <td>-0.015</td>
      <td>2.115</td>
      <td>6.850</td>
      <td>16.577</td>
      <td>0.084</td>
    </tr>
    <tr>
      <td>INTC</td>
      <td>-0.119</td>
      <td>2.225</td>
      <td>10.128</td>
      <td>8.740</td>
      <td>0.557</td>
    </tr>
    <tr>
      <td>JPM</td>
      <td>0.014</td>
      <td>1.625</td>
      <td>6.003</td>
      <td>23.351</td>
      <td>0.010</td>
    </tr>
    <tr>
      <td>MCD</td>
      <td>0.053</td>
      <td>1.112</td>
      <td>4.006</td>
      <td>18.309</td>
      <td>0.050</td>
    </tr>
  </tbody>
</table>
```{julia}
#| output: asis
function lb_test(x)
    lb = LjungBoxTest(x.^2, 10)
    return (lb.Q, pvalue(lb))
end

df_stats = DataFrame(
    "Asset" => tickers,
    "Mean" => [round(mean(Returns[!, t]) * 100, digits=3) for t in tickers],
    "SD" => [round(std(Returns[!, t]) * 100, digits=3) for t in tickers],
    "Max" => [round(maximum(Returns[!, t]) * 100, digits=3) for t in tickers],
    "LB Stat" => [round(lb_test(Returns[!, t])[1], digits=3) for t in tickers],
    "LB p-val" => [round(lb_test(Returns[!, t])[2], digits=3) for t in tickers]
)
io = IOBuffer()
pretty_table(io, df_stats, backend = :html)
print(String(take!(io)))
```

This renders as:

<table>
  <thead>
    <tr class = "columnLabelRow">
      <th style = "font-weight: bold; text-align: right;">Asset</th>
      <th style = "font-weight: bold; text-align: right;">Mean</th>
      <th style = "font-weight: bold; text-align: right;">SD</th>
      <th style = "font-weight: bold; text-align: right;">Max</th>
      <th style = "font-weight: bold; text-align: right;">LB Stat</th>
      <th style = "font-weight: bold; text-align: right;">LB p-val</th>
    </tr>
    <tr class = "columnLabelRow">
      <th style = "text-align: right;">String</th>
      <th style = "text-align: right;">Float64</th>
      <th style = "text-align: right;">Float64</th>
      <th style = "text-align: right;">Float64</th>
      <th style = "text-align: right;">Float64</th>
      <th style = "text-align: right;">Float64</th>
    </tr>
  </thead>
  <tbody>
    <tr class = "dataRow">
      <td style = "text-align: right;">AAPL</td>
      <td style = "text-align: right;">0.008</td>
      <td style = "text-align: right;">1.938</td>
      <td style = "text-align: right;">8.524</td>
      <td style = "text-align: right;">55.333</td>
      <td style = "text-align: right;">0.0</td>
    </tr>
    <tr class = "dataRow">
      <td style = "text-align: right;">DIS</td>
      <td style = "text-align: right;">-0.145</td>
      <td style = "text-align: right;">2.003</td>
      <td style = "text-align: right;">6.106</td>
      <td style = "text-align: right;">13.945</td>
      <td style = "text-align: right;">0.176</td>
    </tr>
    <tr class = "dataRow">
      <td style = "text-align: right;">GE</td>
      <td style = "text-align: right;">-0.015</td>
      <td style = "text-align: right;">2.115</td>
      <td style = "text-align: right;">6.85</td>
      <td style = "text-align: right;">16.577</td>
      <td style = "text-align: right;">0.084</td>
    </tr>
    <tr class = "dataRow">
      <td style = "text-align: right;">INTC</td>
      <td style = "text-align: right;">-0.119</td>
      <td style = "text-align: right;">2.225</td>
      <td style = "text-align: right;">10.128</td>
      <td style = "text-align: right;">8.74</td>
      <td style = "text-align: right;">0.557</td>
    </tr>
    <tr class = "dataRow">
      <td style = "text-align: right;">JPM</td>
      <td style = "text-align: right;">0.014</td>
      <td style = "text-align: right;">1.625</td>
      <td style = "text-align: right;">6.003</td>
      <td style = "text-align: right;">23.351</td>
      <td style = "text-align: right;">0.01</td>
    </tr>
    <tr class = "dataRow">
      <td style = "text-align: right;">MCD</td>
      <td style = "text-align: right;">0.053</td>
      <td style = "text-align: right;">1.112</td>
      <td style = "text-align: right;">4.006</td>
      <td style = "text-align: right;">18.309</td>
      <td style = "text-align: right;">0.05</td>
    </tr>
  </tbody>
</table>

17.7 Inline code in reports

Inline code embeds calculated values directly in your prose. Rather than hardcoding numbers that might change, you reference variables or expressions that are evaluated when the document renders. This keeps your text synchronised with your analysis — if the data changes, the numbers in your text update automatically.

The syntax places the expression inside backticks with a language identifier. When Quarto renders the document, it evaluates the expression and substitutes the result. You can use simple variables or more complex expressions including calculations and function calls.

The knitr engine used throughout this book evaluates inline r expressions, but not inline python or julia expressions. Where the sections below show Python or Julia results, the rendered-looking sentence is static text illustrating what a native Quarto document would produce with the current data, not a live evaluation.

Suppose we want to report on r use stock. We write:

If we take `r use`, we have `r length(Returns[[use]])` observations. The mean return is `r round(mean(Returns[[use]])*100, 4)`%, and on the best day the return was `r round(max(Returns[[use]])*100, 1)`%.

If we take r use, we have r length(Returns[[use]]) observations. The mean return is r round(mean(Returns[[use]])*100, 4)%, and on the best day the return was r round(max(Returns[[use]])*100, 1)%.

If we take `{python} use`, we have `{python} len(Returns[use])` observations. The mean return is `{python} round(Returns[use].mean()*100, 4)`%, and on the best day the return was `{python} round(Returns[use].max()*100, 1)`%.

If we take AAPL, we have 500 observations. The mean return is 0.0076%, and on the best day the return was 8.5%.

If we take `{julia} use`, we have `{julia} length(Returns[!, use])` observations. The mean return is `{julia} round(mean(Returns[!, use])*100, digits=4)`%, and on the best day the return was `{julia} round(maximum(Returns[!, use])*100, digits=1)`%.

If we take AAPL, we have 500 observations. The mean return is 0.0076%, and on the best day the return was 8.5%.

17.7.1 Finding specific dates

We can find the dates of specific events like the best and worst returns. First we compute the dates in a code chunk:

```{r}
best_date = format(
  ymd(Returns$date[which.max(Returns[[use]])]),
  "%d %B %Y"
)
worst_date = format(
  ymd(Returns$date[which.min(Returns[[use]])]),
  "%d %B %Y"
)
```

The best day happened on `r best_date`. The worst day happened on `r worst_date`.

The best day happened on r best_date. The worst day happened on r worst_date.

```{python}
best_idx = Returns[use].idxmax()
worst_idx = Returns[use].idxmin()
best_date = Returns.loc[best_idx, 'date'].strftime("%d %B %Y")
worst_date = Returns.loc[worst_idx, 'date'].strftime("%d %B %Y")
```

The best day happened on `{python} best_date`. The worst day happened on `{python} worst_date`.

The best day happened on 10 November 2022. The worst day happened on 13 September 2022.

```{julia}
best_idx = argmax(Returns[!, use])
worst_idx = argmin(Returns[!, use])
best_date = Dates.format(Returns[best_idx, :date], "dd U Y")
worst_date = Dates.format(Returns[worst_idx, :date], "dd U Y")
```

The best day happened on `{julia} best_date`. The worst day happened on `{julia} worst_date`.

The best day happened on 10 November 2022. The worst day happened on 13 September 2022.

17.7.2 Statistical tests

Chapter 14 covers statistical test details. This section shows how to embed those results in prose. First compute the test in a hidden code chunk, then reference the results inline.

The GE returns have mean `r round(mean(y_r)*100, 4)`% and standard deviation `r round(sd(y_r)*100, 2)`%. The skewness is `r round(moments::skewness(y_r), 2)` and excess kurtosis is `r round(moments::kurtosis(y_r)-3, 2)`. The Jarque-Bera test has p-value `r format(jb\(p.value, digits=2)&#96;</code>, rejecting normality. The Ljung-Box test on returns has p-value <code>&#96;r round(lb_ret\)p.value, 3)`, while on squared returns the p-value is `r round(lb_sq$p.value, 3)`.

The GE returns have mean r round(mean(y_r)*100, 4)% and standard deviation r round(sd(y_r)*100, 2)%. The skewness is r round(moments::skewness(y_r), 2) and excess kurtosis is r round(moments::kurtosis(y_r)-3, 2). The Jarque-Bera test has p-value r format(jb$p.value, digits=2), rejecting normality. The Ljung-Box test on returns has p-value r round(lb_ret$p.value, 3), while on squared returns the p-value is r round(lb_sq$p.value, 3).

The GE returns have mean `{python} round(y_py.mean()*100, 4)`% and standard deviation `{python} round(y_py.std(ddof=1)*100, 2)`%. The skewness is `{python} round(skew(y_py), 2)` and excess kurtosis is `{python} round(kurtosis(y_py), 2)`. The Jarque-Bera test has p-value `{python} f”{jb_pval:.1e}“`, rejecting normality. The Ljung-Box test on returns has p-value `{python} round(lb_ret[‘lb_pvalue’].values[0], 3)`, while on squared returns the p-value is `{python} round(lb_sq[‘lb_pvalue’].values[0], 3)`.

The GE returns have mean -0.0148% and standard deviation 2.12%. The skewness is -0.37 and excess kurtosis is 1.89. The Jarque-Bera test has p-value 2.9e-19, rejecting normality. The Ljung-Box test on returns has p-value 0.309, while on squared returns the p-value is 0.084.

The GE returns have mean `{julia} round(mean(y_jl)*100, digits=4)`% and standard deviation `{julia} round(std(y_jl)*100, digits=2)`%. The skewness is `{julia} round(skewness(y_jl), digits=2)` and excess kurtosis is `{julia} round(kurtosis(y_jl), digits=2)`. The Jarque-Bera test has p-value `{julia} round(pvalue(jb), sigdigits=2)`, rejecting normality. The Ljung-Box test on returns has p-value `{julia} round(pvalue(lb_ret), digits=3)`, while on squared returns the p-value is `{julia} round(pvalue(lb_sq), digits=3)`.

The GE returns have mean -0.0148% and standard deviation 2.12%. The skewness is -0.37 and excess kurtosis is 1.89. The Jarque-Bera test has p-value 2.9e-19, rejecting normality. The Ljung-Box test on returns has p-value 0.309, while on squared returns the p-value is 0.084.

The pieces are now in place — a YAML header, code chunks, controlled display, embedded plots and tables, and inline values. Chapter 31 assembles them into three complete files, one per language, that you can copy and adapt.

Update the saved data and rerun the render, and everything in the document moves with it. That is the whole argument for writing reports this way.