Hands-on Exercise 9: Information Dashboard Design

Author

Michael Djohan

Published

March 15, 2023

Modified

March 15, 2023

1. Install and launching R packages

The code chunk below uses p_load() of pacman package to check if packages are installed in the computer. If they are, then they will be launched into R. The R packages installed are:

  • gtExtras provides some additional helper functions to assist in creating beautiful tables with gt, an R package specially designed for anyone to make wonderful-looking tables using the R programming language.

  • reactablefmtr provides various features to streamline and enhance the styling of interactive reactable tables with easy-to-use and highly-customizable functions and themes.

pacman::p_load(lubridate, ggthemes, gtExtras, gt, reactablefmtr, tidyverse)

2. Importing the data and Data Manipulation

For the purpose of this study, a personal database in Microsoft Access mdb format called Coffee Chain will be used.

library(RODBC)
con <- odbcDriverConnect("Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=data/Coffee Chain.mdb")
coffeechain <- sqlFetch(con, 'CoffeeChain Query')
write_rds(coffeechain, "data/CoffeeChain.rds")
odbcClose(con)
head(coffeechain)
  Profit Margin Sales COGS Total Expenses Marketing Inventory Budget Profit
1     94    130   219   89             36        24       777           100
2     68    107   190   83             39        27       623            80
3    101    139   234   95             38        26       821           110
4     30     56   100   44             26        14       623            30
5     54     80   134   54             26        15       456            70
6     53    108   180   72             55        23       558            80
  Budget Margin Budget Sales Budget COGS       Date  Market    State Area Code
1           130          220          90 2012-01-01 Central Colorado       719
2           110          190          80 2012-01-01 Central Colorado       970
3           140          240         100 2012-01-01 Central Colorado       970
4            50           80          30 2012-01-01 Central Colorado       303
5            90          150          60 2012-01-01 Central Colorado       303
6           130          210          80 2012-01-01 Central Colorado       720
   Market Size Product Type           Product    Type
1 Major Market       Coffee          Amaretto Regular
2 Major Market       Coffee         Colombian Regular
3 Major Market       Coffee Decaf Irish Cream   Decaf
4 Major Market          Tea         Green Tea Regular
5 Major Market     Espresso       Caffe Mocha Regular
6 Major Market     Espresso    Decaf Espresso   Decaf

aggregate Sales and Budgeted Sales at the Product Level

product <- coffeechain %>%
  group_by(`Product`) %>%
  summarise(`target` = sum(`Budget Sales`),
            `current` = sum(`Sales`)) %>%
  ungroup()

head(product)
# A tibble: 6 × 3
  Product     target current
  <chr>        <dbl>   <dbl>
1 Amaretto     27200   26269
2 Caffe Latte  30540   35899
3 Caffe Mocha  84600   84904
4 Chamomile    63840   75578
5 Colombian   134380  128311
6 Darjeeling   57360   73151

3. Using ggplot2 functions

3.1 Plotting bullet chart

ggplot(product, aes(Product, current)) + 
  geom_col(aes(Product, max(target) * 1.01),
           fill="grey85", width=0.85) +
  geom_col(aes(Product, target * 0.75),
           fill="grey60", width=0.85) +
  geom_col(aes(Product, target * 0.5),
           fill="grey50", width=0.85) +
  geom_col(aes(Product, current), 
           width=0.35,
           fill = "black") + 
  geom_errorbar(aes(y = target,
                    x = Product, 
                    ymin = target,
                    ymax= target), 
                width = .4,
                colour = "red",
                size = 1) +
  coord_flip()

3.2 Plotting sparklines

3.2.1 Data preparation

Group by month and product

sales_report <- coffeechain %>%
  filter(Date >= "2013-01-01") %>%
  mutate(Month = month(Date)) %>%
  group_by(Month, Product) %>%
  summarise(Sales = sum(Sales)) %>%
  ungroup() %>%
  select(Month, Product, Sales)

sales_report
# A tibble: 156 × 3
   Month Product           Sales
   <dbl> <chr>             <dbl>
 1     1 Amaretto           1210
 2     1 Caffe Latte        1653
 3     1 Caffe Mocha        3604
 4     1 Chamomile          3313
 5     1 Colombian          5439
 6     1 Darjeeling         3178
 7     1 Decaf Espresso     3493
 8     1 Decaf Irish Cream  2656
 9     1 Earl Grey          2867
10     1 Green Tea          1399
# … with 146 more rows

Compute minimum, maximum, and end of the month sales

mins <- group_by(sales_report, Product) %>% 
  slice(which.min(Sales))

maxs <- group_by(sales_report, Product) %>% 
  slice(which.max(Sales))

ends <- group_by(sales_report, Product) %>% 
  filter(Month == max(Month))

Compute the 25th and 75th quantiles

quarts <- sales_report %>%
  group_by(Product) %>%
  summarise(quart1 = quantile(Sales, 
                              0.25),
            quart2 = quantile(Sales, 
                              0.75)) %>%
  right_join(sales_report)

quarts
# A tibble: 156 × 5
   Product  quart1 quart2 Month Sales
   <chr>     <dbl>  <dbl> <dbl> <dbl>
 1 Amaretto  1058.   1167     1  1210
 2 Amaretto  1058.   1167     2  1144
 3 Amaretto  1058.   1167     3  1100
 4 Amaretto  1058.   1167     4  1117
 5 Amaretto  1058.   1167     5  1057
 6 Amaretto  1058.   1167     6  1059
 7 Amaretto  1058.   1167     7  1016
 8 Amaretto  1058.   1167     8  1039
 9 Amaretto  1058.   1167     9  1188
10 Amaretto  1058.   1167    10  1167
# … with 146 more rows

3.2.2 Plotting the sparklines

ggplot(sales_report, aes(x=Month, y=Sales)) + 
  facet_grid(Product ~ ., scales = "free_y") + 
  geom_ribbon(data = quarts, aes(ymin = quart1, max = quart2), 
              fill = 'grey90') +
  geom_line(size=0.3) +
  geom_point(data = mins, col = 'red') +
  geom_point(data = maxs, col = 'blue') +
  geom_text(data = mins, aes(label = Sales), vjust = -1) +
  geom_text(data = maxs, aes(label = Sales), vjust = 2.5) +
  geom_text(data = ends, aes(label = Sales), hjust = 0, nudge_x = 0.5) +
  geom_text(data = ends, aes(label = Product), hjust = 0, nudge_x = 1) +
  expand_limits(x = max(sales_report$Month) + 
                  (0.25 * (max(sales_report$Month) - min(sales_report$Month)))) +
  scale_x_continuous(breaks = seq(1, 12, 1)) +
  scale_y_continuous(expand = c(0.1, 0)) +
  theme_tufte(base_size = 3, base_family = "Helvetica") +
  theme(axis.title=element_blank(), axis.text.y = element_blank(), 
        axis.ticks = element_blank(), strip.text = element_blank())

4. Using gt and gtExtras methods

In this section, you will learn how to create static information dashboard by using gt and gtExtras packages.

4.1 Plotting bullet chart

product %>%
  gt::gt() %>%
  gt_plt_bullet(column = current, 
              target = target, 
              width = 60,
              palette = c("lightblue", 
                          "black")) %>%
  gt_theme_538()
Product current
Amaretto
Caffe Latte
Caffe Mocha
Chamomile
Colombian
Darjeeling
Decaf Espresso
Decaf Irish Cream
Earl Grey
Green Tea
Lemon
Mint
Regular Espresso

4.2 Plotting sparklines and create sales report

4.2.1 Preparing the data

Group by product and month

report <- coffeechain %>%
  mutate(Year = year(Date)) %>%
  filter(Year == "2013") %>%
  mutate (Month = month(Date, 
                        label = TRUE, 
                        abbr = TRUE)) %>%
  group_by(Product, Month) %>%
  summarise(Sales = sum(Sales)) %>%
  ungroup()

report
# A tibble: 156 × 3
   Product  Month Sales
   <chr>    <ord> <dbl>
 1 Amaretto Jan    1210
 2 Amaretto Feb    1144
 3 Amaretto Mar    1100
 4 Amaretto Apr    1117
 5 Amaretto May    1057
 6 Amaretto Jun    1059
 7 Amaretto Jul    1016
 8 Amaretto Aug    1039
 9 Amaretto Sep    1188
10 Amaretto Oct    1167
# … with 146 more rows

It is important to note that one of the requirement of gtExtras functions is that almost exclusively they require you to pass data.frame with list columns. In view of this, code chunk below will be used to convert the report data.frame into list columns.

