Visualize climate anomalies
When we visualize precipitation and temperature anomalies, we simply use time series as bar graph indicating negative and positive values in red and blue. However, in order to have a better overview we need both anomalies in a single graph. In this way we could more easly answer the question of whether a particular season or month was dry-warm or wet-cold, and even compare these anomalies in the context of previous years.
Packages
In this post we will use the following packages:
Package | Description |
---|---|
tidyverse | Collection of packages (visualization, manipulation): ggplot2, dplyr, purrr, etc. |
lubridate | Easy manipulation of dates and times |
ggrepel | Repel overlapping text labels in ggplot2 |
#we install the packages if necessary
if(!require("tidyverse")) install.packages("tidyverse")
if(!require("ggrepel")) install.packages("ggrepel")
if(!require("lubridate")) install.packages("lubridate")
#packages
library(tidyverse)
library(lubridate)
library(ggrepel)
Preparing the data
First we import the daily precipitation and temperature data from the selected weather station (download). We will use the data from Tenerife South (Spain) [1981-2020] accessible through Open Data AEMET. In R there is a package called meteoland
that facilitates the download with specific functions to access data from AEMET (Spanish State Meteorological Agency), Meteogalicia (Galician Meteorological Service) and Meteocat (Catalan Meteorological Service).
Step 1: import the data
We import the data in csv format, the first column is the date, the second column the precipitation (pr) and the last column the average daily temperature (ta).
data <- read_csv("meteo_tenerife.csv")
## Rows: 14303 Columns: 3
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## dbl (2): pr, ta
## date (1): date
##
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
data
## # A tibble: 14,303 x 3
## date pr ta
## <date> <dbl> <dbl>
## 1 1981-01-02 0 17.6
## 2 1981-01-03 0 16.8
## 3 1981-01-04 0 17.4
## 4 1981-01-05 0 17.6
## 5 1981-01-06 0 17
## 6 1981-01-07 0 17.6
## 7 1981-01-08 0 18.6
## 8 1981-01-09 0 19.8
## 9 1981-01-10 0 21.5
## 10 1981-01-11 3.8 17.6
## # ... with 14,293 more rows
Step 2: preparing the data
In the second step we prepare the data to calculate the anomalies. To do this, we create three new columns: the month, the year, and the season of the year. Since our objective is to analyse winter anomalies, we cannot use the calendar year, because winter includes the month of December of one year and the months of January and February of the following. The custom function meteo_yr()
extracts the year from a date indicating the starting month. The concept is similar to the hydrological year in which it starts on October 1.
meteo_yr <- function(dates, start_month = NULL) {
# convert to POSIXlt
dates.posix <- as.POSIXlt(dates)
# year offset
offset <- ifelse(dates.posix$mon >= start_month - 1, 1, 0)
# new year
adj.year = dates.posix$year + 1900 + offset
return(adj.year)
}
We will use many functions of the package collection tidyverse
(https://www.tidyverse.org/). The mutate()
function helps to add new columns or change existing ones. To define the seasons, we use the case_when()
function from the dplyr
package, which has many advantages compared to a chain of ifelse()
. In case_when()
we use two-side formulas, on the one hand the condition and on the other the action when that condition is met. A two-sided formula in R consists of the operator ~
. The binary operator %in%
allows us to filter several values in a greater set.
data <- mutate(data,
winter_yr = meteo_yr(date, 12),
month = month(date),
season = case_when(month %in% c(12,1:2) ~ "Winter",
month %in% 3:5 ~ "Spring",
month %in% 6:8 ~ "Summer",
month %in% 9:11 ~ "Autum"))
data
## # A tibble: 14,303 x 6
## date pr ta winter_yr month season
## <date> <dbl> <dbl> <dbl> <dbl> <chr>
## 1 1981-01-02 0 17.6 1981 1 Winter
## 2 1981-01-03 0 16.8 1981 1 Winter
## 3 1981-01-04 0 17.4 1981 1 Winter
## 4 1981-01-05 0 17.6 1981 1 Winter
## 5 1981-01-06 0 17 1981 1 Winter
## 6 1981-01-07 0 17.6 1981 1 Winter
## 7 1981-01-08 0 18.6 1981 1 Winter
## 8 1981-01-09 0 19.8 1981 1 Winter
## 9 1981-01-10 0 21.5 1981 1 Winter
## 10 1981-01-11 3.8 17.6 1981 1 Winter
## # ... with 14,293 more rows
Step 3: estimate winter anomalies
In the next step we create a subset of the winter months. Then we group by the defined meteorological year and calculate the sum and average for precipitation and temperature, respectively. To facilitate the work, the magrittr
package introduces the operator called pipe in the form %>%
with the aim of combining several functions without the need to assign the result to a new object. The pipe operator passes the output of a function applied to the first argument of the next function. This way of combining functions allows you to chain several steps simultaneously. The %>%
must be understood and pronounced as then.
data_inv <- filter(data,
season == "Winter") %>%
group_by(winter_yr) %>%
summarise(pr = sum(pr, na.rm = TRUE),
ta = mean(ta, na.rm = TRUE))
Now we only have to calculate the anomalies of precipitation and temperature. The columns pr_mean
and ta_mean
will contain the climate average, the reference for the anomalies with respect to the normal period 1981-2010. Therefore, we need to filter the values to the period before 2010, which we will do in the usual way of filtering vectors in R. Once we have the references we estimate the anomalies pr_anom
and ta_anom
. To facilitate the interpretation, in the case of precipitation we express the anomalies as percentage, with the average set at 0% instead of 100%.
In addition, we add three required columns with information for the creation of the graph: 1) labyr
contains the year of each anomaly as long as it has been greater/less than -+10% or -+0.5ºC, respectively (this is for reducing the number of labels), 2) symb_point
is a dummy variable in order to be able to create different symbols between the cases of (1), and 3) lab_font
for highlighting in bold the year 2020.
data_inv <- mutate(data_inv, pr_mean = mean(pr[winter_yr <= 2010]),
ta_mean = mean(ta[winter_yr <= 2010]),
pr_anom = (pr*100/pr_mean)-100,
ta_anom = ta-ta_mean,
labyr = case_when(pr_anom < -10 & ta_anom < -.5 ~ winter_yr,
pr_anom < -10 & ta_anom > .5 ~ winter_yr,
pr_anom > 10 & ta_anom < -.5 ~ winter_yr,
pr_anom > 10 & ta_anom > .5 ~ winter_yr),
symb_point = ifelse(!is.na(labyr), "yes", "no"),
lab_font = ifelse(labyr == 2020, "bold", "plain")
)
Creating the graph
We will build the chart adding layer by layer the distinctive elements: 1) the background with the different grids (Dry-Warm, Dry-Cold, etc.), 2) the points and labels, and 3) the style adjustments.
Part 1
The idea is that the points with dry-warm anomalies are located in quadrant I (top-right) and those with wet-cold in quadrant III (bottom-left). Therefore, we must invert the sign in the precipitation anomalies. Then we create a data.frame
with the label positions of the four quadrants. For the positions in x and y Inf
and -Inf
are used, which is equivalent to the maximum panel sides with respect to the data. However, it is necessary to adjust the position towards the extreme points within the panel with the known arguments of ggplot2
: hjust and vjust.
data_inv_p <- mutate(data_inv, pr_anom = pr_anom * -1)
bglab <- data.frame(x = c(-Inf, Inf, -Inf, Inf),
y = c(Inf, Inf, -Inf, -Inf),
hjust = c(1, 1, 0, 0),
vjust = c(1, 0, 1, 0),
lab = c("Wet-Warm", "Dry-Warm",
"Wet-Cold", "Dry-Cold"))
bglab
## x y hjust vjust lab
## 1 -Inf Inf 1 1 Wet-Warm
## 2 Inf Inf 1 0 Dry-Warm
## 3 -Inf -Inf 0 1 Wet-Cold
## 4 Inf -Inf 0 0 Dry-Cold
Part 2
In the second part we can start building the chart by adding all graphical elements. First we create the background with different colors of each quadrant. The function annotate()
allows adding geometry layers without the use of variables within data.frames
. With the geom_hline()
and geom_vline()
function we mark the quadrants horizontally and vertically using a dashed line. Finally, we draw the labels of each quadrant, using the function geom_text()
. When we use other data sources than the main one used in ggplot()
, we must indicate it with the argument data
in the corresponding geometry function.
g1 <- ggplot(data_inv_p,
aes(pr_anom, ta_anom)) +
annotate("rect", xmin = -Inf, xmax = 0, ymin = 0, ymax = Inf, fill = "#fc9272", alpha = .6) + #wet-warm
annotate("rect", xmin = 0, xmax = Inf, ymin = 0, ymax = Inf, fill = "#cb181d", alpha = .6) + #dry-warm
annotate("rect", xmin = -Inf, xmax = 0, ymin = -Inf, ymax = 0, fill = "#2171b5", alpha = .6) + #wet-cold
annotate("rect", xmin = 0, xmax = Inf, ymin = -Inf, ymax = 0, fill = "#c6dbef", alpha = .6) + #dry-cold
geom_hline(yintercept = 0,
linetype = "dashed") +
geom_vline(xintercept = 0,
linetype = "dashed") +
geom_text(data = bglab,
aes(x, y, label = lab, hjust = hjust, vjust = vjust),
fontface = "italic", size = 5,
angle = 90, colour = "white")
g1
Part 3
In the third part we simply add the points of the anomalies and the labels of the years. The geom_text_repel()
function is similar to the one known by default in ggplot2
, geom_text()
, but it repels overlapping text labels away from each other.
g2 <- g1 + geom_point(aes(fill = symb_point, colour = symb_point),
size = 2.8, shape = 21, show.legend = FALSE) +
geom_text_repel(aes(label = labyr, fontface = lab_font),
max.iter = 5000,
size = 3.5)
g2
## Warning: Removed 25 rows containing missing values (geom_text_repel).
Part 4
In the last part we adjust, in addition to the general style, the axes, the color type and the (sub)title. Remember that we changed the sign on precipitation anomalies. Hence, we must use the arguments breaks
and labels
in the function scale_x_continouous()
to reverse the sign in the labels corresponding to the breaks.
g3 <- g2 + scale_x_continuous("Precipitation anomaly in %",
breaks = seq(-100, 250, 10) * -1,
labels = seq(-100, 250, 10),
limits = c(min(data_inv_p$pr_anom), 100)) +
scale_y_continuous("Mean temperature anomaly in ºC",
breaks = seq(-2, 2, 0.5)) +
scale_fill_manual(values = c("black", "white")) +
scale_colour_manual(values = rev(c("black", "white"))) +
labs(title = "Winter anomalies in Tenerife South",
caption = "Data: AEMET\nNormal period 1981-2010") +
theme_bw()
g3
## Warning: Removed 25 rows containing missing values (geom_text_repel).