# load_all() # for developers
library(pacea) # for users
library(dplyr)
library(tibble) # Else prints all of a tibble
library(ggplot2)In the buoy_anomalies vignette we showed how to make customisable heatmaps of the buoy sea-surface temperature data that are saved in pacea. They are in a format familiar to oceanographers, using prescribed climatologies and subsequent anomalies.
Here we show how to produce customisable heatmaps that include disparate types of ecosystem information, thus bringing together the various data objects in pacea. Importantly the user can choose:
The aim of pacea is to facilitate inclusion of ecosystem information in advice to fisheries management, such as stock assessments. Given the multitude of stocks in DFO’s Pacific Region, both in terms of the diversity of species and the many stock-specific spatial areas, we decided to create functions for user-customisable plots that easily summarise the relevant ecosystem information.
Expert knowledge can be used to suggest relevant ecosystem information to be used for a particular stock. Furthermore, the ease of generating the heatmaps can allow exploration, formulation of ideas, and quick hypothesis testing.
For comparing disparate types of ecosystem information, we calculate an annual index which is standardised (by subtracting the mean and dividing by the standard deviation across the time series; note that all years might not get shown in a plot). This is so that indices are all dimensionless and can be compared on equivalent scales, and shown on a single heatmap style plot.
As a general proof-of-concept example, let’s use some ecosystem
information for the Strait of Georgia (SOG) that is in pacea. We create
an index for each type of ecosystem information in turn and then make
the plot. We just need to use create_index() multiple times
to create the indices, and then plot_indices() to make the
function (we will explain the functions in more detail later):
# Average sea surface temperature in April from a buoy in the SOG
sst_buoy_halibut_bank_apr_index <- create_index(buoy_sst,
months = 4,
stn_id = "Halibut Bank")
# Large calanoid copepods in the SOG
calanoid_copepods_large_sog_index <- create_index(zooplankton_sog,
zooplankton_species_group =
"calanoid_copepods_large")
# Total zooplankton in the SOG
total_zooplankton_sog_index <- create_index(zooplankton_sog,
zooplankton_species_group = "total_biomass")
# Annual age-2 herring recruitment in the SOG
herring_recruitment_sog_index <- create_index(herring_recruitment,
herring_region = "SOG")
# Annual herring spawning biomass in the SOG
herring_biomass_sog_index <- create_index(herring_spawning_biomass,
herring_region = "SOG")
# Annual population estimates of Harbour Seals in the SOG
harbour_seals_sog_index <- create_index(harbour_seals,
seal_region = "SOG")See the relevant vignettes and help files for details of the pacea objects used for each index. The first index is from the buoy sea surface temperature. Looking at the buoy_anomalies vignette we have used the Halibut Bank buoy in the SoG, which has good timespans of data, and just pick April.
The create_index() function automatically creates an
object of class pacea_standardised_index, no matter what
type the ecosystem information is.
Now make the plot, manually basing the start year on the shortest of those (zooplankton data):
plot_indices(sst_buoy_halibut_bank_apr_index,
calanoid_copepods_large_sog_index,
total_zooplankton_sog_index,
herring_recruitment_sog_index,
herring_biomass_sog_index,
harbour_seals_sog_index,
years = 1996:2026,
gap_after = c("sst_buoy_halibut_bank_apr_index",
"total_zooplankton_sog_index",
"herring_biomass_sog_index"))The first six arguments are the indices that we created above, and
years restricts the plot to the years specified. To
delineate different types of ecosystem information in the plot
(e.g. here we have temperature, zooplankton, fish, mammals), we used the
optional gap_after argument to add a small gap after the
specified indices.
Each index is standardised over its full time series, and red for a year indicates above-average conditions, blue indicates below-average, with the black numbers giving the exact standardised anomaly (number of standard deviations above the mean), as used for the colour coding.
The restriction of the years being shown is why the seal index is always positive. By definition the mean of an index across its full time series is 0, and so the seal index must be negative earlier in the time series. A quick check of the absolute seal populations numbers confirms this, in that the population has indeed increased from the early years:
Here are the details of what is in the buoy SST index object created above:
sst_buoy_halibut_bank_apr_index
#> # A data frame: 35 × 4
#> index year value index_label
#> <chr> <dbl> <dbl> <chr>
#> 1 halibut_bank_sst_apr 1992 1.15 Halibut Bank buoy SST for Apr
#> 2 halibut_bank_sst_apr 1993 -0.481 Halibut Bank buoy SST for Apr
#> 3 halibut_bank_sst_apr 1994 NA Halibut Bank buoy SST for Apr
#> 4 halibut_bank_sst_apr 1995 0.583 Halibut Bank buoy SST for Apr
#> 5 halibut_bank_sst_apr 1996 -0.929 Halibut Bank buoy SST for Apr
#> 6 halibut_bank_sst_apr 1997 -0.533 Halibut Bank buoy SST for Apr
#> 7 halibut_bank_sst_apr 1998 1.14 Halibut Bank buoy SST for Apr
#> 8 halibut_bank_sst_apr 1999 -1.000 Halibut Bank buoy SST for Apr
#> 9 halibut_bank_sst_apr 2000 0.711 Halibut Bank buoy SST for Apr
#> 10 halibut_bank_sst_apr 2001 NA Halibut Bank buoy SST for Apr
#> # ℹ 25 more rowsIt is a tibble, with a value for each year, plus an
index to refer to in code, and an index_label
to use for the labels in the plot (these are repeated in each row, which
just makes things easier at the plotting stage).
We can also choose multiple months. Note that index and
index_label get automatically prescribed; examine the
arguments and results for these three indices (for the default buoy,
namely that in West Dixon Entrance):
create_index(buoy_sst,
months = 5)
#> # A data frame: 38 × 4
#> index year value index_label
#> <chr> <dbl> <dbl> <chr>
#> 1 west_dixon_entrance_sst_may 1989 NA West Dixon Entrance buoy SST for May
#> 2 west_dixon_entrance_sst_may 1990 NA West Dixon Entrance buoy SST for May
#> 3 west_dixon_entrance_sst_may 1991 -0.855 West Dixon Entrance buoy SST for May
#> 4 west_dixon_entrance_sst_may 1992 0.277 West Dixon Entrance buoy SST for May
#> 5 west_dixon_entrance_sst_may 1993 0.947 West Dixon Entrance buoy SST for May
#> 6 west_dixon_entrance_sst_may 1994 0.155 West Dixon Entrance buoy SST for May
#> 7 west_dixon_entrance_sst_may 1995 0.968 West Dixon Entrance buoy SST for May
#> 8 west_dixon_entrance_sst_may 1996 NA West Dixon Entrance buoy SST for May
#> 9 west_dixon_entrance_sst_may 1997 0.594 West Dixon Entrance buoy SST for May
#> 10 west_dixon_entrance_sst_may 1998 0.589 West Dixon Entrance buoy SST for May
#> # ℹ 28 more rows
create_index(buoy_sst,
months = 5:6)
#> # A data frame: 38 × 4
#> index year value index_label
#> <chr> <dbl> <dbl> <chr>
#> 1 west_dixon_entrance_sst_may_and_jun 1989 NA West Dixon Entrance buoy SST f…
#> 2 west_dixon_entrance_sst_may_and_jun 1990 NA West Dixon Entrance buoy SST f…
#> 3 west_dixon_entrance_sst_may_and_jun 1991 -1.10 West Dixon Entrance buoy SST f…
#> 4 west_dixon_entrance_sst_may_and_jun 1992 NA West Dixon Entrance buoy SST f…
#> 5 west_dixon_entrance_sst_may_and_jun 1993 1.25 West Dixon Entrance buoy SST f…
#> 6 west_dixon_entrance_sst_may_and_jun 1994 0.148 West Dixon Entrance buoy SST f…
#> 7 west_dixon_entrance_sst_may_and_jun 1995 0.218 West Dixon Entrance buoy SST f…
#> 8 west_dixon_entrance_sst_may_and_jun 1996 NA West Dixon Entrance buoy SST f…
#> 9 west_dixon_entrance_sst_may_and_jun 1997 1.05 West Dixon Entrance buoy SST f…
#> 10 west_dixon_entrance_sst_may_and_jun 1998 0.427 West Dixon Entrance buoy SST f…
#> # ℹ 28 more rows
create_index(buoy_sst,
months = c(11, 12, 1, 2, 3))
#> # A data frame: 38 × 4
#> index year value index_label
#> <chr> <dbl> <dbl> <chr>
#> 1 west_dixon_entrance_sst_nov_to_mar 1989 NA West Dixon Entrance buoy SST fo…
#> 2 west_dixon_entrance_sst_nov_to_mar 1990 NA West Dixon Entrance buoy SST fo…
#> 3 west_dixon_entrance_sst_nov_to_mar 1991 -1.27 West Dixon Entrance buoy SST fo…
#> 4 west_dixon_entrance_sst_nov_to_mar 1992 0.508 West Dixon Entrance buoy SST fo…
#> 5 west_dixon_entrance_sst_nov_to_mar 1993 0.137 West Dixon Entrance buoy SST fo…
#> 6 west_dixon_entrance_sst_nov_to_mar 1994 1.17 West Dixon Entrance buoy SST fo…
#> 7 west_dixon_entrance_sst_nov_to_mar 1995 NA West Dixon Entrance buoy SST fo…
#> 8 west_dixon_entrance_sst_nov_to_mar 1996 NA West Dixon Entrance buoy SST fo…
#> 9 west_dixon_entrance_sst_nov_to_mar 1997 -1.10 West Dixon Entrance buoy SST fo…
#> 10 west_dixon_entrance_sst_nov_to_mar 1998 1.32 West Dixon Entrance buoy SST fo…
#> # ℹ 28 more rowsIn the third example note how the months are specified from November to March - the year indicated is the year of the January.
Note that in the first example we have a value for 1992, but not in the second one. This must be because June 1992 does not have enough daily values for this particular buoy, which we can confirm from the raw data:
filter(buoy_sst,
stn_id == "C46205", # default in create_index.pacea_buoy()
date >= "1992-06-01",
date <= "1992-06-30",
!is.na(sst))
#> # A tibble: 6 × 3
#> date stn_id sst
#> <date> <fct> <dbl>
#> 1 1992-06-01 C46205 9.46
#> 2 1992-06-02 C46205 9.70
#> 3 1992-06-03 C46205 9.41
#> 4 1992-06-04 C46205 10.0
#> 5 1992-06-05 C46205 10.0
#> 6 1992-06-06 C46205 10.2So there are only 6 daily SST values for this buoy in June 1992, and
so an anomaly is not calculated for that month because the default is to
require 15 daily SST values in a month. We could force an anomaly to be
calculated by changing the arguments min_days_per_month
(default 15) and max_consecutive_missing_days (default 6)
that get passed onto calculate_anomalies.pacea_buoy():
create_index(buoy_sst,
months = 5:6,
min_days_per_month = 6,
max_consecutive_missing_days = 24)
#> # A data frame: 38 × 4
#> index year value index_label
#> <chr> <dbl> <dbl> <chr>
#> 1 west_dixon_entrance_sst_may_and_jun 1989 NA West Dixon Entrance buoy SST …
#> 2 west_dixon_entrance_sst_may_and_jun 1990 NA West Dixon Entrance buoy SST …
#> 3 west_dixon_entrance_sst_may_and_jun 1991 -1.21 West Dixon Entrance buoy SST …
#> 4 west_dixon_entrance_sst_may_and_jun 1992 -0.634 West Dixon Entrance buoy SST …
#> 5 west_dixon_entrance_sst_may_and_jun 1993 1.26 West Dixon Entrance buoy SST …
#> 6 west_dixon_entrance_sst_may_and_jun 1994 0.0977 West Dixon Entrance buoy SST …
#> 7 west_dixon_entrance_sst_may_and_jun 1995 0.170 West Dixon Entrance buoy SST …
#> 8 west_dixon_entrance_sst_may_and_jun 1996 0.721 West Dixon Entrance buoy SST …
#> 9 west_dixon_entrance_sst_may_and_jun 1997 1.04 West Dixon Entrance buoy SST …
#> 10 west_dixon_entrance_sst_may_and_jun 1998 0.389 West Dixon Entrance buoy SST …
#> # ℹ 28 more rowsthus giving an index value for 1992, given these definitions (which we do not recommend, but are showing as examples to help understanding, and to show that arguments get correctly passed onto further functions).
For any type of pacea object, check the exact calculations and
further options by checking class(object) and then
?create_index.<class>. For example
?create_index.pacea_buoy for the buoy_sst
data.
We now create various indices from the Optimally Interpolated Sea Surface Temperature (OISST) spatiotemporal data, that are described in oisst.html. An index is a single standardised value for each year, and for SST would be based on the mean anomalies over a given area.
It is first best to plot the spatial anomalies for the area of
interest, to verify the area and understand what is going on. We will
then calculate an index directly from the anomalies list, or directly
from create_index(oisst_month) with various options.
First, let’s look at the full domain of the OISST data and calculate annual anomalies and do the default plot (which is for the most recent month of data):
The oisst_anomalies list object (of class
pacea_oisst_anomalies_list) contains the climatology for
each month-location combination, plus the anomalies relative to those
for each year-month-location combination, plus the
climatology_years used:
oisst_anomalies
#> $climatology
#> Simple feature collection with 11100 features and 4 fields
#> Geometry type: POINT
#> Dimension: XY
#> Bounding box: xmin: -138.625 ymin: 46.625 xmax: -123.125 ymax: 54.625
#> Geodetic CRS: WGS 84
#> # A tibble: 11,100 × 5
#> month clim_value clim_sd clim_n geometry
#> <dbl> <dbl> <dbl> <int> <POINT [°]>
#> 1 1 9.51 0.842 30 (-129.125 46.625)
#> 2 1 9.33 0.888 30 (-129.625 46.875)
#> 3 1 9.36 0.862 30 (-129.375 46.875)
#> 4 1 9.38 0.836 30 (-129.125 46.875)
#> 5 1 9.38 0.806 30 (-128.875 46.875)
#> 6 1 9.37 0.770 30 (-128.625 46.875)
#> 7 1 9.17 0.900 30 (-130.125 47.125)
#> 8 1 9.19 0.887 30 (-129.875 47.125)
#> 9 1 9.22 0.866 30 (-129.625 47.125)
#> 10 1 9.23 0.840 30 (-129.375 47.125)
#> # ℹ 11,090 more rows
#>
#> $anomalies
#> Simple feature collection with 500425 features and 7 fields
#> Geometry type: POINT
#> Dimension: XY
#> Bounding box: xmin: -138.625 ymin: 46.625 xmax: -123.125 ymax: 54.625
#> Geodetic CRS: WGS 84
#> # A tibble: 500,425 × 8
#> year month sst start_date end_date clim_value anom
#> * <dbl> <dbl> <dbl> <date> <date> <dbl> <dbl>
#> 1 1981 9 16.4 1981-09-01 1981-09-30 16.7 -0.339
#> 2 1981 10 14.3 1981-10-01 1981-10-31 15.2 -0.921
#> 3 1981 11 12.7 1981-11-01 1981-11-30 12.8 -0.124
#> 4 1981 12 10.2 1981-12-01 1981-12-31 10.7 -0.456
#> 5 1981 9 16.4 1981-09-01 1981-09-30 16.6 -0.179
#> 6 1981 10 14.2 1981-10-01 1981-10-31 15.0 -0.750
#> 7 1981 11 12.4 1981-11-01 1981-11-30 12.6 -0.180
#> 8 1981 12 10.0 1981-12-01 1981-12-31 10.5 -0.490
#> 9 1981 9 16.4 1981-09-01 1981-09-30 16.6 -0.231
#> 10 1981 10 14.3 1981-10-01 1981-10-31 15.0 -0.680
#> # ℹ 500,415 more rows
#> # ℹ 1 more variable: geometry <POINT [°]>
#>
#> $climatology_years
#> [1] 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005
#> [16] 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020
#>
#> $climatology_time
#> [1] "month"
#>
#> attr(,"class")
#> [1] "pacea_oisst_anomalies_list" "list"We can specify months and years in a plot, chosen here to show the marine heatwave forming in 2014:
plot(oisst_anomalies,
months = 1:12,
years = 2014:2016,
x_axis_labels = c(-135, -125)) # specify the x-axis labels to be shown We can create an index of the mean anomaly each year averaged over, say, Apr-Jun, over the full domain shown (which is Canada’s Exclusive Economic Zone):
oisst_eez_apr_jun_index <- create_index(oisst_anomalies,
months = 4:6,
index_name =
"oisst_apr_to_jun_over_eez",
index_label = "OISST for Apr to Jun over EEZ")
oisst_eez_apr_jun_index
#> # A data frame: 45 × 4
#> index year value index_label
#> <chr> <dbl> <dbl> <chr>
#> 1 oisst_apr_to_jun_over_eez 1982 -1.11 OISST for Apr to Jun over EEZ
#> 2 oisst_apr_to_jun_over_eez 1983 1.25 OISST for Apr to Jun over EEZ
#> 3 oisst_apr_to_jun_over_eez 1984 -0.241 OISST for Apr to Jun over EEZ
#> 4 oisst_apr_to_jun_over_eez 1985 -1.13 OISST for Apr to Jun over EEZ
#> 5 oisst_apr_to_jun_over_eez 1986 -0.623 OISST for Apr to Jun over EEZ
#> 6 oisst_apr_to_jun_over_eez 1987 -0.392 OISST for Apr to Jun over EEZ
#> 7 oisst_apr_to_jun_over_eez 1988 -1.10 OISST for Apr to Jun over EEZ
#> 8 oisst_apr_to_jun_over_eez 1989 -0.340 OISST for Apr to Jun over EEZ
#> 9 oisst_apr_to_jun_over_eez 1990 -0.367 OISST for Apr to Jun over EEZ
#> 10 oisst_apr_to_jun_over_eez 1991 -1.02 OISST for Apr to Jun over EEZ
#> # ℹ 35 more rowsThe index tibble has the same format as the other types shown above, with one value per day. We can simply add this to our earlier heatmap summary plot:
plot_indices(oisst_eez_apr_jun_index,
sst_buoy_halibut_bank_apr_index,
calanoid_copepods_large_sog_index,
total_zooplankton_sog_index,
herring_recruitment_sog_index,
herring_biomass_sog_index,
harbour_seals_sog_index,
years = 1996:2026,
gap_after = c("sst_buoy_halibut_bank_apr_index",
"total_zooplankton_sog_index",
"herring_biomass_sog_index"))
This is showing the proof of concept, in that, we can easily compare the
mean SST anomaly across the whole EEZ over April to June with the buoy
SST mean anomaly for April, at one location (the point being you can
define an index however you like and easily compare it).
In practice, we likely want to restrict an index (and annual summary figure) to a specific spatial region, and also specific months.
To create anomalies for a specific area, such as Pacific Marine Fisheries Area 126, again showing the heatwave progressing
oisst_anomalies_area_126 <- calculate_anomalies(oisst_month,
area = pfma_area_126_temp)
plot(oisst_anomalies_area_126,
months = 1:12,
years = 2014:2016,
restrict_plot = TRUE, # restrict the plot to the area of the anomalies
buffer_proportion = 0.6, # add a buffer proportion to zoom out a bit
x_axis_labels = c(-130, -128)) # specify the x-axis labels to be shown
(Some of those settings are hard to automate with multiple panels, so
users may need to tweak the options; also
pfma_area_126_temp is a temporary data object in pacea and
will change names when we import many fisheries management areas).
For investigating plots (rather than doing analyses), we can also
simply use the area argument when plotting the full
oisst_anomalies, giving the same plot as above (so we have
not shown it again here):
plot(oisst_anomalies,
area = pfma_area_126_temp,
months = 1:12,
years = 2014:2016,
restrict_plot = TRUE,
buffer_proportion = 0.6,
x_axis_labels = c(-130, -128))To calculate the index for Area 126 in April (the default), and give it an appropriate name and label:
oisst_area_126_apr_index <- create_index(oisst_anomalies_area_126,
index_name = "oisst_apr_area_126",
index_label = "OISST in Apr over Area 126") # April is the default
oisst_area_126_apr_index
#> # A data frame: 45 × 4
#> index year value index_label
#> <chr> <dbl> <dbl> <chr>
#> 1 oisst_apr_area_126 1982 -0.424 OISST in Apr over Area 126
#> 2 oisst_apr_area_126 1983 1.70 OISST in Apr over Area 126
#> 3 oisst_apr_area_126 1984 -0.241 OISST in Apr over Area 126
#> 4 oisst_apr_area_126 1985 -1.05 OISST in Apr over Area 126
#> 5 oisst_apr_area_126 1986 -0.0524 OISST in Apr over Area 126
#> 6 oisst_apr_area_126 1987 0.203 OISST in Apr over Area 126
#> 7 oisst_apr_area_126 1988 -0.878 OISST in Apr over Area 126
#> 8 oisst_apr_area_126 1989 -0.699 OISST in Apr over Area 126
#> 9 oisst_apr_area_126 1990 -0.153 OISST in Apr over Area 126
#> 10 oisst_apr_area_126 1991 -0.708 OISST in Apr over Area 126
#> # ℹ 35 more rowsFor comparison we can also calculate the mean sea surface temperature for April from a buoy within area 126
sst_buoy_south_brooks_apr_index <- create_index(buoy_sst,
months = 4,
stn_id = "South Brooks")
plot_indices(oisst_area_126_apr_index,
sst_buoy_south_brooks_apr_index)
This shows that the two different indices representing SST are fairly
correlated but not exactly the same, as expected.
It makes sense to restrict the plot to 1995 onwards when we have both types of data. You can either restrict the years in the plot, or recalculate the OISST anomalies from 1995 onwards. Note how these give slightly different figures; restricting the plot:
or recalculate a restricted index:
oisst_area_126_apr_from_1995_index <-
create_index(oisst_anomalies_area_126,
years = 1995:2026)
plot_indices(oisst_area_126_apr_from_1995_index,
sst_buoy_south_brooks_apr_index)You can also create an index directly from the
oisst_month data, for which the anomalies are calculated
internally. Though it seems better to calculate the full spatial
anomalies for your area to take a look at them first, as we did above.
Nevertheless, create_index() will work directly on
oisst_month, with the area option, giving the
same result as above
oisst_area_126_apr_index_directly <- create_index(oisst_month,
area = pfma_area_126_temp,
index_name =
"oisst_apr_area_126",
index_label = "OISST in Apr over Area 126")
expect_equal(oisst_area_126_apr_index,
oisst_area_126_apr_index_directly)We can also show every month of a spatially averaged anomaly in a heatmap style plot, like in the buoy_anomalies vignette. This nicely summarises the many spatial maps (which can’t all easily digested by a reader) into one plot. Note that these are NOT annual indices:
You can just do a few months of interest:
See the code of the vignette (specifically the chunk headers) to see how to get the figure sizes looking good; these are not automated in the functions.
If you have read this far then you deserve to learn about another, lesser known, spatial area available in pacea.
oisst_anomalies_sasquatch <- calculate_anomalies(oisst_month,
area = sasquatch)
plot(oisst_anomalies_sasquatch,
restrict_plot = TRUE,
buffer_proportion = 1,
area_outline = sasquatch)In average years, the sasquatch appears to hide away, only coming out when it’s warm or cold:
And we can create a new index, and see how it compares to that above for Area 126:
oisst_sasquatch_apr_index <- create_index(oisst_anomalies_sasquatch,
months = 4,
index_label = "OISST in Sasquatch for Apr")
plot_indices(oisst_sasquatch_apr_index,
oisst_area_126_apr_index)For details of the climatological and oceanographic indices available in pacea see the indices vignette. For example, we have developed a heatmap style that is particularly useful for understanding the emergence of El Niño (see vignette for more details), with shades of blue representing La Niña conditions, grey representing neutral, and shades of red representing El Niño:
We can create an index based on a few months of ONI, let’s say Nov-Jan, based on the above figure:
We can see if that looks to correlate with the previously calculated index of temperature in Area 126, off the west coast of Vancouver Island:
(Note that the standardised ONI here is standardised like all variables
in such a plot, as noted in the title, so values are not the same as for
usually-defined ONI).
The red and blues generally match up, although there are some exceptions. The correlation coefficient is
cor(filter(oni_index_nov_jan, year >= 1982)$value,
oisst_area_126_apr_index$value) %>% round(2)
#> [1] 0.68Interestingly, the averaged temperature over Area 126 (second row) for 2026 is 0.9 – only in 2025 and 2026 has it been >0.5 when the standardised ONI the previous Nov to Jan is negative (blue). So coming into an El Niño for 2026-2027, the water is already relatively warm. This shows the sort of hypotheses you can start looking into and trying to understand. And because we have the values available, we can make a quick plot to demonstrate this last point:
# Values of the standardised ONI that are negative:
oni_index_neg <- filter(oni_index_nov_jan,
year >= 1982, # when OISST data start
value <= 0)
# Corresponding Area 126 April mean OISST for those years:
oisst_area_126_apr_oni_neg <- filter(oisst_area_126_apr_index,
year %in% oni_index_neg$year)
plot(oisst_area_126_apr_oni_neg$year,
oisst_area_126_apr_oni_neg$value,
xlab = "Year",
ylab = "OISST in April over Area 126",
main = "Only for years with a negative standardised ONI")
abline(h = 0, col = "grey")We can also look at potential correlations with the other temperature indices we calculated above (for a buoy and for the whole EEZ):
plot_indices(oni_index_nov_jan,
oisst_area_126_apr_index,
sst_buoy_south_brooks_apr_index,
oisst_sasquatch_apr_index,
oisst_eez_apr_jun_index,
years = 1982:2026,
gap_after = c("oni_index_nov_jan",
"sst_buoy_south_brooks_apr_index"))For the South Brooks buoy, 2026 is the only year with an anomaly of 0.5 and a negative standardised ONI over the previous Nov to Jan, except for 1996 (that had a low standardised ONI value of -0.9).
For the full EEZ (bottom row), the signal is not as strong.
The earlier point above about ONI and Area 126 in 2025 and 2026 does not show really show up in a simple plot (the years in question are highlighted in red, but otherwise would not really stand out; to be improved with a general phase plot function):
plot(filter(oni_index_nov_jan, year >= 1982)$value,
oisst_area_126_apr_index$value,
xlab = oni_index_nov_jan$index_label[1],
ylab = oisst_area_126_apr_index$index_label[1])
points(filter(oni_index_nov_jan,
year %in% 2025:2026)$value,
filter(oisst_area_126_apr_index,
year %in% 2025:2026)$value,
col = "red")This demonstrates the usefulness of the heatmap-style plot.
The create_index() function works on all the
climatological and oceanographic indices in pacea (PDO, NPGO, etc.; see
pacea_indices) and so that plus plot_indices()
could be used to compare and help understand the relationships between
the various indices. An exercise for the reader.
We have not yet tested or worked on extracting indices from the BCCM and HOTSSea model outputs. Coming soon. The OISST functions should help but will need slight adapting. TODO
In the 2025 Pacific Hake stock assessment we presented a hake-specific ecosystem summary as a first step to providing an operational summary of relevant ecosystem information regarding the coastwide Pacific Hake stock (from California to British Columbia). The ecosystem summary was updated in the 2026 assessment, with about half of the variables shown coming directly from pacea.
Without going into too much detail (see the assessments), we now develop an ecosystem summary heatmap of some of the ecosystem influences on Pacific Hake recruitment (age-0 individuals) found by Vestfals et al. (2023). This will improve upon the time-series style plots used in the 2025 and 2026 assessments.
We show five indices from pacea:
| Definition | Effect on recruitment | Expectation for an increase in the index |
|---|---|---|
| Pacific Decadal Oscillation during preconditioning (Apr-Sep) | Indicator of basin-scale processes, negative phase linked to higher productivity and improved adult condition | \(\downarrow\) recruitment the following year |
| North Pacific Gyre Oscillation during preconditioning (Apr-Sep) | Indicator of basin-scale processes, positive phase linked to higher nutrient concentrations and productivity, and improved adult condition | \(\uparrow\) recruitment the following year |
| North Pacific Current Bifurcation Index | Northward-shifted bifurcation leads to increased advection of prey southwards, leading to poorer feeding conditions off British Columbia, Washington, and Oregon, and reduced adult condition | \(\downarrow\) recruitment the following year |
| Pacific Herring spawning biomass off the west coast of Vancouver Island | Increased competition with herring on summer feeding grounds leads to poorer feeding conditions and reduced adult condition | \(\downarrow\) recruitment the following year |
| Biomass of age-1 Pacific Hake | Age-1 hake predate on pelagic juveniles (roughly Apr-Sep) | \(\downarrow\) recruitment that year |
We introduce two new options in plot_indices():
So create the relevant indices from pacea objects and then make the plot:
hake_assessment_year <- 2026
hake_recruitment_final_year_to_use <- 2022 # final years removed as not
# estimated by data, depends on the model assumptions
age_1_hake_final_year_to_use <- hake_recruitment_final_year_to_use + 1
hake_recruitment_index <- create_index(
filter(hake_recruitment,
year <= hake_recruitment_final_year_to_use),
index_name = "hake_recruitment",
index_label = "Pacific Hake age-0 recruitment")
start_year <- hake_recruitment_index$year[1]
pdo_index <- create_index(filter(pdo,
year >= start_year),
months = 4:9,
index_name = "pdo_preconditioning",
index_label = "PDO Preconditioning")
npgo_index <- create_index(filter(npgo,
year >= start_year),
months = 4:9,
index_name = "npgo_preconditioning",
index_label = "NPGO Preconditioning")
bi_index <- create_index(bi,
index_label = "Bifurcation Index")
herring_biomass_wcvi_index <- create_index(filter(herring_spawning_biomass,
year >= start_year),
herring_region = "WCVI",
index_name =
"herring_spawning_biomass_wcvi")
hake_age_1_index <- create_index(filter(hake_total_biomass_age_1,
year <= age_1_hake_final_year_to_use),
index_name = "hake_age_1_total_biomass",
index_label = "Pacific Hake age-1 biomass")
plot_indices(hake_recruitment_index,
pdo_index,
npgo_index,
bi_index,
herring_biomass_wcvi_index,
hake_age_1_index,
years = 1995:2027, # else a bit unwieldy to see
gap_after = c("hake_recruitment_index",
"bi_index",
"herring_biomass_wcvi_index"),
flip = c("pdo_index",
"bi_index",
"herring_biomass_wcvi_index",
"hake_age_1_index"),
shift = c("pdo_index",
"npgo_index",
"bi_index",
"herring_biomass_wcvi_index"))
The top row shows the index of hake recruitment, with the other rows
showing some of the indices that are expected to influence hake
recruitment. For the indices annotated with \(^*\), the numbers show the index but the
colours are flipped, such that red is always ‘good for recruitment’ and
blue is bad; these indices are the four with a \(\downarrow\) in the final column of the
table.
For the four indices that the effect of the index is expected on the
following year’s hake recruitment, the plotted index is shifted
by a year and this is indicated by a + symbol annotated to
the index label. This is so that the x-axis represents the year of the
hake recruitment; so all the influences on, say, 1995 recruitment are in
the 1995 column (even if the index is calculated on 1994 data), and red
basically implies good for hake and blue implies not good.
To briefly interpret some of the results, for the very large 1999 year class (high 1999 hake recruitment), the five indices imply slightly favourable conditions, as they are all coloured red. For the very large 2010 class four of them do, which is the same for the not-so-large (but still way above average) 2008 and 2014 classes. For the strong 2016 and 2021 age classes, only three and two indices indicate favourable conditions, respectively. Thus, as expected, such influences are not definitive.
Note that the latest estimate of hake recruitment is for 2022 (at the original time of writing, October 2026), but we already have estimates of all the other indices for 2023, and some of them for later years. Thus, if a good relationship holds, we have some information for recent recruitments that we do not yet have estimates for from the stock assessment model.
Also, note that we already have estimates of the relevant indices derived from PDO and NPGO for 2027, because the Apr-Sep 2026 values are available now, and the influence of these is expected on the 2027 hake recruitment (so they appear in the 2027 column).
More work is needed regarding interpretation, but the point here is to demonstrate the functionality. In the hake assessment we also showed indices abstracted from outputs of the Global Ocean Physics Reanalysis (GLORYS12V1) product, which is a global ocean eddy-resolving oceanographic model. These looked at, for example, mixed-layer depths and strength of currents that affect eggs and larvae.
Using our functions described above, you should be able to tailor ecosystem summary heatmaps for your own stock or species of interest. We see these allowing you to be exploratory, generate hypotheses, and help you include ecosystem information when providing advice to fisheries management.
This will be removed when all figured out.
See our pacea Issue for the short version of the following. Also see long email to Mike and Tetjana; waiting for response (Mike has asked someone about it).
First want to update indices values and check how the figure looks. Originally a quick cheat as the index has changed so I didn’t want to save it into pacea yet (not evaluating as do not want to keep downloading it), but now (before PICES) I am going to update all the indices, so any mention of ‘oni’ below is before the update (dates are given).
download.file("https://www.cpc.ncep.noaa.gov/data/indices/oni.ascii.txt",
destfile="oni.txt",
mode="wb",
quiet = FALSE)oni_new <- readr::read_table("oni.txt")
#>
#> ── Column specification ───────────────────────────────────────────────────────────
#> cols(
#> SEAS = col_character(),
#> YR = col_double(),
#> TOTAL = col_double(),
#> ANOM = col_double()
#> )
stopifnot(colnames(oni_new) == c("SEAS", "YR", "TOTAL", "ANOM"),
oni_new[1, 1] == "DJF") # If this fails then months as factors
# below will become incorrect because it
# assumes first one is January
colnames(oni_new)<-c("month",
"year",
"value",
"anomaly")
oni_new <- dplyr::relocate(oni_new,
year)
oni_new$month <- as.numeric(factor(oni_new$month,
levels=unique(oni_new$month),
ordered=TRUE))
class(oni_new) <- c("pacea_index",
class(oni_new))
attr(oni_new, "axis_name") <- "Oceanic Niño Index"
plot(oni_new,
years = 1980:2026,
neutral_oni_one_colour = TRUE,
heatmap_style = TRUE)Copying and pasting these in, not running:
# My installed version:
> pacea::pacea_installed()
You last locally installed pacea on 2026-10-08 which is 6 days ago.
# Also Cameron's:
You last locally installed pacea on 2026-09-28 which is 16 days ago.
# Elise:
You last locally installed pacea on 2026-01-27 which is 260 days ago.
> pacea::oni
# A tibble: 917 × 4
year month value anomaly
<dbl> <dbl> <dbl> <dbl>
1 1950 1 24.7 -1.53
2 1950 2 25.2 -1.34
3 1950 3 25.8 -1.16
4 1950 4 26.1 -1.18
5 1950 5 26.3 -1.07
6 1950 6 26.3 -0.85
7 1950 7 26.2 -0.54
8 1950 8 26.0 -0.42
9 1950 9 25.8 -0.39
10 1950 10 25.6 -0.44
# ℹ 907 more rows
# ℹ Use `print(n = ...)` to see more rows
# The new values downloaded on 9th October from https://www.cpc.ncep.noaa.gov/data/indices/oni.ascii.txt
> oni_new
# A tibble: 920 × 4
year month value anomaly
<dbl> <dbl> <dbl> <dbl>
1 1950 1 25.0 -1.32
2 1950 2 25.4 -1.2
3 1950 3 25.9 -1.12
4 1950 4 26.2 -1.08
5 1950 5 26.4 -1.1
6 1950 6 26.3 -0.9
7 1950 7 26.2 -0.62
8 1950 8 25.9 -0.57
9 1950 9 25.8 -0.56
10 1950 10 25.6 -0.62
# ℹ 910 more rows
# ℹ Use `print(n = ...)` to see more rows
# Dates of last commits that changed oni.rda on pacea/main:
2026-07-26
2026-10-26 - this rebuilt the README.Md and has -1.53 as 1950-1 anomaly,
unchanged from previous README.Md commit. Aha - this line in README has not
changed since Nov 2023.
2026-03-03 NEWS update says: In particular for ONI we are now in a new five-year
baseline period for recent anomalies so all ONI values since 2010 have been
recalculated, presumably related to:
2026-03-02
2025-02-25Using the link from Andrea: https://psl.noaa.gov/data/timeseries/month/
Oceanic Niño index:
https://psl.noaa.gov/data/correlation/oni.data
starts with Jan 1950 being -1.53. So the same that I used to have, but not the same as the link I have been using.
This has (saved in oni-from-psl-noaa-gov-from-andrea.csv):
1950-01-01, -1.530
1950-02-01, -1.340
2026-01-01, -0.390
2026-02-01, -0.210
2026-03-01, 0.110
2026-04-01, 0.460
2026-05-01, 0.950
2026-06-01, 1.390
2026-07-01, 1.800
2026-08-01, -9999.000Currently in my load_all() pacea (oni.rda
is dated 2026-07-26):
> oni
1 1950 1 24.7 -1.53
2 1950 2 25.2 -1.34
> tail(oni)
# A tibble: 6 × 4
year month value anomaly
<dbl> <dbl> <dbl> <dbl>
1 2025 12 26.0 -0.54
2 2026 1 26.1 -0.37
3 2026 2 26.6 -0.14
4 2026 3 27.3 0.13
5 2026 4 28.1 0.51
6 2026 5 28.7 0.98So, confusingly, the 1950s values match the PSL values. But the recent ones do not. Values can change with updates, but I thought only in the final few months.
Let’s look at both for 1999 as a middle year:
# PSL values:
1999-01-01, -1.550
1999-02-01, -1.300
1999-03-01, -1.070
1999-04-01, -0.980
1999-05-01, -1.020
1999-06-01, -1.040
1999-07-01, -1.100
1999-08-01, -1.110
1999-09-01, -1.160
1999-10-01, -1.260
1999-11-01, -1.460
1999-12-01, -1.650
> filter(oni, year == 1999)
year month value anomaly
<dbl> <dbl> <dbl> <dbl>
1 1999 1 25.1 -1.55
2 1999 2 25.6 -1.3
3 1999 3 26.2 -1.07
4 1999 4 26.6 -0.98
5 1999 5 26.7 -1.02
6 1999 6 26.5 -1.04
7 1999 7 26.1 -1.1
8 1999 8 25.8 -1.11
9 1999 9 25.6 -1.16
10 1999 10 25.4 -1.26
11 1999 11 25.2 -1.46
12 1999 12 24.9 -1.65Okay, so they match. So recent values must still be getting updated in the PSL or usual one.
Also, oni_new is different for 1999:
> filter(oni_new, year == 1999)
# A tibble: 12 × 4
year month value anomaly
<dbl> <dbl> <dbl> <dbl>
1 1999 1 25.1 -1.51
2 1999 2 25.6 -1.25
3 1999 3 26.3 -0.93
4 1999 4 26.8 -0.8
5 1999 5 26.9 -0.79
6 1999 6 26.7 -0.84
7 1999 7 26.3 -0.9
8 1999 8 26.0 -0.94
9 1999 9 25.8 -1
10 1999 10 25.6 -1.13
11 1999 11 25.3 -1.35
12 1999 12 25.1 -1.53So, issue is that historical Jan 1950 (versions in pacea) has always seemed to be -1.53. Same as the current PSL one. But different to where I usually get oni from, which is:
https://www.cpc.ncep.noaa.gov/data/indices/oni.ascii.txt
and Jan 1950 is now -1.32. The latter is currently in this vignette
as oni_new:
oni_new
#> # A tibble: 920 × 4
#> year month value anomaly
#> <dbl> <dbl> <dbl> <dbl>
#> 1 1950 1 25.0 -1.32
#> 2 1950 2 25.4 -1.2
#> 3 1950 3 25.9 -1.12
#> 4 1950 4 26.2 -1.08
#> 5 1950 5 26.4 -1.1
#> 6 1950 6 26.3 -0.9
#> 7 1950 7 26.2 -0.62
#> 8 1950 8 25.9 -0.57
#> 9 1950 9 25.8 -0.56
#> 10 1950 10 25.6 -0.62
#> # ℹ 910 more rows
filter(oni_new, year == 2026)
#> # A tibble: 8 × 4
#> year month value anomaly
#> <dbl> <dbl> <dbl> <dbl>
#> 1 2026 1 26.2 -0.39
#> 2 2026 2 26.6 -0.21
#> 3 2026 3 27.3 0.11
#> 4 2026 4 28.1 0.46
#> 5 2026 5 28.7 0.95
#> 6 2026 6 29.0 1.39
#> 7 2026 7 29.1 1.8
#> 8 2026 8 29.1 2.16Original oni:
and
oni_new: