You can run this notebook in a live session binder badge or view it on Github.

PredictionEnsemble Objects#

One of the major features of climpred is our objects that are based upon the PredictionEnsemble class. We supply users with a HindcastEnsemble or PerfectModelEnsemble object. We encourage users to take advantage of these high-level objects, which wrap all of our core functions.

Briefly, we consider a HindcastEnsemble to be one that is initialized from some observational-like product (e.g., assimilated data, reanalysis products, or a model reconstruction). Thus, this object is built around comparing the initialized ensemble to various observational products. In contrast, a PerfectModelEnsemble is one that is initialized off of a model control simulation. These forecasting systems are not meant to be compared directly to real-world observations. Instead, they provide a contained model environment with which to theoretically study the limits of predictability. You can read more about the terminology used in climpred here.

Let’s create a demo object to explore some of the functionality and why they are much smoother to use than direct function calls.

# linting
%load_ext nb_black
%load_ext lab_black
%matplotlib inline
import matplotlib.pyplot as plt
import xarray as xr
import numpy as np

from climpred import HindcastEnsemble, PerfectModelEnsemble
from climpred.tutorial import load_dataset
import climpred

We can now pull in some sample data that is packaged with climpred. We’ll start out with a HindcastEnsemble demo, followed by a PerfectModelEnsemble case.

HindcastEnsemble#

initialized = climpred.tutorial.load_dataset(
    "CESM-DP-SST"
)  # CESM-DPLE hindcast ensemble output.
obs = climpred.tutorial.load_dataset("ERSST")  # ERSST observations.

We need to add a units attribute to the hindcast ensemble so that climpred knows how to interpret the lead units.

initialized["lead"].attrs["units"] = "years"

Now we instantiate the HindcastEnsemble object and append all of our products to it.

