Chapter 7 Reporting: tables

Scenario. Lucy seems intrigued your presentation on Australia’s sales. Now, she wants to review the sales difference (growth) for Product Category and Sub Category between year 2015 and 2016. She wants to be able to filter the report by all the options available, including Customer Gender and Age Group.

Load libraries

library(tidyverse, warn.conflicts = F)
library(scales)
library(formattable)
library(htmlwidgets)
library(sparkline)

Load data for this lab:

my_data <- readRDS("./data/processing/data4week3.rds")
str(my_data)
## Classes 'tbl_df', 'tbl' and 'data.frame':    113036 obs. of  19 variables:
##  $ Date            : POSIXct, format: "2013-11-26" "2015-11-26" ...
##  $ Year            : num  2013 2015 2014 2016 2014 ...
##  $ Month           : num  11 11 3 3 5 5 5 5 2 2 ...
##  $ Customer ID     : num  11019 11019 11039 11039 11046 ...
##  $ Customer Age    : num  19 19 49 49 47 47 47 47 35 35 ...
##  $ Age Group       : chr  "Youth" "Youth" "Adults" "Adults" ...
##  $ Customer Gender : chr  "M" "M" "M" "M" ...
##  $ Country         : chr  "Canada" "Canada" "Australia" "Australia" ...
##  $ State           : chr  "British Columbia" "British Columbia" "New South Wales" "New South Wales" ...
##  $ Product Category: chr  "Accessories" "Accessories" "Accessories" "Accessories" ...
##  $ Sub Category    : chr  "Bike Racks" "Bike Racks" "Bike Racks" "Bike Racks" ...
##  $ Product         : chr  "Hitch Rack - 4-Bike" "Hitch Rack - 4-Bike" "Hitch Rack - 4-Bike" "Hitch Rack - 4-Bike" ...
##  $ Frame Size      : num  NA NA NA NA NA NA NA NA NA NA ...
##  $ Order Quantity  : num  8 8 23 20 4 5 4 2 22 21 ...
##  $ Unit Cost       : num  45 45 45 45 45 45 45 45 45 45 ...
##  $ Unit Price      : num  120 120 120 120 120 120 120 120 120 120 ...
##  $ Cost            : num  360 360 1035 900 180 ...
##  $ Revenue         : num  950 950 2401 2088 418 ...
##  $ Profit          : num  590 590 1366 1188 238 ...

In this Lab we’ll be review the sales difference (growth) for Product Category and Sub Category between year 2015 and 2016. So, we will create two type of reports. One with the sales for each year and the other with the % Change in Revenue from year to year. Each report is a cross-tabular format with Product Categories and Sub categories on the rows and year on the columns, with Sum of Revenue as the aggregate data. Also we will create chart representation for each report.

Let’s start! First, create two reports just like on picture below

For this task we’re going to use such packages as formattable and sparklines. Also we’ll include external css file for styling our tables.


7.1 Helper functions for type 1 report

7.1.1 Count totals for Product Category

product_total <- function(df) {
  df %>%
    group_by(`Product Category`, Year) %>%
    summarise(Total = sum(Revenue)) %>%
    mutate(Margin = (Total - lag(Total)) / Total) %>% 
    ungroup() %>% 
    mutate(`Sub Category` = `Product Category`) %>% 
    select(`Product Category`, `Sub Category`, everything())
}
Product Category Sub Category Year Total Margin
Accessories Accessories 2013 3384215 NA
Accessories Accessories 2014 4293592 0.2117987
Accessories Accessories 2015 3285954 -0.3066501
Accessories Accessories 2016 4154231 0.2090103
Bikes Bikes 2011 8964888 NA
Bikes Bikes 2012 9175983 0.0230052
Bikes Bikes 2013 9858787 0.0692584
Bikes Bikes 2014 7611243 -0.2952926
Bikes Bikes 2015 14799083 0.4856950
Bikes Bikes 2016 11372150 -0.3013443
Clothing Clothing 2013 1997035 NA
Clothing Clothing 2014 2247889 0.1115954
Clothing Clothing 2015 1938954 -0.1593308
Clothing Clothing 2016 2187004 0.1134200

7.1.2 List of unique product in Product Category

uniq_product <- function(df) {
  unique(df$`Product Category`)
}
my_data %>% uniq_product()
## [1] "Accessories" "Clothing"    "Bikes"

7.1.3 Count totals for Sub Category:

