Einleitung

Künstliche Intelligenz (KI) ist für deutsche Firmen keineswegs eine Zukunftsvorstellung mehr. Laut einer Umfrage von Deloitte unter ca. 2.700 KI-Experten aus neun Ländern, geben über 90 Prozent der Befragten an, dass ihr Unternehmen Technologien aus einem der Bereiche Machine Learning (ML), Deep Learning, Natural Language Processing (NLP) und Computer Vision nutzt oder plant zu nutzen. Dieser hohe Anteil lässt sich nicht nur dadurch erklären, dass die Firmen das Potenzial von KI erkannt haben. Vielmehr stehen auch deutlich mehr standardisierte Lösungen für den Einsatz dieser Technologien zur Verfügung. Diese Entwicklung hat dazu geführt, dass die Einstiegshürde in den vergangenen Jahren immer weiter gesunken ist.

Das Problem mit Standardlösungen

So bieten zum Beispiel die drei großen Cloud Anbieter – Amazon Web Services (AWS), Google Cloud Plattform (GCP) und Microsoft Azure – für bestimmte Problemstellungen standardisierte Lösungen an (z.B. Objekterkennung auf Bildern, Übersetzung von Texten, und automatisiertes Machine Learning). Bislang lassen sich aber noch nicht alle Probleme mit Hilfe solcher standardisierten Anwendungen lösen. Dafür kann es unterschiedliche Gründe geben: Der häufigste Grund ist, dass die verfügbaren Standardlösungen nicht auf die gewünschte Fragestellung passen. So ist zum Beispiel im Bereich NLP die Klassifikation von ganzen Texten häufig als eine Standardlösung verfügbar. Soll hingegen eine Klassifikation nicht auf Text-, sondern Wort-Ebene stattfinden, werden dazu andere Modelle benötigt, die nicht immer als Teil von Standardlösungen verfügbar sind. Und selbst wenn diese zur Verfügung stehen, sind die möglichen Kategorien in der Regel vordefiniert und lassen sich nicht weiter anpassen. Ein Service also, der für die Klassifikation von Wörtern in die Kategorien Ort, Person und Zeit gebaut wurde, kann nicht ohne weiteres für die Klassifikation von Wörtern in den Kategorien Kunde, Produkt und Preis verwendet werden. Viele Unternehmen sind deshalb auch weiterhin darauf angewiesen, eigene ML Modelle zu entwickeln. Da die Entwicklung von Modellen oftmals auf lokalen Rechnern erfolgt, muss sichergestellt werden, dass diese Modelle nicht nur dem Entwickler zur Verfügung stehen. Nach der Entwicklung eines Modells besteht eine wichtige Herausforderung darin, das Modell unterschiedlichen Nutzern zur Verfügung zu stellen, da nur dann auch ein Mehrwert für das Unternehmen entsteht.

Mit MLOps Modelle nutzbar machen

Bei ML & KI Projekten im Unternehmen treten sowohl bei der Entwicklung als auch bei der Bereitstellung eigene Herausforderungen in Erscheinung. Während die Entwicklung oftmals an der mangelnden Verfügbarkeit geeigneter Daten scheitert, kann die Bereitstellung daran scheitern, dass ein Modell nicht mit der Produktionsumgebung kompatibel ist. So werden beispielsweise Machine Learning Modelle zum überwiegenden Teil mit Open Source Sprachen oder neuen ML-Frameworks (z.B. Dataiku oder H2O) entwickelt, während eine operative Produktionsumgebung oftmals mit proprietärer und langjährig erprobter Software arbeitet. Die enge Verzahnung dieser beiden Welten stellt oftmals beide Komponenten vor gewichtige Herausforderungen. Deshalb ist es wichtig, die Entwicklung von ML Modellen mit der Arbeit der IT Operations zu verknüpfen. Diesen Prozess bezeichnet man als MLOps, da hier Data Scientists mit der IT zusammenarbeiten, um Modelle produktiv nutzbar zu machen.

MLOps ist eine ML-Entwicklungskultur und -praxis, deren Ziel es ist, die Entwicklung von ML-Systemen (Dev) und den Betrieb von ML-Systemen (Ops) zu verbinden. In der Praxis bedeutet MLOps, auf Automatisierung und Überwachung zu setzen. Dieses Prinzip erstreckt sich auf alle Schritte der ML-Systemkonfiguration wie Integration, Testen, Freigabe, Bereitstellung und Infrastrukturverwaltung. Der Code eines Modells ist dabei einer von vielen weiteren Bestandteilen, wie in Abbildung 1 veranschaulicht wird. Die Abbildung zeigt neben dem ML Code auch andere Schritte des MLOps Prozesses auf und verdeutlicht, dass der ML Code selbst einen relativ kleinen Anteil des gesamten Prozesses ausmacht.

Abbildung 1: Wichtige Bestandteile des MLOps Prozesses

