Open In Colab   Open in Kaggle

Tutorial 1: Creating DataArrays and Datasets to Assess Global Climate Data#

Week 1, Day 1, Climate System Overview

Content creators: Sloane Garelick, Julia Kent

Content reviewers: Yosmely Bermúdez, Katrina Dobson, Younkap Nina Duplex, Danika Gupta, Maria Gonzalez, Will Gregory, Nahid Hasan, Sherry Mi, Beatriz Cosenza Muralles, Jenna Pearson, Chi Zhang, Ohad Zivan

Content editors: Jenna Pearson, Chi Zhang, Ohad Zivan

Production editors: Wesley Banfield, Jenna Pearson, Chi Zhang, Ohad Zivan

Our 2023 Sponsors: NASA TOPS, Google DeepMind, NFDI4Earth!

project pythia#

Pythia credit: Rose, B. E. J., Kent, J., Tyle, K., Clyne, J., Banihirwe, A., Camron, D., May, R., Grover, M., Ford, R. R., Paul, K., Morley, J., Eroglu, O., Kailyn, L., & Zacharias, A. (2023). Pythia Foundations (Version v2023.05.01) https://zenodo.org/record/8065851

CMIP.png#

Tutorial Objectives#

As you just learned in the Introduction to Climate video, variations in global climate involve various forcings, feedbacks, and interactions between multiple processes and systems. Because of this complexity, global climate datasets are often very large with multiple dimensions and variables.

One useful computational tool for organizing, analyzing and interpreting large global datasets is Xarray, an open source project and Python package that makes working with labelled multi-dimensional arrays simple and efficient.

In this first tutorial, we will use the DataArray and Dataset objects, which are used to represent and manipulate spatial data, to practice organizing large global climate datasets and to understand variations in Earth’s climate system.

Setup#

Similar to numpy, np; pandas, pd; you may often encounter xarray imported within a shortened namespace as xr.

# imports
import numpy as np
import pandas as pd
import xarray as xr
import matplotlib.pyplot as plt

Figure Settings#

# @title Figure Settings
import ipywidgets as widgets  # interactive display

%config InlineBackend.figure_format = 'retina'
plt.style.use(
    "https://raw.githubusercontent.com/ClimateMatchAcademy/course-content/main/cma.mplstyle"
)

Video 1: Introduction to Climate#

Tutorial slides#

These are the slides for the videos in all tutorials today

Introducing the DataArray and Dataset#

Xarray expands on the capabilities on NumPy arrays, providing a lot of streamlined data manipulation. It is similar in that respect to Pandas, but whereas Pandas excels at working with tabular data, Xarray is focused on N-dimensional arrays of data (i.e. grids). Its interface is based largely on the netCDF data model (variables, attributes, and dimensions), but it goes beyond the traditional netCDF interfaces to provide functionality similar to netCDF-java’s Common Data Model (CDM).

Section 1: Creation of a DataArray Object#

The DataArray is one of the basic building blocks of Xarray (see docs here). It provides a numpy.ndarray-like object that expands to provide two critical pieces of functionality:

  1. Coordinate names and values are stored with the data, making slicing and indexing much more powerful

  2. It has a built-in container for attributes

Here we’ll initialize a DataArray object by wrapping a plain NumPy array, and explore a few of its properties.

Section 1.1: Generate a Random Numpy Array#

For our first example, we’ll just create a random array of “temperature” data in units of Kelvin:

rand_data = 283 + 5 * np.random.randn(5, 3, 4)
rand_data
array([[[284.01981799, 288.60416752, 280.164391  , 276.63442792],
        [290.44831894, 282.59011219, 284.5380792 , 290.42857619],
        [287.15236197, 287.51933151, 275.01154359, 293.17913947]],

       [[274.39799007, 282.79978578, 283.32264251, 268.9884763 ],
        [288.95809125, 284.92039125, 278.51384981, 287.85576971],
        [277.65689332, 278.43746795, 282.13393524, 285.93729069]],

       [[280.46408865, 283.60850813, 285.89537151, 273.99139699],
        [284.64515918, 291.18653695, 279.78812068, 278.50048079],
        [276.85608999, 286.21271191, 272.07878347, 282.29925238]],

       [[282.01366621, 288.77296074, 281.34770578, 293.89862591],
        [285.13819336, 285.68505849, 292.15036797, 285.08722618],
        [286.87037646, 275.77827207, 281.43967177, 273.66207177]],

       [[292.40567693, 278.70365543, 284.09449836, 289.30927515],
        [284.78650261, 278.98630279, 284.09550471, 289.80034213],
        [285.88524684, 280.67820484, 281.88705931, 277.28511325]]])

