Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Open In Colab Open In Kaggle

Photo Credits: Sea Foam by Ivan Bandura licensed under the Unsplash License

A frequently asked question related to this work is “Which mixing processes matter most for climate?” As with many alluringly comprehensive sounding questions, the answer is “it depends.”
\qquad MacKinnon, Jennifer A., et al.
\qquad"Climate process team on internal wave–driven ocean mixing."
\qquad Bulletin of the American Meteorological Society 98.11 (2017): 2429-2454.

In week 4’s final notebook, we will perform clustering to identify regimes in data taken from the realistic numerical ocean model Estimating the Circulation and Climate of the Ocean. Sonnewald et al. point out that finding robust regimes is intractable with a naïve approach, so we will be using reduced dimensionality data.

It is worth pointing out, however, that the reduction was done with an equation instead of one of the algorithms we discussed this week. If you’re interested in the full details, you can check out Sonnewald et al. (2019)

Setup

First, let’s import a few common modules, ensure MatplotLib plots figures inline and prepare a function to save the figures. We also check that Python 3.5 or later is installed (although Python 2.x may work, it is deprecated so we strongly recommend you use Python 3 instead), as well as Scikit-Learn ≥0.20.

Here we’re going to import the StandardScaler function from scikit’s preprocessing tools, import the scikit clustering library, and set up the colormap that we will use when plotting.

Data Preprocessing

The first thing we need to do is retrieve the list of files we’ll be working on. We’ll rely on pooch to access the files hosted on the cloud.

/home/runner/.cache/pooch/d70ddd3cea8fe695fa7b99473a0ec8b8-S4_3_THOR_data.zip.unzip/noiseMask.npy
/home/runner/.cache/pooch/d70ddd3cea8fe695fa7b99473a0ec8b8-S4_3_THOR_data.zip.unzip/curlTau.npy
/home/runner/.cache/pooch/d70ddd3cea8fe695fa7b99473a0ec8b8-S4_3_THOR_data.zip.unzip/curlA.npy
/home/runner/.cache/pooch/d70ddd3cea8fe695fa7b99473a0ec8b8-S4_3_THOR_data.zip.unzip/curlB.npy
/home/runner/.cache/pooch/d70ddd3cea8fe695fa7b99473a0ec8b8-S4_3_THOR_data.zip.unzip/BPT.npy
/home/runner/.cache/pooch/d70ddd3cea8fe695fa7b99473a0ec8b8-S4_3_THOR_data.zip.unzip/curlCori.npy

And now that we have a set of files to load, let’s set up a dictionary with the variable names as keys and the data in numpy array format as the values.

Varname: noiseMask       Shape: (360, 720)
Varname: curlTau         Shape: (360, 720)
Varname: curlA           Shape: (360, 720)
Varname: curlB           Shape: (360, 720)
Varname: BPT             Shape: (360, 720)
Varname: curlCori        Shape: (360, 720)

We now have a dictionary that uses the filename as the key! Feel free to explore the data (e.g., loading the keys, checking the shape of the arrays, plotting)

We’re eventually going to have an array of cluster classes that we’re going to use to label dynamic regimes in the ocean. Let’s make an array full of NaN (not-a-number) values that has the same shape as our other variables and store it in the data dictionary.

Reformatting as Xarray

In the original paper, this data was loaded as numpy arrays. However, we’ll take this opportunity to demonstrate the same procedure while relying on xarray. First, let’s instantiate a blank dataset.

Q1) Make a blank xarray dataset.

Hint: Look at the xarray documentation

Image taken from the xarray Data Structure documentation

In order to build the dataset, we’re going to need a set of coordinate vectors that help us map out our data! For our data, we have two axes corresponding to longitude (λ\lambda) and latitude (ϕ\phi).

We don’t know much about how many lat/lon points we have, so let’s explore one of the variables to make sense of the data the shape of one of the numpy arrays.

Q2) Visualize the data using a plot and printing the shape of the data to the console output.

Now that we know how the resolution of our data, we can prepare a set of axis arrays. We will use these to organize the data we will feed into the dataset.

Q3) Prepare the latitude and longitude arrays to be used as axes for our dataset

Hint 1: You can build ordered numpy arrays using, e.g., numpy.linspace and numpy.arange

Hint 2: You can rely on the data_shape variable we loaded previously to know how many points you need along each axis

Now that we have the axes we need, we can build xarray data arrays for each data variable. Since we’ll be doing it several times, let’s go ahead and defined a function that does this for us!

