Climatology#

Overview#

This section covers the following climatology functions from NCL, which can be replicated in Python using xarray and/or geocat.comp:

Functions#

calcDayAnom*#

calcDayAnomTLL calculates daily anomalies from a daily data climatology.

Grab and Go#

import xarray as xr
import geocat.datafiles as gcd
from matplotlib import pyplot as plt

ds = xr.open_dataset(gcd.get("applications_files/inputs/CMIP6_sea_ice_daily_subset.nc"))

DayTLL = ds.aice_d.groupby("time.dayofyear")
clmDayTLL = DayTLL.mean("time")
calcDayAnomTLL = DayTLL - clmDayTLL

calcDayAnomTLL[0, :, :].assign_attrs(long_name="sea ice anomaly").plot();
---------------------------------------------------------------------------
RemoteDisconnected                        Traceback (most recent call last)
File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/urllib3/connectionpool.py:788, 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)
    787 # Make the request on the HTTPConnection object
--> 788 response = self._make_request(
    789     conn,
    790     method,
    791     url,
    792     timeout=timeout_obj,
    793     body=body,
    794     headers=headers,
    795     chunked=chunked,
    796     retries=retries,
    797     response_conn=response_conn,
    798     preload_content=preload_content,
    799     decode_content=decode_content,
    800     **response_kw,
    801 )
    803 # Everything went great!

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/urllib3/connectionpool.py:534, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
    533 try:
--> 534     response = conn.getresponse()
    535 except (BaseSSLError, OSError) as e:

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/urllib3/connection.py:571, in HTTPConnection.getresponse(self)
    570 # Get the response from http.client.HTTPConnection
--> 571 httplib_response = super().getresponse()
    573 try:

File ~/micromamba/envs/geocat-applications/lib/python3.14/http/client.py:1459, in HTTPConnection.getresponse(self)
   1458 try:
-> 1459     response.begin()
   1460 except ConnectionError:

File ~/micromamba/envs/geocat-applications/lib/python3.14/http/client.py:336, in HTTPResponse.begin(self)
    335 while True:
--> 336     version, status, reason = self._read_status()
    337     if status != CONTINUE:

File ~/micromamba/envs/geocat-applications/lib/python3.14/http/client.py:305, in HTTPResponse._read_status(self)
    302 if not line:
    303     # Presumably, the server closed the connection before
    304     # sending a valid response.
--> 305     raise RemoteDisconnected("Remote end closed connection without"
    306                              " response")
    307 try:

RemoteDisconnected: Remote end closed connection without response

During handling of the above exception, another exception occurred:

ProtocolError                             Traceback (most recent call last)
File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/requests/adapters.py:696, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    695 try:
--> 696     resp = conn.urlopen(
    697         method=request.method,
    698         url=url,
    699         body=request.body,  # type: ignore[arg-type]  # urllib3 stubs don't accept Iterable[bytes | str]
    700         headers=request.headers,  # type: ignore[arg-type]  # urllib3#3072
    701         redirect=False,
    702         assert_same_host=False,
    703         preload_content=False,
    704         decode_content=False,
    705         retries=self.max_retries,
    706         timeout=resolved_timeout,
    707         chunked=chunked,
    708     )
    710 except (ProtocolError, OSError) as err:

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/urllib3/connectionpool.py:842, 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)
    840     new_e = ProtocolError("Connection aborted.", new_e)
--> 842 retries = retries.increment(
    843     method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
    844 )
    845 retries.sleep()

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/urllib3/util/retry.py:498, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
    497 if read is False or method is None or not self._is_method_retryable(method):
--> 498     raise reraise(type(error), error, _stacktrace)
    499 elif read is not None:

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/urllib3/util/util.py:38, in reraise(tp, value, tb)
     37 if value.__traceback__ is not tb:
---> 38     raise value.with_traceback(tb)
     39 raise value

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/urllib3/connectionpool.py:788, 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)
    787 # Make the request on the HTTPConnection object
--> 788 response = self._make_request(
    789     conn,
    790     method,
    791     url,
    792     timeout=timeout_obj,
    793     body=body,
    794     headers=headers,
    795     chunked=chunked,
    796     retries=retries,
    797     response_conn=response_conn,
    798     preload_content=preload_content,
    799     decode_content=decode_content,
    800     **response_kw,
    801 )
    803 # Everything went great!

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/urllib3/connectionpool.py:534, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
    533 try:
--> 534     response = conn.getresponse()
    535 except (BaseSSLError, OSError) as e:

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/urllib3/connection.py:571, in HTTPConnection.getresponse(self)
    570 # Get the response from http.client.HTTPConnection
--> 571 httplib_response = super().getresponse()
    573 try:

File ~/micromamba/envs/geocat-applications/lib/python3.14/http/client.py:1459, in HTTPConnection.getresponse(self)
   1458 try:
-> 1459     response.begin()
   1460 except ConnectionError:

File ~/micromamba/envs/geocat-applications/lib/python3.14/http/client.py:336, in HTTPResponse.begin(self)
    335 while True:
--> 336     version, status, reason = self._read_status()
    337     if status != CONTINUE:

File ~/micromamba/envs/geocat-applications/lib/python3.14/http/client.py:305, in HTTPResponse._read_status(self)
    302 if not line:
    303     # Presumably, the server closed the connection before
    304     # sending a valid response.
--> 305     raise RemoteDisconnected("Remote end closed connection without"
    306                              " response")
    307 try:

ProtocolError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))

During handling of the above exception, another exception occurred:

ConnectionError                           Traceback (most recent call last)
Cell In[1], line 2
      1 import xarray as xr
----> 2 import geocat.datafiles as gcd
      3 from matplotlib import pyplot as plt
      4 
      5 ds = xr.open_dataset(gcd.get("applications_files/inputs/CMIP6_sea_ice_daily_subset.nc"))

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/geocat/datafiles/__init__.py:38
     35 if dev_data_path is not None and os.path.exists(dev_data_path):
     36     POOCH.path = dev_data_path
---> 38 _update_registry(POOCH)
     40 def get(fname):
     41     return POOCH.fetch(fname)

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/geocat/datafiles/__init__.py:28, in _update_registry(p)
     24 """
     25 Updates pooch registry.txt
     26 """
     27 old_sha256 = p.registry["registry.txt"]
---> 28 new_sha256 = hashlib.sha256(requests.get(p.get_url("registry.txt")).content).hexdigest()
     29 if old_sha256 != new_sha256:
     30     p.registry["registry.txt"] = new_sha256

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/requests/api.py:87, in get(url, params, **kwargs)
     74 def get(
     75     url: _t.UriType, params: _t.ParamsType = None, **kwargs: Unpack[_t.GetKwargs]
     76 ) -> Response:
     77     r"""Sends a GET request.
     78 
     79     :param url: URL for the new :class:`Request` object.
   (...)     84     :rtype: requests.Response
     85     """
---> 87     return request("get", url, params=params, **kwargs)

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/requests/api.py:71, in request(method, url, **kwargs)
     67 # By using the 'with' statement we are sure the session is closed, thus we
     68 # avoid leaving sockets open which can trigger a ResourceWarning in some
     69 # cases, and look like a memory leak in others.
     70 with sessions.Session() as session:
---> 71     return session.request(method=method, url=url, **kwargs)

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/requests/sessions.py:651, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
    646 send_kwargs = {
    647     "timeout": timeout,
    648     "allow_redirects": allow_redirects,
    649 }
    650 send_kwargs.update(settings)
--> 651 resp = self.send(prep, **send_kwargs)
    653 return resp

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/requests/sessions.py:784, in Session.send(self, request, **kwargs)
    781 start = preferred_clock()
    783 # Send the request
--> 784 r = adapter.send(request, **kwargs)
    786 # Total elapsed time of the request (approximately)
    787 elapsed = preferred_clock() - start

File ~/micromamba/envs/geocat-applications/lib/python3.14/site-packages/requests/adapters.py:711, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    696     resp = conn.urlopen(
    697         method=request.method,
    698         url=url,
   (...)    707         chunked=chunked,
    708     )
    710 except (ProtocolError, OSError) as err:
--> 711     raise ConnectionError(err, request=request)
    713 except MaxRetryError as e:
    714     if isinstance(e.reason, ConnectTimeoutError):
    715         # TODO: Remove this in 3.0.0: see #2811

ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))
from geocat.comp import climate_anomaly
import geocat.datafiles as gcd

ds = xr.open_dataset(gcd.get("applications_files/inputs/CMIP6_sea_ice_daily_subset.nc"))

calcDayAnomTLL = climate_anomaly(ds.aice_d, "day")

calcMonAnom*#

calcMonAnomTLL, calcMonAnomTLLL, calcMonAnomLLT, and calcMonAnomLLLT calculate monthly anomalies by subtracting the long-term mean from each point.

