Climate Spirals Animation

Last week we visualized the temperature anomalies during 1880-2022 as deviations from the benchmark period 1951-1990. We used interactive climate spirals following Code Club (episode 223) by the Riffomonas Project YouTube Channel and Pat Schloss (highly recommended!), who in turn built from climatologist Ed Hawkins from the University of Reading.

This week we will work on animating the previous visuals to create a dynamic chart showing the changes over time. A complete dive into animations will take more than one session. Therefore, in this session we will focus on animations with plotly and we will leave time for a discussion using animations with gganimate or even other packages in Phython recommended by Prof. Bill Mongan. If you’re interested in our previous IDSIRI meetings, here are the links: Week2, Week 3.

IDSIRI Set Up:

This time, we will work in groups, and each group station will project in their own monitor. To do this, you need to connect to the monitor using Solstice (Mersive). Here are the instructions. The direct link to donwload Mersive is here. /

Anyone can connect to the monitors, but you need to have Solstice (Mersive) installed. If you install it, you can connect and project your screen in your station. For this week (first one we are doing this), however, it is more likely that Ursinus professors that usually attend (e.g., Andrea Kauffman-Berry, Leslie New, Olga Nicoara, Bill Mongan, Hugo Montesinos, Eric Takyi, and Chris Tralie) already have this program downloaded. Therefore, they can be natural leaders of the group.

Install and load key R packages

Install and load the the following R packages: tidyverse, plotly, readr, glue, and htmlwidgets.

Load climate data from NASA

As last week, data come from NASA: https://data.giss.nasa.gov/gistemp/. We want to read the data from the section: Tables of Global and Hemispheric Monthly Means and Zonal Annual Means, subsection: Combined Land-Surface Air and Sea-Surface Water Temperature Anomalies (Land-Ocean Temperature Index, L-OTI). Specific data: Global-mean monthly, seasonal, and annual means, 1880-present, updated through most recent month: TXT, CSV.
If you right-click on the CSV file, you can copy the link which is the URL we will use. Then do the following:

  1. Create a variable named url containing the link to the data within double quotes.
  2. Read the data into R using the read.csv function. Hint: You need to skip one row.
  3. View the data and make it a tibble
#Get latest data from NASA
url <- "https://data.giss.nasa.gov/gistemp/tabledata_v4/GLB.Ts+dSST.csv"
data<-read.csv(url, skip=1, na="***") 
head(data)
##   Year   Jan   Feb   Mar   Apr   May   Jun   Jul   Aug   Sep   Oct   Nov   Dec
## 1 1880 -0.20 -0.25 -0.09 -0.16 -0.09 -0.22 -0.19 -0.09 -0.15 -0.22 -0.21 -0.18
## 2 1881 -0.19 -0.15  0.03  0.04  0.07 -0.18  0.01 -0.04 -0.16 -0.22 -0.18 -0.07
## 3 1882  0.16  0.14  0.05 -0.15 -0.13 -0.22 -0.16 -0.07 -0.14 -0.23 -0.16 -0.36
## 4 1883 -0.29 -0.37 -0.12 -0.18 -0.18 -0.07 -0.07 -0.14 -0.22 -0.11 -0.24 -0.11
## 5 1884 -0.13 -0.08 -0.36 -0.40 -0.34 -0.35 -0.31 -0.28 -0.27 -0.25 -0.33 -0.31
## 6 1885 -0.58 -0.33 -0.26 -0.42 -0.45 -0.43 -0.33 -0.31 -0.29 -0.24 -0.24 -0.10
##     J.D   D.N   DJF   MAM   JJA   SON
## 1 -0.17    NA    NA -0.11 -0.17 -0.19
## 2 -0.09 -0.10 -0.17  0.05 -0.07 -0.18
## 3 -0.11 -0.08  0.08 -0.08 -0.15 -0.18
## 4 -0.18 -0.20 -0.34 -0.16 -0.09 -0.19
## 5 -0.28 -0.27 -0.11 -0.37 -0.31 -0.28
## 6 -0.33 -0.35 -0.41 -0.38 -0.36 -0.25
data<-tibble(data)

