Buoy Sea Surface Temperature Anomalies

Andrew Edwards

Last rendered on 21 August, 2026

Daily average sea surface temperature from buoys – climatologies and anomalies

library(pacea)()
#> ℹ Loading pacea
library(dplyr)
library(tibble)  # Else prints all of a tibble
library(ggplot2)

In pacea we include daily average sea surface temperature (SST) that we have calculated from two-minute measurements from 19 buoys in Canadian Pacific waters, yielding over 200,000 daily means. Data are from Environment and Climate Change Canada, and Fisheries and Oceans Canada. The earliest data are from September 1987, and 18 buoys were still providing data as of August 2026.

See the buoys_vignette and the help file ?buoy_sst for background concerning the calculated daily mean sea surface temperatures and associated simple plotting functions in pacea. Here we focus on constructing climatologies and subsequent anomalies, to plot in a standardised way.

The locations of the buoys are:

If you want to quickly get a sense of current temperatures (in the wider northeast Pacific also), check out Andrea Hilborn et al.’s Pacific_SST_Monitoring website page. The pacea package is more suited for doing more speficic plots and analyses (because the data are easily accessible).

Calculations and plotting

Default plot

We can easily calculate a climatology (by month) and subsequent anomalies for each buoy, and plot results:

all_buoys_anomalies <- calculate_anomalies(buoy_sst)

plot(all_buoys_anomalies)

The plot’s title describes that this plot is for the anomalies in April (the default) for all buoys, and the climatology was 1991-2020 where available. Buoys are arranged in a roughly north-to-south order (see map). So each square gives the SST anomaly for that buoy for April of that year. Empty (white) squares indicate not enough data available for that particular calculation.

The figure clearly shows that April 2015 and April 2016 were warmer than usual for April, for every buoy (with those years having the warmest two Aprils for almost every buoy). The bottom three buoys clearly have limited data.

Absolute SST’s

To show the actual monthly average SST’s (rather than anomalies), again for the default month of April:

plot(all_buoys_anomalies,
     sst_plot = "mean")

Here you can roughly see the water getting warmer from north to south, noting that the three “NOMAD” buoys are much further offshore than the others, and look slightly cooler.

Full results for a specific buoy

If you specify a buoy, you get the monthly anomalies across the years (again with an automated title), with months running from top to bottom:

plot(all_buoys_anomalies,
     stn_id = "Halibut Bank")   # In the Strait of Georgia

You can check that the April 2016 high anomaly matches that shown in the first anomaly plot shown above.

Anomalies for all buoys for different months

You can specify a single month (the default being “4” for April):

plot(all_buoys_anomalies,
     months = 12)

Interestingly, in 2023 the December anomalies are all positive, whereas in the first anomaly plot above the April anomalies are all negative (for the available buoys in each case). Discuss. (We may use this example in future vignettes for future plots; stay tuned).

If you specify multiple consecutive months (e.g. 4:6), then the plot shows the anomalies averaged over those months (and the title is automatically correct):

plot(all_buoys_anomalies,
     months = 4:6)

The eagle-eyed amongst you will have spotted that the S. Georgia Strait buoy does not appear on this plot (it was second from bottom in the previous plots). We will come back to this later (briefly, the plot requires every specified month in months to have an anomaly available, else it shows a blank).

And if you want a wintertime index that includes December, that can be done:

plot(all_buoys_anomalies,
     months = c(12, 1, 2, 3))

As stated in the title, the year on the x-axis is the year of the January.

If you specify months and a single buoy, the anomalies for those months are all shown:

plot(all_buoys_anomalies,
     months = c(12, 1, 2, 3),
     stn_id = "Halibut Bank")  

This plot can be used as a check to see what anomalies are available (if you are only interested in a particular buoy). It also shows explicitly that Dec-Mar is missing at least one month in 1993 (i.e. Dec 1992 - Mar 1993) and 1994, but has all four months available for 1995. That is why in the plot above that one, “Halibut Bank” has no anomaly shown for 1993 or 1994, but does for 1995.

Returning the values shown in the plot

Setting return_results = TRUE returns a list containing the plot and then the results used for the plot (and also automatically shows the plot). Repeating the previous plot with this setting:

res <- plot(all_buoys_anomalies,
            months = c(12, 1, 2, 3),
            stn_id = "Halibut Bank",
            return_results = TRUE)  