Section 1.2: Wrap the Array: First Attempt#

Now we create a basic DataArray just by passing our plain data as an input:

temperature = xr.DataArray(rand_data)
temperature
<xarray.DataArray (dim_0: 5, dim_1: 3, dim_2: 4)>
array([[[284.01981799, 288.60416752, 280.164391  , 276.63442792],
        [290.44831894, 282.59011219, 284.5380792 , 290.42857619],
        [287.15236197, 287.51933151, 275.01154359, 293.17913947]],

       [[274.39799007, 282.79978578, 283.32264251, 268.9884763 ],
        [288.95809125, 284.92039125, 278.51384981, 287.85576971],
        [277.65689332, 278.43746795, 282.13393524, 285.93729069]],

       [[280.46408865, 283.60850813, 285.89537151, 273.99139699],
        [284.64515918, 291.18653695, 279.78812068, 278.50048079],
        [276.85608999, 286.21271191, 272.07878347, 282.29925238]],

       [[282.01366621, 288.77296074, 281.34770578, 293.89862591],
        [285.13819336, 285.68505849, 292.15036797, 285.08722618],
        [286.87037646, 275.77827207, 281.43967177, 273.66207177]],

       [[292.40567693, 278.70365543, 284.09449836, 289.30927515],
        [284.78650261, 278.98630279, 284.09550471, 289.80034213],
        [285.88524684, 280.67820484, 281.88705931, 277.28511325]]])
Dimensions without coordinates: dim_0, dim_1, dim_2

Note two things:

  1. Xarray generates some basic dimension names for us (dim_0, dim_1, dim_2). We’ll improve this with better names in the next example.

  2. Wrapping the numpy array in a DataArray gives us a rich display in the notebook! (Try clicking the array symbol to expand or collapse the view)

Section 1.3: Assign Dimension Names#

Much of the power of Xarray comes from making use of named dimensions. So let’s add some more useful names! We can do that by passing an ordered list of names using the keyword argument dims:

temperature = xr.DataArray(rand_data, dims=["time", "lat", "lon"])
temperature
<xarray.DataArray (time: 5, lat: 3, lon: 4)>
array([[[284.01981799, 288.60416752, 280.164391  , 276.63442792],
        [290.44831894, 282.59011219, 284.5380792 , 290.42857619],
        [287.15236197, 287.51933151, 275.01154359, 293.17913947]],

       [[274.39799007, 282.79978578, 283.32264251, 268.9884763 ],
        [288.95809125, 284.92039125, 278.51384981, 287.85576971],
        [277.65689332, 278.43746795, 282.13393524, 285.93729069]],

       [[280.46408865, 283.60850813, 285.89537151, 273.99139699],
        [284.64515918, 291.18653695, 279.78812068, 278.50048079],
        [276.85608999, 286.21271191, 272.07878347, 282.29925238]],

       [[282.01366621, 288.77296074, 281.34770578, 293.89862591],
        [285.13819336, 285.68505849, 292.15036797, 285.08722618],
        [286.87037646, 275.77827207, 281.43967177, 273.66207177]],

       [[292.40567693, 278.70365543, 284.09449836, 289.30927515],
        [284.78650261, 278.98630279, 284.09550471, 289.80034213],
        [285.88524684, 280.67820484, 281.88705931, 277.28511325]]])
Dimensions without coordinates: time, lat, lon

This is already an improvement over a NumPy array because we have names for each of the dimensions (or axes). Even better, we can associate arrays representing the values for the coordinates for each of these dimensions with the data when we create the DataArray. We’ll see this in the next example.

Section 2: Create a DataArray with Named Coordinates#

Section 2.1: Make Time and Space Coordinates#

Here we will use Pandas to create an array of datetime data, which we will then use to create a DataArray with a named coordinate time.

times_index = pd.date_range("2018-01-01", periods=5)
times_index
DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
               '2018-01-05'],
              dtype='datetime64[ns]', freq='D')