sub_total <- function(df) {
  df %>% 
  group_by(`Product Category`, `Sub Category`, Year) %>%
  summarise(Total = sum(Revenue)) %>%
  mutate(Margin = (Total - lag(Total)) / Total) %>% 
  ungroup()
}
Product Category Sub Category Year Total Margin
Accessories Bike Racks 2013 112605 NA
Accessories Bike Racks 2014 152966 0.2638560
Accessories Bike Racks 2015 107813 -0.4188085
Accessories Bike Racks 2016 144416 0.2534553
Accessories Bike Stands 2013 95396 NA
Accessories Bike Stands 2014 83148 -0.1473036
Accessories Bike Stands 2015 88822 0.0638806
Accessories Bike Stands 2016 76709 -0.1579085
Accessories Bottles and Cages 2013 292535 NA
Accessories Bottles and Cages 2014 422000 0.3067891

7.1.4 Prepare report_1

report_1 <- function(df) {
  bind_rows(product_total(df), sub_total(df)) %>% 
  select(-Margin) %>% 
  group_by(`Product Category`, Year) %>% 
  spread(key = Year, value = Total) %>% 
  group_by(`Product Category`) %>% 
  arrange(desc(`2016`), .by_group = TRUE) %>% 
  ungroup() %>% 
  select(-`Product Category`) %>% 
  mutate_if(is.numeric, formattable::comma, digits = 0)
}
my_data %>% report_1
## # A tibble: 20 x 7
##       `Sub Category`            `2011`            `2012`            `2013`
##                <chr> <S3: formattable> <S3: formattable> <S3: formattable>
##  1       Accessories                NA                NA         3,384,215
##  2           Helmets                NA                NA         1,285,031
##  3   Tires and Tubes                NA                NA         1,033,740
##  4 Bottles and Cages                NA                NA           292,535
##  5           Fenders                NA                NA           284,577
##  6   Hydration Packs                NA                NA           237,491
##  7        Bike Racks                NA                NA           112,605
##  8       Bike Stands                NA                NA            95,396
##  9          Cleaners                NA                NA            42,840
## 10             Bikes         8,964,888         9,175,983         9,858,787
## 11        Road Bikes         6,766,618         6,993,130         4,836,352
## 12    Mountain Bikes         2,198,270         2,182,853         3,732,841
## 13     Touring Bikes                NA                NA         1,289,594
## 14          Clothing                NA                NA         1,997,035
## 15           Jerseys                NA                NA           956,093
## 16            Shorts                NA                NA           447,464
## 17            Gloves                NA                NA           191,818
## 18             Vests                NA                NA           251,704
## 19              Caps                NA                NA           117,068
## 20             Socks                NA                NA            32,888
## # ... with 3 more variables: `2014` <S3: formattable>, `2015` <S3:
## #   formattable>, `2016` <S3: formattable>

7.1.5 Define function that makes sparklines in formattable object

my_sparkline <- function(x){
  as.character(
    htmltools::as.tags(
      sparkline(x) # we can add any chart type!
    )
  )
}

7.1.6 Make tibble with sparklines as html-widgets:

sparkl_df <- function(df) {
  report_1(df) %>% 
  nest(`2011`:`2016`, .key = 'Sparkl') %>% 
  mutate_at('Sparkl', map, as.numeric, my_sparkline) %>% 
  mutate_at('Sparkl', map, my_sparkline)
}

7.1.7 Add sparklines objects to report_1:

report_1_sparkl <- function(df) {
  bind_cols(report_1(df), sparkl_df(df) %>%
                        select(Sparkl))
}
my_data %>% report_1_sparkl
## # A tibble: 20 x 8
##       `Sub Category`            `2011`            `2012`            `2013`
##                <chr> <S3: formattable> <S3: formattable> <S3: formattable>
##  1       Accessories                NA                NA         3,384,215
##  2           Helmets                NA                NA         1,285,031
##  3   Tires and Tubes                NA                NA         1,033,740
##  4 Bottles and Cages                NA                NA           292,535
##  5           Fenders                NA                NA           284,577
##  6   Hydration Packs                NA                NA           237,491
##  7        Bike Racks                NA                NA           112,605
##  8       Bike Stands                NA                NA            95,396
##  9          Cleaners                NA                NA            42,840
## 10             Bikes         8,964,888         9,175,983         9,858,787
## 11        Road Bikes         6,766,618         6,993,130         4,836,352
## 12    Mountain Bikes         2,198,270         2,182,853         3,732,841
## 13     Touring Bikes                NA                NA         1,289,594
## 14          Clothing                NA                NA         1,997,035
## 15           Jerseys                NA                NA           956,093
## 16            Shorts                NA                NA           447,464
## 17            Gloves                NA                NA           191,818
## 18             Vests                NA                NA           251,704
## 19              Caps                NA                NA           117,068
## 20             Socks                NA                NA            32,888
## # ... with 4 more variables: `2014` <S3: formattable>, `2015` <S3:
## #   formattable>, `2016` <S3: formattable>, Sparkl <list>

