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.

The Timeseries case object

Working file-by-file gets tedious. x4c.Timeseries indexes a whole post-processed case directory and gives you variables by name.

import os
import numpy as np
import xarray as xr
import matplotlib.pyplot as plt
import nc_time_axis  # registers the cftime axis converter for matplotlib
import x4c

x4c.set_style('journal')

# after set_style: it resets rcParams from matplotlibrc defaults, which includes the
# backend, so assert the inline backend last or figures are never captured
%matplotlib inline

# The tutorial runs against a reduced copy of a real CESM case. It is published as a
# GitHub Release asset rather than committed, so the first call downloads it into
# ~/.cache/x4c (override with $X4C_CACHE_DIR) and later calls reuse it. Set
# $X4C_SAMPLE_DIR to point at a copy you already have.
case_dir = x4c.fetch_sample_data(case='cesm1', verbose=False)
casename = os.path.basename(case_dir)
print('x4c', x4c.__version__)
x4c 2026.6.11

Opening a case

Point it at the case root. x4c walks <comp>/proc/tseries/<freq>/ and parses the CESM filename convention to find out which variables exist, in which component, and in which history stream.

cesm_ver=1 turns on the CESM1 timestamp shift for every variable it loads.

case = x4c.Timeseries(
    case_dir,
    grid_dict={'atm': 'ne16np4', 'ocn': 'g16'},
    cesm_ver=1,
)
>>> case.root_dir: ~/.cache/x4c/sample_data/cesm1_sample_data/b.e13.B1850C5.ne16_g16.icesm131_d18O_fixer.Miocene.3xCO2.005
>>> case.path_pattern: comp/proc/tseries/*/casename.hstr.vn.timespan.nc
>>> case.grid_dict: {'atm': 'ne16np4', 'ocn': 'g16', 'lnd': 'ne16np4', 'rof': 'ne16np4', 'ice': 'g16'}
>>> case.casename: b.e13.B1850C5.ne16_g16.icesm131_d18O_fixer.Miocene.3xCO2.005
>>> case.paths["atm"]["cam.h0"] created
>>> case.paths["ocn"]["pop.h"] created
>>> case.paths["lnd"]["clm2.h0"] created
>>> case.paths["ice"]["cice.h"] created
>>> case.vns["atm"]["cam.h0"] created
>>> case.vns["ocn"]["pop.h"] created
>>> case.vns["lnd"]["clm2.h0"] created
>>> case.vns["ice"]["cice.h"] created

What it found

case.vns maps component and history stream to the available variables.

for comp in sorted(case.vns):
    for hstr, vns in case.vns[comp].items():
        print(f'{comp:4s} {hstr:9s} {len(vns):2d} variables: {", ".join(sorted(vns))}')
atm  cam.h0    28 variables: FLNT, FSNT, ICEFRAC, LANDFRAC, LWCF, PRECC, PRECL, PRECRC_H216Or, PRECRC_H218Or, PRECRC_H2Or, PRECRC_HDOr, PRECRL_H216OR, PRECRL_H218OR, PRECRL_H2OR, PRECRL_HDOR, PRECSC_H216Os, PRECSC_H218Os, PRECSC_H2Os, PRECSC_HDOs, PRECSL_H216OS, PRECSL_H218OS, PRECSL_H2OS, PRECSL_HDOS, PS, SWCF, T, TS, U
ice  cice.h     1 variables: aice
lnd  clm2.h0    2 variables: RAIN, TSA
ocn  pop.h      6 variables: MOC, R18O, SALT, SSH, TEMP, XMXL

case.paths holds the files behind each variable. TS is split across two five-year files, which x4c will open together when asked for the whole record.

for f in case.paths['atm']['cam.h0']['TS']:
    print(os.path.basename(f))
b.e13.B1850C5.ne16_g16.icesm131_d18O_fixer.Miocene.3xCO2.005.cam.h0.TS.000101-000512.nc
b.e13.B1850C5.ne16_g16.icesm131_d18O_fixer.Miocene.3xCO2.005.cam.h0.TS.000601-001012.nc

Which component holds a variable?

You rarely need to say. get_comp_hstr is what load uses internally to resolve a name; it only needs disambiguating when the same name exists in two streams.

for vn in ['TS', 'SSH', 'aice', 'TEMP']:
    print(f'{vn:6s} ->', case.get_comp_hstr(vn))
TS     -> [('atm', 'cam.h0')]
SSH    -> [('ocn', 'pop.h')]
aice   -> [('ice', 'cice.h')]
TEMP   -> [('ocn', 'pop.h')]

The sample is a reduced case

The bundled data is deliberately small, and not every variable covers the same span. Two-dimensional fields cover years 1-10; three-dimensional fields are shorter, because a single 3-D POP record is larger than an entire 2-D timeseries.

for comp, hstr, vn in [('atm', 'cam.h0', 'TS'), ('atm', 'cam.h0', 'T'),
                       ('ocn', 'pop.h', 'SSH'), ('ocn', 'pop.h', 'TEMP')]:
    files = [os.path.basename(f) for f in case.paths[comp][hstr][vn]]
    spans = [f.split('.')[-2] for f in files]
    print(f'{comp}/{vn:5s} {len(files)} file(s): {", ".join(spans)}')
atm/TS    2 file(s): 000101-000512, 000601-001012
atm/T     2 file(s): 000101-000112, 000201-000212
ocn/SSH   2 file(s): 000101-000512, 000601-001012
ocn/TEMP  1 file(s): 000101-000112