ETC5523: Communicating with Data
Tutorial 10
Preparation
Below is a slightly modified version of the solar dashboard app we built in class. I have added a function to simulate solar production data. You’ll also want the solar data Download.
library(shiny)
library(DT)
library(readr)
library(dplyr)
library(ggplot2)
library(janitor)
library(tidyr)
library(lubridate)
<- function(solar_data, max_kw = 5) {
add_solar_model
<- lubridate::hour(solar_data$datetime)
hours
<- ifelse(
production >= 6 & hours <= 18,
hours * sin(pi * (hours - 6) / 12)^1.5 + rnorm(length(hours), mean = 0, sd = max_kw * 0.02),
max_kw 0
)
# Ensure no negative values due to noise
<- pmax(0, production)
production
|> mutate(production = production)
solar_data
}
<- fluidPage(
ui headerPanel("Solar dashboard"),
sidebarPanel(
fileInput("solar_file", "Upload solar data CSV", accept = c(".csv")),
sliderInput("date_range", "Subset dates", min = as.Date('2024-07-15'), max = as.Date('2025-10-05'),
value = c(as.Date('2024-07-15'), as.Date('2025-10-05'))),
div(id = "price_inputs",
numericInput("electricity_price_1", "Price per kwh", value = 0.28, min = 0)
),div(style = "text-align: right;",
actionButton("add_price", "Add another price"),
),
radioButtons("plot_geom", "Plot type",
choices = c("Line" = "line", "Histogram" = "hist"),
selected = "line"),
conditionalPanel(
condition = "input.plot_geom == 'hist'",
sliderInput("bins", "Number of bins", min = 5, max = 60, value = 30),
),selectInput("aggregation_size", "Aggregation type", choices = c("None", "Daily", "Weekly", "Monthly")),
),mainPanel(
tabsetPanel(
tabPanel("Data Plot", plotOutput("solar_plot"), downloadButton("download_plot", "Download Plot")),
tabPanel("Cost Plot", plotOutput("cost_plot"), downloadButton("download_cost_plot", "Download Cost Plot")),
tabPanel("Data", DTOutput("data_table"))
)
)
)
<- function(input, output) {
server
<- reactiveVal(1)
n_prices observeEvent(input$add_price, {
<- n_prices() + 1
n n_prices(n)
insertUI(
selector = "#price_inputs",
where = "beforeEnd",
ui = numericInput(
paste0("electricity_price_", n),
paste("Price per kwh"), value = 0.28, min = 0)
)
})
<- reactive({
solar_data req(input$solar_file)
read.csv(input$solar_file$datapath) |>
pivot_longer(
cols = starts_with("X"),
names_to = "datetime",
values_to = "energy_kwh"
|>
) mutate(
datetime = gsub("X(\\d{2}\\.\\d{2})\\.\\.\\.(\\d{2}\\.\\d{2})", "\\1", datetime),
energy_kwh = as.numeric(energy_kwh),
datetime = paste0(DATE, " ", datetime),
datetime = lubridate::ymd_hm(datetime)
|>
) ::clean_names()
janitor
})
<- reactive({
solar_data_filtered req(solar_data())
solar_data() |>
filter(datetime >= input$date_range[1] & datetime <= input$date_range[2])
})
<- reactive({
solar_data_aggregated req(solar_data_filtered())
if (input$aggregation_size == "Daily") {
solar_data_filtered() |>
group_by(date) |>
summarise(energy_kwh = sum(energy_kwh, na.rm = TRUE)) |>
rename(datetime = date) |>
mutate(datetime = as.Date(datetime)) |>
ungroup()
else if (input$aggregation_size == "Weekly") {
} solar_data_filtered() |>
mutate(week = lubridate::floor_date(datetime, unit = "week")) |>
group_by(week) |>
summarise(energy_kwh = sum(energy_kwh, na.rm = TRUE)) |>
rename(datetime = week)
else if (input$aggregation_size == "Monthly") {
} solar_data_filtered() |>
mutate(month = lubridate::floor_date(datetime, unit = "month")) |>
group_by(month) |>
summarise(energy_kwh = sum(energy_kwh, na.rm = TRUE)) |>
rename(datetime = month)
else {
} solar_data_filtered()
}
})
<- reactive({
solar_data_priced req(prices())
<- solar_data_filtered()
solar_data_priced for (i in 1:n_prices()) {
paste0("electricity_price_", i)] <- prices()[i] * solar_data_priced$energy_kwh
solar_data_priced[, paste0("total_cost_", i)] <- cumsum(solar_data_priced[, paste0("electricity_price_", i)])
solar_data_priced[,
}
solar_data_priced
})
<- reactive({
solar_plot <- ggplot(solar_data_aggregated()) +
plot labs(y = 'kWh', x = 'Date') +
theme_minimal()
if (input$plot_geom == "line") {
ggplot(solar_data_aggregated()) + geom_line(aes(x=datetime, y=energy_kwh)) +
labs(y='kWh', x='Date') +
theme_minimal()
else if (input$plot_geom == "hist") {
} ggplot(solar_data_aggregated()) + geom_histogram(bins = input$bins, aes(x = energy_kwh)) +
labs(y='Count', x='Energy (kWh)') +
theme_minimal()
}
})
$solar_plot <- renderPlot({
outputsolar_plot()
})
$download_plot <- downloadHandler(
outputfilename = function() { paste("solar_plot", Sys.Date(), ".png", sep = "") },
content = function(file) {
ggsave(file, plot = solar_plot(), device = "png", bg = "white", width = 8, height = 6)
}
)
$download_cost_plot <- downloadHandler(
outputfilename = function() { paste("cost_plot", Sys.Date(), ".png", sep = "") },
content = function(file) {
ggsave(file, plot = cost_plot(), device = "png", bg = "white", width = 8, height = 6)
}
)
$data_table <- renderDT({
outputreq(solar_data())
solar_data_filtered()
})
<- reactive({
prices <- paste0("electricity_price_", 1:n_prices())
ids <- sapply(ids, function(id) input[[id]])
values !is.na(values)]
values[
})
<- reactive({
cost_plot req(solar_data_priced())
req(prices())
solar_data_priced() |>
select(datetime, starts_with("total_cost_")) |>
pivot_longer(!c(datetime, starts_with("electricity_price_")), values_to = "total_cost", names_to = "cost_per_kwh") |>
mutate(
cost_per_kwh = factor(cost_per_kwh, labels = scales::dollar(prices()))
|>
) ggplot(aes(x = datetime, y = total_cost, colour = cost_per_kwh)) +
geom_line() +
labs(y = 'Cost ($)', x = 'Date', colour = "Cost/kwh") +
theme_minimal()
})$cost_plot <- renderPlot({
outputcost_plot()
})
}
shinyApp(ui, server)
You use the add_solar_model
function to simulate solar production data. For example:
|> add_solar_model(max_kw = 5) solar_data
and a new column called production
will be added to the data frame.
Exercise 1
Modify the app so that the user can select the max_kw
parameter via a numeric input. The slider should allow values between 1 and 20, with a default value of 5.
Add a plot to the tabset that shows the simulated production data over time.
Exercise 2
Design plots that show:
- How effective a solar system of a given size can be at reducing electricity costs (as a sales pitch)
- Our sales team wants to increase battery sales. To do this, they would like to show the customer the total amount of production in a day that they are not using. Create a text summary that displays this “wasted” energy.
Hints for part 1:
Instead of looking at raw production data, we should instead look at net consumption, that is usage minus production. This can be done by mutating a new column in solar_data_priced
with mutate(net_use = energy_kwh - production)
Then, we need to change our cost calculation to use net_use
instead of energy_kwh
. But we also need to ensure we don’t get money back for excess energy.
pmax
to ensure the value isn’t negative.
- What do you notice about the money saved by having solar panels, versus the size of the solar system? Why does it seem so low?