Jeff B
April 16, 2020
This presentation is for the final course project for the Coursera course “Developing Data Products.” For this assignment, we developed a Shiny App and made a 5-slide presentation to accompany it. The presentation is structured as follows:
This very simple app allows the user to predict record times of Scottish hill races using the dataset hills, available in the MASS package. This dataset contains data on Scottish hill races in 1984.
The app provides 2 key variables that the user can change: race distance (in miles) and climb (in feet), which is the vertical distance covered by the race.
As the user changes the variables, the user can view live changes on a plot. The plot displays distance in miles on the x axis and time in minutes on the y axis. Beneath the plot, the app displays the estimated record time in a convenient hours-minutes-seconds format.
The output plot looks like this:
library(shiny); library(tidyverse); library(ggrepel); library(MASS)
shinyUI(fluidPage(
# Make horizontal lines more aesthetically pleasing
tags$head(tags$style(HTML("hr {border-top: 1px solid #C0C0C0;}"))),
# Application title
titlePanel("Scottish hill race record time predictor"),
# Sidebar with a slider input for variables
sidebarLayout(
sidebarPanel(
h3("Race variables"),
p("Set the total distance and climb of your race"),
hr(),
sliderInput("dist1", "Distance:", min = 2, max = 28, value = 15, step = 1),
hr(),
sliderInput("climb1", "Climb:", min = 0, max = 10000, value = 4000, step = 100),
),
# Show the plot
mainPanel(
p("Your race will appear below among record times of other races in 1984"),
plotOutput("racePlot"),
p("Estimated record time of your race is:"),
textOutput("raceTime"),
p(),
textOutput("worldRecord")
)
)
)
)
The UI-side code is fairly straightforward.
In the side panel, we set up the input variables, along with some simple horizontal lines to provide a little more aesthetic to the design.
In the main panel, we display the plot. Beneath the plot we display the record time in text form.
A little bit of instruction is sprinkled throughout in lieu of formal documentation.
library(shiny); library(tidyverse); library(ggrepel); library(MASS); library(lubridate)
shinyServer(function(input, output) {
# Load and tidy the data we need
df <- data.frame(hills)
df <- mutate(df, race = rownames(hills))
# Create linear model
fit <- lm(time ~ dist + climb, data = df)
# Make prediction
time1 <- reactive({
raceDist <- input$dist1
raceClimb <- input$climb1
predict(fit, newdata = data.frame(dist = raceDist, climb = raceClimb))})
# Convert the time prediction into something more nicely displayed
time2 <- reactive({ str_to_lower(as.character(seconds_to_period(round(
as.numeric(as.period(time1()))*60)))) })
# Tell a joke if the time is too low
worldRecord <- reactive({ ifelse(time1()*60 < 223,
print("This is faster than the current world record time for 1 mile!"),
ifelse(time1()*60 < 478,
print("This is faster than the world record time for 2 miles!"),""))})
# Create plot
output$racePlot <- renderPlot({
raceDist <- input$dist1
raceClimb <- input$climb1
ggplot(data = hills,
aes(x = dist, y = time, alpha = 0.5)) +
geom_point(shape = 3) +
geom_smooth(method="lm", size = 1) +
labs(title = "Record times in 1984 for 35 Scottish hill races",
x = "Distances in miles",
y = "Time in minutes") +
geom_text(label = df$race, check_overlap=TRUE, size = 3, nudge_y = 8) +
geom_point(aes(x = raceDist, y = time1(), color="red"), shape = 8) +
geom_text(label = "Your Race", aes(x = raceDist, y = time1(), color = "red"), nudge_y = 8) +
theme(legend.position = "none")})
output$raceTime <- renderText({ time2() })
output$worldRecord <- renderText({ worldRecord() })})
The server-side code is slightly more complicated.
First it loads necessary packages and data. It tidies and processes the data a bit. Then it creates a linear model. So far nothing out of the ordinary.
Then it resorts to some reactive() functions. The reactive functions are necessary to run calculations that use inputs from the UI side. For this app, there are three reactive functions. One is for the time prediction itself. Another is to convert the time prediction into a more readable format. And the third is for an Easter egg that checks if the time is beneath a world record.
Next it builds the plot, drawing on the outputs of the reactive functions. The final lines render the text for the formatted prediction time and for the statement about a world record.
The data on display in this presentation is messily formatted because it was squeezed to fit into the page.