Convert the data from wide to long (tidy).

The data are in wide format (each year is a column). We want to make it into long format, where year is a single-column variable containing all the years from 1880 to the present. Do the following:

  1. Select the columns corresponding to the 12 months (i.e. omit aggregates such as J.D D.N DJF MAM JJA SON).
  2. Reshape the data to long format. Assign the values to the new variable t_diff.You can give your variable any name, but these data show temperature differences (or deviations) with respect to the benchmark years 1951-1980.
  3. Practice back and forth reshaping your data from wide to long and viceversa.
# Convert the data from wide to long (tidy).
data_wide<-data %>%  
  select(year = Year, all_of(month.abb))

data_long<- data_wide %>%  
  pivot_longer(-year, 
               names_to = "month", 
               values_to = "t_diff") %>% 
  drop_na() %>%  
  mutate(month = factor(month, levels=month.abb)) %>%  
  arrange(year, month)

data_long
## # A tibble: 1,712 × 3
##     year month t_diff
##    <int> <fct>  <dbl>
##  1  1880 Jan    -0.2 
##  2  1880 Feb    -0.25
##  3  1880 Mar    -0.09
##  4  1880 Apr    -0.16
##  5  1880 May    -0.09
##  6  1880 Jun    -0.22
##  7  1880 Jul    -0.19
##  8  1880 Aug    -0.09
##  9  1880 Sep    -0.15
## 10  1880 Oct    -0.22
## # … with 1,702 more rows

Transform data to Polar Coordinates.

To visualize the data in a circular fashion (necessary for the climate spiral) we need to use polar coordinates:
1. Convert the data to Polar Coordinates. 2. Inspect the data.

# Transform data to Polar Coordinates.
t_data <- data_long %>%  
  mutate(month_number = as.numeric(month), 
         year_cont = year + month_number/12,
         Date = make_date(year = year, month = month),
         radius = t_diff + 1.5,     #We add an arbitrary constant to avoid negative radius
         theta = 2*pi*(month_number - 1)/12,
         x = radius * cos(theta),
         y = radius * sin(theta),
         z = year, 
         label = glue("{month} {year}\n {t_diff} \u00B0C"))
head(t_data)
## # A tibble: 6 × 12
##    year month t_diff month_number year_cont Date       radius theta         x
##   <int> <fct>  <dbl>        <dbl>     <dbl> <date>      <dbl> <dbl>     <dbl>
## 1  1880 Jan    -0.2             1     1880. 1880-01-01   1.3  0      1.3 e+ 0
## 2  1880 Feb    -0.25            2     1880. 1880-02-01   1.25 0.524  1.08e+ 0
## 3  1880 Mar    -0.09            3     1880. 1880-03-01   1.41 1.05   7.05e- 1
## 4  1880 Apr    -0.16            4     1880. 1880-04-01   1.34 1.57   8.20e-17
## 5  1880 May    -0.09            5     1880. 1880-05-01   1.41 2.09  -7.05e- 1
## 6  1880 Jun    -0.22            6     1880. 1880-06-01   1.28 2.62  -1.11e+ 0
## # … with 3 more variables: y <dbl>, z <int>, label <glue>

Last week we did the transformation explicitly. The data came naturally in polar coordinates (the month being the angle, and the temperature deviation being the radius), so we needed to tell R the corresponding Cartesian coordinates (x, y, z), manually. Alternatively, we could have instructed R to do the graph in polar coordinates automatically.

Before we animate the climate spiral from last week, let’s animate the simple line chart using plotly, to understand how the syntax works.

Simple visualization using Plot_ly

First, produce a simple (not animated) visual of the data using Plot_ly

# Simple visualization with animation
t_data %>%  plot_ly(x = ~year_cont, y = ~t_diff,text = ~label,
                    hoverinfo=~'text', 
                    type='scatter', mode='lines', name="ΔT (°C)") -> fig1

