From 5b4d5b01717b0800552e94ca1be553f16b069fd6 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Sat, 18 Jan 2025 12:53:00 +0100 Subject: [PATCH 1/4] added lat/lon coord rules --- .../plugins/core/rules/test_lat_lon_coords.py | 62 +++++++ tests/plugins/core/test_plugin.py | 2 + xrlint/plugins/core/__init__.py | 2 + xrlint/plugins/core/rules/lat_lon_coords.py | 156 ++++++++++++++++++ 4 files changed, 222 insertions(+) create mode 100644 tests/plugins/core/rules/test_lat_lon_coords.py create mode 100644 xrlint/plugins/core/rules/lat_lon_coords.py diff --git a/tests/plugins/core/rules/test_lat_lon_coords.py b/tests/plugins/core/rules/test_lat_lon_coords.py new file mode 100644 index 0000000..b902bd3 --- /dev/null +++ b/tests/plugins/core/rules/test_lat_lon_coords.py @@ -0,0 +1,62 @@ +import numpy as np +import xarray as xr + +from xrlint.plugins.core.rules.lat_lon_coords import LatCoords +from xrlint.plugins.core.rules.lat_lon_coords import LonCoords +from xrlint.testing import RuleTest +from xrlint.testing import RuleTester + +valid_dataset_0 = xr.Dataset() +valid_dataset_1 = xr.Dataset( + coords={ + "lat": xr.DataArray( + np.array([3, 4, 5]), + dims="lat", + attrs={ + "units": "degrees_north", + "standard_name": "latitude", + "long_name": "latitude", + }, + ), + "lon": xr.DataArray( + np.array([-2, -1, 0, 1]), + dims="lon", + attrs={ + "units": "degrees_east", + "standard_name": "longitude", + "long_name": "longitude", + }, + ), + }, + data_vars={ + "mask": xr.DataArray( + [[10, 20, 30, 40], [30, 40, 50, 60], [50, 60, 70, 80]], dims=("lat", "lon") + ) + }, +) + +# Valid, because the coord names doesn't matter as long their metadata is ok +valid_dataset_2 = valid_dataset_1.rename_vars({"lon": "x", "lat": "y"}) + + +LatCoordsTest = RuleTester.define_test( + "lat-coord", + LatCoords, + valid=[ + RuleTest(dataset=valid_dataset_0), + RuleTest(dataset=valid_dataset_1), + RuleTest(dataset=valid_dataset_2), + ], + invalid=[], +) + +LonCoordsTest = RuleTester.define_test( + "lon-coord", + LonCoords, + valid=[ + RuleTest(dataset=valid_dataset_0), + RuleTest(dataset=valid_dataset_1), + RuleTest(dataset=valid_dataset_2), + ], + invalid=[], +) diff --git a/tests/plugins/core/test_plugin.py b/tests/plugins/core/test_plugin.py index f283145..2e6e3ca 100644 --- a/tests/plugins/core/test_plugin.py +++ b/tests/plugins/core/test_plugin.py @@ -22,6 +22,8 @@ def test_rules_complete(self): "coords-for-dims", "dataset-title-attr", "grid-mappings", + "lat-coords", + "lon-coords", "no-empty-attrs", "time-coords", "var-units-attr", diff --git a/xrlint/plugins/core/__init__.py b/xrlint/plugins/core/__init__.py index 17bdf18..57cdf20 100644 --- a/xrlint/plugins/core/__init__.py +++ b/xrlint/plugins/core/__init__.py @@ -15,6 +15,8 @@ def export_plugin() -> Plugin: "coords-for-dims": "error", "dataset-title-attr": "warn", "grid-mappings": "error", + "lat-coord": "error", + "lon-coord": "error", "no-empty-attrs": "warn", "time-coords": "error", "var-units-attr": "warn", diff --git a/xrlint/plugins/core/rules/lat_lon_coords.py b/xrlint/plugins/core/rules/lat_lon_coords.py new file mode 100644 index 0000000..5028ec8 --- /dev/null +++ b/xrlint/plugins/core/rules/lat_lon_coords.py @@ -0,0 +1,156 @@ +from typing import Any + +import xarray as xr + +from xrlint.node import DataArrayNode +from xrlint.plugins.core.rules import plugin +from xrlint.rule import RuleContext +from xrlint.rule import RuleOp + +LAT_NAME = "latitude" +LON_NAME = "longitude" + +LAT_ALIASES = {LAT_NAME, "lat"} +LON_ALIASES = {LON_NAME, "lon", "long"} + +LAT_UNITS = "degrees_north" +LON_UNITS = "degrees_east" + +LAT_UNITS_ALIASES = {"degree_north", "degree_N", "degrees_N", "degreeN", "degreesN"} +LON_UNITS_ALIASES = {"degree_east", "degree_E2", "degrees_E", "degreeE", "degreesE"} + + +@plugin.define_rule( + "lat-coords", + version="1.0.0", + type="problem", + description="Latitude coordinate should have standard units and standard names.", + docs_url=( + "https://cfconventions.org/cf-conventions/cf-conventions.html" + "#latitude-coordinate" + ), +) +class LatCoords(RuleOp): + def data_array(self, ctx: RuleContext, node: DataArrayNode): + if node.name in ctx.dataset.coords and _is_lat_var( + str(node.name), node.data_array + ): + _maybe_report( + ctx, + node.data_array.attrs, + LAT_UNITS, + LAT_UNITS_ALIASES, + LAT_NAME, + "Y", + ) + + +@plugin.define_rule( + "lon-coords", + version="1.0.0", + type="problem", + description=("Longitude coordinate should have standard units and standard names."), + docs_url=( + "https://cfconventions.org/cf-conventions/cf-conventions.html" + "#longitude-coordinate" + ), +) +class LonCoords(RuleOp): + def data_array(self, ctx: RuleContext, node: DataArrayNode): + if node.name in ctx.dataset.coords and _is_lon_var( + str(node.name), node.data_array + ): + _maybe_report( + ctx, + node.data_array.attrs, + LON_UNITS, + LON_UNITS_ALIASES, + LON_NAME, + "X", + ) + + +def _is_lat_var(var_name: str, var: xr.DataArray) -> bool: + return _is_var(var_name.lower(), var, LAT_ALIASES) + + +def _is_lon_var(var_name: str, var: xr.DataArray) -> bool: + return _is_var(var_name.lower(), var, LON_ALIASES) + + +def _is_var(var_name: str, var: xr.DataArray, name_aliases: set[str]) -> bool: + return bool( + name_aliases.intersection( + { + var_name, + var.attrs.get("standard_name"), + var.attrs.get("long_name"), + } + ) + ) + + +def _maybe_report( + ctx: RuleContext, + attrs: dict[str, Any], + expected_units: str, + expected_units_aliases: set[str], + expected_name: str, + expected_axis: str, +): + _maybe_report_attr( + ctx, + attrs, + "units", + expected_units, + expected_units_aliases, + None, + None, + ) + _maybe_report_attr( + ctx, + attrs, + "standard_name", + expected_name, + None, + "axis", + expected_axis, + ) + _maybe_report_attr( + ctx, + attrs, + "long_name", + expected_name, + None, + None, + None, + ) + + +def _maybe_report_attr( + ctx: RuleContext, + attrs: dict[str, Any], + attr_name: str, + expected_value: str, + expected_value_aliases: set[str] | None, + alt_attr_name: str | None, + expected_alt_value: str | None, +): + actual_value = attrs.get(attr_name) + actual_alt_value = attrs.get(alt_attr_name) if alt_attr_name else None + + if not actual_value and not actual_alt_value: + ctx.report(f"Missing attribute {attr_name!r} with value {expected_value!r}.") + elif actual_value != expected_value and not ( + expected_value_aliases and actual_value in expected_value_aliases + ): + if expected_alt_value is None: + ctx.report( + f"Attribute {attr_name!r} should be {expected_value!r}," + f" was {actual_value!r}." + ) + elif actual_alt_value != expected_alt_value: + ctx.report( + f"Attribute {alt_attr_name!r} should be {expected_alt_value!r}," + f" was {actual_alt_value!r}." + ) From b3298f0b66d3e3e58a27e7c19c700d627b961c4d Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Sat, 18 Jan 2025 13:39:08 +0100 Subject: [PATCH 2/4] added core rules "lat-coordinate" and "lon-coordinate" --- CHANGES.md | 2 + docs/rule-ref.md | 14 +++ .../core/rules/test_lat_lon_coordinate.py | 108 ++++++++++++++++++ .../plugins/core/rules/test_lat_lon_coords.py | 62 ---------- tests/plugins/core/test_plugin.py | 4 +- ...at_lon_coords.py => lat_lon_coordinate.py} | 12 +- 6 files changed, 132 insertions(+), 70 deletions(-) create mode 100644 tests/plugins/core/rules/test_lat_lon_coordinate.py delete mode 100644 tests/plugins/core/rules/test_lat_lon_coords.py rename xrlint/plugins/core/rules/{lat_lon_coords.py => lat_lon_coordinate.py} (93%) diff --git a/CHANGES.md b/CHANGES.md index 30d7c94..5b6c3f0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,8 @@ ## Version 0.3.0 (in development) - Added more rules + - core rule "lon-coordinate" + - core rule "lat-coordinate" - core rule "time-coordinate" (#15) - xcube rule "time-naming" (#15) diff --git a/docs/rule-ref.md b/docs/rule-ref.md index 8204844..89562cb 100644 --- a/docs/rule-ref.md +++ b/docs/rule-ref.md @@ -23,6 +23,20 @@ Grid mappings, if any, shall have valid grid mapping coordinate variables. Contained in: `all`-:material-lightning-bolt: `recommended`-:material-lightning-bolt: +### :material-bug: `lat-coordinate` + +Latitude coordinate should have standard units and standard names. +[More information.](https://cfconventions.org/cf-conventions/cf-conventions.html#latitude-coordinate) + +Contained in: `all`-:material-lightning-bolt: + +### :material-bug: `lon-coordinate` + +Longitude coordinate should have standard units and standard names. +[More information.](https://cfconventions.org/cf-conventions/cf-conventions.html#longitude-coordinate) + +Contained in: `all`-:material-lightning-bolt: + ### :material-lightbulb: `no-empty-attrs` Every dataset element should have metadata that describes it. diff --git a/tests/plugins/core/rules/test_lat_lon_coordinate.py b/tests/plugins/core/rules/test_lat_lon_coordinate.py new file mode 100644 index 0000000..42d1d26 --- /dev/null +++ b/tests/plugins/core/rules/test_lat_lon_coordinate.py @@ -0,0 +1,108 @@ +import numpy as np +import xarray as xr + +from xrlint.plugins.core.rules.lat_lon_coordinate import LatCoordinate +from xrlint.plugins.core.rules.lat_lon_coordinate import LonCoordinate +from xrlint.testing import RuleTest +from xrlint.testing import RuleTester + +valid_dataset_0 = xr.Dataset() +valid_dataset_1 = xr.Dataset( + coords={ + "lat": xr.DataArray( + np.array([3, 4, 5]), + dims="lat", + attrs={ + "units": "degrees_north", + "standard_name": "latitude", + "long_name": "latitude", + }, + ), + "lon": xr.DataArray( + np.array([-2, -1, 0, 1]), + dims="lon", + attrs={ + "units": "degrees_east", + "standard_name": "longitude", + "long_name": "longitude", + }, + ), + }, + data_vars={ + "mask": xr.DataArray( + [[10, 20, 30, 40], [30, 40, 50, 60], [50, 60, 70, 80]], dims=("lat", "lon") + ) + }, +) + +# Valid, because the coord names doesn't matter as long their metadata is ok +valid_dataset_2 = valid_dataset_1.rename_vars({"lon": "x", "lat": "y"}) + +# Valid, because the coord units have aliases +valid_dataset_3 = valid_dataset_1.copy() +valid_dataset_3.lat.attrs["units"] = "degreeN" +valid_dataset_3.lon.attrs["units"] = "degreeE" + +# Valid, because the coord units have aliases +valid_dataset_4 = valid_dataset_1.copy() +del valid_dataset_4.lat.attrs["standard_name"] +del valid_dataset_4.lon.attrs["standard_name"] +valid_dataset_4.lat.attrs["axis"] = "Y" +valid_dataset_4.lon.attrs["axis"] = "X" + +invalid_lat_dataset_0 = valid_dataset_1.copy() +del invalid_lat_dataset_0.lat.attrs["standard_name"] + +invalid_lon_dataset_0 = valid_dataset_1.copy() +del invalid_lon_dataset_0.lon.attrs["units"] + +invalid_lat_dataset_1 = valid_dataset_1.copy() +invalid_lat_dataset_1.lat.attrs["units"] = "deg" + +invalid_lon_dataset_1 = valid_dataset_1.copy() +invalid_lon_dataset_1.lon.attrs["long_name"] = "poo" + +invalid_lat_dataset_2 = valid_dataset_1.copy() +del invalid_lat_dataset_2.lat.attrs["standard_name"] +invalid_lat_dataset_2.lat.attrs["axis"] = "y" # should be "Y" + +invalid_lon_dataset_2 = valid_dataset_1.copy() +del invalid_lon_dataset_2.lon.attrs["standard_name"] +invalid_lon_dataset_2.lon.attrs["axis"] = "x" # should be "X" + +LatCoordsTest = RuleTester.define_test( + "lat-coordinate", + LatCoordinate, + valid=[ + RuleTest(dataset=valid_dataset_0), + RuleTest(dataset=valid_dataset_1), + RuleTest(dataset=valid_dataset_2), + RuleTest(dataset=valid_dataset_3), + RuleTest(dataset=valid_dataset_4), + RuleTest(dataset=invalid_lon_dataset_0), + RuleTest(dataset=invalid_lon_dataset_1), + RuleTest(dataset=invalid_lon_dataset_2), + ], + invalid=[ + RuleTest(dataset=invalid_lat_dataset_0), + RuleTest(dataset=invalid_lat_dataset_1), + RuleTest(dataset=invalid_lat_dataset_2), + ], +) + +LonCoordsTest = RuleTester.define_test( + "lon-coordinate", + LonCoordinate, + valid=[ + RuleTest(dataset=valid_dataset_0), + RuleTest(dataset=valid_dataset_1), + RuleTest(dataset=invalid_lat_dataset_0), + RuleTest(dataset=invalid_lat_dataset_1), + RuleTest(dataset=invalid_lat_dataset_2), + ], + invalid=[ + RuleTest(dataset=invalid_lon_dataset_0), + RuleTest(dataset=invalid_lon_dataset_1), + RuleTest(dataset=invalid_lon_dataset_2), + ], +) diff --git a/tests/plugins/core/rules/test_lat_lon_coords.py b/tests/plugins/core/rules/test_lat_lon_coords.py deleted file mode 100644 index b902bd3..0000000 --- a/tests/plugins/core/rules/test_lat_lon_coords.py +++ /dev/null @@ -1,62 +0,0 @@ -import numpy as np -import xarray as xr - -from xrlint.plugins.core.rules.lat_lon_coords import LatCoords -from xrlint.plugins.core.rules.lat_lon_coords import LonCoords -from xrlint.testing import RuleTest -from xrlint.testing import RuleTester - -valid_dataset_0 = xr.Dataset() -valid_dataset_1 = xr.Dataset( - coords={ - "lat": xr.DataArray( - np.array([3, 4, 5]), - dims="lat", - attrs={ - "units": "degrees_north", - "standard_name": "latitude", - "long_name": "latitude", - }, - ), - "lon": xr.DataArray( - np.array([-2, -1, 0, 1]), - dims="lon", - attrs={ - "units": "degrees_east", - "standard_name": "longitude", - "long_name": "longitude", - }, - ), - }, - data_vars={ - "mask": xr.DataArray( - [[10, 20, 30, 40], [30, 40, 50, 60], [50, 60, 70, 80]], dims=("lat", "lon") - ) - }, -) - -# Valid, because the coord names doesn't matter as long their metadata is ok -valid_dataset_2 = valid_dataset_1.rename_vars({"lon": "x", "lat": "y"}) - - -LatCoordsTest = RuleTester.define_test( - "lat-coord", - LatCoords, - valid=[ - RuleTest(dataset=valid_dataset_0), - RuleTest(dataset=valid_dataset_1), - RuleTest(dataset=valid_dataset_2), - ], - invalid=[], -) - -LonCoordsTest = RuleTester.define_test( - "lon-coord", - LonCoords, - valid=[ - RuleTest(dataset=valid_dataset_0), - RuleTest(dataset=valid_dataset_1), - RuleTest(dataset=valid_dataset_2), - ], - invalid=[], -) diff --git a/tests/plugins/core/test_plugin.py b/tests/plugins/core/test_plugin.py index af1e0d1..d3fb590 100644 --- a/tests/plugins/core/test_plugin.py +++ b/tests/plugins/core/test_plugin.py @@ -22,8 +22,8 @@ def test_rules_complete(self): "coords-for-dims", "dataset-title-attr", "grid-mappings", - "lat-coords", - "lon-coords", + "lat-coordinate", + "lon-coordinate", "no-empty-attrs", "time-coordinate", "var-units-attr", diff --git a/xrlint/plugins/core/rules/lat_lon_coords.py b/xrlint/plugins/core/rules/lat_lon_coordinate.py similarity index 93% rename from xrlint/plugins/core/rules/lat_lon_coords.py rename to xrlint/plugins/core/rules/lat_lon_coordinate.py index 5028ec8..cbf3554 100644 --- a/xrlint/plugins/core/rules/lat_lon_coords.py +++ b/xrlint/plugins/core/rules/lat_lon_coordinate.py @@ -17,11 +17,11 @@ LON_UNITS = "degrees_east" LAT_UNITS_ALIASES = {"degree_north", "degree_N", "degrees_N", "degreeN", "degreesN"} -LON_UNITS_ALIASES = {"degree_east", "degree_E2", "degrees_E", "degreeE", "degreesE"} +LON_UNITS_ALIASES = {"degree_east", "degree_E", "degrees_E", "degreeE", "degreesE"} @plugin.define_rule( - "lat-coords", + "lat-coordinate", version="1.0.0", type="problem", description="Latitude coordinate should have standard units and standard names.", @@ -30,7 +30,7 @@ "#latitude-coordinate" ), ) -class LatCoords(RuleOp): +class LatCoordinate(RuleOp): def data_array(self, ctx: RuleContext, node: DataArrayNode): if node.name in ctx.dataset.coords and _is_lat_var( str(node.name), node.data_array @@ -46,16 +46,16 @@ def data_array(self, ctx: RuleContext, node: DataArrayNode): @plugin.define_rule( - "lon-coords", + "lon-coordinate", version="1.0.0", type="problem", - description=("Longitude coordinate should have standard units and standard names."), + description="Longitude coordinate should have standard units and standard names.", docs_url=( "https://cfconventions.org/cf-conventions/cf-conventions.html" "#longitude-coordinate" ), ) -class LonCoords(RuleOp): +class LonCoordinate(RuleOp): def data_array(self, ctx: RuleContext, node: DataArrayNode): if node.name in ctx.dataset.coords and _is_lon_var( str(node.name), node.data_array From 565692ca5d5aebf2413cf00d94832067d8e4c772 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Sat, 18 Jan 2025 14:28:59 +0100 Subject: [PATCH 3/4] upd notebook, report alt value invalid --- notebooks/mkdataset.py | 16 +- notebooks/xrlint-cli.ipynb | 2 +- notebooks/xrlint-linter.ipynb | 164 ++++++++++-------- .../core/rules/test_lat_lon_coordinate.py | 3 + tests/plugins/core/test_plugin.py | 33 ++-- tests/plugins/xcube/test_plugin.py | 33 ++-- xrlint/plugins/core/__init__.py | 4 +- .../plugins/core/rules/lat_lon_coordinate.py | 23 ++- 8 files changed, 164 insertions(+), 114 deletions(-) diff --git a/notebooks/mkdataset.py b/notebooks/mkdataset.py index bb64a9b..39b9a04 100644 --- a/notebooks/mkdataset.py +++ b/notebooks/mkdataset.py @@ -13,10 +13,18 @@ def make_dataset() -> xr.Dataset: attrs=dict(title="SST-Climatology Subset"), coords={ "x": xr.DataArray( - np.linspace(-180, 180, nx), dims="x", attrs={"units": "degrees"} + np.linspace(-180, 180, nx), dims="x", attrs={ + "standard_name": "longitude", + "long_name": "longitude", + "units": "degrees_east" + } ), "y": xr.DataArray( - np.linspace(-90, 90, ny), dims="y", attrs={"units": "degrees"} + np.linspace(-90, 90, ny), dims="y", attrs={ + "standard_name": "latitude", + "long_name": "latitude", + "units": "degrees_north" + } ), "time": xr.DataArray( [365 * i for i in range(nt)], @@ -55,6 +63,10 @@ def make_dataset() -> xr.Dataset: def make_dataset_with_issues() -> xr.Dataset: """Create a dataset that produces issues with xrlint core rules.""" invalid_ds = make_dataset() + invalid_ds.x.attrs["units"] = "degrees" + invalid_ds.x.attrs["axis"] = "x" + del invalid_ds.y.attrs["standard_name"] + invalid_ds.y.attrs["axis"] = "y" invalid_ds.time.attrs["units"] = "days since 2020-01-01 ß0:000:00" invalid_ds.attrs = {} invalid_ds.sst.attrs["units"] = 1 diff --git a/notebooks/xrlint-cli.ipynb b/notebooks/xrlint-cli.ipynb index 5b471c9..16de09d 100644 --- a/notebooks/xrlint-cli.ipynb +++ b/notebooks/xrlint-cli.ipynb @@ -231,7 +231,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" + "version": "3.13.1" } }, "nbformat": 4, diff --git a/notebooks/xrlint-linter.ipynb b/notebooks/xrlint-linter.ipynb index b8474e7..0c1e987 100644 --- a/notebooks/xrlint-linter.ipynb +++ b/notebooks/xrlint-linter.ipynb @@ -437,38 +437,38 @@ " * time (time) int64 32B 0 365 730 1095\n", " spatial_ref int64 8B 0\n", "Data variables:\n", - " sst (time, y, x) float64 192B 0.3002 0.935 ... 0.2138 0.04308\n", - " sst_anomaly (time, y, x) float64 192B 0.1556 0.3582 ... 0.4877 0.04322\n", + " sst (time, y, x) float64 192B 0.357 0.4776 0.8956 ... 0.2892 0.1911\n", + " sst_anomaly (time, y, x) float64 192B 0.1693 0.4643 ... 0.6467 0.9646\n", "Attributes:\n", - " title: SST-Climatology Subset" + " title: SST-Climatology Subset" ], "text/plain": [ " Size: 464B\n", @@ -479,8 +479,8 @@ " * time (time) int64 32B 0 365 730 1095\n", " spatial_ref int64 8B 0\n", "Data variables:\n", - " sst (time, y, x) float64 192B 0.3002 0.935 ... 0.2138 0.04308\n", - " sst_anomaly (time, y, x) float64 192B 0.1556 0.3582 ... 0.4877 0.04322\n", + " sst (time, y, x) float64 192B 0.357 0.4776 0.8956 ... 0.2892 0.1911\n", + " sst_anomaly (time, y, x) float64 192B 0.1693 0.4643 ... 0.6467 0.9646\n", "Attributes:\n", " title: SST-Climatology Subset" ] @@ -516,7 +516,7 @@ "

