ESGF logo

Complex Searching with intake-esgf

Overview

In this tutorial we will present an interface under design to facilitate complex searching using intake-esgf. intake-esgf is a small intake and intake-esm inspired package under development in ESGF2. Please note that there is a name collison with an existing package in PyPI and conda. You will need to install the package from source.

Prerequisites

Concepts

Importance

Notes

Install Package

Necessary

Understanding of NetCDF

Helpful

Familiarity with metadata structure

Familiar with intake-esm

Helpful

Similar interface

Transient climate response

Background

  • Time to learn: 30 minutes

Imports

import intake_esgf
from intake_esgf import ESGFCatalog

Initializing the Catalog

As with intake-esm we first instantiate the catalog. However, since we will populate the catalog with search results, the catalog starts empty. Internally, we query different ESGF index nodes for information about what datasets you wish to include in your analysis. As ESGF2 is actively working on an index redesign, our catlogs by default point to a Globus (ElasticSearch) based index at ALCF (Argonne Leadership Computing Facility).

cat = ESGFCatalog()
print(cat)
for ind in cat.indices: # Which indices are included?
    print(ind)
Perform a search() to populate the catalog.
GlobusESGFIndex('anl-dev')
GlobusESGFIndex('ornl-dev')

We also provide support for connecting to the ESGF1 Solr-based indices. You may specify a server in the dictionary or multiple servers - just make sure to include True.

Uncommend the line setting all_indices=True to include all available indices.

intake_esgf.conf.set(indices={"esgf-node.llnl.gov": True,
                              "esgf-node.ornl.gov": True,
                              "esgf.ceda.ac.uk": True})

intake_esgf.conf.set(all_indices=True)  # all federated indices

cat = ESGFCatalog()
for ind in cat.indices:
    print(ind)
GlobusESGFIndex('anl-dev')
GlobusESGFIndex('ornl-dev')
SolrESGFIndex('esgf.ceda.ac.uk')
SolrESGFIndex('esgf-data.dkrz.de')
SolrESGFIndex('esgf-node.ipsl.upmc.fr')
SolrESGFIndex('esg-dn1.nsc.liu.se')
SolrESGFIndex('esgf-node.llnl.gov')
SolrESGFIndex('esgf.nci.org.au')
SolrESGFIndex('esgf-node.ornl.gov')

Populate the catalog

Many times, an analysis will require several variables across multiple experiments. For example, if one were to compute the transient climate response (TCRE), you would need tempererature (tas) and carbon emissions from land (nbp) and ocean (fgco2) for a 1% CO2 increase experiment (1pctCO2) as well as the control experiment (piControl). If TCRE is not in your particular science, that is ok for this notebook. It is a motivating example and the specifics are less important than the search concepts. First, we perform a search in a familiar syntax.