Grab and Go#

import xarray as xr
import geocat.datafiles as gcd

ds = xr.open_dataset(
    gcd.get("applications_files/inputs/CMIP6_sea_ice_monthly_subset.nc")
)

MonTLL = ds.aice.groupby("time.month")
clmMonTLL = MonTLL.mean("time")
calcMonAnomTLL = MonTLL - clmMonTLL

calcMonAnomTLL = calcMonAnomTLL.assign_attrs(long_name="sea ice anomaly")
calcMonAnomTLL[0, :, :].plot();
from geocat.comp import climate_anomaly
import geocat.datafiles as gcd

ds = xr.open_dataset(
    gcd.get("applications_files/inputs/CMIP6_sea_ice_monthly_subset.nc")
)

calcMonAnomTLL = climate_anomaly(ds.aice, "month")

clmDay*#

clmDayTLL and clmDayTLLL calculate long-term daily means (daily climatology) from daily data.

Grab and Go#

import xarray as xr
import geocat.datafiles as gcd

ds = xr.open_dataset(gcd.get("applications_files/inputs/CMIP6_sea_ice_daily_subset.nc"))

clmDayTLL = ds.aice_d.groupby("time.dayofyear").mean("time")

clmDayTLL[:, 10, 10].plot()
plt.title("daily climatology")
plt.xlabel("day of year")
plt.ylabel("sea ice area");
from geocat.comp import climatology_average
import geocat.datafiles as gcd

ds = xr.open_dataset(gcd.get("applications_files/inputs/CMIP6_sea_ice_daily_subset.nc"))

clmDayTLL = climatology_average(ds.aice_d, "day")

clmMon*#

clmMonTLL, clmMonTLLL, clmMonLLT, and clmMonLLLT calculate long-term monthly means (monthly climatology) from monthly data.

Grab and Go#

import xarray as xr
import geocat.datafiles as gcd

ds = xr.open_dataset(
    gcd.get("applications_files/inputs/CMIP6_sea_ice_monthly_subset.nc")
)

clmMonTLL = ds.aice.groupby("time.month").mean("time")

clmMonTLL[:, 10, 10].plot()
plt.title("monthly climatology")
plt.xlabel("month of year")
plt.ylabel("sea ice area");
from geocat.comp import climatology_average
import geocat.datafiles as gcd

ds = xr.open_dataset(
    gcd.get("applications_files/inputs/CMIP6_sea_ice_monthly_subset.nc")
)

clmMonTLL = climatology_average(ds.aice, "month")

month_to_season#

month_to_season computes a user-specified three-month seasonal mean (DJF, JFM, FMA, MAM, AMJ, MJJ, JJA, JAS, ASO, SON, OND, NDJ).

Note

You can do something similar directly with Xarray as shown here in the Xarray documentation. However, it requires more code.

Grab and Go#

import xarray as xr
import geocat.datafiles as gcd
from geocat.comp import month_to_season

ds = xr.open_dataset(
    gcd.get("applications_files/inputs/CMIP6_sea_ice_monthly_subset.nc")
)

mon_to_season = month_to_season(ds.aice, "ASO").assign_attrs(long_name="sea ice area")

mon_to_season[0, :, :].plot()

plt.title("2010 seasonal mean");

rmMonAnnCyc*#

rmMonAnnCycTLL, rmMonAnnCycLLT, and rmMonAnnCycLLLT remove the annual cycle from monthly data.

Grab and Go#

import xarray as xr
import geocat.datafiles as gcd

ds = xr.open_dataset(
    gcd.get("applications_files/inputs/CMIP6_sea_ice_monthly_subset.nc")
)

MonTLL = ds.aice.groupby("time.month")
clmMonTLL = MonTLL.mean("time")
rmMonAnnCycTLL = MonTLL - clmMonTLL

rmMonAnnCycTLL[:, 10, 10].plot()
plt.title("annual cycle removed")
plt.ylabel("sea ice area");

stdMon*#

stdMonTLL, stdMonTLLL, stdMonLLT, and stdMonLLLT calculate standard deviations of monthly means.

Grab and Go#

import xarray as xr
import geocat.datafiles as gcd

ds = xr.open_dataset(
    gcd.get("applications_files/inputs/CMIP6_sea_ice_monthly_subset.nc")
)

stdMonTLL = ds.aice.groupby("time.month").std(ddof=1)

stdMonTLL[0, :, :].plot();

Python Resources#