Plotting Fluorescent Spectra

Comparing excitation and emission spectra.

ggplot2
R
coding
fluoresence
Author

Brady Johnston

Published

September 3, 2022

How do we go about plotting some spectral data from fluorophores, to get something like below?

Getting the Data

I just downloaded as a .csv from FPBase for the fluorophores of interest.

library(here)
library(tidyverse)
dat <- readr::read_csv(
  file =  here("posts/2022-09-03-plotting-fluorescence/spectra.csv"), 
  col_types = readr::cols()
  )


dat <- dat |> 
  janitor::clean_names() |> 
  mutate(
    across(
      -wavelength, 
      ~if_else(is.na(.x), 0, .x)
    )
  )

dat
# A tibble: 473 × 8
   wavelength egfp_em egfp_ex m_turquoise2_em m_turquo…¹ m_tur…² m_sca…³ m_sca…⁴
        <dbl>   <dbl>   <dbl>           <dbl>      <dbl>   <dbl>   <dbl>   <dbl>
 1        300       0  0.0962               0      0.248   0.248       0       0
 2        301       0  0.0872               0      0.227   0.227       0       0
 3        302       0  0.0801               0      0.205   0.205       0       0
 4        303       0  0.0739               0      0.185   0.185       0       0
 5        304       0  0.0675               0      0.163   0.163       0       0
 6        305       0  0.0612               0      0.148   0.148       0       0
 7        306       0  0.0579               0      0.133   0.133       0       0
 8        307       0  0.0541               0      0.124   0.124       0       0
 9        308       0  0.0528               0      0.113   0.113       0       0
10        309       0  0.049                0      0.107   0.107       0       0
# … with 463 more rows, and abbreviated variable names ¹​m_turquoise2_ab,
#   ²​m_turquoise2_ex, ³​m_scarlet_em, ⁴​m_scarlet_ex

Making a Test Plot

dat |> 
  pivot_longer(-wavelength) |> 
  ggplot(aes(wavelength, value, fill = name, colour = name)) + 
  geom_area(alpha = 0.3, position = "identity") + 
  theme_classic()

This is looking pretty good, but we want to try and get the line types distinguished by whether they are excitation or emission, and colour them based on just their fluorophore and not their fluorophore and their excitation / emission.

dat |> 
  pivot_longer(-wavelength) |> 
  mutate(
    type = if_else(str_detect(name, "em$"), "emission", "excitation"), 
    fluorophore = str_remove(name, "\\_(ex|em)$")
  )
# A tibble: 3,311 × 5
   wavelength name             value type       fluorophore    
        <dbl> <chr>            <dbl> <chr>      <chr>          
 1        300 egfp_em         0      emission   egfp           
 2        300 egfp_ex         0.0962 excitation egfp           
 3        300 m_turquoise2_em 0      emission   m_turquoise2   
 4        300 m_turquoise2_ab 0.248  excitation m_turquoise2_ab
 5        300 m_turquoise2_ex 0.248  excitation m_turquoise2   
 6        300 m_scarlet_em    0      emission   m_scarlet      
 7        300 m_scarlet_ex    0      excitation m_scarlet      
 8        301 egfp_em         0      emission   egfp           
 9        301 egfp_ex         0.0872 excitation egfp           
10        301 m_turquoise2_em 0      emission   m_turquoise2   
# … with 3,301 more rows

This is working, but I have realised that we also have some absorbance readings in there, which isn’t what we are after.

dat_long <- dat |> 
  pivot_longer(-wavelength) |> 
  mutate(
    type = if_else(str_detect(name, "em$"), "Emission", "Excitation"), 
    fluorophore = str_remove(name, "\\_(ex|em)$"), 
    type = factor(type, levels = c("Excitation", "Emission"))
  ) |> 
  filter(
    str_detect(name, "ab$", negate = TRUE)
  )

dat_long
# A tibble: 2,838 × 5
   wavelength name             value type       fluorophore 
        <dbl> <chr>            <dbl> <fct>      <chr>       
 1        300 egfp_em         0      Emission   egfp        
 2        300 egfp_ex         0.0962 Excitation egfp        
 3        300 m_turquoise2_em 0      Emission   m_turquoise2
 4        300 m_turquoise2_ex 0.248  Excitation m_turquoise2
 5        300 m_scarlet_em    0      Emission   m_scarlet   
 6        300 m_scarlet_ex    0      Excitation m_scarlet   
 7        301 egfp_em         0      Emission   egfp        
 8        301 egfp_ex         0.0872 Excitation egfp        
 9        301 m_turquoise2_em 0      Emission   m_turquoise2
10        301 m_turquoise2_ex 0.227  Excitation m_turquoise2
# … with 2,828 more rows

Plotting Again

plt <- dat_long |> 
  ggplot(aes(
    x = wavelength, 
    y = value, 
    fill = fluorophore, 
    group = name
    )) + 
  geom_area(
    aes(linetype = type),
    position = "identity", 
    alpha = 0.3, 
    colour = alpha("black", 0.7)
  ) + 
  scale_fill_manual(
    values = c(
      "egfp" = "green", 
      "m_turquoise2" = "skyblue", 
      "m_scarlet" = "tomato"
    )
  ) +
  scale_linetype_manual(
    values = c(
      "Excitation" = "dotted", 
      "Emission" = "solid"
    )
  ) + 
  theme_classic() 

plt

Let’s pretty it up a bit more.

plt <- plt + 
  labs(
    x = "Wavelength (nm)", 
    fill = "Fluorphore", 
    linetype = ""
  ) + 
  theme(
    legend.position = "top", 
    axis.title.y = element_blank()
  )

plt

An Interactive Version?

With {plotly} we can quickly create an interactive web version from our hard work creating the {ggplot2} object.

plotly::ggplotly(plt)