diff --git a/.github/workflows/build_workflow.yml b/.github/workflows/build_workflow.yml index 192e22322..e59217003 100644 --- a/.github/workflows/build_workflow.yml +++ b/.github/workflows/build_workflow.yml @@ -56,9 +56,10 @@ jobs: - name: Set up Conda Environment uses: conda-incubator/setup-miniconda@v2 with: - activate-environment: "pcmdi_metrics_dev" + activate-environment: "pcmdi_metrics_ci" miniforge-variant: Mambaforge miniforge-version: latest + environment-file: conda-env/ci.yml use-mamba: true mamba-version: "*" channel-priority: strict @@ -83,11 +84,6 @@ jobs: # Increase this value to reset cache if conda/dev.yml has not changed in the workflow CACHE_NUMBER: 0 - - name: Update environment - run: - mamba env update -n pcmdi_metrics_dev -f conda-env/dev.yml - if: steps.cache.outputs.cache-hit != 'true' - - name: Install pcmdi_metrics # Source: https://github.com/conda/conda-build/issues/4251#issuecomment-1053460542 run: | @@ -96,3 +92,80 @@ jobs: - name: Run Tests run: | pytest + + - name: Run Unit Tests + run: pytest tests + + + publish-docs: + if: ${{ github.event_name == 'push' }} + runs-on: ubuntu-latest + defaults: + run: + shell: bash -l {0} + timeout-minutes: 5 + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Cache Conda + uses: actions/cache@v3 + env: + # Increase this value to reset cache if conda-env/ci.yml has not changed in the workflow + CACHE_NUMBER: 0 + with: + path: ~/conda_pkgs_dir + key: ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-${{ + hashFiles('conda-env/ci.yml') }} + + - name: Set up Conda Environment + uses: conda-incubator/setup-miniconda@v2 + with: + activate-environment: "pcmdi_metrics_dev" + miniforge-variant: Miniforge3 + miniforge-version: latest + environment-file: conda-env/dev.yml + channel-priority: strict + auto-update-conda: true + + - name: Build Sphinx Docs + run: | + cd docs + sphinx-multiversion source _build/html + + - name: Copy Docs and Commit + run: | + # gh-pages branch must already exist + git clone https://github.com/PCMDI/pcmdi_metrics.git --branch gh-pages --single-branch gh-pages + + # Make sure we're in the gh-pages directory. + cd gh-pages + + # Create `.nojekyll` (if it doesn't already exist) for proper GH Pages configuration. + touch .nojekyll + + # Add `index.html` to point to the `main` branch automatically. + printf '' > index.html + + # Only replace `main` docs with latest changes. Docs for tags should be untouched. + rm -rf _build/html/main + mkdir -p _build/html/main + cp -r ../docs/_build/html/main _build/html + + # Configure git using GitHub Actions credentials. + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + # The below command will fail if no changes were present, so we ignore it + git add . + git commit -m "Update documentation" -a || true + + - name: Push Changes + uses: ad-m/github-push-action@master + with: + branch: gh-pages + directory: gh-pages + github_token: ${{ secrets.GITHUB_TOKEN }} + force: true \ No newline at end of file diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml deleted file mode 100644 index 6df2e0719..000000000 --- a/.github/workflows/documentation.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: Docs -on: [push, pull_request, workflow_dispatch] -permissions: - contents: write -jobs: - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v3 - - name: Install dependencies - run: | - pip install sphinx sphinx_rtd_theme sphinx_book_theme - - name: Sphinx build - run: | - sphinx-build docs _build - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - with: - publish_branch: gh-pages - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: _build/ - force_orphan: true diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index ff9fc2152..000000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# .readthedocs.yaml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Set the OS, Python version and other tools you might need -build: - os: ubuntu-22.04 - tools: - python: "mambaforge-4.10" - -# Build documentation in the "docs/" directory with Sphinx -sphinx: - configuration: docs/conf.py - -# Optionally build your docs in additional formats such as PDF and ePub -formats: - - pdf - -conda: - environment: conda-env/readthedocs.yml diff --git a/conda-env/readthedocs.yml b/conda-env/ci.yml similarity index 51% rename from conda-env/readthedocs.yml rename to conda-env/ci.yml index 44321a4ff..3abae185c 100644 --- a/conda-env/readthedocs.yml +++ b/conda-env/ci.yml @@ -1,4 +1,5 @@ -name: pcmdi_metrics_rtd +# Conda pcmdi_metrics CI/CD environment (used in GH Actions). +name: pcmdi_metrics_ci channels: - conda-forge - defaults @@ -10,43 +11,29 @@ dependencies: - python=3.10.10 - pip=23.1.2 - numpy=1.23.5 - - cartopy=0.21.1 + - cartopy=0.22.0 - matplotlib=3.7.1 - cdms2=3.1.5 - genutil=8.2.1 - cdutil=8.2.1 - cdp=1.7.0 - - eofs=1.4.0 + - eofs=1.4.1 - seaborn=0.12.2 - enso_metrics=1.1.1 - - xcdat=0.5.0 + - xcdat>=0.7.0 - xmltodict=0.13.0 - setuptools=67.7.2 - - netcdf4=1.6.3 + - netcdf4>=1.6.3 - regionmask=0.9.0 - - rasterio=1.3.6 + - rasterio>=1.3.6 - shapely=2.0.1 + - numdifftools + - nc-time-axis # ================== # Testing # ================== - pre_commit=3.2.2 - pytest=7.3.1 - pytest-cov=4.0.0 - # ================== - # Developer Tools - # ================== - - jupyterlab=3.6.3 - - nb_conda=2.2.1 - - nb_conda_kernels=2.3.1 - # ================== - # Documentation - # ================== - - sphinx=5.3.0 - - sphinx-autosummary-accessors=2022.4.0 - - sphinx-book-theme=1.0.1 - - sphinx-copybutton=0.5.1 - - nbsphinx=0.9.1 - - pandoc=3.1.1 - - ipython=8.11.0 # Required for nbsphinx syntax highlighting - -prefix: /opt/miniconda3/envs/pcmdi_metrics_rtd + +prefix: /opt/miniconda3/envs/pmcdi_metrics_ci diff --git a/conda-env/dev.yml b/conda-env/dev.yml index 98ed112be..1ebb21307 100644 --- a/conda-env/dev.yml +++ b/conda-env/dev.yml @@ -1,4 +1,4 @@ -# Conda pcmdi_metrics development environment +# A conda development environment with all dependencies, including optional and documentation dependencies. name: pcmdi_metrics_dev channels: - conda-forge @@ -45,11 +45,13 @@ dependencies: # Documentation # ================== - sphinx + - sphinx-autosummary-accessors - sphinx-book-theme - sphinx-copybutton - sphinx_rtd_theme - nbsphinx - pandoc + - sphinx-design - ipython # Required for nbsphinx syntax highlighting prefix: /opt/miniconda3/envs/pmcdi_metrics_dev diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst new file mode 100644 index 000000000..b2c0c5ed6 --- /dev/null +++ b/docs/_templates/autosummary/class.rst @@ -0,0 +1,34 @@ +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. add toctree option to make autodoc generate the pages + +.. autoclass:: {{ objname }} + + {% block attributes %} + {% if attributes %} + .. rubric:: Attributes + + .. autosummary:: + :toctree: . + {% for item in attributes %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block methods %} + {% if methods %} + .. rubric:: Methods + + .. autosummary:: + :toctree: . + {% for item in methods %} + {%- if item != '__init__' %} + ~{{ name }}.{{ item }} + {%- endif -%} + {%- endfor %} + {% endif %} + {% endblock %} + diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 000000000..2286d80fa --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,90 @@ +API Reference +============= + +API Functions for Developpers +----------------------------- + +.. currentmodule:: pcmdi_metrics + +Below is a list of some API functions that are available in `pcmdi_metrics.` for developpers. + + +Land-sea mask +~~~~~~~~~~~~~ + +.. autosummary:: + :toctree: generated/ + + pcmdi_metrics.utils.create_land_sea_mask + pcmdi_metrics.utils.apply_landmask + pcmdi_metrics.utils.apply_oceanmask + + +Grid handling and re-gridding (horizontal interpolation) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autosummary:: + :toctree: generated/ + + pcmdi_metrics.utils.create_target_grid + pcmdi_metrics.utils.regrid + + +Quality control (QC) +~~~~~~~~~~~~~~~~~~~~ + +.. autosummary:: + :toctree: generated/ + + pcmdi_metrics.utils.check_daily_time_axis + pcmdi_metrics.utils.check_monthly_time_axis + + +Calendar-related functions +~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. autosummary:: + :toctree: generated/ + + pcmdi_metrics.utils.custom_season_average + pcmdi_metrics.utils.custom_season_departure + + +Miscellaneous tools +~~~~~~~~~~~~~~~~~~~ +.. autosummary:: + :toctree: generated/ + + pcmdi_metrics.utils.sort_human + pcmdi_metrics.utils.fill_template + pcmdi_metrics.utils.tree + + +Region handling +~~~~~~~~~~~~~~~ +.. autosummary:: + :toctree: generated/ + + pcmdi_metrics.io.region_subset + pcmdi_metrics.io.region_from_file + + +Retrieve data from xarray Dataset +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. autosummary:: + :toctree: generated/ + + pcmdi_metrics.io.get_grid + pcmdi_metrics.io.get_axis_list + pcmdi_metrics.io.get_data_list + pcmdi_metrics.io.get_latitude + pcmdi_metrics.io.get_latitude_bounds + pcmdi_metrics.io.get_latitude_key + pcmdi_metrics.io.get_longitude + pcmdi_metrics.io.get_longitude_bounds + pcmdi_metrics.io.get_longitude_key + pcmdi_metrics.io.get_time + pcmdi_metrics.io.get_time_bounds + pcmdi_metrics.io.get_time_bounds_key + pcmdi_metrics.io.get_time_key + pcmdi_metrics.io.select_subset + diff --git a/docs/conf.py b/docs/conf.py index c5728ccae..786951964 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,13 +13,19 @@ import os sys.path.insert(0, os.path.abspath('../pcmdi_metrics/utils')) -# import sphinx_autosummary_accessors +import sphinx_autosummary_accessors +from sphinx.application import Sphinx +from sphinx.util import logging -# -- Project information ----------------------------------------------------- +LOGGER = logging.getLogger("conf") + +import pcmdi_metrics -project = 'PCMDI Metrics Package' -copyright = '2024 PCMDI' -author = 'PCMDI' +# -- Project information ----------------------------------------------------- +# General information about the project. +project = "PCMDI Metrics Package (PMP)" +copyright = "2024, PMP Developers" +author = "PMP Developers" # The version info for the project you're documenting, acts as replacement # for |version| and |release|, also used in various other places throughout @@ -36,11 +42,22 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx_rtd_theme', 'sphinx.ext.napoleon'] +#extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx_rtd_theme', 'sphinx.ext.napoleon'] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx_autosummary_accessors", + "sphinx_copybutton", + "sphinx_rtd_theme", + "nbsphinx", + "sphinx_design", +] # autosummary and autodoc configurations -# autosummary_generate = True -""" +autosummary_generate = True + autodoc_member_order = "bysource" autodoc_default_options = { "members": True, @@ -48,7 +65,6 @@ "private-members": True, } autodoc_typehints = "none" -""" # Napoleon configurations napoleon_google_docstring = False @@ -57,9 +73,13 @@ napoleon_use_rtype = False napoleon_preprocess_types = True +# sphinx-copybutton configurations +copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: " +copybutton_prompt_is_regexp = True + # Add any paths that contain templates here, relative to this directory. -# templates_path = ['_templates', sphinx_autosummary_accessors.templates_path] -templates_path = ['_templates'] +templates_path = ["_templates", sphinx_autosummary_accessors.templates_path] +# templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: @@ -80,7 +100,12 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] +exclude_patterns = [ + "_build", + "Thumbs.db", + ".DS_Store", + "demos/1-25-23-cwss-seminar/xsearch-xcdat-example.ipynb", +] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' diff --git a/docs/resources.rst b/docs/resources.rst index e25fa645e..331e1119d 100644 --- a/docs/resources.rst +++ b/docs/resources.rst @@ -7,5 +7,5 @@ Resources .. toctree:: :maxdepth: 1 - utils - resources_legacy \ No newline at end of file + api + resources_legacy diff --git a/docs/utils.rst b/docs/utils.rst deleted file mode 100644 index 45749ab04..000000000 --- a/docs/utils.rst +++ /dev/null @@ -1,9 +0,0 @@ -***** -Utils -***** - - -.. automodule:: pcmdi_metrics.utils - :members: check_daily_time_axis, check_monthly_time_axis, create_land_sea_mask, apply_landmask, apply_oceanmask, regrid - :undoc-members: - :show-inheritance: diff --git a/pcmdi_metrics/io/region_from_file.py b/pcmdi_metrics/io/region_from_file.py index bcb5144fe..10eea8712 100644 --- a/pcmdi_metrics/io/region_from_file.py +++ b/pcmdi_metrics/io/region_from_file.py @@ -3,13 +3,43 @@ def region_from_file(data, rgn_path, attr, feature): - # Return data masked from a feature in input file. - # Arguments: - # data: xcdat dataset - # feature: str, name of region - # rgn_path: str, path to file - # attr: str, attribute name + """Return data masked from a feature in the input file. + This function reads a region from a file, creates a mask based on the specified feature, + and applies the mask to the input data. + + Parameters + ---------- + data : xarray.Dataset or xarray.DataArray + The input data to be masked. Must have 'lon' and 'lat' coordinates. + rgn_path : str + Path to the file containing region information. + attr : str + Attribute name in the region file to use for feature selection. + feature : str + Name of the region to be selected. + + Returns + ------- + xarray.Dataset or xarray.DataArray + The input data masked to the specified region. + + Raises + ------ + Exception + If there's an error in creating the region subset from the file or in applying the mask. + + Notes + ----- + This function uses geopandas to read the region file and regionmask to create and apply the mask. + The input data must have 'lon' and 'lat' coordinates. + + Examples + -------- + >>> import xarray as xr + >>> data = xr.open_dataset('path/to/data.nc') + >>> masked_data = region_from_file(data, 'path/to/regions.shp', 'region_name', 'Europe') + """ lon = data["lon"].data lat = data["lat"].data diff --git a/pcmdi_metrics/io/xcdat_dataset_io.py b/pcmdi_metrics/io/xcdat_dataset_io.py index db97c5945..3683888dd 100644 --- a/pcmdi_metrics/io/xcdat_dataset_io.py +++ b/pcmdi_metrics/io/xcdat_dataset_io.py @@ -9,6 +9,29 @@ def _find_key( ds: Union[xr.Dataset, xr.DataArray], axis: str, potential_names: list ) -> str: + """ + Internal function to find the appropriate key for a given axis. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + axis : str + The axis to find the key for ('T', 'X', or 'Y'). + potential_names : list + List of potential names for the axis. + + Returns + ------- + str + The key corresponding to the given axis. + + Raises + ------ + Exception + If no appropriate key can be found. + """ + try: key = xc.get_dim_keys(ds, axis) except Exception as e: @@ -33,11 +56,39 @@ def _find_key( def get_axis_list(ds: Union[xr.Dataset, xr.DataArray]) -> list[str]: + """ + Retrieve coordinate key names from the dataset or data array. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + list[str] + List of coordinate key names. + """ + axes = list(ds.coords.keys()) return axes def get_data_list(ds: Union[xr.Dataset, xr.DataArray]) -> list[str]: + """ + Retrieve data variable names from the dataset or data array. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + list[str] + List of data variable names. + """ + if isinstance(ds, xr.Dataset): return list(ds.data_vars.keys()) elif isinstance(ds, xr.DataArray): @@ -45,18 +96,60 @@ def get_data_list(ds: Union[xr.Dataset, xr.DataArray]) -> list[str]: def get_time_key(ds: Union[xr.Dataset, xr.DataArray]) -> str: + """ + Get the key for the time dimension. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + str + The key for the time dimension. + """ + axis = "T" potential_names = ["time", "t"] return _find_key(ds, axis, potential_names) def get_latitude_key(ds: Union[xr.Dataset, xr.DataArray]) -> str: + """ + Get the key for the latitude dimension. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + str + The key for the latitude dimension. + """ + axis = "Y" potential_names = ["lat", "latitude"] return _find_key(ds, axis, potential_names) def get_longitude_key(ds: Union[xr.Dataset, xr.DataArray]) -> str: + """ + Get the key for the longitude dimension. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + str + The key for the longitude dimension. + """ + axis = "X" potential_names = ["lon", "longitude"] return _find_key(ds, axis, potential_names) @@ -66,16 +159,58 @@ def get_longitude_key(ds: Union[xr.Dataset, xr.DataArray]) -> str: def get_time_bounds_key(ds: Union[xr.Dataset, xr.DataArray]) -> str: + """ + Get the key for the time bounds. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + str + The key for the time bounds. + """ + lat_key = get_time_key(ds) return ds[lat_key].attrs["bounds"] def get_latitude_bounds_key(ds: Union[xr.Dataset, xr.DataArray]) -> str: + """ + Get the key for the latitude bounds. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + str + The key for the latitude bounds. + """ + lat_key = get_latitude_key(ds) return ds[lat_key].attrs["bounds"] def get_longitude_bounds_key(ds: Union[xr.Dataset, xr.DataArray]) -> str: + """ + Get the key for the longitude bounds. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + str + The key for the longitude bounds. + """ + lon_key = get_longitude_key(ds) return ds[lon_key].attrs["bounds"] @@ -84,18 +219,60 @@ def get_longitude_bounds_key(ds: Union[xr.Dataset, xr.DataArray]) -> str: def get_time(ds: Union[xr.Dataset, xr.DataArray]) -> xr.DataArray: + """ + Extract time coordinate data. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + xr.DataArray + The time coordinate data. + """ + time_key = get_time_key(ds) time = ds[time_key] return time def get_longitude(ds: Union[xr.Dataset, xr.DataArray]) -> xr.DataArray: + """ + Extract longitude coordinate data. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + xr.DataArray + The longitude coordinate data. + """ + lon_key = get_longitude_key(ds) lon = ds[lon_key] return lon def get_latitude(ds: Union[xr.Dataset, xr.DataArray]) -> xr.DataArray: + """ + Extract latitude coordinate data. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + xr.DataArray + The latitude coordinate data. + """ + lat_key = get_latitude_key(ds) lat = ds[lat_key] return lat @@ -105,18 +282,60 @@ def get_latitude(ds: Union[xr.Dataset, xr.DataArray]) -> xr.DataArray: def get_time_bounds(ds: Union[xr.Dataset, xr.DataArray]) -> xr.DataArray: + """ + Extract time bounds data. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + xr.DataArray + The time bounds data. + """ + time_bounds_key = get_time_bounds_key(ds) time_bounds = ds[time_bounds_key] return time_bounds def get_longitude_bounds(ds: Union[xr.Dataset, xr.DataArray]) -> xr.DataArray: + """ + Extract longitude bounds data. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + xr.DataArray + The longitude bounds data. + """ + lon_bounds_key = get_longitude_bounds_key(ds) lon_bounds = ds[lon_bounds_key] return lon_bounds def get_latitude_bounds(ds: Union[xr.Dataset, xr.DataArray]) -> xr.DataArray: + """ + Extract latitude bounds data. + + Parameters + ---------- + ds : Union[xr.Dataset, xr.DataArray] + The input dataset or data array. + + Returns + ------- + xr.DataArray + The latitude bounds data. + """ + lat_bounds_key = get_latitude_bounds_key(ds) lat_bounds = ds[lat_bounds_key] return lat_bounds @@ -129,33 +348,47 @@ def select_subset( ds: xr.Dataset, lat: tuple = None, lon: tuple = None, time: tuple = None ) -> xr.Dataset: """ - Selects a subset of the given xarray dataset based on specified latitude, longitude, and time ranges. - - Parameters: - - ds (xr.Dataset): The input xarray dataset. - - lat (tuple, optional): Latitude range in the form of (min, max). - - lon (tuple, optional): Longitude range in the form of (min, max). - - time (tuple, optional): Time range. If time is specified, it should be in the form of (start_time, end_time), - where start_time and end_time can be integers, floats, string, or cftime.datetime objects. + Select a subset of the given xarray dataset based on specified latitude, longitude, and time ranges. - Returns: - - xr.Dataset: Subset of the input dataset based on the specified latitude, longitude, and time ranges. - - Example Usage: - ``` - import cftime - - # Define latitude, longitude, and time ranges - lat_tuple = (30, 50) # Latitude range - lon_tuple = (110, 130) # Longitude range - time_tuple = ("1850-01-01 00:00:00", "1851-12-31 23:59:59") # Time range - - # Load your xarray dataset (ds) here + Parameters + ---------- + ds : xr.Dataset + The input xarray dataset. + lat : tuple, optional + Latitude range in the form of (min, max). + lon : tuple, optional + Longitude range in the form of (min, max). + time : tuple, optional + Time range in the form of (start_time, end_time). start_time and end_time + can be integers, floats, strings, or cftime.datetime objects. - # Select subset based on specified ranges - ds_subset = select_subset(ds, lat=lat_tuple, lon=lon_tuple, time=time_tuple) - ``` + Returns + ------- + xr.Dataset + Subset of the input dataset based on the specified latitude, longitude, and time ranges. + + Notes + ----- + This function allows for flexible subsetting of xarray datasets based on + geographical coordinates and time ranges. + + Examples + -------- + >>> import xarray as xr + >>> import cftime + >>> + >>> # Load your xarray dataset (ds) here + >>> ds = xr.open_dataset('path/to/your/dataset.nc') + >>> + >>> # Define latitude, longitude, and time ranges + >>> lat_tuple = (30, 50) # Latitude range + >>> lon_tuple = (110, 130) # Longitude range + >>> time_tuple = ("1850-01-01 00:00:00", "1851-12-31 23:59:59") # Time range + >>> + >>> # Select subset based on specified ranges + >>> ds_subset = select_subset(ds, lat=lat_tuple, lon=lon_tuple, time=time_tuple) """ + sel_keys = {} if lat is not None: lat_key = get_latitude_key(ds) diff --git a/pcmdi_metrics/utils/sort_human.py b/pcmdi_metrics/utils/sort_human.py index 7e13e017b..757f4b9fc 100644 --- a/pcmdi_metrics/utils/sort_human.py +++ b/pcmdi_metrics/utils/sort_human.py @@ -3,17 +3,37 @@ def sort_human(input_list: list[str]) -> list: - """Sort list by natual order + """ + Sort a list of strings in natural order. + + This function sorts a list of strings using a natural sorting algorithm, + which means that strings containing numbers are sorted in a way that + respects numerical order within the string. Parameters ---------- - input_list : list - input list + input_list : list of str + The input list of strings to be sorted. Returns ------- - list - sorted list + list of str + A new list containing the input strings sorted in natural order. + + Notes + ----- + The natural sorting algorithm used in this function considers the + numerical values within strings when determining the sort order. For + example, "file2" will be sorted before "file10". + + Examples + -------- + >>> sort_human(['file1', 'file10', 'file2']) + ['file1', 'file2', 'file10'] + + >>> sort_human(['1.txt', '10.txt', '2.txt', 'foo.txt']) + ['1.txt', '2.txt', '10.txt', 'foo.txt'] + """ lst = copy(input_list)