Weitere Aspekte von MLOps sind z.B. die kontinuierliche Bereitstellung und Qualitätsprüfung der Daten, oder das Testen des Modells und ggf. die Fehlerbehebung (sog. Debugging). Docker Container haben sich bei der Bereitstellung von eigens entwickelten ML Modellen als Kerntechnologie herauskristallisiert und werden deshalb in diesem Beitrag vorgestellt.

Warum Docker Container?

Die Herausforderung bei der Bereitstellung von ML Modellen ist, dass ein Modell in einer bestimmten Version einer Programmiersprache geschrieben ist. Diese Sprache ist im Regelfall nicht in der Produktionsumgebung verfügbar und muss deshalb erst installiert werden. Zudem verfügt das Modell über eigene Bibliotheken, Laufzeiten und andere technische Abhängigkeiten, die ebenfalls erst in der Produktionsumgebung installiert werden müssten. Docker löst dieses Problem über sogenannte Container, in denen sich Anwendungen samt aller Bestandteile isoliert verpacken und als eigene Services bereitstellen lassen. Diese Container enthalten alle Komponenten, die die Anwendung bzw. das ML Modell zum Ausführen benötigt inklusive Code, Bibliotheken, Laufzeiten und Systemtools. Über Container lassen sich deshalb auch eigene Modelle und Algorithmen in jeder Umgebung bereitstellen und zwar ohne Sorge, dass bspw. fehlende oder inkompatible Bibliotheken zu Fehlern führen.

Abbildung 2: Vergleich von Docker Containern und virtuellen Maschinen

Vor dem Siegeszug von Docker waren lange Zeit virtuelle Maschinen das Tool der Wahl, um Anwendungen und ML Modelle isoliert bereitzustellen. Allerdings hat sich herausgestellt, dass Docker über diverse Vorteile gegenüber virtuellen Maschinen verfügt. Dies sind insbesondere verbesserte Ressourcennutzung, Skalierbarkeit und die schnellere Bereitstellung neuer Software. Im Folgenden sollen die drei Punkte detaillierter beleuchtet werden.

Verbesserte Ressourcennutzung

Abbildung 2 vergleicht schematisch, wie Applikationen in Docker Containern bzw. auf virtuellen Maschinen laufen können. Virtuelle Maschinen verfügen über ihr eigenes Gastbetriebssystem, auf welchem unterschiedliche Applikationen laufen. Die Virtualisierung des Gastbetriebssystems auf der Hardwareebene nimmt viel Rechenleistung und Speicherplatz in Anspruch. Deshalb können auf einer virtuellen Maschine, bei gleichbleibender Effizienz, weniger Applikationen gleichzeitig laufen.

Docker Container teilen sich hingegen das Betriebssystem des Hosts und benötigen kein eigenes Betriebssystem. Applikationen in Docker Containern fahren deshalb schneller hoch und nehmen, durch das geteilte Betriebssystems des Hosts, weniger Rechenleistung und Speicherplatz in Anspruch. Diese geringere Ressourcenauslastung ermöglicht es auf einem Server mehrere Applikationen parallel laufen zu lassen, was die Nutzungsrate eines Servers verbessert.

Skalierbarkeit

Einen weiteren Vorteil bieten Container im Bereich der Skalierung: Soll innerhalb des Unternehmens ein ML Modell häufiger genutzt werden, muss die Anwendung die zusätzlichen Anfragen bedienen können. Glücklicherweise lassen sich ML Modelle mit Docker leicht skalieren indem zusätzliche Container mit der gleichen Anwendung hochgefahren werden. Insbesondere Kubernetes, eine Open Source Technologie zur Container-Orchestrierung und skalierbaren Bereitstellung von Web-Services, eignet sich aufgrund seiner Kompatibilität mit Docker für flexible Skalierung. Mit Kubernetes können Web-Services basierend auf der aktuellen Auslastung flexibel und automatisiert hoch- oder runterskaliert werden.

Bereitstellung neuer Software

Von Vorteil ist außerdem, dass Container sich nahtlos von lokalen Entwicklungsmaschinen auf Produktionsmaschinen schieben lassen. Deshalb sind sie einfach auszutauschen, wenn z.B. eine neue Version des Modells bereitgestellt werden soll. Die Isolierung des Codes und aller Abhängigkeiten in einem Container führt zudem zu einer stabileren Umgebung, in der das Modell betrieben werden kann. Dadurch treten Fehler aufgrund von bspw. falschen Versionen einzelner Bibliotheken weniger häufig auf und lassen sich gezielter beheben.

Das Modell wird innerhalb eines Containers dabei als Web-Service bereitgestellt, den andere Nutzer und Anwendungen über gewöhnliche Internetprotokolle (z.B. HTTP) ansprechen können. Auf diesem Weg kann das Modell als Web-Service von anderen Systemen und Nutzern angefragt werden, ohne dass diese selbst spezifische technischen Voraussetzungen erfüllen müssen. Es müssen also nicht erst Bibliotheken oder die Programmiersprache des Modells installiert werden, um das Modell nutzbar zu machen.