res$results
#> # A tibble: 124 × 9
#>    stn_id  year month sst_mean sst_n sst_anomaly month_as_factor sst_plot_value
#>    <fct>  <dbl> <dbl>    <dbl> <int>       <dbl> <fct>                    <dbl>
#>  1 C46146  1992    12     7.56    26      0.152  Dec                     0.152 
#>  2 C46146  1993     2     6.82    27     -0.0682 Feb                    -0.0682
#>  3 C46146  1993     3     7.89    30      0.0577 Mar                     0.0577
#>  4 C46146  1993    12     7.65    31      0.244  Dec                     0.244 
#>  5 C46146  1994     1     7.61    24      0.797  Jan                     0.797 
#>  6 C46146  1994    12     7.24    20     -0.165  Dec                    -0.165 
#>  7 C46146  1995     1     6.56    17     -0.247  Jan                    -0.247 
#>  8 C46146  1995     2     7.03    18      0.142  Feb                     0.142 
#>  9 C46146  1995     3     7.91    26      0.0768 Mar                     0.0768
#> 10 C46146  1996     1     6.69    31     -0.118  Jan                    -0.118 
#> # ℹ 114 more rows
#> # ℹ 1 more variable: label <chr>

So res is a list (res$plot will show the above plot again), and res$results is a tibble that includes the values in the plot, as sst_plot_value and then label for the rounded values that are shown numerically. You can then use these for analyses if desired.

That tibble contains results that depend on options used in the plotting. But we will now examine the full results generated at the very beginning in all_buoys_anomalies.

Examining the climatology and anomalies

At the beginning we ran

all_buoys_anomalies <- calculate_anomalies(buoy_sst)

which we have plotted using various options, and now examine all_buoys_anomalies in detail:

all_buoys_anomalies
#> $climatology
#> # A tibble: 228 × 5
#>    stn_id month clim_sst_mean clim_sst_sd clim_sst_n
#>    <fct>  <dbl>         <dbl>       <dbl>      <int>
#>  1 C46184     1          6.24       0.913        718
#>  2 C46184     2          5.82       0.864        623
#>  3 C46184     3          5.65       0.849        692
#>  4 C46184     4          6.13       0.821        696
#>  5 C46184     5          7.52       1.21         658
#>  6 C46184     6         10.0        1.41         758
#>  7 C46184     7         12.5        1.38         839
#>  8 C46184     8         14.0        1.16         753
#>  9 C46184     9         13.5        1.23         727
#> 10 C46184    10         11.3        1.23         730
#> # ℹ 218 more rows
#> 
#> $anomalies
#> # A tibble: 7,305 × 6
#>    stn_id  year month sst_mean sst_n sst_anomaly
#>    <fct>  <dbl> <dbl>    <dbl> <int>       <dbl>
#>  1 C46184  1987     9    NA        0       NA   
#>  2 C46184  1987    10    10.1     29       -1.16
#>  3 C46184  1987    11    NA        0       NA   
#>  4 C46184  1987    12    NA        0       NA   
#>  5 C46184  1988     1    NA        0       NA   
#>  6 C46184  1988     2     4.56    26       -1.27
#>  7 C46184  1988     3     4.33    27       -1.32
#>  8 C46184  1988     4    NA        0       NA   
#>  9 C46184  1988     5     6.18    31       -1.34
#> 10 C46184  1988     6     7.97    22       -2.04
#> # ℹ 7,295 more rows
#> 
#> $climatology_years
#>  [1] 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006
#> [17] 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020
#> 
#> $climatology_time
#> [1] "month"
#> 
#> attr(,"class")
#> [1] "pacea_buoy_anomalies_list" "list"

This is a list object, with $climatology giving, for each buoy and month, a mean SST (clim_sst_mean), standard deviation (clim_sst_sd), and sample size (clim_sst_n) of the values used for that buoy-month combination. The default for calculate_anomalies() creates the monthly climatology for each buoy based on the years 1991-2020 (details of other arguments will be investigated later).

The $anomalies tibble gives, for each buoy-year-month combination, the anomaly for that month compared to the climatology. The first buoy shown is “C46184” which is the North NOMAD buoy. For October 1987 the mean SST was 10.1 degC (sst_mean), which was calculated from 29 days (sst_n) in that month. The resulting anomaly from the climatology is -1.16 (sst_anomaly). This can be manually checked – the climatology for buoy C46184 in October is a mean SST of 11.3 (the top tibble), the mean SST in October 1987 was 10.1, which is -1.16 less (sst_anomaly). This should help you understand how calculations are done.

So the same calculations are done for all buoy-year-month combinations. There happen to be a few NA’s early on in the data for this buoy (more on that later).

The vector $climatology_years gives the years prescribed for the climatology in the calculate_anomalies() call, for which the default is 1991:2020.

The attribute shown (under attr(, "class")) given just tells R to use our customised plotting function on these results, which is automatically called with plot().

There is more functionality already in the functions, which I will add to soon (I have examples further down in this .Rmd file, but they are not rendered). Hopefully this is enough for users to start playing around with.

knitr::knit_exit()