Shiny is een pakket om interactieve webtoepassingen te maken met R.
We installeren het shiny pakket met de volgende opdracht:
Het shiny pakket bevat 11 werkende voorbeelden. We kunnen een voorbeeld uitvoeren:
wsappapp.Rshiny en bslib in je app. Installeer ze als je ze nog niet geïnstalleerd hebt.ui object, user interface en een server functie. Functie shinyApp(ui,server) maakt een shiny App van het paar ui en serverproductiedata.dat. Maak een map data in je mappenstructuur en plaats het bestand daarinapp.R bestanddplyr pakket. We laden dit dus ook. Ook laden we het lubridate pakket om de datum juist te krijgen, en het ggplot2 pakket voor de grafiekenproductiedata.dat bestand te bekijken, en kijk ook hoe het data object er vervolgens uitziet. Gebruik bijvoorbeeld de opdracht summary(data) in de consolecode.R
library(shiny)
library(bslib)
library(dplyr)
library(lubridate)
library(ggplot2)
data <- read.table(
'data/productiedata.dat',
sep = ",",
dec = ".",
header = TRUE,
row.names = 1, ## eerste kolom is een rijnummer
na.strings = "NA"
) |>
rename(vreettijd = Totale.vreettijd.in.minuten) |>
rename_with(tolower) |>
mutate(datum = ymd(datum)) |> ## zorg dat datum goed staat
mutate(id = factor(id)) ## id moet een factor zijnggplot2. Dit moet in de server functie gebeurenui objectapp.R het ui object en de server functie door de code hieronderRemoved ... rows containing missing values. Dat is geen probleem: rijen zonder productie worden niet getekendcode.R
ui = page_navbar(
title = 'Analyse productiedata',
nav_panel(
title = "Dashboard",
card(
full_screen = TRUE,
card_header("Productie over de tijd"),
card_body(
plotOutput("grafiek")
)
)
)
)
server = function(input, output, session) {
output$grafiek <- renderPlot({
ggplot(data, aes(x = datum, y = productie)) +
geom_point(size = 1.5) +
labs(
title = 'Melkproductie',
x = "Datum",
y = "Productie"
)
})
}
shinyApp(ui, server)server functie hiervoor aan: vervang in app.R alleen de server functie door de code hieronder. Het ui object blijft hetzelfdecode.R
server = function(input, output, session) {
dt <- reactive({
data |>
filter(productie > 0, !is.na(datum)) |>
group_by(datum) |>
summarise(productie = mean(productie, na.rm = TRUE), .groups = "drop")
})
output$grafiek <- renderPlot({
ggplot(dt(), aes(x = datum, y = productie)) +
geom_line(linewidth = 0.8, alpha = 0.8) +
geom_point(size = 1.5) +
labs(
title = 'Melkproductie',
x = "Datum",
y = "Productie"
)
})
}
shinyApp(ui, server)We willen nu graag selecteren op laktatiedagen en laktatienummer, zodat de grafiek het gemiddelde van de geselecteerde dieren laat zien.
ui object. Om de code mooi te houden maken we eerst een lijst met daarin de twee schuifregelaars.sidebar van het dashboard komen, hiervoor gebruiken we functie sidebarserver functiereactive functie. Dit moet omdat deze code telkens moet lopen als we de selecties veranderen. Het resultaat van een reactive functie is een reactive functie, hier moeten we mee werken alsof het een functie is, vandaar dt(). Voor achtergrond, lees Basic Reactivity.code.R
inputs <- list(
sliderInput(
inputId = 'lacts',
label = "Laktaties:",
min = min(data$lactatie, na.rm = TRUE),
max = max(data$lactatie, na.rm = TRUE),
step = 1,
value = c(1, 3)
),
sliderInput(
inputId = 'dims',
label = "Laktatiedagen:",
min = min(data$dim, na.rm = TRUE),
max = max(data$dim, na.rm = TRUE),
step = 1,
value = c(1, 365)
)
)
ui = page_navbar(
title = 'Analyse productiedata',
sidebar = sidebar(inputs),
nav_panel(
title = "Dashboard",
card(
full_screen = TRUE,
card_header("Productie over de tijd"),
card_body(
plotOutput("grafiek")
)
)
)
)
server = function(input, output, session) {
dt <- reactive({
data |>
filter(
productie > 0,
!is.na(datum),
lactatie >= input$lacts[1],
lactatie <= input$lacts[2],
dim >= input$dims[1],
dim <= input$dims[2]
)
})
output$grafiek <- renderPlot({
req(dt())
dt() |>
group_by(datum) |>
summarise(productie = mean(productie, na.rm = TRUE), .groups = "drop") |>
ggplot(aes(x = datum, y = productie)) +
geom_line(linewidth = 0.8, alpha = 0.8) +
geom_point(size = 1.5) +
labs(
title = 'Melkproductie',
x = "Datum",
y = "Productie"
)
})
}
shinyApp(ui, server)DT. Zet library(DT) bovenaan app.R, bij de andere pakkettenoutput lijst in server gezet en we geven hem vervolgens weer in uicode.R
library(DT)
ui = page_navbar(
title = 'Analyse productiedata',
sidebar = sidebar(inputs),
nav_panel(
title = "Dashboard",
card(
full_screen = TRUE,
card_header("Kenmerken over de tijd"),
card_body(plotOutput("grafiek"))
),
card(
full_screen = TRUE,
card_header("Tabel van de gegevens"),
card_body(fillable = TRUE, DTOutput("tabel"))
)
)
)
server = function(input, output, session) {
dt <- reactive({
data |>
filter(
productie > 0,
!is.na(datum),
lactatie >= input$lacts[1],
lactatie <= input$lacts[2],
dim >= input$dims[1],
dim <= input$dims[2]
)
})
output$grafiek <- renderPlot({
req(dt())
dt() |>
group_by(datum) |>
summarise(productie = mean(productie, na.rm = TRUE), .groups = "drop") |>
ggplot(aes(x = datum, y = productie)) +
geom_line(linewidth = 0.8, alpha = 0.8) +
geom_point(size = 1.5) +
labs(
title = 'Melkproductie',
x = "Datum",
y = "Productie"
)
})
output$tabel <- renderDT({
req(dt())
datatable(
dt() |>
arrange(id, datum)
)
})
}
shinyApp(ui, server)De tabel ziet er niet goed genoeg uit:
bootstrap5datum moet op een juiste manier weergegeven worden. Hiervoor gebruiken we een aantal functiesdatum moet aangepast worden omdat het anders niet past. Hiervoor zoeken we eerst het kolomnummer van deze kolom op (datum_index), en geven vervolgens aan dat de kolom van deze index breder moet worden en links uitlijntrownames=FALSEserver functie de oude output$tabel door de code hieronder. Let op: deze code hoort binnen de server functiecode.R
output$tabel <- renderDT({
req(dt())
# Zoek de positie van de kolom 'datum' op; DT telt vanaf 0, R vanaf 1
datum_index <- which(colnames(dt()) == "datum") - 1
datatable(
dt() |>
arrange(id, datum),
style = "bootstrap5",
fillContainer = TRUE,
rownames = FALSE,
options = list(
pageLength = 10,
dom = 'ltp',
autoWidth = TRUE, # Verplicht om handmatige breedtes toe te staan
columnDefs = list(
list(
targets = datum_index, # De index van de datumkolom
width = '150px', # Pas dit getal aan om hem breder of smaller te maken
className = 'dt-left' # Zorgt dat de datum netjes links uitlijnt
)
)
)
) |>
# Formatteer de datumkolom naar Nederlands formaat (DD-MM-YYYY)
formatDate(
columns = "datum",
method = "toLocaleDateString",
params = list(
locales = "nl-NL",
options = list(
year = "numeric",
month = "2-digit",
day = "2-digit",
timeZone = "UTC" # datum is UTC; voorkomt een dag verschuiving in andere tijdzones
)
)
)
})We willen nu graag een blokje met het aantal geselecteerde dieren en een blokje met het aantal geselecteerde rijen in de data. Verder plaatsen we de cards met de grafiek en de tabel, samen met de twee nieuwe blokjes, buiten het ui object, zodat het er netter uitziet:
layout_column_wrap.server wordt uitgevoerdcards worden nu in het ui object geladen, we zetten er !!! voor om de lijst goed uit te pakkenserver berekend en komen vervolgens in ui. Let op: de laatste twee output$... regels horen binnen de server functiecode.R
cards <- list(
# Waarde-boxen voor snelle statistieken bovenaan
layout_column_wrap(
width = 1/2,
value_box(
title = "Geselecteerde Rijen",
value = tags$span(
textOutput("stat_rijen"),
style = "font-size: clamp(1.5rem, 4vw, 2.5rem); font-weight: bold;"
),
showcase = shiny::icon("database"),
theme = "primary"
),
value_box(
title = "Geselecteerde Dieren",
value = tags$span(
textOutput("stat_dieren"),
style = "font-size: clamp(1.5rem, 4vw, 2.5rem); font-weight: bold;"
),
showcase = shiny::icon("cow"),
theme = "teal"
)
),
card(
full_screen = TRUE,
card_header("Kenmerken over de tijd"),
card_body(plotOutput("grafiek"))
),
card(
full_screen = TRUE,
card_header("Tabel van de gegevens"),
card_body(fillable = TRUE, DTOutput("tabel"))
)
)
ui = page_navbar(
theme = bs_theme(version = 5, bootswatch = "flatly"),
title = "Analyse productiedata",
sidebar = sidebar(inputs),
nav_panel(
title = "Dashboard",
!!!cards
)
)
# Statistiek 1: aantal rijen
output$stat_rijen <- renderText({ nrow(dt()) })
# Statistiek 2: aantal unieke dieren in selectie
output$stat_dieren <- renderText({ n_distinct(dt()$id) })We laten nu de hele app zien, zodat je het grote geheel niet uit het oog verliest
code.R
library(shiny)
library(bslib)
library(dplyr)
library(lubridate)
library(ggplot2)
library(DT)
data <- read.table(
'data/productiedata.dat',
sep = ",",
dec = ".",
header = TRUE,
row.names = 1, ## eerste kolom is een rijnummer
na.strings = "NA"
) |>
rename(vreettijd = Totale.vreettijd.in.minuten) |>
rename_with(tolower) |>
mutate(datum = ymd(datum)) |> ## zorg dat datum goed staat
mutate(id = factor(id)) ## id moet een factor zijn
inputs <- list(
sliderInput(
inputId = 'lacts',
label = "Laktaties:",
min = min(data$lactatie, na.rm = TRUE),
max = max(data$lactatie, na.rm = TRUE),
step = 1,
value = c(1, 3)
),
sliderInput(
inputId = 'dims',
label = "Laktatiedagen:",
min = min(data$dim, na.rm = TRUE),
max = max(data$dim, na.rm = TRUE),
step = 1,
value = c(1, 365)
)
)
cards <- list(
# Waarde-boxen voor snelle statistieken bovenaan
layout_column_wrap(
width = 1/2,
value_box(
title = "Geselecteerde Rijen",
value = tags$span(
textOutput("stat_rijen"),
style = "font-size: clamp(1.5rem, 4vw, 2.5rem); font-weight: bold;"
),
showcase = shiny::icon("database"),
theme = "primary"
),
value_box(
title = "Geselecteerde Dieren",
value = tags$span(
textOutput("stat_dieren"),
style = "font-size: clamp(1.5rem, 4vw, 2.5rem); font-weight: bold;"
),
showcase = shiny::icon("cow"),
theme = "teal"
)
),
card(
full_screen = TRUE,
card_header("Kenmerken over de tijd"),
card_body(plotOutput("grafiek"))
),
card(
full_screen = TRUE,
card_header("Tabel van de gegevens"),
card_body(fillable = TRUE, DTOutput("tabel"))
)
)
ui = page_navbar(
theme = bs_theme(version = 5, bootswatch = "flatly"),
title = "Analyse productiedata",
sidebar = sidebar(inputs),
nav_panel(
title = "Dashboard",
!!!cards
)
)
server = function(input, output, session) {
dt <- reactive({
data |>
filter(
productie > 0,
!is.na(datum),
lactatie >= input$lacts[1],
lactatie <= input$lacts[2],
dim >= input$dims[1],
dim <= input$dims[2]
)
})
output$grafiek <- renderPlot({
req(dt())
dt() |>
group_by(datum) |>
summarise(productie = mean(productie, na.rm = TRUE), .groups = "drop") |>
ggplot(aes(x = datum, y = productie)) +
geom_line(linewidth = 0.8, alpha = 0.8) +
geom_point(size = 1.5) +
labs(
title = 'Melkproductie',
x = "Datum",
y = "Productie"
)
})
output$tabel <- renderDT({
req(dt())
# Zoek de positie van de kolom 'datum' op; DT telt vanaf 0, R vanaf 1
datum_index <- which(colnames(dt()) == "datum") - 1
datatable(
dt() |>
arrange(id, datum),
style = "bootstrap5",
fillContainer = TRUE,
rownames = FALSE,
options = list(
pageLength = 10,
dom = 'ltp',
autoWidth = TRUE, # Verplicht om handmatige breedtes toe te staan
columnDefs = list(
list(
targets = datum_index, # De index van de datumkolom
width = '150px', # Pas dit getal aan om hem breder of smaller te maken
className = 'dt-left' # Zorgt dat de datum netjes links uitlijnt
)
)
)
) |>
# Formatteer de datumkolom naar Nederlands formaat (DD-MM-YYYY)
formatDate(
columns = "datum",
method = "toLocaleDateString",
params = list(
locales = "nl-NL",
options = list(
year = "numeric",
month = "2-digit",
day = "2-digit",
timeZone = "UTC" # datum is UTC; voorkomt een dag verschuiving in andere tijdzones
)
)
)
})
# Statistiek 1: aantal rijen
output$stat_rijen <- renderText({ nrow(dt()) })
# Statistiek 2: aantal unieke dieren in selectie
output$stat_dieren <- renderText({ n_distinct(dt()$id) })
}
shinyApp(ui, server)De app geeft tot nu toe enkel melkproductie weer, terwijl de dataset ook andere variabelen bevat die we kunnen weergeven. Dit regelen we nu:
id, datum, lactatie, dim, kenmerk, waarde), het zogenaamde lange formaat. Dit doen we met functie pivot_longer uit het pakket tidyr bij het maken van de grafiek. Zet daarom library(tidyr) bovenaan app.Rbkv, km en inputs komen na het inlezen van de data, en de server functie vervangt de oudecode.R
library(tidyr)
bkv <- c("datum", "id", "lactatie", "dim", "productie") # lijstje met vaste variabelen
km <- colnames(data)[-c(1:4)] ## de andere variabelen, kenmerken waaruit we kunnen kiezen
inputs <- list(
selectInput(
inputId = "kenmerk",
label = "Selecteer kenmerk(en)",
choices = km,
selected = km[1],
multiple = TRUE
),
sliderInput(
inputId = 'lacts',
label = "Laktaties:",
min = min(data$lactatie, na.rm = TRUE),
max = max(data$lactatie, na.rm = TRUE),
step = 1,
value = c(1, 3)
),
sliderInput(
inputId = 'dims',
label = "Laktatiedagen:",
min = min(data$dim, na.rm = TRUE),
max = max(data$dim, na.rm = TRUE),
step = 1,
value = c(1, 365)
)
)
server = function(input, output, session) {
dt <- reactive({
req(input$lacts, input$dims, input$kenmerk)
# Dynamisch bepalen welke kolommen we behouden (bkv + geselecteerde kenmerken)
geselecteerde_kolommen <- unique(c(bkv, input$kenmerk))
data |>
filter(
productie > 0,
lactatie >= input$lacts[1],
lactatie <= input$lacts[2],
dim >= input$dims[1],
dim <= input$dims[2]
) |>
select(all_of(geselecteerde_kolommen)) ## hier de kolommen die we willen laten zien
})
output$grafiek <- renderPlot({
req(dt(), nrow(dt()) > 0)
# Pivot en bereid data voor de grafiek voor
plot_data <- dt() |>
pivot_longer(
cols = all_of(input$kenmerk),
values_to = "waarde",
names_to = "kenmerk"
) |>
filter(!is.na(waarde), waarde > 0) |>
group_by(datum, kenmerk) |>
summarise(waarde = mean(waarde, na.rm = TRUE), .groups = "drop")
## hier berekenen we dus gemiddelde per kenmerk per dag
req(nrow(plot_data) > 0) # geen grafiek als een kenmerk geen waarden heeft
ggplot(plot_data, aes(x = datum, y = waarde, color = kenmerk, group = kenmerk)) +
geom_line(linewidth = 0.8, alpha = 0.8) +
geom_point(size = 1.5) +
labs(
title = 'Groepsgemiddelde over tijd',
x = "Datum",
y = "Waarde",
color = "Kenmerk"
) +
# grafieken per kenmerk als er meerdere gekozen zijn
facet_wrap(~kenmerk, scales = "free_y")
})
# DataTable output genereren binnen de bslib card
output$tabel <- renderDT({
req(dt())
# Zoek de positie van de kolom 'datum' op; DT telt vanaf 0, R vanaf 1
datum_index <- which(colnames(dt()) == "datum") - 1
datatable(
dt() |>
arrange(id, datum),
style = "bootstrap5",
fillContainer = TRUE,
rownames = FALSE,
options = list(
pageLength = 10,
dom = 'ltp',
autoWidth = TRUE, # Verplicht om handmatige breedtes toe te staan
columnDefs = list(
list(
targets = datum_index, # De index van de datumkolom
width = '150px', # Pas dit getal aan om hem breder of smaller te maken
className = 'dt-left' # Zorgt dat de datum netjes links uitlijnt
)
)
)
) |>
# Formatteer de datumkolom naar Nederlands formaat (DD-MM-YYYY)
formatDate(
columns = "datum",
method = "toLocaleDateString",
params = list(
locales = "nl-NL",
options = list(
year = "numeric",
month = "2-digit",
day = "2-digit",
timeZone = "UTC" # datum is UTC; voorkomt een dag verschuiving in andere tijdzones
)
)
)
})
# Statistiek 1: aantal rijen
output$stat_rijen <- renderText({ nrow(dt()) })
# Statistiek 2: aantal unieke dieren in selectie
output$stat_dieren <- renderText({ n_distinct(dt()$id) })
}We willen het ook mogelijk maken om individuele dieren te selecteren.
dt() filteren we op de gekozen dieren. Zijn er geen dieren gekozen, dan houden we alle dierenserver functie dt() en output$grafiek door de code hierondercode.R
inputs <- list(
selectInput(
inputId = "kenmerk",
label = "Selecteer kenmerk(en)",
choices = km,
selected = km[1],
multiple = TRUE
),
selectInput(
inputId = "dieren",
label = "Selecteer dier(en)",
choices = sort(unique(data$id)),
multiple = TRUE
),
sliderInput(
inputId = 'lacts',
label = "Laktaties:",
min = min(data$lactatie, na.rm = TRUE),
max = max(data$lactatie, na.rm = TRUE),
step = 1,
value = c(1, 3)
),
sliderInput(
inputId = 'dims',
label = "Laktatiedagen:",
min = min(data$dim, na.rm = TRUE),
max = max(data$dim, na.rm = TRUE),
step = 1,
value = c(1, 365)
)
)
dt <- reactive({
req(input$lacts, input$dims, input$kenmerk)
# Dynamisch bepalen welke kolommen we behouden (bkv + geselecteerde kenmerken)
geselecteerde_kolommen <- unique(c(bkv, input$kenmerk))
data |>
filter(
productie > 0,
lactatie >= input$lacts[1],
lactatie <= input$lacts[2],
dim >= input$dims[1],
dim <= input$dims[2],
is.null(input$dieren) | id %in% input$dieren # geen dieren gekozen: alle dieren
) |>
select(all_of(geselecteerde_kolommen))
})
output$grafiek <- renderPlot({
req(dt(), nrow(dt()) > 0)
# 1. Pivot en bereid data voor de grafiek voor
plot_data <- dt() |>
pivot_longer(
cols = all_of(input$kenmerk),
values_to = "waarde",
names_to = "kenmerk"
) |>
filter(!is.na(waarde), waarde > 0)
req(nrow(plot_data) > 0) # geen grafiek als een kenmerk geen waarden heeft
# 2. Bepaal de logica op basis van wel/geen dierselectie
if (is.null(input$dieren)) {
# GEEN DIEREN GESELECTEERD -> Bereken groepsgemiddelde per dag
plot_data <- plot_data |>
group_by(datum, kenmerk) |>
summarise(waarde = mean(waarde, na.rm = TRUE), .groups = "drop")
mapping <- aes(x = datum, y = waarde, color = kenmerk, group = kenmerk)
grafiek_titel <- "Groepsgemiddelde over de tijd"
} else {
# WEL DIEREN GESELECTEERD -> Bereken het gemiddelde PER DIER per dag
plot_data <- plot_data |>
group_by(id, datum, kenmerk) |>
summarise(waarde = mean(waarde, na.rm = TRUE), .groups = "drop")
mapping <- aes(x = datum, y = waarde, color = id, group = id)
grafiek_titel <- "Individueel verloop per geselecteerd dier"
}
# 3. grafiek
ggplot(plot_data, mapping) +
geom_line(linewidth = 0.8, alpha = 0.8) +
geom_point(size = 1.5) +
labs(
title = grafiek_titel,
x = "Datum",
y = "Waarde",
color = if (is.null(input$dieren)) "Kenmerk" else "Dier ID"
) +
# grafieken per kenmerk als er meerdere gekozen zijn
facet_wrap(~kenmerk, scales = "free_y")
})We willen de geselecteerde gegevens ook kunnen downloaden
downloadserver functie moeten de geselecteerde gegevens (dt()) naar de juiste knop worden gestuurdcode.R
inputs <- list(
selectInput(
inputId = "kenmerk",
label = "Selecteer kenmerk(en)",
choices = km,
selected = km[1],
multiple = TRUE
),
selectInput(
inputId = "dieren",
label = "Selecteer dier(en)",
choices = sort(unique(data$id)),
multiple = TRUE
),
sliderInput(
inputId = 'lacts',
label = "Laktaties:",
min = min(data$lactatie, na.rm = TRUE),
max = max(data$lactatie, na.rm = TRUE),
step = 1,
value = c(1, 3)
),
sliderInput(
inputId = 'dims',
label = "Laktatiedagen:",
min = min(data$dim, na.rm = TRUE),
max = max(data$dim, na.rm = TRUE),
step = 1,
value = c(1, 365)
),
hr(),
# Downloadknop
downloadButton(
"download_data",
"Download gefilterde data",
class = "btn-outline-primary btn-sm w-100"
)
)
server = function(input, output, session) {
## .... de code van de vorige stappen
# Download handlerfunctionaliteit
output$download_data <- downloadHandler(
filename = function() {
paste("productiedata-export-", Sys.Date(), ".csv", sep = "")
},
content = function(file) {
write.csv(dt(), file, row.names = FALSE)
}
)
}We willen graag een betere layout.
flatly voor onze appminimal voor de grafiekcode.R
ui = page_navbar(
theme = bs_theme(version = 5, bootswatch = "flatly"),
### ...
)
# in output$grafiek:
ggplot(plot_data, mapping) +
geom_line(linewidth = 0.8, alpha = 0.8) +
geom_point(size = 1.5) +
theme_minimal() +
labs(
title = grafiek_titel,
x = "Datum",
y = "Waarde",
color = if (is.null(input$dieren)) "Kenmerk" else "Dier ID"
) +
# grafieken per kenmerk als er meerdere gekozen zijn
facet_wrap(~kenmerk, scales = "free_y")
# in output$tabel:
output$tabel <- renderDT({
req(dt())
# Zoek de positie van de kolom 'datum' op; DT telt vanaf 0, R vanaf 1
datum_index <- which(colnames(dt()) == "datum") - 1
datatable(
dt() |>
arrange(id, datum),
style = "bootstrap5",
fillContainer = TRUE,
### ...
)
})We geven nu opnieuw de hele app weer:
code.R
library(shiny)
library(bslib)
library(dplyr)
library(tidyr)
library(lubridate)
library(ggplot2)
library(DT)
data <- read.table(
'data/productiedata.dat',
sep = ",",
dec = ".",
header = TRUE,
row.names = 1, ## eerste kolom is een rijnummer
na.strings = "NA"
) |>
rename(vreettijd = Totale.vreettijd.in.minuten) |>
rename_with(tolower) |>
mutate(datum = ymd(datum)) |> ## zorg dat datum goed staat
mutate(id = factor(id)) ## id moet een factor zijn
bkv <- c("datum", "id", "lactatie", "dim", "productie") # lijstje met vaste variabelen
km <- colnames(data)[-c(1:4)] ## de andere variabelen, kenmerken waaruit we kunnen kiezen
inputs <- list(
selectInput(
inputId = "kenmerk",
label = "Selecteer kenmerk(en)",
choices = km,
selected = km[1],
multiple = TRUE
),
selectInput(
inputId = "dieren",
label = "Selecteer dier(en)",
choices = sort(unique(data$id)),
multiple = TRUE
),
sliderInput(
inputId = 'lacts',
label = "Laktaties:",
min = min(data$lactatie, na.rm = TRUE),
max = max(data$lactatie, na.rm = TRUE),
step = 1,
value = c(1, 3)
),
sliderInput(
inputId = 'dims',
label = "Laktatiedagen:",
min = min(data$dim, na.rm = TRUE),
max = max(data$dim, na.rm = TRUE),
step = 1,
value = c(1, 365)
),
hr(),
# Downloadknop
downloadButton(
"download_data",
"Download gefilterde data",
class = "btn-outline-primary btn-sm w-100"
)
)
cards <- list(
# Waarde-boxen voor snelle statistieken bovenaan
layout_column_wrap(
width = 1/2,
value_box(
title = "Geselecteerde Rijen",
value = tags$span(
textOutput("stat_rijen"),
style = "font-size: clamp(1.5rem, 4vw, 2.5rem); font-weight: bold;"
),
showcase = shiny::icon("database"),
theme = "primary"
),
value_box(
title = "Geselecteerde Dieren",
value = tags$span(
textOutput("stat_dieren"),
style = "font-size: clamp(1.5rem, 4vw, 2.5rem); font-weight: bold;"
),
showcase = shiny::icon("cow"),
theme = "teal"
)
),
card(
full_screen = TRUE,
card_header("Kenmerken over de tijd"),
card_body(plotOutput("grafiek"))
),
card(
full_screen = TRUE,
card_header("Tabel van de gegevens"),
card_body(fillable = TRUE, DTOutput("tabel"))
)
)
ui = page_navbar(
theme = bs_theme(version = 5, bootswatch = "flatly"),
title = "Analyse productiedata",
sidebar = sidebar(inputs),
nav_panel(
title = "Dashboard",
!!!cards
)
)
server = function(input, output, session) {
dt <- reactive({
req(input$lacts, input$dims, input$kenmerk)
# Dynamisch bepalen welke kolommen we behouden (bkv + geselecteerde kenmerken)
geselecteerde_kolommen <- unique(c(bkv, input$kenmerk))
data |>
filter(
productie > 0,
lactatie >= input$lacts[1],
lactatie <= input$lacts[2],
dim >= input$dims[1],
dim <= input$dims[2],
is.null(input$dieren) | id %in% input$dieren # geen dieren gekozen: alle dieren
) |>
select(all_of(geselecteerde_kolommen))
})
output$grafiek <- renderPlot({
req(dt(), nrow(dt()) > 0)
# 1. Pivot en bereid data voor de grafiek voor
plot_data <- dt() |>
pivot_longer(
cols = all_of(input$kenmerk),
values_to = "waarde",
names_to = "kenmerk"
) |>
filter(!is.na(waarde), waarde > 0)
req(nrow(plot_data) > 0) # geen grafiek als een kenmerk geen waarden heeft
# 2. Bepaal de logica op basis van wel/geen dierselectie
if (is.null(input$dieren)) {
# GEEN DIEREN GESELECTEERD -> Bereken groepsgemiddelde per dag
plot_data <- plot_data |>
group_by(datum, kenmerk) |>
summarise(waarde = mean(waarde, na.rm = TRUE), .groups = "drop")
mapping <- aes(x = datum, y = waarde, color = kenmerk, group = kenmerk)
grafiek_titel <- "Groepsgemiddelde over de tijd"
} else {
# WEL DIEREN GESELECTEERD -> Bereken het gemiddelde PER DIER per dag
plot_data <- plot_data |>
group_by(id, datum, kenmerk) |>
summarise(waarde = mean(waarde, na.rm = TRUE), .groups = "drop")
mapping <- aes(x = datum, y = waarde, color = id, group = id)
grafiek_titel <- "Individueel verloop per geselecteerd dier"
}
# 3. grafiek
ggplot(plot_data, mapping) +
geom_line(linewidth = 0.8, alpha = 0.8) +
geom_point(size = 1.5) +
theme_minimal() +
labs(
title = grafiek_titel,
x = "Datum",
y = "Waarde",
color = if (is.null(input$dieren)) "Kenmerk" else "Dier ID"
) +
# grafieken per kenmerk als er meerdere gekozen zijn
facet_wrap(~kenmerk, scales = "free_y")
})
# DataTable output genereren binnen de bslib card
output$tabel <- renderDT({
req(dt())
# Zoek de positie van de kolom 'datum' op; DT telt vanaf 0, R vanaf 1
datum_index <- which(colnames(dt()) == "datum") - 1
datatable(
dt() |>
arrange(id, datum),
style = "bootstrap5",
fillContainer = TRUE,
rownames = FALSE,
options = list(
pageLength = 10,
dom = 'ltp',
autoWidth = TRUE, # Verplicht om handmatige breedtes toe te staan
columnDefs = list(
list(
targets = datum_index, # De index van de datumkolom
width = '150px', # Pas dit getal aan om hem breder of smaller te maken
className = 'dt-left' # Zorgt dat de datum netjes links uitlijnt
)
)
)
) |>
# Formatteer de datumkolom naar Nederlands formaat (DD-MM-YYYY)
formatDate(
columns = "datum",
method = "toLocaleDateString",
params = list(
locales = "nl-NL",
options = list(
year = "numeric",
month = "2-digit",
day = "2-digit",
timeZone = "UTC" # datum is UTC; voorkomt een dag verschuiving in andere tijdzones
)
)
)
})
# Statistiek 1: aantal rijen
output$stat_rijen <- renderText({ nrow(dt()) })
# Statistiek 2: aantal unieke dieren in selectie
output$stat_dieren <- renderText({ n_distinct(dt()$id) })
# Download handlerfunctionaliteit
output$download_data <- downloadHandler(
filename = function() {
paste("productiedata-export-", Sys.Date(), ".csv", sep = "")
},
content = function(file) {
write.csv(dt(), file, row.names = FALSE)
}
)
}
shinyApp(ui, server)De app werkt nu, maar alleen lokaal. Je kunt de app ook online publiceren. Dit behandelen we niet in deze cursus. Je hebt de volgende mogelijkheden:
runGitHub("naamvanjerepo", "jegithubnaam"), bijvoorbeeld runGitHub( "costerAnalytics/wsappproductie", "albartcoster") (ik weet niet of dit werkt als je niet ingelogd bent)