Neben Docker existieren noch weitere Containertechnologien wie rkt und Mesos, wobei Docker mit seiner nutzerfreundlichen Bedienung und ausführlichen Dokumentation neuen Entwicklern einen leichten Einstieg ermöglicht. Durch die große Nutzerbasis existieren für viele Standardanwendungen Vorlagen, die mit geringem Aufwand in Containern hochgefahren werden können. Gleichzeitig dienen diese kostenlosen Vorlagen als Basis für Entwicklungen eigener Anwendungen.

Nicht zuletzt wegen dieser Vorteile wird Docker im MLOps Prozess mittlerweile als Best Practice angesehen. Der Prozess der Modellentwicklung gleicht zunehmend dem Softwareentwicklungsprozess, nicht zuletzt auch wegen Docker. Deutlich wird dies daran, dass containerbasierte Anwendungen durch Standardtools zur stetigen Integration und Bereitstellung (CI/CD) von Web-Services unterstützt werden.

Welche Rolle spielen Docker Container in der MLOps Pipeline?

Wie bereits erwähnt, handelt es sich bei MLOps um einen komplexen Prozess der kontinuierlichen Bereitstellung von ML Modellen. Zentrale Bestandteile eines solchen Systems sind in Abbildung 1 veranschaulicht. Der MLOps Prozess ähnelt stark dem DevOps Prozess, da auch die Entwicklung von Systemen des maschinellen Lernens eine Form der Softwareentwicklung ist. Standardkonzepte aus dem DevOps Bereich, wie stetige Integration von neuem Code und Bereitstellung neuer Software, finden sich deshalb im MLOps Prozess wieder. Dabei kommen neue, ML-spezifische Bestandteile wie das kontinuierliche Modelltraining und die Modell- und Datenvalidierung hinzu.

Grundsätzlich gilt es als Best Practice die Entwicklung von ML Modellen in einer MLOps Pipeline einzubetten. Die MLOps Pipeline umfasst alle Schritte von der Bereitstellung und Transformation von Daten über das Modelltraining bis hin zur kontinuierlichen Bereitstellung fertiger Modelle auf Produktionsservern. Der Code für jeden Schritt in der Pipeline wird hierbei in einem Docker Container verpackt und die Pipeline startet die Container in einer festgelegten Reihenfolge. Docker Container zeigen besonders hier ihre Stärke, da durch die Isolierung des Codes innerhalb einzelner Container kontinuierlich Codeänderungen an den entsprechenden Stellen der Pipeline eingearbeitet werden können, ohne dass die gesamte Pipeline ausgetauscht werden muss. Dadurch sind die Kosten für die Wartung der Pipeline relativ gering. Die großen Cloud Anbieter (GCP, AWS und Microsoft Azure) bieten zudem Services an, mit denen Docker Container automatisch gebaut, bereitgestellt und als Web-Services gehostet werden können. Um die Skalierung von Containern zu erleichtern und möglichst flexibel zu gestalten, bieten Cloud Anbieter ebenfalls vollständig verwaltete Kubernetes Produkte an. Für die Verwendung von ML Modellen im Unternehmen bedeutet diese Flexibilität Kostenersparnisse, da eine ML Anwendung einfach runterskaliert wird für den Fall, dass die Nutzungsrate sinkt. Gleichermaßen kann eine höhere Nachfrage durch Bereitstellung weiterer Container gewährleistet werden, ohne dass der Container mit dem Modell gestoppt werden muss. Nutzer der Anwendung erfahren so keine unnötige Downtime.

Fazit

Für die Entwicklung von Machine Learning Modellen und MLOps Pipelines stellen Docker Container eine Kerntechnologie dar. Die Vorteile bestehen in der Portabilität, Modularisierung und Isolierung des Codes von Modellen, geringem Wartungsaufwand bei Integration in Pipelines, schnellere Bereitstellung neuer Versionen des Modells und Skalierbarkeit über serverlose Cloudprodukte zur Bereitstellung von Containern. Bei STATWORX haben wir das Potenzial von Docker Containern erkannt und nutzen diese aktiv. Mit diesem Wissen unterstützten wir unsere Kunden bei der Verwirklichung ihrer Machine Learning und KI-Projekte. Sie wollen Docker in Ihrer MLOps Pipeline nutzen? Unsere Academy bietet Remote Trainings zu Data Science mit Docker, sowie kostenlose Webinare zu den Themen MLOps und Docker an.

Introduction

Sometimes here at STATWORX, we have impromptu discussions about statistical methods. In one such discussion, one of my colleagues decided to declare (albeit jokingly) Bayesian statistics unnecessary. That made me ask myself: Why would I ever use Bayesian models in the context of a standard regression problem? Existing approaches such as Ridge Regression are just as good, if not better. However, the Bayesian approach has the advantage that it lets you regularize your model to prevent overfitting and meaningfully interpret the regularization parameters. Contrary to the usual way of looking at ridge regression, the regularization parameters are no longer abstract numbers but can be interpreted through the Bayesian paradigm as derived from prior beliefs. In this post, I’ll show you the formal similarity between a generalized ridge estimator and the Bayesian equivalent.