report %>%
  group_by(Product) %>%
  summarize('Monthly Sales' = list(Sales), 
            .groups = "drop")
# A tibble: 13 × 2
   Product           `Monthly Sales`
   <chr>             <list>         
 1 Amaretto          <dbl [12]>     
 2 Caffe Latte       <dbl [12]>     
 3 Caffe Mocha       <dbl [12]>     
 4 Chamomile         <dbl [12]>     
 5 Colombian         <dbl [12]>     
 6 Darjeeling        <dbl [12]>     
 7 Decaf Espresso    <dbl [12]>     
 8 Decaf Irish Cream <dbl [12]>     
 9 Earl Grey         <dbl [12]>     
10 Green Tea         <dbl [12]>     
11 Lemon             <dbl [12]>     
12 Mint              <dbl [12]>     
13 Regular Espresso  <dbl [12]>     

4.2.2 Plotting the sparklines

Use the code above to convert the report data.frame into list columns before applying gt functions

report %>%
  group_by(Product) %>%
  summarize('Monthly Sales' = list(Sales), 
            .groups = "drop") %>%
   gt() %>%
   gt_plt_sparkline('Monthly Sales')
Product Monthly Sales
Amaretto 1.2K
Caffe Latte 1.5K
Caffe Mocha 3.7K
Chamomile 3.3K
Colombian 5.5K
Darjeeling 3.0K
Decaf Espresso 3.2K
Decaf Irish Cream 2.7K
Earl Grey 3.0K
Green Tea 1.5K
Lemon 4.4K
Mint 1.5K
Regular Espresso 1.1K

4.2.3 Create the statistics sales report

Use the code above to create the statistic sales report

report %>% 
  group_by(Product) %>% 
  summarise("Min" = min(Sales, na.rm = T),
            "Max" = max(Sales, na.rm = T),
            "Average" = mean(Sales, na.rm = T)
            ) %>%
  gt() %>%
  fmt_number(columns = 4,
    decimals = 2)
Product Min Max Average
Amaretto 1016 1210 1,119.00
Caffe Latte 1398 1653 1,528.33
Caffe Mocha 3322 3828 3,613.92
Chamomile 2967 3395 3,217.42
Colombian 5132 5961 5,457.25
Darjeeling 2926 3281 3,112.67
Decaf Espresso 3181 3493 3,326.83
Decaf Irish Cream 2463 2901 2,648.25
Earl Grey 2730 3005 2,841.83
Green Tea 1339 1476 1,398.75
Lemon 3851 4418 4,080.83
Mint 1388 1669 1,519.17
Regular Espresso 890 1218 1,023.42

4.2.4 Combining the sales report and sparklines

Firstly, we need to add the min and max to report tibble in list format

spark <- report %>%
  group_by(Product) %>%
  summarize('Monthly Sales' = list(Sales), 
            .groups = "drop")

spark
# A tibble: 13 × 2
   Product           `Monthly Sales`
   <chr>             <list>         
 1 Amaretto          <dbl [12]>     
 2 Caffe Latte       <dbl [12]>     
 3 Caffe Mocha       <dbl [12]>     
 4 Chamomile         <dbl [12]>     
 5 Colombian         <dbl [12]>     
 6 Darjeeling        <dbl [12]>     
 7 Decaf Espresso    <dbl [12]>     
 8 Decaf Irish Cream <dbl [12]>     
 9 Earl Grey         <dbl [12]>     
10 Green Tea         <dbl [12]>     
11 Lemon             <dbl [12]>     
12 Mint              <dbl [12]>     
13 Regular Espresso  <dbl [12]>     
sales <- report %>% 
  group_by(Product) %>% 
  summarise("Min" = min(Sales, na.rm = T),
            "Max" = max(Sales, na.rm = T),
            "Average" = mean(Sales, na.rm = T)
            )

sales
# A tibble: 13 × 4
   Product             Min   Max Average
   <chr>             <dbl> <dbl>   <dbl>
 1 Amaretto           1016  1210   1119 
 2 Caffe Latte        1398  1653   1528.
 3 Caffe Mocha        3322  3828   3614.
 4 Chamomile          2967  3395   3217.
 5 Colombian          5132  5961   5457.
 6 Darjeeling         2926  3281   3113.
 7 Decaf Espresso     3181  3493   3327.
 8 Decaf Irish Cream  2463  2901   2648.
 9 Earl Grey          2730  3005   2842.
