Just a quick disclaimer at the top to remind you that I am an entomologist and verifiably NOT a programmer. I cannot claim that this code is the best or the fastest for this application. Please reach out if you have thought/ suggestions for improvement!

After YourTaxa_CDL.csv has been generated, you have all the information you need to build your RShiny App.

Just as in the last tutorial, first we need to install and load the appropriate packages:

#Install/Load packages

#needed for shiny interface
library("shiny")
library("bs4Dash")
library("fresh")

#needed for formatting data

library("lubridate")
library("dplyr")

#needed for visualizing data
library("plotly")
library("leaflet")
library("ggplot2")

Next, we need to extract, manipulate and prepare the data before pushing it to an R Shiny interface. A note: various Sp1/Species1 labels are a really useful, generic reference important for all downstream analysis. I do not recommend changing/customizing these labels until you see the rest of the assignment.

#import your .csv from iNat
Species1 <- read.csv(file.path("data","YourTaxa_CDL.csv"), stringsAsFactors = FALSE) #Edit 'YourTaxa' here!!!

#Pull year and month out of the observed_on column
Sp1year <-lubridate::year(Species1$observed_on) #if observations were pulled from different years
Sp1month <-lubridate::month (Species1$observed_on)

Sp1num_sightings <- nrow(Species1) #we are going to report this value on our RShiny page
Sp1over_years <- names(which.max(table(Sp1year))) #this one too (as an info box); if you are working with only 1 year of data you may choose to customize this info box differently

Sp1number_counties <- ((length(unique(Species1$place_county_name)))/67)*100

#(I manually put in 67, since PA has 67 counties. You CAN automate this using the tigris package or by referencing a local, cross-listed .csv file)

#Sp1number_states <- (length(unique(Species1$place_state_name))) #Run this line instead if data is nationwide and not statewide

Pick your R Shiny Color Theme

You can customize the colors you’d like to use for your website. I referenced color picker to pick custom colors for my interface. Here we are using the bs4Dash framework to set up these themes. bs4Dash colors can be assigned any hex color code you like. If you do not set up custom hex codes bs4Dash_color terms will still work by referencing default hex codes. In my case, yellow, orange and green) have default values but I customized them here. You can type bs4Dash_color into your console to see the default colors offered by bs4Dash.

Note: while the themes can use any colors in the RShiny app, you must define them using existing color definitions in bs4Dash_color. This means you can’t label three shades of green as ‘green1’, ‘green2’ and ‘green3’, or ‘green’ ‘emerald’ and ‘mint’. That will break your code. You must use reference default bs4Dash_color labels to define the specific hex colors of your choosing, whether or not that label accurately reflects the color you choose.

This is really a superficial block of code - so if what you just read is confusing, ignore it, and just run the code as listed below:

plot_colour <- "#B5DB93"

theme <- create_theme(
  bs4dash_color(
    yellow = "#eddb68", #Customize color themes using Color Picker
    orange = "#e39e14",
    green = "#B5DB93"
  ),
  bs4dash_status(
    primary = "#f0eac7",
    info = "#E4E4E4"
  )
)

Designing your App

Alright - now it’s time to buckle in. We have our data, and we’ve successfully transformed it to reveal some (hopefully) meaningful/relevant ecological information. Now we are going to build up the architecture for our RShiny app. For the sake of time, I am not going to build this with you iteratively. Just know that this is something that took me some time to piece together,and there is a steep learning curve associated with learning the platform and its quirks. If you choose to customize your own Shiny app to display different data/trends, I hope that this is a helpful start as a general framework you can reference.

All of this code needs to run in a single block, so I will introduce these sections piece-by-piece as we go through them together.

Specify Interface

First we needs to tell R Studio that we are making a Shiny dashboard and design a header for the page. This is where you can change the title and image displayed at the top left corner of your RShiny page. If you choose the change the image, make sure that the URL you are referencing is via the hosting website and not pulled directly from a Google image search.