A (very brief) Primer on Bayesian Stats

To understand the Bayesian regression estimator, a minimal amount of knowledge about Bayesian statistics is necessary, so here’s what you need to know (if you don’t already): In Bayesian statistics, we think about model parameters (i.e., regression coefficients) probabilistically. In other words, the data given to us is fixed, and the parameters are considered random. That runs counter to the standard frequentist perspective in which the underlying model parameters are treated as fixed. At the same time, the data are considered random realizations of the stochastic process driven by those fixed model parameters. The end goal of Bayesian analysis is to find the posterior distribution, which you may remember from Bayes Rule:

    \[p(theta|y) = frac{p(y|theta) p(theta)}{p(y)}\]


While p(y|theta) is our likelihood and p(y) is a normalizing constant, p(theta) is our prior which does not depend on the data, y. In classical statistics, p(theta) is set to 1 (an improper reference prior) so that when the posterior ‚probability‘ is maximized, really just the likelihood is maximized because it’s the only part that still depends on theta. However, in Bayesian statistics, we use an actual probability distribution in place of p(theta), a Normal distribution, for example. So let’s consider the case of a regression problem, and we’ll assume that our target, y, and our prior follow normal distributions. That leads us to conjugate Bayesian analysis, in which we can neatly write down an equation for the posterior distribution. In many cases, this is not possible, and for this reason, Markov Chain Monte Carlo methods were invented to sample from the posterior – taking a frequentist approach, ironically.

We’ll make the usual assumption about the data: y_i is i.i.d. N(bold {x_i beta}, sigma^2) for all observations i. This gives us our standard likelihood for the Normal distribution. Now we can specify the prior for the parameter we’re trying to estimate, (beta, sigma^2). If we choose a Normal prior (conditional on the variance, sigma^2) for the vector or weights in beta, i.e. N(b_0, sigma^2 B_0) and an inverse-Gamma prior over the variance parameter it can be shown that the posterior distribution for beta is Normally distributed with mean

    \[hatbeta_{Bayesian} = (B_0^{-1} + X'X)^{-1}(B_0^{-1} b_0 + X'X hatbeta)\]


If you’re interested in a proof of this result check out Jackman (2009, p.526).