10 Green Tea          1339  1476   1399.
11 Lemon              3851  4418   4081.
12 Mint               1388  1669   1519.
13 Regular Espresso    890  1218   1023.

Combined the two tibbles

sales_data = left_join(sales, spark)
sales_data
# A tibble: 13 × 5
   Product             Min   Max Average `Monthly Sales`
   <chr>             <dbl> <dbl>   <dbl> <list>         
 1 Amaretto           1016  1210   1119  <dbl [12]>     
 2 Caffe Latte        1398  1653   1528. <dbl [12]>     
 3 Caffe Mocha        3322  3828   3614. <dbl [12]>     
 4 Chamomile          2967  3395   3217. <dbl [12]>     
 5 Colombian          5132  5961   5457. <dbl [12]>     
 6 Darjeeling         2926  3281   3113. <dbl [12]>     
 7 Decaf Espresso     3181  3493   3327. <dbl [12]>     
 8 Decaf Irish Cream  2463  2901   2648. <dbl [12]>     
 9 Earl Grey          2730  3005   2842. <dbl [12]>     
10 Green Tea          1339  1476   1399. <dbl [12]>     
11 Lemon              3851  4418   4081. <dbl [12]>     
12 Mint               1388  1669   1519. <dbl [12]>     
13 Regular Espresso    890  1218   1023. <dbl [12]>     

Plotting the updated data.table

sales_data %>%
  gt() %>%
  gt_plt_sparkline('Monthly Sales')
Product Min Max Average Monthly Sales
Amaretto 1016 1210 1119.000 1.2K
Caffe Latte 1398 1653 1528.333 1.5K
Caffe Mocha 3322 3828 3613.917 3.7K
Chamomile 2967 3395 3217.417 3.3K
Colombian 5132 5961 5457.250 5.5K
Darjeeling 2926 3281 3112.667 3.0K
Decaf Espresso 3181 3493 3326.833 3.2K
Decaf Irish Cream 2463 2901 2648.250 2.7K
Earl Grey 2730 3005 2841.833 3.0K
Green Tea 1339 1476 1398.750 1.5K
Lemon 3851 4418 4080.833 4.4K
Mint 1388 1669 1519.167 1.5K
Regular Espresso 890 1218 1023.417 1.1K

4.3 Combining bullet charts and sparklines

Modify the data as per Section 2 by aggregating Sales and Budgeted Sales at the Product Level

bullet <- coffeechain %>%
  filter(Date >= "2013-01-01") %>%
  group_by(`Product`) %>%
  summarise(`Target` = sum(`Budget Sales`),
            `Actual` = sum(`Sales`)) %>%
  ungroup() 

bullet
# A tibble: 13 × 3
   Product           Target Actual
   <chr>              <dbl>  <dbl>
 1 Amaretto           13600  13428
 2 Caffe Latte        15270  18340
 3 Caffe Mocha        42300  43367
 4 Chamomile          31920  38609
 5 Colombian          67190  65487
 6 Darjeeling         28680  37352
 7 Decaf Espresso     37860  39922
 8 Decaf Irish Cream  33520  31779
 9 Earl Grey          25450  34102
10 Green Tea          12670  16785
11 Lemon              39150  48970
12 Mint               14160  18230
13 Regular Espresso   11310  12281

Use the sales_data created in section 4.2.4

sales_data = sales_data %>%
  left_join(bullet)
sales_data
# A tibble: 13 × 7
   Product             Min   Max Average `Monthly Sales` Target Actual
   <chr>             <dbl> <dbl>   <dbl> <list>           <dbl>  <dbl>
 1 Amaretto           1016  1210   1119  <dbl [12]>       13600  13428
 2 Caffe Latte        1398  1653   1528. <dbl [12]>       15270  18340
 3 Caffe Mocha        3322  3828   3614. <dbl [12]>       42300  43367
 4 Chamomile          2967  3395   3217. <dbl [12]>       31920  38609
 5 Colombian          5132  5961   5457. <dbl [12]>       67190  65487
 6 Darjeeling         2926  3281   3113. <dbl [12]>       28680  37352
 7 Decaf Espresso     3181  3493   3327. <dbl [12]>       37860  39922
 8 Decaf Irish Cream  2463  2901   2648. <dbl [12]>       33520  31779
 9 Earl Grey          2730  3005   2842. <dbl [12]>       25450  34102