7.1.8 Final table for the first report

report_1_table <- function(df) {
  formattable(report_1_sparkl(df),
    align = c("l", "r", "r", "r", "r", "r", "r"), table.attr = "class=\'my_table\'",
    list(
      area(, `2011`:`2016`) ~ color_tile("#FFFF00", "#FF0000"),
      `Sub Category` = formatter("span",
               style = x ~ ifelse(x %in% uniq_product(df), 
                            style(font.weight = "bold"),
                            style(padding.left = "0.25cm"))
                                )
        )
    ) %>% 
  formattable::as.htmlwidget() %>% 
  htmltools::tagList() %>%
  htmltools::attachDependencies(htmlwidgets:::widget_dependencies("sparkline","sparkline")) %>%
  htmltools::browsable()
}
my_data %>% report_1_table()

7.2 Helper functions for report type 2

7.2.1 Calculate changes in totals by year for product category

report_2 <- function(df) {
  bind_rows(product_total(df), sub_total(df)) %>% 
  select(-Total) %>% 
  group_by(`Product Category`, Year) %>% 
  spread(key = Year, value = Margin) %>% 
  ungroup() %>% 
  arrange(match(`Sub Category`, report_1(df)$`Sub Category`)) %>% 
  select(-`Product Category`, -`2011`) %>% 
  mutate_if(is.numeric, formattable::percent, digits = 1)
}
my_data %>% report_2
## # A tibble: 20 x 6
##       `Sub Category`            `2012`            `2013`            `2014`
##                <chr> <S3: formattable> <S3: formattable> <S3: formattable>
##  1       Accessories                NA                NA             21.2%
##  2           Helmets                NA                NA             21.1%
##  3   Tires and Tubes                NA                NA             22.7%
##  4 Bottles and Cages                NA                NA             30.7%
##  5           Fenders                NA                NA             18.0%
##  6   Hydration Packs                NA                NA             10.2%
##  7        Bike Racks                NA                NA             26.4%
##  8       Bike Stands                NA                NA            -14.7%
##  9          Cleaners                NA                NA             26.2%
## 10             Bikes              2.3%              6.9%            -29.5%
## 11        Road Bikes              3.2%            -44.6%            -60.3%
## 12    Mountain Bikes             -0.7%             41.5%            -25.1%
## 13     Touring Bikes                NA                NA             20.0%
## 14          Clothing                NA                NA             11.2%
## 15           Jerseys                NA                NA             15.4%
## 16            Shorts                NA                NA             -2.7%
## 17            Gloves                NA                NA             23.4%
## 18             Vests                NA                NA            -10.1%
## 19              Caps                NA                NA             27.5%
## 20             Socks                NA                NA             21.4%
## # ... with 2 more variables: `2015` <S3: formattable>, `2016` <S3:
## #   formattable>

7.2.2 Final table for the second report

report_2_table <- function(df) {
  formattable(report_2(df), align = c("l", "l", "l", "l", "l", "l"),
    table.attr = "class=\'my_table\'",
    list(
      area(, `2012`:`2016`) ~ formatter("span",
        style = x ~ style(color = ifelse(x < 0 , "red", "green")),
                x ~ icontext(ifelse(x < 0, "arrow-down", "arrow-up"), formattable::percent(x, digits = 1))),
     `Sub Category` = formatter("span",
                style = x ~ ifelse(x %in% uniq_product(df),
                           style(font.weight = "bold"),
                           style(padding.left = "0.25cm")
                                  )
                                )
      )
  )
}
my_data %>% report_2_table()
Sub Category 2012 2013 2014 2015 2016
Accessories NA NA 21.2% -30.7% 20.9%
Helmets NA NA 21.1% -30.3% 20.7%
Tires and Tubes NA NA 22.7% -33.1% 22.5%
Bottles and Cages NA NA 30.7% -48.0% 30.4%
Fenders NA NA 18.0% -25.2% 17.6%
Hydration Packs NA NA 10.2% -14.6% 10.4%
Bike Racks NA NA 26.4% -41.9% 25.3%
Bike Stands NA NA -14.7% 6.4% -15.8%
Cleaners NA NA 26.2% -39.6% 26.3%
Bikes 2.3% 6.9% -29.5% 48.6% -30.1%
Road Bikes 3.2% -44.6% -60.3% 58.6% -62.9%
Mountain Bikes -0.7% 41.5% -25.1% 46.6% -25.6%
Touring Bikes NA NA 20.0% 16.9% 21.1%
Clothing NA NA 11.2% -15.9% 11.3%
Jerseys NA NA 15.4% -21.8% 15.7%
Shorts NA NA -2.7% -0.4% -2.3%
Gloves NA NA 23.4% -35.2% 23.9%
Vests NA NA -10.1% 7.3% -11.1%
Caps NA NA 27.5% -42.0% 27.5%
Socks NA NA 21.4% -30.9% 21.2%