ui <- local({
  message("UI: start")
  x <- bs4Dash::dashboardPage(
    title = "iNat Observations",
    
    freshTheme = theme,
    dark = NULL,
    help = NULL,
    fullscreen = TRUE,
    scrollToTop = TRUE,
    
    header = bs4Dash::dashboardHeader(
      status = "yellow",
      title  = bs4Dash::dashboardBrand(
        title = "iNat Observations",
        color = "orange",
        image = "https://images.unsplash.com/photo-1672532324606-896877df8065?q=80&w=1404&auto=format&fit=crop"
      )
    ),
    sidebar = bs4Dash::dashboardSidebar(
      bs4Dash::sidebarMenu(
        id = "sidebarMenuid",
        bs4Dash::menuItem("Home",     tabName = "Home",     icon = shiny::icon("home", verify_fa = FALSE)),
        bs4Dash::menuItem("Species1", tabName = "Species1", icon = shiny::icon("chart-bar", verify_fa = FALSE))
      )
    ),
    controlbar = bs4Dash::dashboardControlbar(),
    footer = bs4Dash::dashboardFooter(left = "Your Name Here", right = "RShiny Demo Fall 2025"),
    body = bs4Dash::dashboardBody(
      bs4Dash::tabItems(

NOTE: Do not run this code yet (you will get an error if you do). Instead, copy/paste and personalize this code in R Studio, and keep reading:

Layout for your home page

This is the splash page for your site. If, in the future, you design a webpage and want to provide some contextual information before you user begins exploring the data, you can add that information here.

 # Home
        bs4Dash::tabItem(
          tabName = "Home",
          bs4Dash::bs4Jumbotron(
            title = "Welcome!", status = "info",
            lead  = "Visualizing iNaturalist Observations Using an RShiny Framework",
            href  = "https://www.inaturalist.org/",
            "Data freely available from iNaturalist.org"
          ),
          shiny::fluidRow(
            bs4Dash::userBox(
              collapsible = FALSE,
              title = bs4Dash::userDescription(
                title = "INSECT NET RShiny Demo",
                subtitle = "CSE-ENT-ME 597",
                image = "https://upload.wikimedia.org/wikipedia/en/thumb/3/3a/Penn_State_Nittany_Lions_logo.svg/800px-Penn_State_Nittany_Lions_logo.svg.png",
                type = 1
              ),
              status = "orange",
              "Visit our website: insectnet.psu.edu"
            ),
            bs4Dash::box(
              title = "Think about these questions as you customize your dashboard:",
              width = 6, collapsible = FALSE,
              bs4Dash::blockQuote(
                "When and where might I be mostly likely to encounter my species of interest? Where might there be biases in my data? What is a new figure I'd like to generate, informed by the data contained herein?",
                color = "purple"
              ) #you might want to customize the above blockquote, or change the purspose of this box altogether.
            )
          )
        ),

NOTE: Do not run this code yet (you will get an error if you do). Instead, copy/paste and personalize this code in R Studio, and keep reading:

Layout for the Species1 tab

Here we are building the UI that will house our data. You will want to customize the text content listed in that final box.

        # Species1
        bs4Dash::tabItem(
          tabName = "Species1",
          
          # Info boxes along top of page
          shiny::fluidRow(
            shiny::column(4, bs4Dash::infoBoxOutput("box_obs", width = 12)),
            shiny::column(4, bs4Dash::infoBoxOutput("box_peak",  width = 12)),
            shiny::column(4, bs4Dash::infoBoxOutput("box_pct",   width = 12))
          ),
          
          # 4 boxes featuring figures, add'l info
          shiny::fluidRow(
            bs4Dash::sortable(
              width = 6,
              bs4Dash::box(
                title = "Observations by Landscape (2024 CDL Data)",
                width = 12, status = "orange", collapsible = FALSE,
                plotlyOutput("Sp1plot_CDL")
              ),
              bs4Dash::tabBox(
                id = "tabcard", title = "Observations Over Time",
                width = 12, status = "orange", solidHeader = TRUE, type = "tabs",
                tabPanel(title = "By Month", plotOutput("Sp1monthlyobs")),
                tabPanel(title = "By Year",  plotOutput("Sp1annualobs"))
              )
            ),
            bs4Dash::sortable(
              width = 6,
              bs4Dash::box(
                title = "Observations by Location",
                width = 12, status = "orange", collapsible = FALSE, maximizable = TRUE,
                leafletOutput("Sp1plot_sightings_by_location")
              ),
              bs4Dash::box(
                title = "Your_Taxa", #Add text here
                width = 12, collapsible = FALSE,
                bs4Dash::blockQuote("Write some information here about Your_Taxa", color = "orange") #And here
              )
            )
          )
        )
      )
    )
  )
  message("UI: built OK")
  x
})

