This is the second post following the the previous one, where we used tracking logs from an online course platform to create animated visualizations.

In this post, we explore three aspects of the user engagement: (1) global distribution of the users over time, (2) resource usage over time, and (3) the peak hours of accessing the platform by device types (desktop versus mobile).

With the capabilities of the slider, we are able to pick a certain date or select a date range to explore the graphs interactively; we can also run animation sequences with the slider controls.


Global distributions

We use the same data as in the earlier post, where we have created an animated graph with gganimate.

head(map)
##         date latitude longitude                        city    n
## 1 2016-01-24  22.2643  114.1880                   Hong Kong  158
## 2 2016-01-24  40.7323  -73.9874                    New York   49
## 3 2016-01-24  36.8529  -75.9780              Virginia Beach   23
## 4 2016-01-25  51.0951 -113.9440 Calgary (Northeast Calgary)   13
## 5 2016-01-25  22.2643  114.1880                   Hong Kong 1005
## 6 2016-01-25  40.7323  -73.9874                    New York  123

Here we want to (1) view the map on a selected date, and (2) run the animation sequences once we hit the play button, as shown below.

To create something as shown above, on the ui side, for the slider, we need to specify the display range for the sliderInput(): min sets the minimum value to use in the slider bar, and max sets the maximum value; both should be date objects in our case. With the animate argument and the interval option, we set the pace of the iterations in the animation. We set the step to be 1 so that the increment between the values will be one day and in the animation the map will reflect the change daily. value sets the default value to show in the slider bar when the app is first loaded.

library(shiny)
library(ggthemes)
library(maps)
library(ggplot2)
library(dplyr)

load("map")

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      sliderInput(inputId = "distribution", 
                  label = "Dates:",
                  min = as.Date("2016-01-24","%Y-%m-%d"),
                  max = as.Date("2016-04-02","%Y-%m-%d"),
                  value = as.Date("2016-02-29"), timeFormat="%Y-%m-%d", 
                  step = 1,
                  animate = animationOptions(interval = 1800))),
    mainPanel(plotOutput(outputId = "map", height = "70vh"))))

On the server side, we plot the graph on the subset of data whose date range is within the min and max values by setting date == input$distribution. distribution is the inputId of the slider.

server <- function(input, output){
  output$map <- renderPlot({
    map %>% 
      filter(date == input$distribution) %>%
      ggplot() +
      borders("world", colour = "gray90", fill = "gray85") +
      theme_map() + 
      geom_point(aes(x = longitude, y = latitude, size = n), 
                 colour = "#351C4D", alpha = 0.55) +
      labs(size = "Users") + 
      ggtitle("Distribution of Users Online") 
  })
}

shinyApp(ui = ui, server = server)

Resource usage trajectory

Not only can we input single values in the slider, but also ranges. To explore how users accessed the platform within a date range, we customize the slider so that we can extract data within a period of time.

Below is the sample data, same as what we used to create the animated line graph.

head(resource_day_sum)
##         date resource    sum
## 1 2016-01-24   Slides  69.09
## 2 2016-01-24    Watch  31.61
## 3 2016-01-25    Intro  41.59
## 4 2016-01-25     Read  60.95
## 5 2016-01-25   Slides 429.25
## 6 2016-01-25     Task  17.10

On the ui side, same as what we have done in the section above, we need to set the sliderInput() for display range with arguments min and max.

library(shiny)
library(ggplot2)
library(dplyr)

load("resource_day_sum")

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      sliderInput(inputId = "trajectory", 
                  label = "Date Range:",
                  min = as.POSIXct("2016-01-24","%Y-%m-%d"),
                  max = as.POSIXct("2016-04-02","%Y-%m-%d"),
                  value = c(as.POSIXct("2016-02-01"), as.POSIXct("2016-03-21")), 
                  timeFormat="%Y-%m-%d", step = 1)),
    mainPanel(plotOutput(outputId = "lines", height = "70vh"))))

On the server side, to match the date range we set for the slider, we plot the graph on the subset of data where date >= input$trajectory[1] & date <= input$trajectory[2]. trajectory is the inputId of the slider.

