Overview¶
Take the data we read in in the previous notebook, and learn how to make a simple map of a few different variables.
Spin up a Dask cluster and load the data
Super quick plot
Two nicer plots on a map projection
Prerequisites¶
| Concepts | Importance | Notes |
|---|---|---|
| Matplotlib | Necessary | |
| Intro to Cartopy | Necessary | |
| Dask Cookbook | Helpful | |
| Intro to Xarray | Helpful |
Time to learn: 10 min
Imports¶
import xarray as xr
from dask.distributed import LocalCluster
import glob
import numpy as np
import matplotlib.pyplot as plt
import cartopy
import cartopy.crs as ccrs
import pop_tools
import s3fs
from module import adjust_pop_grid
from display_source import display_source/home/runner/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/pop_tools/__init__.py:4: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
from pkg_resources import DistributionNotFound, get_distribution
Connect to cluster¶
Since we’re doing a little more processing in this notebook, we spin up a local Dask cluster. See the Dask Cookbook or Dask tutorial to learn more about this!
cluster = LocalCluster()
client = cluster.get_client()/home/runner/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/distributed/node.py:187: UserWarning: Port 8787 is already in use.
Perhaps you already have a cluster running?
Hosting the HTTP server on port 44599 instead
warnings.warn(
Load the data¶
jetstream_url = 'https://js2.jetstream-cloud.org:8001/'
s3 = s3fs.S3FileSystem(anon=True, client_kwargs=dict(endpoint_url=jetstream_url))
# Generate a list of all files in CESM folder
s3path = 's3://pythia/ocean-bgc/cesm/g.e22.GOMIPECOIAF_JRA-1p4-2018.TL319_g17.4p2z.002branch/ocn/proc/tseries/month_1/*'
remote_files = s3.glob(s3path)
s3.invalidate_cache()
# Open all files from folder
fileset = [s3.open(file) for file in remote_files]
# Open with xarray
ds = xr.open_mfdataset(fileset, data_vars="minimal", coords='minimal', compat="override", parallel=True,
drop_variables=["transport_components", "transport_regions", 'moc_components'], decode_times=True)
dsSuper quick plot¶
We use xarray’s isel() (select by index) function to grab the first entry in time and vertical coordinate available in our data set. Note that our dataset has some metadata associated with it, so xarray knows that the units are in degrees Celsius without us manually specifying. Xarray’s plot() function is great for looking at data quickly to make sure things look right before diving into more involved analysis or plotting.
We arbitrarily choose to plot temperature, but there are lots of options for variables to plot!
ds["TEMP"].isel(time=0, z_t=0).plot()
Making a plot on a nicer map projection¶
Bringing in some POP grid tools¶
This version of CESM uses POP2 (the Parallel Ocean Program) as its ocean model. All of the ocean variable output is on the POP grid, which requires some extra wrangling to get it to work properly with standard mapping utilities.
ds_grid = pop_tools.get_grid('POP_gx1v7')
lons = ds_grid.TLONG
lats = ds_grid.TLAT
depths = ds_grid.z_t * 0.01Downloading file 'inputdata/ocn/pop/gx1v7/grid/horiz_grid_20010402.ieeer8' from 'https://svn-ccsm-inputdata.cgd.ucar.edu/trunk/inputdata/ocn/pop/gx1v7/grid/horiz_grid_20010402.ieeer8' to '/home/runner/.pop_tools'.
---------------------------------------------------------------------------
ConnectionRefusedError Traceback (most recent call last)
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/urllib3/connection.py:198, in HTTPConnection._new_conn(self)
197 try:
--> 198 sock = connection.create_connection(
199 (self._dns_host, self.port),
200 self.timeout,
201 source_address=self.source_address,
202 socket_options=self.socket_options,
203 )
204 except socket.gaierror as e:
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/urllib3/util/connection.py:85, in create_connection(address, timeout, source_address, socket_options)
84 try:
---> 85 raise err
86 finally:
87 # Break explicitly a reference cycle
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/urllib3/util/connection.py:73, in create_connection(address, timeout, source_address, socket_options)
72 sock.bind(source_address)
---> 73 sock.connect(sa)
74 # Break explicitly a reference cycle
ConnectionRefusedError: [Errno 111] Connection refused
The above exception was the direct cause of the following exception:
NewConnectionError Traceback (most recent call last)
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/urllib3/connectionpool.py:787, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
786 # Make the request on the HTTPConnection object
--> 787 response = self._make_request(
788 conn,
789 method,
790 url,
791 timeout=timeout_obj,
792 body=body,
793 headers=headers,
794 chunked=chunked,
795 retries=retries,
796 response_conn=response_conn,
797 preload_content=preload_content,
798 decode_content=decode_content,
799 **response_kw,
800 )
802 # Everything went great!
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/urllib3/connectionpool.py:488, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
487 new_e = _wrap_proxy_error(new_e, conn.proxy.scheme)
--> 488 raise new_e
490 # conn.request() calls http.client.*.request, not the method in
491 # urllib3.request. It also calls makefile (recv) on the socket.
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/urllib3/connectionpool.py:464, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
463 try:
--> 464 self._validate_conn(conn)
465 except (SocketTimeout, BaseSSLError) as e:
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/urllib3/connectionpool.py:1093, in HTTPSConnectionPool._validate_conn(self, conn)
1092 if conn.is_closed:
-> 1093 conn.connect()
1095 # TODO revise this, see https://github.com/urllib3/urllib3/issues/2791
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/urllib3/connection.py:753, in HTTPSConnection.connect(self)
752 sock: socket.socket | ssl.SSLSocket
--> 753 self.sock = sock = self._new_conn()
754 server_hostname: str = self.host
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/urllib3/connection.py:213, in HTTPConnection._new_conn(self)
212 except OSError as e:
--> 213 raise NewConnectionError(
214 self, f"Failed to establish a new connection: {e}"
215 ) from e
217 sys.audit("http.client.connect", self, self.host, self.port)
NewConnectionError: <urllib3.connection.HTTPSConnection object at 0x7f12e09d34d0>: Failed to establish a new connection: [Errno 111] Connection refused
The above exception was the direct cause of the following exception:
MaxRetryError Traceback (most recent call last)
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/requests/adapters.py:644, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
643 try:
--> 644 resp = conn.urlopen(
645 method=request.method,
646 url=url,
647 body=request.body,
648 headers=request.headers,
649 redirect=False,
650 assert_same_host=False,
651 preload_content=False,
652 decode_content=False,
653 retries=self.max_retries,
654 timeout=timeout,
655 chunked=chunked,
656 )
658 except (ProtocolError, OSError) as err:
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/urllib3/connectionpool.py:841, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
839 new_e = ProtocolError("Connection aborted.", new_e)
--> 841 retries = retries.increment(
842 method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
843 )
844 retries.sleep()
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/urllib3/util/retry.py:519, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
518 reason = error or ResponseError(cause)
--> 519 raise MaxRetryError(_pool, url, reason) from reason # type: ignore[arg-type]
521 log.debug("Incremented Retry for (url='%s'): %r", url, new_retry)
MaxRetryError: HTTPSConnectionPool(host='svn-ccsm-inputdata.cgd.ucar.edu', port=443): Max retries exceeded with url: /trunk/inputdata/ocn/pop/gx1v7/grid/horiz_grid_20010402.ieeer8 (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7f12e09d34d0>: Failed to establish a new connection: [Errno 111] Connection refused'))
During handling of the above exception, another exception occurred:
ConnectionError Traceback (most recent call last)
Cell In[5], line 1
----> 1 ds_grid = pop_tools.get_grid('POP_gx1v7')
2 lons = ds_grid.TLONG
3 lats = ds_grid.TLAT
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/pop_tools/grid.py:137, in get_grid(grid_name, scrip)
134 nlon = grid_attrs['lateral_dims'][1]
136 # read horizontal grid
--> 137 horiz_grid_fname = INPUTDATA.fetch(grid_attrs['horiz_grid_fname'], downloader=downloader)
138 grid_file_data = np.fromfile(horiz_grid_fname, dtype='>f8', count=-1)
139 grid_file_data = grid_file_data.reshape((7, nlat, nlon))
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/pop_tools/grid.py:92, in fetch(self, fname, processor, downloader)
89 if downloader is None:
90 downloader = pooch.downloaders.choose_downloader(url)
---> 92 pooch.core.stream_download(url, full_path, known_hash, downloader, pooch=self)
94 if processor is not None:
95 return processor(str(full_path), action, self)
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/pooch/core.py:807, in stream_download(url, fname, known_hash, downloader, pooch, retry_if_failed)
803 try:
804 # Stream the file to a temporary so that we can safely check its
805 # hash before overwriting the original.
806 with temporary_file(path=str(fname.parent)) as tmp:
--> 807 downloader(url, tmp, pooch)
808 hash_matches(tmp, known_hash, strict=True, source=str(fname.name))
809 shutil.move(tmp, str(fname))
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/pooch/downloaders.py:220, in HTTPDownloader.__call__(self, url, output_file, pooch, check_only)
218 # pylint: enable=consider-using-with
219 try:
--> 220 response = requests.get(url, timeout=timeout, **kwargs)
221 response.raise_for_status()
222 content = response.iter_content(chunk_size=self.chunk_size)
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/requests/api.py:73, in get(url, params, **kwargs)
62 def get(url, params=None, **kwargs):
63 r"""Sends a GET request.
64
65 :param url: URL for the new :class:`Request` object.
(...) 70 :rtype: requests.Response
71 """
---> 73 return request("get", url, params=params, **kwargs)
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/requests/api.py:59, in request(method, url, **kwargs)
55 # By using the 'with' statement we are sure the session is closed, thus we
56 # avoid leaving sockets open which can trigger a ResourceWarning in some
57 # cases, and look like a memory leak in others.
58 with sessions.Session() as session:
---> 59 return session.request(method=method, url=url, **kwargs)
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/requests/sessions.py:589, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
584 send_kwargs = {
585 "timeout": timeout,
586 "allow_redirects": allow_redirects,
587 }
588 send_kwargs.update(settings)
--> 589 resp = self.send(prep, **send_kwargs)
591 return resp
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/requests/sessions.py:703, in Session.send(self, request, **kwargs)
700 start = preferred_clock()
702 # Send the request
--> 703 r = adapter.send(request, **kwargs)
705 # Total elapsed time of the request (approximately)
706 elapsed = preferred_clock() - start
File ~/micromamba/envs/ocean-bgc-cookbook-dev/lib/python3.13/site-packages/requests/adapters.py:677, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
673 if isinstance(e.reason, _SSLError):
674 # This branch is for urllib3 v1.22 and later.
675 raise SSLError(e, request=request)
--> 677 raise ConnectionError(e, request=request)
679 except ClosedPoolError as e:
680 raise ConnectionError(e, request=request)
ConnectionError: HTTPSConnectionPool(host='svn-ccsm-inputdata.cgd.ucar.edu', port=443): Max retries exceeded with url: /trunk/inputdata/ocn/pop/gx1v7/grid/horiz_grid_20010402.ieeer8 (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7f12e09d34d0>: Failed to establish a new connection: [Errno 111] Connection refused'))In this Cookbook, we have written a function for adjusting the POP2 grid. For better code reuse, we have moved this code into a module called module.py that is imported into every notebook within this Cookbook. The content of module.py is:
display_source(adjust_pop_grid)Making the map¶
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(1,1,1, projection=ccrs.Robinson(central_longitude=305.0))
# Using the utilities we added above to process our data, and plotting it
lon, lat, field = adjust_pop_grid(lons, lats, ds["TEMP"].isel(time=0, z_t=0))
pc1=ax.pcolormesh(lon, lat,field, cmap='plasma',
vmin=0, vmax=30,
transform=ccrs.PlateCarree())
# Adding the land features
land = cartopy.feature.NaturalEarthFeature('physical', 'land', scale='110m', edgecolor='k', facecolor='white', linewidth=0.5)
ax.add_feature(land)
# Adding colorbar and title
cbar1 = fig.colorbar(pc1, ax=ax,extend='max',label=ds["TEMP"].units)
ax.set_title('CESM Surface Temperature', fontsize=10)
plt.show()Let’s try the same thing with another variable, salinity (SALT). We replace which variable we’re extracting from ds in the adjust_pop_grid() function, where we preprocess the data. If you’re trying this on your own, some other good ones to try looking at are dissolved inorganic carbon (DIC), oxygen (O2), or pH (PH).
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(1,1,1, projection=ccrs.Robinson(central_longitude=305.0))
# Using the utilities we added above to process our data, and plotting it
lon, lat, field = adjust_pop_grid(lons, lats, ds['SALT'].isel(time=0, z_t=0))
# Pick a different colorscheme from the plot above so we can distinguish them more easily
pc1=ax.pcolormesh(lon, lat,field, cmap='cividis',
vmin=32, vmax=38,
transform=ccrs.PlateCarree())
# Adding the land features
land = cartopy.feature.NaturalEarthFeature('physical', 'land', scale='110m', edgecolor='k', facecolor='white', linewidth=0.5)
ax.add_feature(land)
# Adding colorbar and title
cbar1 = fig.colorbar(pc1, ax=ax,extend='both',label=ds["SALT"].units)
ax.set_title('CESM Surface Salinity', fontsize=10)
plt.show()Close the Dask cluster we spun up at the beginning.
cluster.close()Summary¶
You’ve learned how to make simple plots of CESM output.