<dataset> - ok

\n" ], "text/plain": [ - "Result(config=Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'__core__': Plugin(meta=PluginMeta(name='__core__', version='0.3.0.dev0', ref='xrlint.plugins.core:export_plugin'), rules={'coords-for-dims': Rule(meta=RuleMeta(name='coords-for-dims', version='1.0.0', description='Dimensions of data variables should have corresponding coordinates.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'dataset-title-attr': Rule(meta=RuleMeta(name='dataset-title-attr', version='1.0.0', description='Datasets should be given a non-empty title.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'grid-mappings': Rule(meta=RuleMeta(name='grid-mappings', version='1.0.0', description='Grid mappings, if any, shall have valid grid mapping coordinate variables.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'no-empty-attrs': Rule(meta=RuleMeta(name='no-empty-attrs', version='1.0.0', description='Every dataset element should have metadata that describes it.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'time-coords': Rule(meta=RuleMeta(name='time-coords', version='1.0.0', description=\"Time coordinates (standard_name='time') should have unambiguous time encoding.\", schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'var-units-attr': Rule(meta=RuleMeta(name='var-units-attr', version='1.0.0', description=\"Every variable should have a valid 'units' attribute.\", schema=None, ref=None, docs_url=None, type='suggestion'), op_class=)}, processors={}, configs={'recommended': Config(name='recommended', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'time-coords': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None), 'all': Config(name='all', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=2, args=(), kwargs={}), 'time-coords': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)})}, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'time-coords': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None), file_path='', messages=[], fixable_error_count=0, fixable_warning_count=0, error_count=0, fatal_error_count=0, warning_count=0)" + "Result(config=Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'__core__': Plugin(meta=PluginMeta(name='__core__', version='0.3.0.dev0', ref='xrlint.plugins.core:export_plugin'), rules={'coords-for-dims': Rule(meta=RuleMeta(name='coords-for-dims', version='1.0.0', description='Dimensions of data variables should have corresponding coordinates.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'dataset-title-attr': Rule(meta=RuleMeta(name='dataset-title-attr', version='1.0.0', description='Datasets should be given a non-empty title.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'grid-mappings': Rule(meta=RuleMeta(name='grid-mappings', version='1.0.0', description='Grid mappings, if any, shall have valid grid mapping coordinate variables.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'lat-coordinate': Rule(meta=RuleMeta(name='lat-coordinate', version='1.0.0', description='Latitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#latitude-coordinate', type='problem'), op_class=), 'lon-coordinate': Rule(meta=RuleMeta(name='lon-coordinate', version='1.0.0', description='Longitude coordinate should have standard units and standard names.', schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#longitude-coordinate', type='problem'), op_class=), 'no-empty-attrs': Rule(meta=RuleMeta(name='no-empty-attrs', version='1.0.0', description='Every dataset element should have metadata that describes it.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'time-coordinate': Rule(meta=RuleMeta(name='time-coordinate', version='1.0.0', description=\"Time coordinate (standard_name='time') should have unambiguous time units encoding.\", schema=None, ref=None, docs_url='https://cfconventions.org/cf-conventions/cf-conventions.html#time-coordinate', type='problem'), op_class=), 'var-units-attr': Rule(meta=RuleMeta(name='var-units-attr', version='1.0.0', description=\"Every variable should have a valid 'units' attribute.\", schema=None, ref=None, docs_url=None, type='suggestion'), op_class=)}, processors={}, configs={'recommended': Config(name='recommended', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None), 'all': Config(name='all', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=2, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)})}, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'lat-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'lon-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'time-coordinate': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None), file_path='', messages=[], fixable_error_count=0, fixable_warning_count=0, error_count=0, fatal_error_count=0, warning_count=0)" ] }, "execution_count": 5, @@ -914,38 +914,38 @@ " * time (time) int64 32B 0 365 730 1095\n", " spatial_ref int64 8B 0\n", "Data variables:\n", - " sst (time, y, x) float64 192B 0.8253 0.09788 ... 0.9227 0.03201\n", - " sst_anomaly (time, y, x) float64 192B 0.1763 0.1965 ... 0.8407 0.6847\n", - " sst_avg (x, y) float64 48B 0.2573 0.7063 0.8764 0.2189 0.7286 0.889" + " sst (time, y, x) float64 192B 0.9673 0.8684 ... 0.6397 0.2261\n", + " sst_anomaly (time, y, x) float64 192B 0.5013 0.1469 0.03853 ... 0.51 0.9694\n", + " sst_avg (x, y) float64 48B 0.5494 0.02223 0.5723 0.4118 0.1869 0.872" ], "text/plain": [ " Size: 512B\n", @@ -956,9 +956,9 @@ " * time (time) int64 32B 0 365 730 1095\n", " spatial_ref int64 8B 0\n", "Data variables:\n", - " sst (time, y, x) float64 192B 0.8253 0.09788 ... 0.9227 0.03201\n", - " sst_anomaly (time, y, x) float64 192B 0.1763 0.1965 ... 0.8407 0.6847\n", - " sst_avg (x, y) float64 48B 0.2573 0.7063 0.8764 0.2189 0.7286 0.889" + " sst (time, y, x) float64 192B 0.9673 0.8684 ... 0.6397 0.2261\n", + " sst_anomaly (time, y, x) float64 192B 0.5013 0.1469 0.03853 ... 0.51 0.9694\n", + " sst_avg (x, y) float64 48B 0.5494 0.02223 0.5723 0.4118 0.1869 0.872" ] }, "execution_count": 6, @@ -985,14 +985,18 @@ "\n", "\n", "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "
dataset warn Missing 'title' attribute in dataset. dataset-title-attr
dataset.coords['y'] errorAttribute 'standard_name' should be 'latitude', was None. lat-coordinate
dataset.coords['y'] errorAttribute 'axis' should be 'Y', was 'y'. lat-coordinate
dataset.coords['x'] errorAttribute 'units' should be 'degrees_east', was 'degrees'. lon-coordinate
dataset.coords['x'] errorAttribute 'axis' should be 'X', was 'x'. lon-coordinate
dataset.attrs warn Missing metadata, attributes are empty. no-empty-attrs
dataset.coords['time'] errorMissing timezone in 'units' attribute: days since 2020-01-01 ß0:000:00time-coords
dataset.coords['time'] errorMissing timezone in 'units' attribute: days since 2020-01-01 ß0:000:00time-coordinate
dataset.data_vars['sst']warn Invalid 'units' attribute in variable 'sst'. var-units-attr

4 problems (one error and 3 warnings)

\n" + "

8 problems (5 errors and 3 warnings)

\n" ], "text/plain": [ - "Result(config=Config(name=None, files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins={'__core__': Plugin(meta=PluginMeta(name='__core__', version='0.3.0.dev0', ref='xrlint.plugins.core:export_plugin'), rules={'coords-for-dims': Rule(meta=RuleMeta(name='coords-for-dims', version='1.0.0', description='Dimensions of data variables should have corresponding coordinates.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'dataset-title-attr': Rule(meta=RuleMeta(name='dataset-title-attr', version='1.0.0', description='Datasets should be given a non-empty title.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'grid-mappings': Rule(meta=RuleMeta(name='grid-mappings', version='1.0.0', description='Grid mappings, if any, shall have valid grid mapping coordinate variables.', schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'no-empty-attrs': Rule(meta=RuleMeta(name='no-empty-attrs', version='1.0.0', description='Every dataset element should have metadata that describes it.', schema=None, ref=None, docs_url=None, type='suggestion'), op_class=), 'time-coords': Rule(meta=RuleMeta(name='time-coords', version='1.0.0', description=\"Time coordinates (standard_name='time') should have unambiguous time encoding.\", schema=None, ref=None, docs_url=None, type='problem'), op_class=), 'var-units-attr': Rule(meta=RuleMeta(name='var-units-attr', version='1.0.0', description=\"Every variable should have a valid 'units' attribute.\", schema=None, ref=None, docs_url=None, type='suggestion'), op_class=)}, processors={}, configs={'recommended': Config(name='recommended', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'time-coords': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None), 'all': Config(name='all', files=None, ignores=None, linter_options=None, opener_options=None, processor=None, plugins=None, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=2, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=2, args=(), kwargs={}), 'time-coords': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=2, args=(), kwargs={})}, settings=None)})}, rules={'coords-for-dims': RuleConfig(severity=2, args=(), kwargs={}), 'dataset-title-attr': RuleConfig(severity=1, args=(), kwargs={}), 'grid-mappings': RuleConfig(severity=2, args=(), kwargs={}), 'no-empty-attrs': RuleConfig(severity=1, args=(), kwargs={}), 'time-coords': RuleConfig(severity=2, args=(), kwargs={}), 'var-units-attr': RuleConfig(severity=1, args=(), kwargs={})}, settings=None), file_path='', messages=[Message(message=\"Missing 'title' attribute in dataset.\", node_path='dataset', rule_id='dataset-title-attr', severity=1, fatal=None, fix=None, suggestions=None), Message(message='Missing metadata, attributes are empty.', node_path='dataset.attrs', rule_id='no-empty-attrs', severity=1, fatal=None, fix=None, suggestions=[Suggestion(desc='Make sure to add appropriate metadata attributes to dataset elements.', data=None, fix=None)]), Message(message=\"Missing timezone in 'units' attribute: days since 2020-01-01 ß0:000:00\", node_path=\"dataset.coords['time']\", rule_id='time-coords', severity=2, fatal=None, fix=None, suggestions=[Suggestion(desc=\"Specify 'units' attribute using format ' since