We’ll also create arrays to represent sample longitude and latitude:

lons = np.linspace(-120, -60, 4)
lats = np.linspace(25, 55, 3)

Section 2.1.1: Initialize the DataArray with Complete Coordinate Info#

When we create the DataArray instance, we pass in the arrays we just created:

temperature = xr.DataArray(
    rand_data, coords=[times_index, lats, lons], dims=["time", "lat", "lon"]
)
temperature
<xarray.DataArray (time: 5, lat: 3, lon: 4)>
array([[[284.01981799, 288.60416752, 280.164391  , 276.63442792],
        [290.44831894, 282.59011219, 284.5380792 , 290.42857619],
        [287.15236197, 287.51933151, 275.01154359, 293.17913947]],

       [[274.39799007, 282.79978578, 283.32264251, 268.9884763 ],
        [288.95809125, 284.92039125, 278.51384981, 287.85576971],
        [277.65689332, 278.43746795, 282.13393524, 285.93729069]],

       [[280.46408865, 283.60850813, 285.89537151, 273.99139699],
        [284.64515918, 291.18653695, 279.78812068, 278.50048079],
        [276.85608999, 286.21271191, 272.07878347, 282.29925238]],

       [[282.01366621, 288.77296074, 281.34770578, 293.89862591],
        [285.13819336, 285.68505849, 292.15036797, 285.08722618],
        [286.87037646, 275.77827207, 281.43967177, 273.66207177]],

       [[292.40567693, 278.70365543, 284.09449836, 289.30927515],
        [284.78650261, 278.98630279, 284.09550471, 289.80034213],
        [285.88524684, 280.67820484, 281.88705931, 277.28511325]]])
Coordinates:
  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
  * lat      (lat) float64 25.0 40.0 55.0
  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0

Section 2.1.2: Set Useful Attributes#

We can also set some attribute metadata, which will help provide clear descriptions of the data. In this case, we can specify that we’re looking at ‘air_temperature’ data and the units are ‘kelvin’.

temperature.attrs["units"] = "kelvin"
temperature.attrs["standard_name"] = "air_temperature"

temperature
<xarray.DataArray (time: 5, lat: 3, lon: 4)>
array([[[284.01981799, 288.60416752, 280.164391  , 276.63442792],
        [290.44831894, 282.59011219, 284.5380792 , 290.42857619],
        [287.15236197, 287.51933151, 275.01154359, 293.17913947]],

       [[274.39799007, 282.79978578, 283.32264251, 268.9884763 ],
        [288.95809125, 284.92039125, 278.51384981, 287.85576971],
        [277.65689332, 278.43746795, 282.13393524, 285.93729069]],

       [[280.46408865, 283.60850813, 285.89537151, 273.99139699],
        [284.64515918, 291.18653695, 279.78812068, 278.50048079],
        [276.85608999, 286.21271191, 272.07878347, 282.29925238]],

       [[282.01366621, 288.77296074, 281.34770578, 293.89862591],
        [285.13819336, 285.68505849, 292.15036797, 285.08722618],
        [286.87037646, 275.77827207, 281.43967177, 273.66207177]],

       [[292.40567693, 278.70365543, 284.09449836, 289.30927515],
        [284.78650261, 278.98630279, 284.09550471, 289.80034213],
        [285.88524684, 280.67820484, 281.88705931, 277.28511325]]])
Coordinates:
  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
  * lat      (lat) float64 25.0 40.0 55.0
  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
Attributes:
    units:          kelvin
    standard_name:  air_temperature

Section 2.1.3: Attributes Are Not Preserved by Default!#

Notice what happens if we perform a mathematical operaton with the DataArray: the coordinate values persist, but the attributes are lost. This is done because it is very challenging to know if the attribute metadata is still correct or appropriate after arbitrary arithmetic operations.

To illustrate this, we’ll do a simple unit conversion from Kelvin to Celsius:

temperature_in_celsius = temperature - 273.15
temperature_in_celsius
<xarray.DataArray (time: 5, lat: 3, lon: 4)>
array([[[10.86981799, 15.45416752,  7.014391  ,  3.48442792],
        [17.29831894,  9.44011219, 11.3880792 , 17.27857619],
        [14.00236197, 14.36933151,  1.86154359, 20.02913947]],

       [[ 1.24799007,  9.64978578, 10.17264251, -4.1615237 ],
        [15.80809125, 11.77039125,  5.36384981, 14.70576971],
        [ 4.50689332,  5.28746795,  8.98393524, 12.78729069]],

       [[ 7.31408865, 10.45850813, 12.74537151,  0.84139699],
        [11.49515918, 18.03653695,  6.63812068,  5.35048079],
        [ 3.70608999, 13.06271191, -1.07121653,  9.14925238]],

       [[ 8.86366621, 15.62296074,  8.19770578, 20.74862591],
        [11.98819336, 12.53505849, 19.00036797, 11.93722618],
        [13.72037646,  2.62827207,  8.28967177,  0.51207177]],

       [[19.25567693,  5.55365543, 10.94449836, 16.15927515],
        [11.63650261,  5.83630279, 10.94550471, 16.65034213],
        [12.73524684,  7.52820484,  8.73705931,  4.13511325]]])
Coordinates:
  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
  * lat      (lat) float64 25.0 40.0 55.0
  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0

We usually wish to keep metadata with our dataset, even after manipulating the data. For example it can tell us what the units are of a variable of interest. So when you perform operations on your data, make sure to check that all the information you want is carried over. If it isn’t, you can add it back in following the instructions in the section before this. For an in-depth discussion of how Xarray handles metadata, you can find more information in the Xarray documents here.

Section 3: The Dataset: a Container for DataArrays with Shared Coordinates#

Along with DataArray, the other key object type in Xarray is the Dataset, which is a dictionary-like container that holds one or more DataArrays, which can also optionally share coordinates (see docs here).

The most common way to create a Dataset object is to load data from a file (which we will practice in a later tutorial). Here, instead, we will create another DataArray and combine it with our temperature data.

This will illustrate how the information about common coordinate axes is used.

Section 3.1: Create a Pressure DataArray Using the Same Coordinates#

For our next DataArry example, we’ll create a random array of pressure data in units of hectopascal (hPa). This code mirrors how we created the temperature object above.

pressure_data = 1000.0 + 5 * np.random.randn(5, 3, 4)
pressure = xr.DataArray(
    pressure_data, coords=[times_index, lats,
                           lons], dims=["time", "lat", "lon"]
)
pressure.attrs["units"] = "hPa"
pressure.attrs["standard_name"] = "air_pressure"

pressure
<xarray.DataArray (time: 5, lat: 3, lon: 4)>
array([[[1004.64146473, 1006.84757627,  999.31906943, 1003.22967581],
        [1008.98670864, 1000.86710195,  990.66851016, 1002.45970938],
        [1004.42856238, 1001.55868931, 1001.42978692, 1004.59538135]],

       [[1004.52531051,  997.53257341,  999.41947525, 1004.30791479],
        [1000.12362022, 1009.89717286,  996.3365003 , 1003.2551161 ],
        [1006.17574426, 1003.81403509,  995.84088782, 1001.5614222 ]],

       [[ 991.91908825,  997.03955202, 1002.03001891,  999.81604441],
        [1002.13080994,  994.00068654, 1002.6863116 , 1003.7254171 ],
        [ 999.84729948, 1005.08868472,  995.56250769,  997.16122121]],

       [[ 997.35027124, 1010.45087141,  994.40647803,  999.70202961],
        [ 998.75566828, 1009.30988376,  999.28978265, 1001.10874974],
        [1002.7095302 ,  998.4085219 ,  997.76262118,  994.4978373 ]],

       [[1000.75772269,  998.6423106 , 1002.10625859, 1002.56582785],
        [ 993.48957487,  992.17160333,  997.30998531, 1004.32794381],
        [1006.20532675,  992.86629569,  998.52379474,  999.49518871]]])
Coordinates:
  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
  * lat      (lat) float64 25.0 40.0 55.0
  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
Attributes:
    units:          hPa
    standard_name:  air_pressure

Section 3.2: Create a Dataset Object#

Each DataArray in our Dataset needs a name!

The most straightforward way to create a Dataset with our temperature and pressure arrays is to pass a dictionary using the keyword argument data_vars:

ds = xr.Dataset(data_vars={"Temperature": temperature, "Pressure": pressure})
ds
<xarray.Dataset>
Dimensions:      (time: 5, lat: 3, lon: 4)
Coordinates:
  * time         (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
  * lat          (lat) float64 25.0 40.0 55.0
  * lon          (lon) float64 -120.0 -100.0 -80.0 -60.0
Data variables:
    Temperature  (time, lat, lon) float64 284.0 288.6 280.2 ... 281.9 277.3
    Pressure     (time, lat, lon) float64 1.005e+03 1.007e+03 ... 998.5 999.5

Notice that the Dataset object ds is aware that both data arrays sit on the same coordinate axes.

Section 3.3: Access Data Variables and Coordinates in a Dataset#

We can pull out any of the individual DataArray objects in a few different ways.

Using the “dot” notation:

ds.Pressure
<xarray.DataArray 'Pressure' (time: 5, lat: 3, lon: 4)>
array([[[1004.64146473, 1006.84757627,  999.31906943, 1003.22967581],
        [1008.98670864, 1000.86710195,  990.66851016, 1002.45970938],
        [1004.42856238, 1001.55868931, 1001.42978692, 1004.59538135]],

       [[1004.52531051,  997.53257341,  999.41947525, 1004.30791479],
        [1000.12362022, 1009.89717286,  996.3365003 , 1003.2551161 ],
        [1006.17574426, 1003.81403509,  995.84088782, 1001.5614222 ]],

       [[ 991.91908825,  997.03955202, 1002.03001891,  999.81604441],
        [1002.13080994,  994.00068654, 1002.6863116 , 1003.7254171 ],
        [ 999.84729948, 1005.08868472,  995.56250769,  997.16122121]],

       [[ 997.35027124, 1010.45087141,  994.40647803,  999.70202961],
        [ 998.75566828, 1009.30988376,  999.28978265, 1001.10874974],
        [1002.7095302 ,  998.4085219 ,  997.76262118,  994.4978373 ]],

       [[1000.75772269,  998.6423106 , 1002.10625859, 1002.56582785],
        [ 993.48957487,  992.17160333,  997.30998531, 1004.32794381],
        [1006.20532675,  992.86629569,  998.52379474,  999.49518871]]])
Coordinates:
  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
  * lat      (lat) float64 25.0 40.0 55.0
  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
Attributes:
    units:          hPa
    standard_name:  air_pressure

… or using dictionary access like this:

ds["Pressure"]
<xarray.DataArray 'Pressure' (time: 5, lat: 3, lon: 4)>
array([[[1004.64146473, 1006.84757627,  999.31906943, 1003.22967581],
        [1008.98670864, 1000.86710195,  990.66851016, 1002.45970938],
        [1004.42856238, 1001.55868931, 1001.42978692, 1004.59538135]],

       [[1004.52531051,  997.53257341,  999.41947525, 1004.30791479],
        [1000.12362022, 1009.89717286,  996.3365003 , 1003.2551161 ],
        [1006.17574426, 1003.81403509,  995.84088782, 1001.5614222 ]],

       [[ 991.91908825,  997.03955202, 1002.03001891,  999.81604441],
        [1002.13080994,  994.00068654, 1002.6863116 , 1003.7254171 ],
        [ 999.84729948, 1005.08868472,  995.56250769,  997.16122121]],

       [[ 997.35027124, 1010.45087141,  994.40647803,  999.70202961],
        [ 998.75566828, 1009.30988376,  999.28978265, 1001.10874974],
        [1002.7095302 ,  998.4085219 ,  997.76262118,  994.4978373 ]],

       [[1000.75772269,  998.6423106 , 1002.10625859, 1002.56582785],
        [ 993.48957487,  992.17160333,  997.30998531, 1004.32794381],
        [1006.20532675,  992.86629569,  998.52379474,  999.49518871]]])
Coordinates:
  * time     (time) datetime64[ns] 2018-01-01 2018-01-02 ... 2018-01-05
  * lat      (lat) float64 25.0 40.0 55.0
  * lon      (lon) float64 -120.0 -100.0 -80.0 -60.0
Attributes:
    units:          hPa
    standard_name:  air_pressure

We’ll return to the Dataset object when we start loading data from files in later tutorials today.

Summary#

In this initial tutorial, the DataArray and Dataset objects were utilized to create and explore synthetic examples of climate data.

Resources#

Code and data for this tutorial is based on existing content from Project Pythia.