Q4) Define a function that takes in: 1) an array name, 2) a numpy array, 3) a lat vector, and 4) a lon vector. The function should return a dataArray with lat-lon as the coordinate dimensions

We’re now ready to build our data array! Let’s iterate through the items and merge our blank dataset with the data arrays we create.

Q5) Build the dataset from the data dictionary

Hint: We’ll be using the xarray merge command to put everything together.

Congratulations! You should now have a nicely set up xarray dataset. This let’s you access a ton of nice features, e.g.:

Data plotting by calling, e.g., ds.BPT.plot.imshow(cmap='ocean')

Find statistical measures of all variables at once! (e.g.: ds.std(), ds.mean())

Now we want to find clusters of data considering each grid point as a datapoint with 5 dimensional data. However, we went through a lot of work to get the data nicely associated with a lat and lon - do we really want to undo that?

Luckily, xarray developers foresaw the need to group dimensions together. Let’s create a ‘flat’ version of our dataset using the stack method. Let’s make a flattened version of our dataset.

Q6) Store a flattened version of our dataset

Hint 1: You’ll need to pass a dictionary with the ‘new’ stacked dimension name as the key and the ‘flattened’ dimensions as the values.

Hint 2: xarrays have a ‘.values’ attribute that return their data as a numpy array.

So far we’ve ignored an important point - we’re supposed to have 5 variables, not 6! As you may have guessed, noiseMask helps us throw away data we dont want (e.g., from land mass or bad pixels).

We’re now going to clean up the stacked dataset using the noise mask. Relax and read through the code, since there won’t be a question in this part :)

We now have several thousand points which we want to divide into clusters using the kmeans clustering algorithm (you can check out the documentation for scikit’s implementation of kmeans here).

You’ll note that the algorithm expects the input data X to be fed as (n_samples, n_features). This is the opposite of what we have! Let’s go ahead and make a copy to a numpy array has the axes in the right order.

You’ll need xarray’s .to_array() method and .values parameter, as well as numpy’s .moveaxis method.

Q7) Load the datapoints into a numpy array following the convention where the 0th axis corresponds to the samples and the 1st axis corresponds to the features.

Kmeans clustering

In previous classes we discussed the importance of the scaling the data before implementing our algorithms. Now that our data is all but ready to be fed into an algorithm, let’s make sure that it’s been scaled.

Q8) Scale the input data

Hint 1: Import the StandardScaler class from scikit and instantiate it

Hint 2: Update the input array to the one returned by the .fit_transform(X) method

Now we’re finally ready to train our algorithm! Let’s load up the kmeans model and find clusters in our data.

Q9) Instantiate the kmeans clustering algorithm, and then fit it using 50 clusters, trying out 10 different initial centroids.

Hint 1: sklearn.cluster was imported as cluser during the notebook setup! Here is the scikit KMeans documentation.

Hint 2: Use the fit_predict method to organize the data into clusters

*Warning! : Fitting the data may take some time (under a minute during the testing of the notebook)

We now have a set of cluster labels that group the data into 50 similar groups. Let’s store it in our stacked dataset!

Visualization

We now have a set of labels, but they’re stored in a flattened array. Since we’d like to see the data as a map, we still have some work to do. Let’s go back to a 2D representation of our values.

Q10) Turn the flattened xarray back into a set of 2D fields

Hint: xarrays have an .unstack method that you will find to be very useful for this.

Now we have an unstacked dataset, and can now easily plot out the clusters we found!

Q11) Plot the ‘cluster’ variable using the built-in xarray function

Hint: .plot() link text let’s you access the xarray implementations of pcolormesh and imshow.

Compare your results to those from the paper:

We now want to find the 5 most common regimes, and group the rest. This isn’t straightforward, so we’ve gone ahead and prepared the code for you. Run through it and try to understand what the code is doing!

Compare it to the regimes found in the paper:

The authors then went on to train neural networks to infer in-depth dynamics from data that is largely readily available from for example CMIP6 models, using NN methods to infer the source of predictive skill and to apply the trained Ensemble MLP to a climate model in order to assess circulation changes under global heating.

For our purposes, however, we will say goodbye to THOR at this point 😃

References
  1. Sonnewald, M., Wunsch, C., & Heimbach, P. (2019). Unsupervised Learning Reveals Geography of Global Ocean Dynamical Regions. Earth and Space Science, 6(5), 784–794. 10.1029/2018ea000519