Source code for smif.metadata.coordinates

"""Each dimension of a multi-dimensional dataset can be indexed by a set of coordinates.

Dimensions might be represented as Euclidean coordinates over a grid::

    >>> x = Coordinates('x', range(0, 100))
    >>> y = Coordinates('y', range(0, 100))

A subset of Local Authority Districts in England would be a region-based spatial dimension::

    >>> local_authority_districts = Coordinates('LADs', ['E07000128', 'E07000180'])

The hours of an average annual day would be a temporal dimension::

    >>> hours = Coordinates('daily_hours', [
    ...     {'name': 0, 'interval': [['PT0H', 'PT1H']]}
    ...     {'name': 1, 'interval': [['PT1H', 'PT2H']]}
    ...     {'name': 2, 'interval': [['PT2H', 'PT3H']]}
    ... ])

The economic sectors from the International Standard Industrial Classification of All Economic
Activities (ISIC), revision 4 would be a categorical dimension::

    >>> economic_sector = Coordinates('ISICrev4', [
    ...     {'name': 'A', 'desc': 'Agriculture, forestry and fishing'},
    ...     {'name': 'B', 'desc': 'Mining and quarrying'},
    ...     {'name': 'C', 'desc': 'Manufacturing'},
    ...     {'name': 'D', 'desc': 'Electricity, gas, steam and air conditioning supply'}
    ... ])

"""


[docs]class Coordinates(object): """Coordinates provide the labels to index a dimension, along with metadata that may be useful to describe, visualise or convert between dimensions. Coordinate element names are used to label each position along a finite dimension. A dict mapping dimension name to list of coordinate elements can be passed to a :class:`~smif.metadata.spec.Spec` (or :class:`xarray.DataArray`) as `coords`. Attributes ---------- name : str Dimension name dim : str Alias for dimension name ids : list List of labels elements : list[dict] List of labels with metadata Parameters ---------- name : str Name to identify the dimension that these coordinates index elements : list List of simple data types (used to identify elements), or a list of dicts with 'name' key and other metadata Raises ------ ValueError If the list of elements is empty, or infinite KeyError If the elements are not a list of simple data types or a list of dicts with a 'name' key """ def __init__(self, name, elements): self.name = name self._ids = None self._elements = None self._set_elements(elements) def __eq__(self, other): return self.name == other.name and self.elements == other.elements def __hash__(self): return hash(tuple(frozenset(e.items()) for e in self._elements)) def __repr__(self): return "<Coordinates name='{}' elements={}>".format(self.name, self.ids) @property def elements(self): """Elements are a list of dicts with at least a 'name' key Coordinate elements should not be changed. """ return self._elements @property def ids(self): """Element ids is a list of coordinate identifiers""" return self._ids @property def names(self): """Names is an alias for Coordinates.ids""" return self._ids def _set_elements(self, elements): """Set elements with a list of ids (string or numeric) or dicts (including key 'id')""" if not elements: raise ValueError("Coordinates.elements must not be empty") try: len(elements) except TypeError: raise ValueError("Coordinate.elements must be finite in length") try: self._ids = [e["name"] for e in elements] self._elements = elements except KeyError: # elements must have name msg = ( "Elements in dimension '{}' must have a name field, " "or be a simple list of identifiers" ) raise KeyError(msg.format(self.name)) except (TypeError, IndexError): # elements might not be dict-like - in which case, treat them as names self._ids = elements self._elements = [{"name": e} for e in elements] @property def dim(self): """Dim (dimension) is an alias for Coordinates.name""" return self.name @dim.setter def dim(self, dim): """Set name as dim""" self.name = dim