fig1 <- fig1 %>%  add_lines(y = ~0.5, name = '+0.5 °C', 
                            line=list(color='orange',width=1, dash='dot'))
fig1 <- fig1 %>%  add_lines(y = ~1, name = '+1 °C', 
                            line=list(color='red',width=1, dash='dot')) 
fig1 <- fig1 %>%  add_lines(y = ~-0.5, name = '-0.5 °C', 
                            line=list(color='blue',width=1, dash='dot')) 

fig1 <- fig1 %>% layout(xaxis = list(title = "Year"), 
         yaxis = list(title = "Temperature Deviation (°C)"),
         title = "Global Temperature Deviations Relative to 1951-1980 \n")

fig1

Animate the previous visualization

Then, we can animate the previous plot_ly figure simply with add_markers(frame = ~year)

# Simple visualization with animation

fig1 %>%  add_markers(frame = ~year)

Adding animation options

There are a few options we can control with the animation, such as the speed and type of the transitions. Let’s explore a few of them together.

See example at: https://plotly.com/r/animations/ Documentation: https://rdrr.io/cran/plotly/man/animation.html

# Simple visualization with animation

fig1 %>%  add_markers(frame = ~year_cont) %>%  animation_opts(frame=100, transition=100, redraw = FALSE)
#The frame and transition argument options receive time in milliseconds. So, 1000 would be 1 second. The smaller we make them, the faster the animation should go. 

Now, let’s animate the climate spiral interactively using plot_ly:

Interactive Climate Spiral (Version 1.0)

Option 1 (Station 1?): Making the the spiral year over year.

Let’s see what happens if we make year as a direct argument of the function:
Let’s display this in one of the stations and discuss it.

# Interactive Climate Spiral (Version 1.0)
fig2<-plot_ly(t_data, 
        x = ~x, y = ~y, z = ~z, frame = ~year,
        type = 'scatter3d',
        mode = 'lines',
        line = list(width = 4, color = ~t_diff, 
                    cmid = 0, 
                    colorscale = list(c(0,'blue'),
                                    c(0.5,'white'),
                                    c(1,'red'))))

fig2
#saveWidget(fig2, "climate_spiral_plotly1.html")

#fig2 %>%  add_markers(frame = ~year, mode='lines+markers') %>%   animation_opts(frame=100, transition=100, redraw = TRUE)

Option 2 (Station 2?): Make the spiral accumulate over the years

Here, let’s do the same as above but trying to make it cumulative frame by frame. This is inefficient done this way, but it’s good to practice.

accumulate_by <- function(dat, var) {
  var <- lazyeval::f_eval(var, dat)
  lvls <- plotly:::getLevels(var)
  dats <- lapply(seq_along(lvls), function(x) {
    cbind(dat[var %in% lvls[seq(1, x)], ], frame = lvls[[x]])
  })
  dplyr::bind_rows(dats)
}

fig3 <- t_data %>%  accumulate_by(~year)

fig3<-fig3 %>%  plot_ly(x = ~x, y = ~y, z = ~z, frame = ~frame,
        type = 'scatter3d',
        mode = 'lines',
        line = list(simplify=F, width = 4, color = ~t_diff, 
                    cmid = 0, 
                    colorscale = list(c(0,'blue'),
                                    c(0.5,'white'),
                                    c(1,'red'))))

fig3
#saveWidget(fig2, "climate_spiral_plotly1.html")

#fig2 %>%  add_markers(frame = ~year, mode='lines') %>%   animation_opts(frame=1000, transition=1000, redraw = TRUE)

Option 3 (Station 3?): Making the the spiral and add the animation on top.

knitr::opts_chunk$set(warning = FALSE, message = FALSE) 

# Interactive Climate Spiral (Version 1.0)
fig2<-plot_ly(t_data, 
        x = ~x, y = ~y, z = ~z, #frame = ~year,
        type = 'scatter3d',
        mode = 'lines',
        line = list(width = 4, color = ~t_diff, 
                    cmid = 0, 
                    colorscale = list(c(0,'blue'),
                                    c(0.5,'white'),
                                    c(1,'red'))))