server <- function(input, output){
  color <- c("#765285","#D1A827","#709FB0", "#849974", "#A0C1B8")
  due <- c(as.POSIXct("2016-01-25 UTC"), as.POSIXct("2016-02-01 UTC"), 
         as.POSIXct("2016-02-15 UTC"), as.POSIXct("2016-02-22 UTC"), 
         as.POSIXct("2016-02-29 UTC"), as.POSIXct("2016-03-07 UTC"),
         as.POSIXct("2016-03-14 UTC"), as.POSIXct("2016-03-21 UTC"), 
         as.POSIXct("2016-03-28 UTC"))

  output$lines <- renderPlot({
    resource_day_sum %>%
      filter(date >= input$trajectory[1] & date <= input$trajectory[2]) %>% 
      ggplot(aes(date, sum, group = resource, colour = resource)) + 
      geom_line(alpha = 0.9, size = 0.65) + 
      scale_x_datetime(breaks = seq(as.POSIXct("2016-01-26 UTC"), as.POSIXct("2016-04-02 UTC"), "7 days"), date_labels = "%b %d") +
      geom_vline(xintercept = due, alpha = 0.6, size = 0.65, colour = "grey55") +
      scale_colour_manual(values = color, name = "Resource") +
      labs(title = "Time Spent Online Learning", x = "Date", y = "Total Minutes per Day")
  })
}

shinyApp(ui = ui, server = server)

Peak hours by device access

Finally, we would like to create a heatmap to reveal the peak hours of users accessing the online resources. We showed how to make a static heatmap previously. Here we add the feature that enables people to interactively view the heatmap by device types.

We have added the device data to the data of local hours.

head(local, 15)
##     localdate localhour n  device
## 1  2016-01-24         0 1     all
## 2  2016-01-24         6 2     all
## 3  2016-01-24        18 6     all
## 4  2016-01-24        20 1     all
## 5  2016-01-24        21 2     all
## 6  2016-01-24        23 2     all
## 7  2016-01-24         0 1 desktop
## 8  2016-01-24         6 1 desktop
## 9  2016-01-24        18 5 desktop
## 10 2016-01-24        20 1 desktop
## 11 2016-01-24        21 2 desktop
## 12 2016-01-24        23 2 desktop
## 13 2016-01-24         6 1  mobile
## 14 2016-01-24        18 1  mobile
## 15 2016-01-25         0 1     all

The tick marks on the X axis indicate the due dates. Clearly, when the due dates approached, there were more students accessing online resources, ususally lasting from evening to midnight. Inspecting the peak hours by device types, it is apparent that the majority accessed the online resources via desktop devices, while very few accessed the resources via mobile devices.

We have shown how to build a simple dashboard like this before.

library(shiny)
library(shinythemes)
library(dplyr)
library(ggplot2)

load("local")

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      radioButtons(
        inputId = "peak",
        label = "Devicce:",
        choices = c("All types", "Desktop", "Mobile"))),
    mainPanel(plotOutput(outputId = "heatmap", height = "70vh"))))

server <- function(input, output){
  dataInput <- reactive({
    switch(input$peak,  
           "All types" = local %>% filter(device == "all"),
           "Desktop" = local %>% filter(device == "desktop"),
           "Mobile" = local %>% filter(device == "mobile"))
  })
  
  output$heatmap <- renderPlot({
    ggplot(dataInput(), aes(localdate, localhour2)) + 
      geom_tile(aes(fill = n), colour = "white") + 
      scale_fill_gradient(low = "#f3da4c", high = "blue", breaks = seq(0, 13, 2)) +
      scale_x_date(breaks = seq(as.Date("2016-01-26 UTC"), as.Date("2016-04-02 UTC"), "7 days"), date_labels = "%b %d") + 
      scale_y_continuous(breaks = seq(0, 23, 1)) +
      labs(x = "Date", y = "Hour", fill = "Freq of Online Access") +
      ggtitle("Peak Hours of Studying Online (Local Time)") 
  })
}

shinyApp(ui = ui, server = server)