cat.search(
    experiment_id=["piControl", "1pctCO2"],
    variable_id=["tas", "fgco2", "nbp"],
    table_id=["Amon", "Omon", "Lmon"],
)
print(cat)
Summary information for 415 results:
mip_era                                                     [CMIP6]
activity_drs                                                 [CMIP]
institution_id    [MOHC, MRI, MPI-M, NCAR, NOAA-GFDL, NCC, NIMS-...
source_id         [UKESM1-0-LL, MRI-ESM2-0, MPI-ESM1-2-LR, CESM2...
experiment_id                                  [piControl, 1pctCO2]
member_id         [r1i1p1f2, r1i2p1f1, r1i1p1f1, r2i1p1f1, r3i1p...
table_id                                         [Lmon, Omon, Amon]
variable_id                                       [nbp, fgco2, tas]
grid_label                                            [gn, gr1, gr]
dtype: object

Internally, this launches simultaneous searches that are combined locally to provide a global view of what datasets are available. While the Solr indices themselves can be searched in distributed fashion, they will not report if an index has failed to return a response. As index nodes go down from time to time, this can leave you with a false impression that you have found all the datasets of interest. By managing the searches locally, intake-esgf can report back to you that an index has failed and that your results may be incomplete.

If you would like details about what intake-esgf is doing, look in the local cache directory (${HOME}/.esgf/) for a esgf.log file. This is a full history of everything that intake-esgf has searched, downloaded, or accessed. You can also look at just this session by calling session_log(). In this case you will see how long each index took to return a response and if any failed

print(cat.session_log())
2025-01-08 16:15:41 └─GlobusESGFIndex('anl-dev') results=329 response_time=1.80
2025-01-08 16:15:42 └─GlobusESGFIndex('ornl-dev') results=650 response_time=2.33
2025-01-08 16:15:44 └─SolrESGFIndex('esgf-node.llnl.gov') results=681 response_time=4.91
2025-01-08 16:15:45 └─SolrESGFIndex('esgf-node.ipsl.upmc.fr') results=58 response_time=5.86
2025-01-08 16:15:47 └─SolrESGFIndex('esg-dn1.nsc.liu.se') results=62 response_time=7.97
2025-01-08 16:15:50 └─SolrESGFIndex('esgf.nci.org.au') results=109 response_time=10.38
2025-01-08 16:15:57 └─SolrESGFIndex('esgf-node.ornl.gov') results=650 response_time=17.79
2025-01-08 16:15:58 └─SolrESGFIndex('esgf.ceda.ac.uk') results=231 response_time=18.70
2025-01-08 16:16:03 └─SolrESGFIndex('esgf-data.dkrz.de') results=266 response_time=23.49
2025-01-08 16:16:03 combine_time=0.16
2025-01-08 16:16:03 search end total_time=23.72

At this stage of the search you have a catalog full of possibly relevant datasets for your analysis, stored in a pandas dataframe. You are free to view and manipulate this dataframe to help hone these results down. It is available to you as the df member of the ESGFCatalog. You should be careful to only remove rows as internally we could use any column in the downloading of the data. Also note that we have removed the user-facing notion of where the data is hosted. The id column of this dataframe is a list of full dataset_ids which includes the location information. At the point when you are ready to download data, we will choose locations automatically that are fastest for you.

cat.df
project mip_era activity_drs institution_id source_id experiment_id member_id table_id variable_id grid_label version id
0 CMIP6 CMIP6 CMIP MOHC UKESM1-0-LL piControl r1i1p1f2 Lmon nbp gn 20200828 [CMIP6.CMIP.MOHC.UKESM1-0-LL.piControl.r1i1p1f...
1 CMIP6 CMIP6 CMIP MRI MRI-ESM2-0 1pctCO2 r1i2p1f1 Omon fgco2 gn 20210311 [CMIP6.CMIP.MRI.MRI-ESM2-0.1pctCO2.r1i2p1f1.Om...
2 CMIP6 CMIP6 CMIP MPI-M MPI-ESM1-2-LR piControl r1i1p1f1 Omon fgco2 gn 20190710 [CMIP6.CMIP.MPI-M.MPI-ESM1-2-LR.piControl.r1i1...
3 CMIP6 CMIP6 CMIP NCAR CESM2-FV2 piControl r1i1p1f1 Amon tas gn 20191120 [CMIP6.CMIP.NCAR.CESM2-FV2.piControl.r1i1p1f1....
4 CMIP6 CMIP6 CMIP NOAA-GFDL GFDL-CM4 1pctCO2 r1i1p1f1 Amon tas gr1 20180701 [CMIP6.CMIP.NOAA-GFDL.GFDL-CM4.1pctCO2.r1i1p1f...
... ... ... ... ... ... ... ... ... ... ... ... ...
1484 CMIP6 CMIP6 CMIP EC-Earth-Consortium EC-Earth3-ESM-1 1pctCO2 r1i1p1f1 Omon fgco2 gn 20240925 [CMIP6.CMIP.EC-Earth-Consortium.EC-Earth3-ESM-...
1485 CMIP6 CMIP6 CMIP EC-Earth-Consortium EC-Earth3-ESM-1 piControl r1i1p1f1 Amon tas gr 20240925 [CMIP6.CMIP.EC-Earth-Consortium.EC-Earth3-ESM-...
1486 CMIP6 CMIP6 CMIP EC-Earth-Consortium EC-Earth3 piControl r3i1p1f1 Amon tas gr 20231010 [CMIP6.CMIP.EC-Earth-Consortium.EC-Earth3.piCo...
1814 CMIP6 CMIP6 CMIP MOHC UKESM1-0-LL 1pctCO2 r1i1p1f2 Omon fgco2 gn 20191105 [CMIP6.CMIP.MOHC.UKESM1-0-LL.1pctCO2.r1i1p1f2....
2092 CMIP6 CMIP6 CMIP AWI AWI-ESM-1-REcoM piControl r1i1p1f1 Amon tas gn 20230314 [CMIP6.CMIP.AWI.AWI-ESM-1-REcoM.piControl.r1i1...

415 rows × 12 columns

Model Groups

However, intake-esgf also provides you with some tools to help locate relevant data for your analysis. When conducting these kinds of analyses, we are seeking for unique combinations of a source_id, member_id, and grid_label that have all the variables that we need. We call these model groups. In an ESGF search, it is common to find a model that has, for example, a tas for r1i1p1f1 but not a fgco2. Sorting this out is time consuming and labor intensive. So first, we provide you a function to print out all model groups with the following function.

cat.model_groups().to_frame()
project
source_id member_id grid_label
ACCESS-CM2 r1i1p1f1 gn 2
ACCESS-ESM1-5 r1i1p1f1 gn 6
AWI-CM-1-1-MR r1i1p1f1 gn 2
AWI-ESM-1-1-LR r1i1p1f1 gn 2
AWI-ESM-1-REcoM r1i1p1f1 gn 1
... ... ... ...
UKESM1-0-LL r1i1p1f2 gn 6
r2i1p1f2 gn 3
r3i1p1f2 gn 3
r4i1p1f2 gn 3
UKESM1-1-LL r1i1p1f2 gn 6

154 rows × 1 columns

The function model_groups() returns a pandas Series (converted to a dataframe here for printing) with all unique combinations of (source_id,member_id,grid_label) along with the dataset count for each. This helps illustrate why it can be so difficult to locate all the data relevant to a given analysis. At the time of this writing, there are 148 model groups but relatively few of them with all 6 (2 experiments and 3 variables) datasets that we need. Furthermore, you cannot rely on a model group using r1i1p1f1 for its primary result. The results above show that UKESM does not even use f1 at all, further complicating the process of finding results.

In addition to this notion of model groups, intake-esgf provides you a method remove_incomplete() for determing which model groups you wish to keep in the current search. Internally, we will group the search results dataframe by model groups and apply a function of your design to the grouped portion of the dataframe. For example, for the current work, I could just check that there are 6 datasets in the sub-dataframe.

def shall_i_keep_it(sub_df):
    if len(sub_df) == 6:
        return True
    return False


cat.remove_incomplete(shall_i_keep_it)
cat.model_groups().to_frame()
project
source_id member_id grid_label
ACCESS-ESM1-5 r1i1p1f1 gn 6
CanESM5 r1i1p1f1 gn 6
r1i1p2f1 gn 6
CanESM5-1 r1i1p1f1 gn 6
r1i1p2f1 gn 6
CanESM5-CanOE r1i1p2f1 gn 6
CESM2 r1i1p1f1 gn 6
CESM2-FV2 r1i1p1f1 gn 6
CESM2-WACCM r1i1p1f1 gn 6
CESM2-WACCM-FV2 r1i1p1f1 gn 6
CMCC-ESM2 r1i1p1f1 gn 6
GISS-E2-1-G r101i1p1f1 gn 6
r102i1p1f1 gn 6
INM-CM4-8 r1i1p1f1 gr1 6
INM-CM5-0 r1i1p1f1 gr1 6
MIROC-ES2L r1i1p1f2 gn 6
MPI-ESM-1-2-HAM r1i1p1f1 gn 6
MPI-ESM1-2-LR r1i1p1f1 gn 6
MRI-ESM2-0 r1i2p1f1 gn 6
NorCPM1 r1i1p1f1 gn 6
NorESM2-LM r1i1p1f1 gn 6
r1i1p4f1 gn 6
NorESM2-MM r1i1p1f1 gn 6
UKESM1-0-LL r1i1p1f2 gn 6
UKESM1-1-LL r1i1p1f2 gn 6

You could write a much more complex check–it depends on what is relevant to your analysis. The effect is that the list of possible models with consistent results is now much more manageable. This method has the added benefit of forcing the user to be concrete about which models were included in an analysis.

Removing Additional Variants

It may also be that you wish to only include a single member_id in your analysis. The above search shows we have a few models with multiple variants that have all 6 required datasets. To be fair to those that only have 1, you may wish to only keep the smallest variant. We also provide this function as part of the ESGFCatalog object.

cat.remove_ensembles()
cat.model_groups().to_frame()
project
source_id member_id grid_label
ACCESS-ESM1-5 r1i1p1f1 gn 6
CanESM5 r1i1p1f1 gn 6
CanESM5-1 r1i1p1f1 gn 6
CanESM5-CanOE r1i1p2f1 gn 6
CESM2 r1i1p1f1 gn 6
CESM2-FV2 r1i1p1f1 gn 6
CESM2-WACCM r1i1p1f1 gn 6
CESM2-WACCM-FV2 r1i1p1f1 gn 6
CMCC-ESM2 r1i1p1f1 gn 6
GISS-E2-1-G r101i1p1f1 gn 6
INM-CM4-8 r1i1p1f1 gr1 6
INM-CM5-0 r1i1p1f1 gr1 6
MIROC-ES2L r1i1p1f2 gn 6
MPI-ESM-1-2-HAM r1i1p1f1 gn 6
MPI-ESM1-2-LR r1i1p1f1 gn 6
MRI-ESM2-0 r1i2p1f1 gn 6
NorCPM1 r1i1p1f1 gn 6
NorESM2-LM r1i1p1f1 gn 6
NorESM2-MM r1i1p1f1 gn 6
UKESM1-0-LL r1i1p1f2 gn 6
UKESM1-1-LL r1i1p1f2 gn 6

Summary

At this point, you would be ready to use to_dataset_dict() to download and load all datasets into a dictionary for analysis. The point of this notebook however is to expose the search capabilities. It is our goal to make annoying and time-consuming tasks easier by providing you smart interfaces for common operations. Let us know what else is painful for you in locating relevant data for your science.