7.3 Questions

7.3.1 Without applying any filter, which year does the Accessories category have negative growth?

Answer: 2015


7.3.2 Filter the reports for youth age group. Which two years do the accessories category have negative growth?

Report 1

my_data %>%
  filter(`Age Group` == "Youth") %>%
  report_1_table()

Report 2

my_data %>%
  filter(`Age Group` == "Youth") %>%
  report_2_table()
Sub Category 2012 2013 2014 2015 2016
Accessories NA NA -7.8% 4.9% -8.9%
Tires and Tubes NA NA 13.7% -18.0% 13.1%
Helmets NA NA -16.6% 12.0% -18.3%
Bottles and Cages NA NA 30.3% -45.9% 29.1%
Fenders NA NA -39.2% 25.9% -37.9%
Hydration Packs NA NA -106.0% 49.6% -110.6%
Cleaners NA NA 15.1% -21.7% 15.9%
Bike Stands NA NA -144.7% 57.6% -185.4%
Bike Racks NA NA -53.4% 32.2% -44.9%
Bikes -1.3% -8.9% -140.8% 72.8% -143.7%
Road Bikes -0.9% -73.4% -127.8% 71.7% -142.6%
Mountain Bikes -2.7% 41.8% -169.9% 75.2% -152.9%
Touring Bikes NA NA -113.8% 69.6% -120.1%
Clothing NA NA -92.8% 47.0% -95.0%
Jerseys NA NA -59.1% 35.6% -59.6%
Gloves NA NA 18.6% -25.5% 17.5%
Caps NA NA 1.7% -5.1% 3.1%
Shorts NA NA -612.6% 85.8% -661.4%
Vests NA NA -246.9% 70.1% -245.8%
Socks NA NA -9.9% 8.3% -16.4%

Answer: 2014, 2016


7.4 Without applying any filter, which two years do the bikes category have negative growth?

Answer: 2014, 2016


7.5 Filter the report for Australia. Which year do the bikes category have the highest growth?

Report 1

my_data %>%
  filter(Country == "Australia") %>% 
  report_1_table()

Report 2

my_data %>%
  filter(Country == "Australia") %>% 
  report_2_table()
Sub Category 2012 2013 2014 2015 2016
Accessories NA NA 18.9% -26.7% 18.5%
Helmets NA NA 18.5% -25.9% 18.0%
Tires and Tubes NA NA 24.7% -36.3% 24.4%
Bottles and Cages NA NA 29.6% -45.8% 28.8%
Hydration Packs NA NA -11.7% 7.7% -12.0%
Fenders NA NA 9.6% -13.0% 9.4%
Bike Racks NA NA 60.9% -170.0% 60.3%
Bike Stands NA NA -39.0% 24.9% -38.1%
Cleaners NA NA 35.6% -58.2% 36.0%
Bikes 1.1% 16.2% -84.2% 64.6% -88.5%
Road Bikes 2.5% -4.6% -84.5% 64.6% -85.7%
Mountain Bikes -2.1% 25.4% -126.4% 71.3% -139.0%
Touring Bikes NA NA -11.3% 41.3% -13.7%
Clothing NA NA -14.7% 11.7% -17.1%
Jerseys NA NA -7.2% 4.6% -8.7%
Shorts NA NA -35.0% 23.8% -36.3%
Gloves NA NA 21.9% -33.3% 22.8%
Vests NA NA -98.3% 51.1% -109.4%
Caps NA NA 26.3% -39.4% 25.2%
Socks NA NA 21.7% -29.7% 20.9%

Answer: 2015


7.6 Keep the Australia filter. In the year that bikes sales have the highest growth (previous question), which sub category of bikes has the highest growth?

Answer: Mounting Bikes, 71%