Let’s look at it piece by piece:

  • hatbeta is our standard OLS estimator, (X'X)^{-1}X'y
  • b_0 is the mean vector of (multivariate normal) prior distribution, so it lets us specify what we think the average values of each of our model parameters are
  • B_0 is the covariance matrix and contains our respective uncertainties about the model parameters. The inverse of the variance is called the precision

What we can see from the equation is that the mean of our posterior is a precision weighted average of our prior mean (information not based on data) and the OLS estimator (based solely on the data). The second term in parentheses indicates that we are taking the uncertainty weighted prior mean, B_0^{-1} b_0, and adding it to the weighted OLS estimator, X'Xhatbeta. Imagine for a moment that B_0^{-1} = 0 . Then

    \[hatbeta_{Bayesian} = (X'X)^{-1}(X'X hatbeta) = hatbeta\]


That would mean that we are infinitely uncertain about our prior beliefs that the mean vector of our prior distribution would vanish, contributing nothing to our posterior! Likewise, if our uncertainty decreases (and the precision thus increases), the prior mean, b_0, would contribute more to the posterior mean.

After this short primer on Bayesian statistics, we can now formally compare the Ridge estimator with the above Bayesian estimator. But first, we need to take a look at a more general version of the Ridge estimator.

Generalizing the Ridge estimator

A standard tool used in many regression problems, the standard Ridge estimator is derived by solving a least-squares problem from the following loss function:

    \[L(beta,lambda) = frac{1}{2}sum(y-Xbeta)^2 + frac{1}{2} lambda ||beta||^2\]


While minimizing this gives us the standard Ridge estimator you have probably seen in textbooks on the subject, there’s a slightly more general version of this loss function:

    \[L(beta,lambda,mu) = frac{1}{2}sum(y-Xbeta)^2 + frac{1}{2} lambda ||beta - mu||^2\]


Let’s derive the estimator by first re-writing the loss function in terms of matrices:

    \[begin{aligned}L(beta,lambda,mu) &= frac{1}{2}(y - X beta)^{T}(y - X beta) + frac{1}{2} lambda||beta - mu||^2 &= frac{1}{2} y^Ty - beta^T X^T y + frac{1}{2} beta^T X^T X beta + frac{1}{2} lambda||beta - mu||^2end{aligned}\]


Differentiating with respect to the parameter vector, we end up with this expression for the gradient:

    \[nabla_{beta} L (beta, lambda, mu) = -X^T y + X^T X beta + lambda (beta - mu)\]


So, Minimizing over beta we get this expression for the generalized ridge estimator:

    \[hatbeta_{Ridge} = (X'X + lambda I )^{-1}(lambda mu + X'y)\]


The standard Ridge estimator can be recovered by setting mu=0. Usually we regard lambda as an abstract parameter that regulates the penalty size and mu as a vector of values (one for each predictor) that increases the loss the further these coefficients deviate from these values. When mu=0 the coefficients are pulled towards zero.

Let’s take a look at how the estimator behaves when the parameters, mu, and lambda change. We’ll define a meaningful ‚prior‘ for our example and then vary the penalty parameter. As an example, we’ll use the diamonds dataset from the ggplot2 package and model the price as a linear function of the number of carats, in each diamond, the depth, table, x, y and z attributes

As we can see from the plot, both with and without a prior, the coefficient estimates change rapidly for the first few increases in the penalty size. We also see that the ’shrinkage‘ effect holds from the upper plot: as the penalty increases, the coefficients tend towards zero, some faster than others. The plot on the right shows how the coefficients change when we set a sensible ‚prior‘. The coefficients still change, but they now tend towards the ‚prior‘ we specified. That’s because lambda penalizes deviations from our mu, which means that larger values for the penalty pull the coefficients towards mu. You might be asking yourself how this compares to the Bayesian estimator. Let’s find out!

Comparing the Ridge and Bayesian Estimator

Now that we’ve seen both the Ridge and the Bayesian estimators, it’s time to compare them. We discovered, that the Bayesian estimator contains the OLS estimator. Since we know its form, let’s substitute it and see what happens:

    \[begin{aligned}hatbeta_{Bayesian} &= (X'X + B_0^{-1})^{-1}(B_0^{-1} b_0 + X'X hatbeta) &= (X'X + B_0^{-1})^{-1}(B_0^{-1} b_0 + X'X (X'X)^{-1}X'y) &= (X'X + B_0^{-1})^{-1}(B_0^{-1} b_0 + X'y)end{aligned}\]


This form makes the analogy much clearer:

  • lambda I corresponds to B_0^{-1}, the matrix of precisions. In other words, since I is the identity matrix, the ridge estimator assumes no covariances between the regression coefficients and a constant precision across all coefficients (recall that lambda is a scalar)
  • lambda mu corresponds to B_0^{-1} b_0, which makes sense, since the vector b_0 is the mean of our prior distribution, which essentially pulls the estimator towards it, just like mu ’shrinks‘ the coefficients towards its values. This ‚pull‘ depends on the uncertainty captured by B_0^{-1} or lambda I in the ridge estimator.

That’s all well and good, but let’s see how changing the uncertainty in the Bayesian case compares to the behavior of the ridge estimator. Using the same data and the same model specification as above, we’ll set the covariance matrix B_0 matrix to equal lambda I and then change lambda. Remember, smaller values of lambda now imply a more significant contribution of the prior (less uncertainty), and therefore increasing them makes the prior less important.

The above plots match out understanding so far: With a prior mean of zeros, the coefficients are shrunken towards zero, as in the ridge regression case when the prior dominates, i.e., when the precision is high. And when a previous mean is set, the coefficients tend towards it as the precision increases. So much for the coefficients, but what about the performance? Let’s have a look!

Performance comparison

Lastly, we’ll compare the predictive performance of the two models. Although we could treat the parameters in the model as hyperparameters, which we would need to tune, this would defy the purpose of using prior knowledge. Instead, let’s choose a previous specification for both models, and then compare the performance on a holdout set (30% of the data). While we can use the simple Xhatbeta as our predictor for the Ridge model, the Bayesian model provides us with a full posterior predictive distribution, which we can sample from to get model predictions. To estimate the model I used the brmspackage.

  RMSE MAE MAPE
Bayesian Linear Model 1625.38 1091.36 44.15
Ridge Estimator 1756.01 1173.50 43.44

Overall, both models perform similarly, although some error metrics slightly favor one model over the other. Judging by these errors, we could certainly improve our models by specifying a more appropriate probability distribution for our target variable. After all, prices can not be negative, yet our models can and do produce negative predictions.

Recap

In this post, I’ve shown you how the ridge estimator compares to the Bayesian conjugate linear model. Now you understand the connection between the two models and how a Bayesian approach can provide a more readily interpretable way of regularizing your model. Normally lambda would be considered a penalty size, but now it can be interpreted as a measure of prior uncertainty. Similarly, the parameter vector mu can be seen as a vector of prior means for our model parameters in the extended ridge model. As far as the Bayesian approach goes, we also can use prior distributions to implement expert knowledge in your estimation process. This regularizes your model and allows for incorporation of external information in your model. If you are interested in the code, check it out at our GitHub page!

References

  • Jackman S. 2009. Bayesian Analysis for the Social Sciences. West Sussex: Wiley.

Introduction

Here at STATWORX, we value reader-friendly presentations of our work. For many R users, the choice is usually a Markdown file that generates a .html or .pdf document, or a Shiny application, which provides users with an easily navigable dashboard.

What if you want to construct a dashboard-style presentation without much hassle? Well, look no further. R Studio’s package flexdashboard gives data scientists a Markdown-based way of easily setting up dashboards without having to resort to full-on front end development. Using Shiny may be a bit too involved when the goal is to present your work in a dashboard.

Why should you learn about flexdashboards ? If you’re familiar with R Markdown and know a bit about Shiny, flexdashboards are easy to learn and give you an alternative to Shiny dashboards.

In this post, you will learn the basics on how to design a flexdashboard. By the end of this article, you’ll be able to :

  • build a simple dashboard involving multiple pages
  • put tabs on each page and adjust the layout
  • integrate widgets
  • deploy your Shiny document on ShinyApps.io.

The basic rules

To set up a flexdashboard, install the package from CRAN using the standard command. To get started, enter the following into the console:

rmarkdown::draft(file = "my_dashboard", 
                 template = "flex_dashboard", 
                 package = "flexdashboard")

This function creates a .Rmd file with the name associated file name, and uses the package’s flexdashboard template. Rendering your newly created dashboard, you get a column-oriented layout with a header, one page, and three boxes. By default, the page is divided into columns, and the left-hand column is made to be double the height of the two right-hand boxes.

You can change the layout-orientation to rows and also select a different theme. Adding runtime: shiny to the YAML header allows you to use HTML widgets.

Each row (or column) is created using the ——— header, and the panels themselves are created with a ### header followed by the title of the panel. You can introduce tabsetting for each row by adding the {.tabset} attribute after its name. To add a page, use the (=======) header and put the page name above it. Row height can be modified by using {.data-height = } after a row name if you chose a row-oriented layout. Depending on the layout, it may make sense to use {.data-width = } instead.

Here, I’ll design a dashboard which explores the famous diamonds dataset, found in the ggplot2 package. While the first page contains some exploratory plots, the second page compares the performance of a linear model and a ridge regression in predicting the price.

This is the skeleton of the dashboard (minus R code and descriptive text):

---
title: "Dashing diamonds"
output:
 flexdashboard::flex_dashboard:
   orientation: rows
   vertical_layout: fill
   css: bootswatch-3.3.5-4/flatly/bootstrap.css
   logo: STATWORX_2.jpg
runtime: shiny
---

Exploratory plots
=======================================================================

Sidebar {.sidebar data-width=700}
-----------------------------------------------------------------------

**Exploratory plots**

<br>

**Scatterplots**

<br>

**Density plot**

<br>

**Summary statistics**

<br>


Row {.tabset}
-----------------------------------------------------------------------

### Scatterplot of selected variables

### Density plot for selected variable

Row
-----------------------------------------------------------------------

### Maximum carats {data-width=50}

### Most expensive color {data-width=50}

### Maximal price {data-width=50}

Row {data-height=500}
-----------------------------------------------------------------------

### Summary statistics {data-width=500}

Model comparison
=======================================================================

Sidebar {.sidebar data-width=700}
-----------------------------------------------------------------------

**Model comparison**

<br>

Row{.tabset}
-----------------------------------------------------------------------

**Comparison of Predictions and Target**

### Linear Model

### Ridge Regression

Row
-----------------------------------------------------------------------
### Densities of predictions vs. target

The sidebars were added by specifying the attribute {.sidebar} after the name, followed by a page or row header. Page headers (========) create global sidebars, whereas local sidebars are made using row headers (---------). If you choose a global sidebar, it appears on all pages whereas a local sidebar only appears on the page it is put on. In general, it’s a good idea to add the sidebar after the beginning of the page and before the first row of the page. Sidebars are also good for adding descriptions of what your dashboard/application is about. Here I also changed the width using the attribute data-width. That widens the sidebar and makes the description easier to read. You can also display outputs in your sidebar by adding code chunks below it.

Adding interactive widgets

Now that the basic layout is done let’s add some interactivity. Below the description in the sidebar on the first page, I’ve added several widgets.

```{r}
selectInput("x", "X-Axis", choices = names(train_df), selected = "x")
selectInput("y", "Y-Axis", choices = names(train_df), selected = "price")
selectInput("z", "Color by:", choices = names(train_df), selected = "carat")
selectInput("model_type", "Select model", choices = c("LOESS" = "loess", "Linear" = "lm"), selected = "lm")
checkboxInput("se", "Confidence intervals ?")
```

Notice that the widgets are identical to those you typically find in a Shiny application and they’ll work because runtime: shiny is specified in the YAML.

To make the plots react to changes in the date selection, you need to specify the input ID’s of your widgets within the appropriate render function. For example, the scatterplot is rendered as a plotly output:

```{r}
renderPlotly({
 p <- train_df %>%
ggplot(aes_string(x = input$x, y = input$y, col = input$z)) +
   geom_point() +
   theme_minimal() +
   geom_smooth(method = input$model_type, position = "identity", se = input$se) +
   labs(x = input$x, y = input$y)
 
 p %>% ggplotly()
})
```

You can use the render functions you would also use in a Shiny application. Of course, you don’t have to use render-functions to display graphics, but they have the advantage of resizing the plots whenever the browser window is resized.

Adding value boxes

Aside from plots and tables, one of the more stylish features of dashboards are value boxes. flexdashboard provides its own function for value boxes, with which you can nicely convey information about key indicators relevant to your work. Here, I’ll add three such boxes displaying the maximal price, the most expensive color of diamonds and the maximal amount of carats found in the dataset.

flexdashboard::valueBox(max(train_df$carat), 
                       caption = "maximal amount of carats",
                       color = "info",
                       icon = "fa-gem")

There are multiple sources from which icons can be drawn. In this example, I’ve used the gem icon from font awesome. This code chunk follows a header for what would otherwise be a plot or a table, i.e., a ### header.

flexdashboard-example-title

Final touches and deployment

To finalize your dashboard, you can add a logo and chose from one of several themes, or attach a CSS file. Here, I’ve added a bootswatch theme and modified the colors slightly. Most themes require the logo to be 48×48 pixels large.

---
title: "Dashing diamonds"
output:
 flexdashboard::flex_dashboard:
   orientation: rows
   vertical_layout: fill
   css: bootswatch-3.3.5-4/flatly/bootstrap.css
   logo: STATWORX_2.jpg
runtime: shiny
---

After creating your dashboard with runtime: shiny, it can be hosted on ShinyApps.io, provided you have an account. You also need to install the package rsconnect. The document can be published with the ‚publish to server‘ in RStudio or with:

rsconnect::deployDoc('path')

You can use this function after you’ve obtained your account and authorized it using rsconnect::setAccountInfo() with an access token and a secret provided by the website. Make sure that all of the necessary files are part of the same folder. RStudio’s publish to server has the advantage of automatically recognizing the external files your application requires. You can view the example dashboard here and the code on our GitHub page.

Recap

In this post, you’ve learned how to set up a flexdashboard, customize and deploy it – all without knowing JavaScript or CSS, or even much R Shiny. However, what you’ve learned here is only the beginning! This powerful package also allows you to create storyboards, integrate them in a more modularized way with R Shiny and even set up dashboards for mobile devices. We will explore these topics together in future posts. Stay tuned!

Introduction

Here at STATWORX, we value reader-friendly presentations of our work. For many R users, the choice is usually a Markdown file that generates a .html or .pdf document, or a Shiny application, which provides users with an easily navigable dashboard.

What if you want to construct a dashboard-style presentation without much hassle? Well, look no further. R Studio’s package flexdashboard gives data scientists a Markdown-based way of easily setting up dashboards without having to resort to full-on front end development. Using Shiny may be a bit too involved when the goal is to present your work in a dashboard.

Why should you learn about flexdashboards ? If you’re familiar with R Markdown and know a bit about Shiny, flexdashboards are easy to learn and give you an alternative to Shiny dashboards.

In this post, you will learn the basics on how to design a flexdashboard. By the end of this article, you’ll be able to :

The basic rules

To set up a flexdashboard, install the package from CRAN using the standard command. To get started, enter the following into the console:

rmarkdown::draft(file = "my_dashboard", 
                 template = "flex_dashboard", 
                 package = "flexdashboard")

This function creates a .Rmd file with the name associated file name, and uses the package’s flexdashboard template. Rendering your newly created dashboard, you get a column-oriented layout with a header, one page, and three boxes. By default, the page is divided into columns, and the left-hand column is made to be double the height of the two right-hand boxes.

You can change the layout-orientation to rows and also select a different theme. Adding runtime: shiny to the YAML header allows you to use HTML widgets.

Each row (or column) is created using the ——— header, and the panels themselves are created with a ### header followed by the title of the panel. You can introduce tabsetting for each row by adding the {.tabset} attribute after its name. To add a page, use the (=======) header and put the page name above it. Row height can be modified by using {.data-height = } after a row name if you chose a row-oriented layout. Depending on the layout, it may make sense to use {.data-width = } instead.

Here, I’ll design a dashboard which explores the famous diamonds dataset, found in the ggplot2 package. While the first page contains some exploratory plots, the second page compares the performance of a linear model and a ridge regression in predicting the price.

This is the skeleton of the dashboard (minus R code and descriptive text):

---
title: "Dashing diamonds"
output:
 flexdashboard::flex_dashboard:
   orientation: rows
   vertical_layout: fill
   css: bootswatch-3.3.5-4/flatly/bootstrap.css
   logo: STATWORX_2.jpg
runtime: shiny
---

Exploratory plots
=======================================================================

Sidebar {.sidebar data-width=700}
-----------------------------------------------------------------------

**Exploratory plots**

<br>

**Scatterplots**

<br>

**Density plot**

<br>

**Summary statistics**

<br>


Row {.tabset}
-----------------------------------------------------------------------

### Scatterplot of selected variables

### Density plot for selected variable

Row
-----------------------------------------------------------------------

### Maximum carats {data-width=50}

### Most expensive color {data-width=50}

### Maximal price {data-width=50}

Row {data-height=500}
-----------------------------------------------------------------------

### Summary statistics {data-width=500}

Model comparison
=======================================================================

Sidebar {.sidebar data-width=700}
-----------------------------------------------------------------------

**Model comparison**

<br>

Row{.tabset}
-----------------------------------------------------------------------

**Comparison of Predictions and Target**

### Linear Model

### Ridge Regression

Row
-----------------------------------------------------------------------
### Densities of predictions vs. target

The sidebars were added by specifying the attribute {.sidebar} after the name, followed by a page or row header. Page headers (========) create global sidebars, whereas local sidebars are made using row headers (---------). If you choose a global sidebar, it appears on all pages whereas a local sidebar only appears on the page it is put on. In general, it’s a good idea to add the sidebar after the beginning of the page and before the first row of the page. Sidebars are also good for adding descriptions of what your dashboard/application is about. Here I also changed the width using the attribute data-width. That widens the sidebar and makes the description easier to read. You can also display outputs in your sidebar by adding code chunks below it.

Adding interactive widgets

Now that the basic layout is done let’s add some interactivity. Below the description in the sidebar on the first page, I’ve added several widgets.

```{r}
selectInput("x", "X-Axis", choices = names(train_df), selected = "x")
selectInput("y", "Y-Axis", choices = names(train_df), selected = "price")
selectInput("z", "Color by:", choices = names(train_df), selected = "carat")
selectInput("model_type", "Select model", choices = c("LOESS" = "loess", "Linear" = "lm"), selected = "lm")
checkboxInput("se", "Confidence intervals ?")
```

Notice that the widgets are identical to those you typically find in a Shiny application and they’ll work because runtime: shiny is specified in the YAML.

To make the plots react to changes in the date selection, you need to specify the input ID’s of your widgets within the appropriate render function. For example, the scatterplot is rendered as a plotly output:

```{r}
renderPlotly({
 p <- train_df %>%
ggplot(aes_string(x = input$x, y = input$y, col = input$z)) +
   geom_point() +
   theme_minimal() +
   geom_smooth(method = input$model_type, position = "identity", se = input$se) +
   labs(x = input$x, y = input$y)
 
 p %>% ggplotly()
})
```

You can use the render functions you would also use in a Shiny application. Of course, you don’t have to use render-functions to display graphics, but they have the advantage of resizing the plots whenever the browser window is resized.

Adding value boxes

Aside from plots and tables, one of the more stylish features of dashboards are value boxes. flexdashboard provides its own function for value boxes, with which you can nicely convey information about key indicators relevant to your work. Here, I’ll add three such boxes displaying the maximal price, the most expensive color of diamonds and the maximal amount of carats found in the dataset.

flexdashboard::valueBox(max(train_df$carat), 
                       caption = "maximal amount of carats",
                       color = "info",
                       icon = "fa-gem")

There are multiple sources from which icons can be drawn. In this example, I’ve used the gem icon from font awesome. This code chunk follows a header for what would otherwise be a plot or a table, i.e., a ### header.

flexdashboard-example-title

Final touches and deployment

To finalize your dashboard, you can add a logo and chose from one of several themes, or attach a CSS file. Here, I’ve added a bootswatch theme and modified the colors slightly. Most themes require the logo to be 48×48 pixels large.

---
title: "Dashing diamonds"
output:
 flexdashboard::flex_dashboard:
   orientation: rows
   vertical_layout: fill
   css: bootswatch-3.3.5-4/flatly/bootstrap.css
   logo: STATWORX_2.jpg
runtime: shiny
---

After creating your dashboard with runtime: shiny, it can be hosted on ShinyApps.io, provided you have an account. You also need to install the package rsconnect. The document can be published with the ‚publish to server‘ in RStudio or with:

rsconnect::deployDoc('path')

You can use this function after you’ve obtained your account and authorized it using rsconnect::setAccountInfo() with an access token and a secret provided by the website. Make sure that all of the necessary files are part of the same folder. RStudio’s publish to server has the advantage of automatically recognizing the external files your application requires. You can view the example dashboard here and the code on our GitHub page.

Recap

In this post, you’ve learned how to set up a flexdashboard, customize and deploy it – all without knowing JavaScript or CSS, or even much R Shiny. However, what you’ve learned here is only the beginning! This powerful package also allows you to create storyboards, integrate them in a more modularized way with R Shiny and even set up dashboards for mobile devices. We will explore these topics together in future posts. Stay tuned!