10 Green Tea          1339  1476   1399. <dbl [12]>       12670  16785
11 Lemon              3851  4418   4081. <dbl [12]>       39150  48970
12 Mint               1388  1669   1519. <dbl [12]>       14160  18230
13 Regular Espresso    890  1218   1023. <dbl [12]>       11310  12281
sales_data %>%
  gt() %>%
  gt_plt_sparkline('Monthly Sales') %>%
  gt_plt_bullet(column = Actual, 
                target = Target, 
                width = 28,
                palette = c("lightblue", 
                          "black")) %>%
  gt_theme_538()
Product Min Max Average Monthly Sales Actual
Amaretto 1016 1210 1119.000 1.2K
Caffe Latte 1398 1653 1528.333 1.5K
Caffe Mocha 3322 3828 3613.917 3.7K
Chamomile 2967 3395 3217.417 3.3K
Colombian 5132 5961 5457.250 5.5K
Darjeeling 2926 3281 3112.667 3.0K
Decaf Espresso 3181 3493 3326.833 3.2K
Decaf Irish Cream 2463 2901 2648.250 2.7K
Earl Grey 2730 3005 2841.833 3.0K
Green Tea 1339 1476 1398.750 1.5K
Lemon 3851 4418 4080.833 4.4K
Mint 1388 1669 1519.167 1.5K
Regular Espresso 890 1218 1023.417 1.1K

5. Using reactable and reactablefmtr methods

In this section, we will create interactive information dashboard by using reactable and reactablefmtr packages.

In order to build an interactive sparklines, we need to install dataui R package by using the code chunk below

remotes::install_github("timelyportfolio/dataui")
library(dataui)

5.1 Plotting sparklines

Use the code to convert the report data.frame into list columns before applying gt functions

report1 <- report %>%
  group_by(Product) %>%
  summarize(`Monthly Sales` = list(Sales))

report1
# A tibble: 13 × 2
   Product           `Monthly Sales`
   <chr>             <list>         
 1 Amaretto          <dbl [12]>     
 2 Caffe Latte       <dbl [12]>     
 3 Caffe Mocha       <dbl [12]>     
 4 Chamomile         <dbl [12]>     
 5 Colombian         <dbl [12]>     
 6 Darjeeling        <dbl [12]>     
 7 Decaf Espresso    <dbl [12]>     
 8 Decaf Irish Cream <dbl [12]>     
 9 Earl Grey         <dbl [12]>     
10 Green Tea         <dbl [12]>     
11 Lemon             <dbl [12]>     
12 Mint              <dbl [12]>     
13 Regular Espresso  <dbl [12]>     

Next, react_sparkline will be to plot the sparklines as shown below.

reactable(
  report1,
  defaultPageSize = 13,    #default pagesize is 10
  columns = list(
    Product = colDef(maxWidth = 200),
    `Monthly Sales` = colDef(
      cell = react_sparkline(
        report1,
        
        #highlight_points is used to show the min and max values points with label argument is used to label firsr and last values
        highlight_points = highlight_points(
          min = "red", max = "blue"),
        labels = c("first", "last"),
        
        #adding reference line
        statline = "mean"
        )
    )
  )
)

Instead of reference line, we can also add bandline

reactable(
  report1,
  defaultPageSize = 13,    #default pagesize is 10
  columns = list(
    Product = colDef(maxWidth = 200),
    `Monthly Sales` = colDef(
      cell = react_sparkline(
        report1,
        
        #highlight_points is used to show the min and max values points with label argument is used to label firsr and last values
        highlight_points = highlight_points(
          min = "red", max = "blue"),
        labels = c("first", "last"),
        line_width = 1,
        
        #adding bands
        bandline = "innerquartiles",
        bandline_color = "green"
        )
    )
  )
)

5.2 Plotting sparkbar

reactable(
  report1,
  defaultPageSize = 13,
  columns = list(
    Product = colDef(maxWidth = 200),
    `Monthly Sales` = colDef(
      cell = react_sparkbar(
        report1,
        highlight_bars = highlight_bars(
          min = "red", max = "blue"),
        bandline = "innerquartiles",
        statline = "mean")
    )
  )
)