hindcast = HindcastEnsemble(
    initialized
)  # Instantiate object by passing in our initialized ensemble.
hindcast
/Users/aaron.spring/Coding/climpred/climpred/utils.py:191: UserWarning: Assuming annual resolution starting Jan 1st due to numeric inits. Please change ``init`` to a datetime if it is another resolution. We recommend using xr.CFTimeIndex as ``init``, see https://climpred.readthedocs.io/en/stable/setting-up-data.html.
  warnings.warn(

climpred.HindcastEnsemble

<Initialized Ensemble>
Dimensions:     (lead: 10, member: 10, init: 64)
Coordinates:
  * lead        (lead) int32 1 2 3 4 5 6 7 8 9 10
  * member      (member) int32 1 2 3 4 5 6 7 8 9 10
  * init        (init) object 1954-01-01 00:00:00 ... 2017-01-01 00:00:00
    valid_time  (lead, init) object 1955-01-01 00:00:00 ... 2027-01-01 00:00:00
Data variables:
    SST         (init, lead, member) float64 -0.2404 -0.2085 ... 0.7442 0.7384

Now we just use HindcastEnsemble.add_observations() to attach other objects. See the API here. Note that we strive to make our conventions follow those of xarray. For example, we don’t allow inplace operations. One has to run hindcast = hindcast.add_observations(...) to modify the object upon later calls rather than just hindcast.add_observations(...).

hindcast = hindcast.add_observations(obs)
hindcast
/Users/aaron.spring/Coding/climpred/climpred/utils.py:191: UserWarning: Assuming annual resolution starting Jan 1st due to numeric inits. Please change ``init`` to a datetime if it is another resolution. We recommend using xr.CFTimeIndex as ``init``, see https://climpred.readthedocs.io/en/stable/setting-up-data.html.
  warnings.warn(

climpred.HindcastEnsemble

<Initialized Ensemble>
Dimensions:     (lead: 10, member: 10, init: 64)
Coordinates:
  * lead        (lead) int32 1 2 3 4 5 6 7 8 9 10
  * member      (member) int32 1 2 3 4 5 6 7 8 9 10
  * init        (init) object 1954-01-01 00:00:00 ... 2017-01-01 00:00:00
    valid_time  (lead, init) object 1955-01-01 00:00:00 ... 2027-01-01 00:00:00
Data variables:
    SST         (init, lead, member) float64 -0.2404 -0.2085 ... 0.7442 0.7384
<Observations>
Dimensions:  (time: 61)
Coordinates:
  * time     (time) object 1955-01-01 00:00:00 ... 2015-01-01 00:00:00
Data variables:
    SST      (time) float32 17.79 17.84 18.0 18.03 ... 18.41 18.44 18.54 18.64

You can apply most standard xarray functions directly to our objects! climpred will loop through the objects and apply the function to all applicable xarray.Dataset within the object. If you reference a dimension that doesn’t exist for the given xarray.Dataset, it will ignore it. This is useful, since the initialized ensemble is expected to have dimension init, while other products have dimension time (see more here).

Let’s start by taking the ensemble mean of the initialized ensemble. Just using deterministic metrics here, so we don’t need the individual ensemble members. Note that above our initialized ensemble had a member dimension, and now it is reduced. Those xarray functions do not raise errors such as ValueError, KeyError, DimensionError, but show respective warnings, which can be filtered away with warnings.filterwarnings("ignore").

hindcast = hindcast.mean("member")
hindcast
/Users/aaron.spring/Coding/climpred/climpred/classes.py:713: UserWarning: Error due to verification/control/uninitialized: xr.mean(('member',), {}) failed
ValueError: Dataset does not contain the dimensions: ['member']
  warnings.warn(

climpred.HindcastEnsemble

<Initialized Ensemble>
Dimensions:     (lead: 10, init: 64)
Coordinates:
  * lead        (lead) int32 1 2 3 4 5 6 7 8 9 10
  * init        (init) object 1954-01-01 00:00:00 ... 2017-01-01 00:00:00
    valid_time  (lead, init) object 1955-01-01 00:00:00 ... 2027-01-01 00:00:00
Data variables:
    SST         (init, lead) float64 -0.2121 -0.1637 -0.1206 ... 0.7286 0.7532
<Observations>
Dimensions:  (time: 61)
Coordinates:
  * time     (time) object 1955-01-01 00:00:00 ... 2015-01-01 00:00:00
Data variables:
    SST      (time) float32 17.79 17.84 18.0 18.03 ... 18.41 18.44 18.54 18.64

Arithmetic Operations with PredictionEnsemble Objects#

PredictionEnsemble objects support arithmetic operations, i.e., +, -, /, *. You can perform these operations on a HindcastEnsemble or PerfectModelEnsemble by pairing the operation with an int, float, np.ndarray, xarray.DataArray, xarray.Dataset, or with another PredictionEnsemble object.

An obvious application would be to area-weight an initialized ensemble and all of its associated datasets simultaneously.

dple3d = climpred.tutorial.load_dataset("CESM-DP-SST-3D")
verif3d = climpred.tutorial.load_dataset("FOSI-SST-3D")
area = dple3d["TAREA"]

Here, we load in a subset of CESM-DPLE over the eastern tropical Pacific. The file includes TAREA, which describes the area of each cell on the curvilinear mesh.

hindcast3d = HindcastEnsemble(dple3d)
hindcast3d = hindcast3d.add_observations(verif3d)
hindcast3d
/Users/aaron.spring/Coding/climpred/climpred/utils.py:191: UserWarning: Assuming annual resolution starting Jan 1st due to numeric inits. Please change ``init`` to a datetime if it is another resolution. We recommend using xr.CFTimeIndex as ``init``, see https://climpred.readthedocs.io/en/stable/setting-up-data.html.
  warnings.warn(
/Users/aaron.spring/Coding/climpred/climpred/utils.py:191: UserWarning: Assuming annual resolution starting Jan 1st due to numeric inits. Please change ``init`` to a datetime if it is another resolution. We recommend using xr.CFTimeIndex as ``init``, see https://climpred.readthedocs.io/en/stable/setting-up-data.html.
  warnings.warn(

climpred.HindcastEnsemble

<Initialized Ensemble>
Dimensions:     (init: 64, lead: 10, nlat: 37, nlon: 26)
Coordinates:
    TLAT        (nlat, nlon) float64 -9.75 -9.75 -9.75 ... -0.1336 -0.1336
    TLONG       (nlat, nlon) float64 250.8 251.9 253.1 ... 276.7 277.8 278.9
  * init        (init) object 1954-01-01 00:00:00 ... 2017-01-01 00:00:00
  * lead        (lead) int32 1 2 3 4 5 6 7 8 9 10
    TAREA       (nlat, nlon) float64 3.661e+13 3.661e+13 ... 3.714e+13 3.714e+13
    valid_time  (lead, init) object 1955-01-01 00:00:00 ... 2027-01-01 00:00:00
Dimensions without coordinates: nlat, nlon
Data variables:
    SST         (init, lead, nlat, nlon) float32 -0.3323 -0.3238 ... 1.179 1.123
<Observations>
Dimensions:  (time: 68, nlat: 37, nlon: 26)
Coordinates:
    TLAT     (nlat, nlon) float64 -9.75 -9.75 -9.75 ... -0.1336 -0.1336 -0.1336
    TLONG    (nlat, nlon) float64 250.8 251.9 253.1 254.2 ... 276.7 277.8 278.9
  * time     (time) object 1948-01-01 00:00:00 ... 2015-01-01 00:00:00
    TAREA    (nlat, nlon) float64 3.661e+13 3.661e+13 ... 3.714e+13 3.714e+13
Dimensions without coordinates: nlat, nlon
Data variables:
    SST      (time, nlat, nlon) float32 25.53 25.43 25.35 ... 27.03 27.1 27.05

Now we can perform an area-weighting operation with the HindcastEnsemble object and the area xarray.DataArray. climpred cycles through all of the datasets appended to the HindcastEnsemble and applies them. You can see below that the dimensionality is reduced to single time series without spatial information.

hindcast3d_aw = (hindcast3d * area).sum(["nlat", "nlon"]) / area.sum(["nlat", "nlon"])
hindcast3d_aw

climpred.HindcastEnsemble

<Initialized Ensemble>
Dimensions:     (init: 64, lead: 10)
Coordinates:
  * init        (init) object 1954-01-01 00:00:00 ... 2017-01-01 00:00:00
  * lead        (lead) int32 1 2 3 4 5 6 7 8 9 10
    valid_time  (lead, init) object 1955-01-01 00:00:00 ... 2027-01-01 00:00:00
Data variables:
    SST         (init, lead) float64 -0.3539 0.1947 0.3623 ... 0.662 1.016 1.249
<Observations>
Dimensions:  (time: 68)
Coordinates:
  * time     (time) object 1948-01-01 00:00:00 ... 2015-01-01 00:00:00
Data variables:
    SST      (time) float64 24.76 24.48 23.73 24.68 ... 24.78 24.21 24.92 25.95

NOTE: Be careful with the arithmetic operations. Some of the behavior can be unexpected in combination with the fact that generic xarray methods can be applied to climpred objects. For instance, one might be interested in removing a climatology from the verification data to move it to anomaly space. It’s safest to do anything like climatology removal before constructing climpred objects.

Naturally, they would remove some climatology time slice as we do here below. However, note that in the below example, the intialized ensemble returns all zeroes for SST. The reasoning here is that when hindcast.sel(time=...) is called, climpred only applies that slicing to datasets that include the time dimension. Thus, it skips the initialized ensemble and returns the original dataset untouched. This feature is advantageous for cases like hindcast.mean('member'), where it takes the ensemble mean in all cases that ensemble members exist. So when it performs hindcast - hindcast.sel(time=...), it subtracts the identical initialized ensemble from itself returning all zeroes.

In short, any sort of bias correcting or drift correction can also be done prior to instantiating a PredictionEnsemble object. Alternatively, detrending or removing a mean state can also be done after instantiating a PredictionEnsemble object. Also consider HindcastEnsemble.remove_bias() and HindcastEnsemble.remove_seasonality(). But beware of unintuitive behaviour. Removing a time anomaly in PredictionEnsemble, does not modify initialized and therefore returns all 0s.

hindcast3d - hindcast3d.sel(time=slice("1960", "2014")).mean("time")
/Users/aaron.spring/Coding/climpred/climpred/classes.py:707: UserWarning: Error due to initialized:  xr.sel((), {'time': slice('1960', '2014', None)}) failed
KeyError: 'time is not a valid dimension or coordinate'
  warnings.warn(f"Error due to initialized:  {msg}")
/Users/aaron.spring/Coding/climpred/climpred/classes.py:707: UserWarning: Error due to initialized:  xr.mean(('time',), {}) failed
ValueError: Dataset does not contain the dimensions: ['time']
  warnings.warn(f"Error due to initialized:  {msg}")

climpred.HindcastEnsemble

<Initialized Ensemble>
Dimensions:     (nlat: 37, nlon: 26, init: 64, lead: 10)
Coordinates:
    TLAT        (nlat, nlon) float64 -9.75 -9.75 -9.75 ... -0.1336 -0.1336
    TLONG       (nlat, nlon) float64 250.8 251.9 253.1 ... 276.7 277.8 278.9
  * init        (init) object 1954-01-01 00:00:00 ... 2017-01-01 00:00:00
  * lead        (lead) int32 1 2 3 4 5 6 7 8 9 10
    TAREA       (nlat, nlon) float64 3.661e+13 3.661e+13 ... 3.714e+13 3.714e+13
    valid_time  (lead, init) object 1955-01-01 00:00:00 ... 2027-01-01 00:00:00
Dimensions without coordinates: nlat, nlon
Data variables:
    SST         (init, lead, nlat, nlon) float32 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0
<Observations>
Dimensions:  (nlat: 37, nlon: 26, time: 68)
Coordinates:
    TLAT     (nlat, nlon) float64 -9.75 -9.75 -9.75 ... -0.1336 -0.1336 -0.1336
    TLONG    (nlat, nlon) float64 250.8 251.9 253.1 254.2 ... 276.7 277.8 278.9
  * time     (time) object 1948-01-01 00:00:00 ... 2015-01-01 00:00:00
    TAREA    (nlat, nlon) float64 3.661e+13 3.661e+13 ... 3.714e+13 3.714e+13
Dimensions without coordinates: nlat, nlon
Data variables:
    SST      (time, nlat, nlon) float32 0.01611 0.01459 0.0161 ... 1.543 1.49

To fix this always handle all PredictionEnsemble datasets initialized with dimensions lead or init and observations/control with dimension time at the same time to avoid these zeros.

hindcast - hindcast.sel(time=slice("1960", "2014")).mean("time").sel(
    init=slice("1960", "2014")
).mean("init")
/Users/aaron.spring/Coding/climpred/climpred/classes.py:707: UserWarning: Error due to initialized:  xr.sel((), {'time': slice('1960', '2014', None)}) failed
KeyError: 'time is not a valid dimension or coordinate'
  warnings.warn(f"Error due to initialized:  {msg}")
/Users/aaron.spring/Coding/climpred/climpred/classes.py:707: UserWarning: Error due to initialized:  xr.mean(('time',), {}) failed
ValueError: Dataset does not contain the dimensions: ['time']
  warnings.warn(f"Error due to initialized:  {msg}")
/Users/aaron.spring/Coding/climpred/climpred/classes.py:718: UserWarning: xr.sel((), {'init': slice('1960', '2014', None)}) failed
KeyError: 'init is not a valid dimension or coordinate'
  warnings.warn(msg)
/Users/aaron.spring/Coding/climpred/climpred/classes.py:718: UserWarning: xr.mean(('init',), {}) failed
ValueError: Dataset does not contain the dimensions: ['init']
  warnings.warn(msg)

climpred.HindcastEnsemble

<Initialized Ensemble>
Dimensions:     (lead: 10, init: 64)
Coordinates:
  * lead        (lead) int32 1 2 3 4 5 6 7 8 9 10
  * init        (init) object 1954-01-01 00:00:00 ... 2017-01-01 00:00:00
    valid_time  (lead, init) object 1955-01-01 00:00:00 ... 2027-01-01 00:00:00
Data variables:
    SST         (init, lead) float64 -0.2046 -0.1688 -0.1335 ... 0.6326 0.6463
<Observations>
Dimensions:  (time: 61)
Coordinates:
  * time     (time) object 1955-01-01 00:00:00 ... 2015-01-01 00:00:00
Data variables:
    SST      (time) float32 -0.3864 -0.3373 -0.17 ... 0.2632 0.3611 0.4653

Note: Thinking in initialization space is not very intuitive and such combined init and time operations can lead to unanticipated changes in the PredictionEnsemble. The safest way is subtracting means before instantiating PredictionEnsemble or use HindcastEnsemble.remove_bias().

Visualizing PredictionEnsemble#

PredictionEnsemble.plot() showings all datasets associated if only member, init and lead dimensions are present.

hindcast.plot()
<AxesSubplot:xlabel='validity time', ylabel='SST'>
_images/57e2d60c1da77ab206a4d3d3366fa400e8990d05b0d61e45cdc22df50afea10c.png

We have a huge bias because the initialized data is already converted to an anomaly, but uninitialized and observations is not. We also have a trend in all of our products, so we could also detrend them as well.

Detrend#

We can leverage xarray’s .map() function to apply/map a function to all variables in our datasets. We use climpred.stats.rm_poly() to remove the trend.

from climpred.stats import rm_poly

hindcast_detrended = hindcast.map(rm_poly, deg=2, dim="init_or_time")
hindcast_detrended.plot()
<AxesSubplot:xlabel='validity time', ylabel='SST'>
_images/ea416f895bcde596ecec4b4cca325a8c1fbc2b87d24a90fc72456c0b0318b37f.png

And it looks like everything got detrended by a quadratic fit! That wasn’t too hard.

Verify#

Now that we’ve done our pre-processing, let’s quickly compute some metrics. Check the metrics for all the keywords you can use. The API is currently pretty simple for the HindcastEnsemble. You can essentially compute standard skill metrics and reference forecasts, here persistence.

hindcast_detrended.verify(
    metric="mse",
    comparison="e2o",
    dim="init",
    alignment="same_verif",
    reference="persistence",
)
<xarray.Dataset>
Dimensions:  (skill: 2, lead: 10)
Coordinates:
  * lead     (lead) int32 1 2 3 4 5 6 7 8 9 10
  * skill    (skill) <U11 'initialized' 'persistence'
Data variables:
    SST      (skill, lead) float64 0.003274 0.004149 ... 0.01109 0.008786
Attributes:
    prediction_skill_software:     climpred https://climpred.readthedocs.io/
    skill_calculated_by_function:  HindcastEnsemble.verify()
    number_of_initializations:     64
    alignment:                     same_verif
    metric:                        mse
    comparison:                    e2o
    dim:                           init
    reference:                     ['persistence']

Here we leverage xarray’s plotting method to compute Mean Absolute Error and the Anomaly Correlation Coefficient against the ERSST observations, as well as the equivalent metrics computed for persistence forecasts for each of those metrics.

metrics = ["mae", "acc"]
for metric in metrics:
    hindcast_detrended.verify(
        metric=metric,
        comparison="e2o",
        dim="init",
        alignment="same_verif",
        reference="persistence",
    ).assign_coords(metric=metric.upper()).SST.plot(hue="skill")
    plt.suptitle("CESM Decadal Prediction Large Ensemble Global SSTs", fontsize=16)
    plt.show()
_images/3312961f791f23226b84499feceb2ba729faa2109a15f6b42e21bf10cb979da7.png _images/96ce0850182e3f1b48bc0a5f544d8c7095028f2c18dc6254ae1fb1839973ac91.png

PerfectModelEnsemble#

We’ll now play around a bit with the PerfectModelEnsemble object, using sample data from the MPI-ESM perfect model configuration.

initialized = load_dataset("MPI-PM-DP-1D")  # initialized ensemble from MPI
control = load_dataset("MPI-control-1D")  # base control run that initialized it

initialized["lead"].attrs["units"] = "years"
pm = climpred.PerfectModelEnsemble(initialized).add_control(control)
pm
/Users/aaron.spring/Coding/climpred/climpred/utils.py:191: UserWarning: Assuming annual resolution starting Jan 1st due to numeric inits. Please change ``init`` to a datetime if it is another resolution. We recommend using xr.CFTimeIndex as ``init``, see https://climpred.readthedocs.io/en/stable/setting-up-data.html.
  warnings.warn(
/Users/aaron.spring/Coding/climpred/climpred/utils.py:191: UserWarning: Assuming annual resolution starting Jan 1st due to numeric inits. Please change ``init`` to a datetime if it is another resolution. We recommend using xr.CFTimeIndex as ``init``, see https://climpred.readthedocs.io/en/stable/setting-up-data.html.
  warnings.warn(

climpred.PerfectModelEnsemble

<Initialized Ensemble>
Dimensions:     (period: 5, lead: 20, area: 3, init: 12, member: 10)
Coordinates:
  * lead        (lead) int64 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
  * period      (period) object 'DJF' 'JJA' 'MAM' 'SON' 'ym'
  * area        (area) object 'global' 'North_Atlantic' 'North_Atlantic_SPG'
  * init        (init) object 3014-01-01 00:00:00 ... 3257-01-01 00:00:00
  * member      (member) int64 0 1 2 3 4 5 6 7 8 9
    valid_time  (lead, init) object 3015-01-01 00:00:00 ... 3277-01-01 00:00:00
Data variables:
    tos         (period, lead, area, init, member) float32 17.38 17.58 ... 8.955
    sos         (period, lead, area, init, member) float32 34.38 34.37 ... 34.59
    AMO         (period, lead, area, init, member) float32 0.1675 ... 0.06075
<Control Simulation>
Dimensions:  (period: 5, time: 300, area: 3)
Coordinates:
  * time     (time) object 3000-01-01 00:00:00 ... 3299-01-01 00:00:00
  * period   (period) object 'DJF' 'JJA' 'MAM' 'SON' 'ym'
  * area     (area) object 'global' 'North_Atlantic' 'North_Atlantic_SPG'
Data variables:
    tos      (period, time, area) float32 17.38 8.76 7.321 ... 17.23 10.84 8.346
    sos      (period, time, area) float32 34.37 33.5 34.72 ... 34.35 33.38 34.45
    AMO      (period, time, area) float32 0.17 0.17 0.17 ... 0.07905 0.07905

Our objects are carrying sea surface temperature (tos), sea surface salinity (sos), and the Atlantic Multidecadal Oscillation index (AMO). Say we just want to look at skill metrics for temperature and salinity over the North Atlantic in JJA. We can just call a few easy xarray commands to filter down our object.

pm = pm[["tos", "sos"]].sel(area="North_Atlantic", period="JJA", drop=True)

Now we can easily compute for a host of metrics. Here I just show a number of deterministic skill metrics comparing all individual members to the initialized ensemble mean. See comparisons for more information on the comparison keyword.

METRICS = ["mse", "rmse", "mae", "acc", "nmse", "nrmse", "nmae", "msss"]

result = []
for metric in METRICS:
    result.append(pm.verify(metric=metric, comparison="m2e", dim=["init", "member"]))

result = xr.concat(result, "metric")
result["metric"] = METRICS

# Leverage the `xarray` plotting wrapper to plot all results at once.
result.to_array().plot(
    col="metric", hue="variable", col_wrap=4, sharey=False, sharex=True
)
<xarray.plot.facetgrid.FacetGrid at 0x14febcb20>
_images/c419c6877d613e206f7d6b66c45e49a34bb62484da6b182ab0b800381e90ae23.png

It is useful to compare the initialized ensemble to an uninitialized run. See terminology for a description on uninitialized simulations. This gives us information about how initialization leads to enhanced predictability over knowledge of external forcing, whereas a comparison to persistence just tells us how well a dynamical forecast simulation does in comparison to a naive method. We can use the PerfectModelEnsemble.generate_uninitialized() to resample the control run and create a pseudo-ensemble that approximates what an uninitialized ensemble would look like.

pm = pm.generate_uninitialized()
pm

climpred.PerfectModelEnsemble

<Initialized Ensemble>
Dimensions:     (lead: 20, init: 12, member: 10)
Coordinates:
  * lead        (lead) int64 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
  * init        (init) object 3014-01-01 00:00:00 ... 3257-01-01 00:00:00
  * member      (member) int64 0 1 2 3 4 5 6 7 8 9
    valid_time  (lead, init) object 3015-01-01 00:00:00 ... 3277-01-01 00:00:00
Data variables:
    tos         (lead, init, member) float32 13.46 13.64 13.72 ... 13.55 13.57
    sos         (lead, init, member) float32 33.18 33.15 33.05 ... 33.18 33.26
<Control Simulation>
Dimensions:  (time: 300)
Coordinates:
  * time     (time) object 3000-01-01 00:00:00 ... 3299-01-01 00:00:00
Data variables:
    tos      (time) float32 13.5 13.74 13.78 13.86 ... 13.12 12.92 13.08 13.47
    sos      (time) float32 33.23 33.19 33.2 33.21 ... 33.15 33.22 33.16 33.18
<Uninitialized>
Dimensions:     (lead: 20, init: 12, member: 10)
Coordinates:
    valid_time  (lead, init) object 3015-01-01 00:00:00 ... 3277-01-01 00:00:00
  * lead        (lead) int64 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
  * init        (init) object 3014-01-01 00:00:00 ... 3257-01-01 00:00:00
  * member      (member) int64 0 1 2 3 4 5 6 7 8 9
Data variables:
    tos         (lead, init, member) float32 13.01 12.84 12.84 ... 13.44 13.57
    sos         (lead, init, member) float32 33.08 33.08 33.0 ... 33.16 33.32

pm = pm[["sos"]]  # Just assess for salinity.

Here we plot the ACC for the initialized, uninitialized, climatology and persistence forecasts for North Atlantic sea surface salinity in the JJA summer season.

acc = pm.verify(
    metric="acc",
    comparison="m2e",
    dim=["init", "member"],
    reference=["persistence", "uninitialized", "climatology"],
)
acc.sos.plot(hue="skill")
plt.title("North Atlantic Surface Salinity JJA ACC")
plt.show()
_images/924a8fd5b79d3d38f8e69697beb90f7de12e4ff0ad616e9a6b239a351dd146d8.png