NOTE: Do not run this code yet (you will get an error if you do). Instead, copy/paste and personalize this code in R Studio, and keep reading:

Specify server outputs

Now that we set up the UI we need to tell the server which data we want displayed within each box specified in the UI

server <- function(input, output, session) {

  
  # Info boxes
  output$box_obs <- bs4Dash::renderInfoBox({
    bs4Dash::infoBox(
      title = "Total Observations",
      value = Sp1num_sightings,
      icon  = shiny::icon("list", verify_fa = FALSE),
      color = "primary",
      width = 12
    )
  })
  
  output$box_peak <- bs4Dash::renderInfoBox({
    bs4Dash::infoBox(
      title = "Year with most observations",
      value = Sp1over_years,
      icon  = shiny::icon("bug", verify_fa = FALSE),
      color = "primary",
      width = 12
    )
  })
  
  output$box_pct <- bs4Dash::renderInfoBox({
    bs4Dash::infoBox(
      title = "%age of PA counties with obs",
      #if data is nationwide swap to "No. States with Obs" instead
      value = format(round(Sp1number_counties, 0), nsmall = 0),
      # If nationwide instead, swap to the next line and comment this one:
      # value = format(round(Sp1number_states, 0), nsmall = 0),
      icon  = shiny::icon("location-dot", verify_fa = FALSE),
      color = "primary",
      width = 12
    )
  })
  
  output$Sp1plot_CDL <- renderPlotly({
    
    Species1 %>% 
      ggplot(aes(x = class_name)) + 
      geom_bar(fill = "purple") + 
      labs(
        x = ""
      ) + 
      theme_bw() + 
      coord_flip()
  })
  
  output$Sp1monthlyobs <- renderPlot({hist(Sp1month)}) #Observations by month
  output$Sp1annualobs <- renderPlot({hist(Sp1year)}) #Observations by year
  output$Sp2monthlyobs <- renderPlot({hist(Sp2month)}) #Observations by month
  output$Sp2annualobs <- renderPlot({hist(Sp2year)}) #Observations by year
  
  #Interactive map of observations
  output$Sp1plot_sightings_by_location <- renderLeaflet({
    
    leaflet(data = Species1) %>% 
      addProviderTiles(providers$CartoDB.Positron) %>% 
      addCircleMarkers(
        ~longitude,
        ~latitude,
        radius = 3,
        color = plot_colour,
        fillOpacity = 1,
        popup = ~paste0("Observed on ", observed_on, " in ", Species1$class_name)
      )
  })
}
shinyApp(ui, server)

OK! Now’s the time to view your app - go ahead and click the ‘run app’ button in the top right of the source pane within your RStudio window. Hoepfully this loads. Here you are looking at a local instance of the app. Next we will go over how to publish this publicly via rsconnect - this URL is the one you will submit with your homework assignment.

Pushing your app to shinyapps.io

In your R console, install and load the “rsconnect” package

Go to https://www.shinyapps.io/ and make an account. After you are signed in, toggle to Account -> Tokens

OPTIONAL: This section is superfluous but may serve some of you: Some of you might be wondering why I didn’t use Rinat to have you pull your .csv files. This task is too demanding for the servers to process in real time, but it works really well when launched locally on your machine. If you are not making an app, below is the code you can use to pull down observations directly from iNat. In theory you could write the .csv file from Rinat and plop that into your wd if visiting the iNat website feels like a chore.

#For other applications you CAN query iNat observations directly via R using the code below
 
  #library(rinat)
  #Species1 <- get_inat_obs(taxon_name = "TAXA_NAME", place_id = 42, quality = "research", maxresults = 10000)

#-----Choose whatever taxa you are interested in under taxon_name
#-----May need to confirm that your taxa name is searchable on iNat
#-----For this assignment - try to limit total # observations between 200 and 10,000
#-----Filter quality to research grade
#-----max results upper limit is 10,000 and will default to 100 if not defined

#confirm the number of observations pulled from iNaturalist
#nrow(Species1)

#preview distribution of observations on a map
#Sp1_map<- inat_map(Species1, plot = TRUE)

#_____________________