#fig2
#saveWidget(fig2, "climate_spiral_plotly1.html")

fig2 %>%  add_markers(frame = ~year, mode='lines+markers') %>%   animation_opts(frame=100, transition=100, redraw = TRUE)

Option 4 (Station 4?): Refine the axis and remove gridlines

  1. Add a hover text with labels *Month, Year, [Temperature difference value] °C.
  2. Remove gridlinesand axis titles for x and y.
# Interactive Climate Spiral (Version 2.0)
axx <- list(title = "", showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE)
axy <- list(title = "", showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE)
axz <- list(title = "")

fig3<-plot_ly(t_data, 
        x = ~x, y = ~y, z = ~z, text=~label,
        hoverinfo = "text",
        type = 'scatter3d',
        mode = 'lines',
        line = list(width = 4, color = ~t_diff, 
                    cmid = 0, 
                    colorscale = list(c(0,'blue'),
                                    c(0.5,'white'),
                                    c(1,'red')))) 

fig3<- fig3 %>%  layout(scene = list(xaxis=axx,yaxis=axy,zaxis=axz))
fig3 %>%  add_trace( frame = ~year, mode='lines+markers', name="Current Year")
saveWidget(fig3, "climate_spiral_plotly2.html")

Show the same data using base R

# Non Interactive Data using Base R (Version 2.0)
t_data <- t_data %>%  mutate(year2=year+month_number/12)
p2<-plot(t_data$t_diff~t_data$year2, type="l", col="blue", 
     xlab="Year", ylab="Temperature Deviation (°C)", 
     main = "Global Temperature Anomalies Relative to 1951-1980")
abline(h=-0.5, lty=2); abline(h=+0.5, lty=2); abline(h=+1, lty=2, col="red")

p2
## NULL

Next steps (Incomplete):

For us to complete together!

  1. Animating Pat Schloss Viz using plot_ly instead of gganimate. Follow Github code from Riffomonas Project. Link: /

This figure was not made using plot_ly but rather ggplot. The option of using ggplotly to convert ggplot figures into plotly does not work well all the time, especially in rich figures like this one.

  1. Recreate Ed Hawkins figure from scracth using plot_ly. Since this is a visual with many details, it is convenient to get familirized with the ample documentation with plot_ly. In particular, we may need the scatterpolar mode. Documentation Link:. /

The code could look like this (Station 5?):

#recreate the figure from scracth using plot_ly
#Create figure: 
fig5 <- plot_ly(t_data, 
        theta = ~120+360*((-month_number-1)/12), r = ~t_diff+1.5, text=~label,
        #color=~t_diff,
        hoverinfo = "text",
        type = 'scatterpolar',
        mode = 'lines+markers', opacity=0.9, 
        line=list( width=1), 
        #color=list(color=~t_diff, cmin="blue", cmid="white", cmax="red"),
        marker=list(size=2)) 

#        line = list(width = 4, color = ~t_diff, 
#                    cmid = 0, 
#                    colorscale = list(c(0,'blue'),
#                                    c(0.5,'white'),
#                                    c(1,'red')))) 

fig5
fig5 %>%  add_trace( frame = ~year, mode='lines+markers') %>%  animation_opts(frame = 100, transition = 5, redraw = TRUE)

If anyone completes this figure nicely using plot_ly, please share it with me and everyone else here.

Again, the trend we see here is alarming, especially if we use these data alone to forecast future global temperatures (say in the next 100 years). Doing so, however, would be a mistake. Why? We need to understand the fundamental drivers and mechanisms of the global temperature rise and these data don’t tell us that. Is it man-made or is it not? And to what degree? In addition, it is convenient to see the temperature anomalies on a larger time frame (say, thousands of years). While the data are less precise the further back we go in time, there are some projects attempting to measure that accurately. For example, see this amazing 800 Thousand Years Graph from the European Project for Ice Coring in Antartica (EPICA): Read more about it at Wikipedia. Also, see this another wonderful graph showing Multiple Models spanning up to 500 Years. Read more about it in Wikipedia