Source code for xdas.core.dataarray

"""
:class:`DataArray`: the primary N-dimensional labeled array object.

Features labeled coordinates, NumPy/Dask backing, and lazy
:class:`VirtualArray` support.
"""

import copy
import warnings
from functools import partial

import numpy as np
import xarray as xr
from dask.array import Array as DaskArray
from numpy.lib.mixins import NDArrayOperatorsMixin

from ..coordinates import Coordinates
from ..dask.core import from_dict, to_dict
from ..virtual import VirtualArray, _to_human

HANDLED_NUMPY_FUNCTIONS = {}
HANDLED_METHODS = {}


[docs] class DataArray(NDArrayOperatorsMixin): """ N-dimensional array with labeled coordinates and dimensions. It is the equivalent of an xarray.DataArray but with custom coordinate objects. Most of the DataArray API follows the DataArray one. DataArray objects also provide virtual dataset capabilities to manipulate huge multi-file NETCDF4 or HDF5 datasets. Parameters ---------- data : array_like Values of the array. Can be a VirtualSource or a VirtualLayout for lazy loading of netCDF4/HDF5 files. coords : dict of Coordinate Coordinates to use for indexing along each dimension. dims : sequence of string, optional Name(s) of the data dimension(s). If provided, must be equal to the keys of `coords`. name : str, optional Name of this array. attrs : dict_like, optional Attributes to assign to the new instance. Raises ------ ValueError If dims do not match the keys of coords. """
[docs] def __init__(self, data=None, coords=None, dims=None, name=None, attrs=None): # data if data is None: data = np.array(np.nan) if not hasattr(data, "__array__"): data = np.asarray(data) self._data = data # coords & dims if dims is None: if coords is None: dims = tuple(f"dim_{index}" for index in range(data.ndim)) elif isinstance(coords, Coordinates): dims = coords.dims if dims is not None and len(dims) != data.ndim: raise ValueError("different number of dimensions on `data` and `dims`") coords = Coordinates(coords, dims) coords._assign_parent(self) self._coords = coords # metadata self.name = name self.attrs = attrs
def __getitem__(self, key): if isinstance(key, str): return self.coords[key] else: query = self.coords.get_query(key) data = self.data.__getitem__(tuple(query.values())) coords = { name: ( coord.__getitem__(query[coord.dim]) if coord.dim is not None else coord ) for name, coord in self.coords.items() } dims = tuple(dim for dim in self.dims if not np.isscalar(query[dim])) return self.__class__(data, coords, dims, self.name, self.attrs) def __setitem__(self, key, value): if isinstance(key, str): self.coords[key] = value else: query = self.coords.get_query(key) self.data.__setitem__(tuple(query.values()), value) def __repr__(self): edgeitems = 3 if not np.issubdtype(self.dtype, np.complexfloating) else 2 precision = 6 if not np.issubdtype(self.dtype, np.complexfloating) else 4 if isinstance(self.data, np.ndarray): data_repr = np.array2string( self.data, precision=precision, threshold=0, edgeitems=edgeitems ) elif isinstance(self.data, DaskArray): data_repr = f"DaskArray: {_to_human(self.data.nbytes)} ({self.data.dtype})" else: data_repr = repr(self.data) string = "<xdas.DataArray (" string += ", ".join([f"{dim}: {size}" for dim, size in self.sizes.items()]) string += ")>\n" string += data_repr if self.coords: string += "\n" + repr(self.coords) dim_without_coords = tuple(dim for dim in self.dims if dim not in self.coords) if dim_without_coords: string += ( "\n" + "Dimensions without coordinates: " + ",".join(dim_without_coords) ) return string def __len__(self): return self.shape[0] def __array__(self, dtype=None): if dtype is None: return self.data.__array__() else: return self.data.__array__(dtype) def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): from .routines import broadcast_coords, broadcast_to # TODO: circular import if not method == "__call__": return NotImplemented coords = broadcast_coords( *tuple(input for input in inputs if isinstance(input, self.__class__)) ) inputs = tuple(broadcast_to(input, coords) for input in inputs) arrays = tuple(input.values for input in inputs) if "out" in kwargs: # TODO: check outputs alignements kwargs["out"] = tuple(np.asarray(output) for output in kwargs["out"]) if "where" in kwargs: kwargs["where"] = np.asarray(broadcast_to(kwargs["where"], coords)) outputs = getattr(ufunc, method)(*arrays, **kwargs) if isinstance(outputs, tuple): return tuple(self.__class__(output, coords) for output in outputs) else: return self.__class__(outputs, coords) def __array_function__(self, func, types, args, kwargs): if func not in HANDLED_NUMPY_FUNCTIONS: return NotImplemented # Note: this allows subclasses that don't override # __array_function__ to handle MyArray objects if not all(issubclass(t, self.__class__) for t in types): return NotImplemented return HANDLED_NUMPY_FUNCTIONS[func](*args, **kwargs) def __getattr__(self, name): if name in HANDLED_METHODS: func = HANDLED_METHODS[name] method = partial(func, self) method.__name__ = name method.__doc__ = ( f" Method implementation of {name} function.\n\n" + " *Original docstring below. Skip first parameter.*\n" + func.__doc__ ) return method else: raise AttributeError(f"'DataArray' object has no attribute '{name}'")
[docs] def conj(self): """Return the complex conjugate, element-wise.""" return np.conj(self)
[docs] def conjugate(self): """Return the complex conjugate, element-wise (alias of :meth:`conj`).""" return np.conjugate(self)
@property def data(self): """The underlying array (numpy, dask, or :class:`~xdas.virtual.VirtualArray`).""" return self._data @data.setter def data(self, value): """Replace the underlying data; must have the same shape as the current array.""" if not hasattr(value, "__array__"): value = np.asarray(value) if not value.shape == self.shape: raise ValueError( f"replacement data must match the same shape. Replacement data " f"has shape {value.shape}; original data has shape {self.shape}" ) self._data = value @property def coords(self): """The :class:`~xdas.coordinates.Coordinates` container for this array.""" return self._coords @coords.setter def coords(self, value): """Replace the coordinates container; dimensions must remain unchanged.""" value = Coordinates(value) if not value.dims == self.coords.dims: raise ValueError( f"replacement coords must have the same dimensions. Replacement coords " f"has dims {value.dims}; original coords has dims {self.dims}" ) value._assign_parent(self) self._coords = value @property def dims(self): """Tuple of dimension names in axis order.""" return self.coords.dims @dims.setter def dims(self, value): """Not supported — raises :exc:`AttributeError` directing users to rename/transpose instead.""" raise AttributeError( "you cannot assign dims on a DataArray, " "use .rename(), .transpose() or .swap_dims() instead" ) @property def shape(self): """Shape tuple of the underlying data array.""" return self.data.shape @property def dtype(self): """NumPy dtype of the underlying data array.""" return self.data.dtype @property def ndim(self): """Number of dimensions.""" return self.data.ndim @property def size(self): """Total number of elements.""" return self.data.size @property def sizes(self): """Dict-like mapping from dimension name to its size.""" return DimSizer(self) @property def nbytes(self): """Total byte size of the underlying data.""" return self.data.nbytes @property def values(self): """Materialised numpy array of all values.""" return self.__array__() @property def empty(self): """``True`` if any dimension has size zero.""" return np.prod(self.data.shape) == 0 @property def loc(self): """Label-based indexer; supports ``da.loc[label]`` and ``da.loc[label] = value``.""" return LocIndexer(self)
[docs] def equals(self, other): """Return ``True`` if *other* has equal data, coordinates, dims, name, and attrs.""" if isinstance(other, self.__class__): if not self.dtype == other.dtype: return False if not np.array_equal(self.values, other.values, equal_nan=True): return False if not self.coords.equals(other.coords): return False if not self.dims == other.dims: return False if not self.name == other.name: return False if not self.attrs == other.attrs: return False return True else: return False
[docs] def get_axis_num(self, dim): """ Return axis number corresponding to dimension in this array. Parameters ---------- dim : str Dimension name for which to lookup axis. Returns ------- int Axis number corresponding to the given dimension """ if dim == "first": return 0 elif dim == "last": return self.ndim - 1 elif dim in self.dims: return self.dims.index(dim) else: raise ValueError("dim not found")
[docs] def isel(self, indexers=None, drop=False, **indexers_kwargs): """ Return a new DataArray selecting indexes along the specified dimension(s). Parameters ---------- indexers : dict, optional A dict with keys matching dimensions and values given by integers, slice objects or arrays. drop : bool, optional If ``drop=True``, drop coordinates variables in `indexers` instead of making them scalar. **indexers_kwargs : dict, optional The keyword arguments form of integers. Overwrite indexers input if both are provided. Returns ------- DataArray The selected subset of the DataArray. """ if indexers is None: indexers = {} indexers.update(indexers_kwargs) da = self[indexers] if drop: for dim in indexers: if da[dim].isscalar(): da = da.drop_coords(dim) return da
[docs] def sel( self, indexers=None, method=None, endpoint=True, drop=False, **indexers_kwargs ): """ Return a new DataArray selecting index labels along the specified dimension(s). In contrast to DataArray.isel, indexers for this method should use labels instead of integers. Parameters ---------- indexers : dict, optional A dict with keys matching dimensions and values given by scalars, slices or arrays of tick labels. method : str, optional Method to use for inexact matches. None (default) means only exact matches. "nearest" finds the nearest index value. endpoint : bool, optional Whether to include the endpoint of a slice. Default is True. drop : bool, optional If ``drop=True``, drop coordinates variables in `indexers` instead of making them scalar. **indexers_kwargs : dict, optional The keyword arguments form of integers. Overwrite indexers input if both are provided. Returns ------- DataArray The selected part of the original data array. """ if indexers is None: indexers = {} indexers.update(indexers_kwargs) # handle not monotonic increasing coordinates for dim in indexers: if not self[dim].is_monotonic_increasing(): if isinstance(indexers[dim], slice): warnings.warn( f"dimension {dim} is not monotonic increasing, " f"spliting on overlaps, slicing and concatenating can be slow..." ) from ..core.routines import concat, split chunks = [ chunk.sel(indexers, method, endpoint, drop) for chunk in split(self, "overlaps", dim, False) ] return concat(chunks, dim, False) else: raise NotImplementedError( f"cannot find specific coordinate value(s) because " f"dimension {dim} contains overlaps" ) key = self.coords.to_index(indexers, method, endpoint) da = self[key] if drop: for dim in indexers: if da[dim].isscalar(): da = da.drop_coords(dim) return da
[docs] def drop_dims(self, *dims): """Return a new :class:`DataArray` with *dims* and their coordinates removed.""" coords = self.coords.drop_dims(*dims) return self.__class__(self.data, coords, coords.dims, self.name, self.attrs)
[docs] def drop_coords(self, *names): """Return a new :class:`DataArray` with the named coordinates removed.""" coords = self.coords.drop_coords(*names) return self.__class__(self.data, coords, coords.dims, self.name, self.attrs)
[docs] def copy(self, deep=True, data=None): """ Return a copy of this array. If deep=True, a deep copy is made of the data array. Otherwise, a shallow copy is made, and the returned data array's values are a new view of this data array's values. Use data to create a new object with the same structure as original but entirely new data. Parameters ---------- deep : bool, optional Whether the data array and its coordinates are loaded into memory and copied onto the new object. Default is True. data : array_like, optional Data to use in the new object. Must have same shape as original. When data is used, deep is ignored for all data variables, and only used for coords. Returns ------- DataArray New object with dimensions, attributes, coordinates, name, encoding, and optionally data copied from original. """ if deep: copy_fn = copy.deepcopy else: copy_fn = copy.copy if data is None: data = copy_fn(self.data) return self.__class__( data, copy_fn(self.coords), copy_fn(self.dims), copy_fn(self.name), copy_fn(self.attrs), )
[docs] def rename(self, new_name_or_name_dict=None, **names): """ Return a new DataArray with renamed coordinates, dimensions or a new name. Parameters ---------- new_name_or_name_dict : str or dict-like, optional If the argument is dict-like, it used as a mapping from old names to new names for coordinates or dimensions. Otherwise, use the argument as the new name for this array. **names : Hashable, optional The keyword arguments form of a mapping from old names to new names for coordinates or dimensions. One of new_name_or_name_dict or names must be provided. Returns ------- DataArray Renamed array or array with renamed coordinates. """ if new_name_or_name_dict is None: new_name = self.name name_dict = names elif isinstance(new_name_or_name_dict, dict): new_name = self.name name_dict = new_name_or_name_dict | names else: new_name = new_name_or_name_dict name_dict = names new_dims = tuple(name_dict.get(dim, dim) for dim in self.dims) new_coords = {} for name, coord in self.coords.items(): dim = name_dict.get(coord.dim, coord.dim) name = name_dict.get(name, name) new_coords[name] = coord.__class__(coord.data, dim, coord.dtype) return self.__class__(self.data, new_coords, new_dims, new_name, self.attrs)
[docs] def load(self): """Load the data into memory and return a new :class:`DataArray` backed by a numpy array.""" return self.copy(data=self.data.__array__())
[docs] def assign_coords(self, coords=None, **coords_kwargs): """Assign new coordinates to this object. Returns a new object with all the original data in addition to the new coordinates. Parameters ---------- coords : mapping of dim to coord, optional A mapping whose keys are the names of the coordinates and values are the coordinates to assign. The mapping will generally be a dict or :class:`Coordinates`. - If a value is a standard data value that can be parsed by Coordinate — the data is simply assigned as a coordinate. - A coordinate can also be defined and attached to an existing dimension using a tuple with the first element the dimension name and the second element the values for this new coordinate. **coords_kwargs : optional The keyword arguments form of ``coords``. One of ``coords`` or ``coords_kwargs`` must be provided. Returns ------- assigned : same type as caller A new object with the new coordinates in addition to the existing data. Examples -------- Reset `DataArray` time to start at zero: >>> import xdas as xd >>> import numpy as np >>> da = xd.DataArray( ... data=np.zeros(3), ... coords={"time": np.array([3, 4, 5])}, ... ) >>> da <xdas.DataArray (time: 3)> [0. 0. 0.] Coordinates: * time (time): [3 ... 5] >>> da.assign_coords(time=[0, 1, 2]) <xdas.DataArray (time: 3)> [0. 0. 0.] Coordinates: * time (time): [0 ... 2] The function also accepts dictionary arguments: >>> da.assign_coords({"time": [0, 1, 2]}) <xdas.DataArray (time: 3)> [0. 0. 0.] Coordinates: * time (time): [0 ... 2] New coordinate can also be attached to an existing dimension: >>> da.assign_coords(relative_time=("time", [0, 1, 2])) <xdas.DataArray (time: 3)> [0. 0. 0.] Coordinates: * time (time): [3 ... 5] relative_time (time): [0 ... 2] """ da = self.copy(deep=False) if coords is None: coords = {} coords.update(coords_kwargs) for name, coord in coords.items(): da.coords[name] = coord return da
[docs] def swap_dims(self, dims_dict=None, **dims_kwargs): """ Return a new DataArray with swapped dimensions. Parameters ---------- dims_dict : dict-like Dictionary whose keys are current dimension names and whose values are new names. **dims_kwargs : {existing_dim: new_dim, ...}, optional The keyword arguments form of ``dims_dict``. One of dims_dict or dims_kwargs must be provided. Returns ------- swapped : DataArray DataArray with swapped dimensions. Examples -------- >>> import xdas as xd >>> da = xd.DataArray( ... data=[0, 1], ... coords={"x": ["a", "b"], "y": ("x", [0, 1])}, ... ) >>> da <xdas.DataArray (x: 2)> [0 1] Coordinates: * x (x): ['a' 'b'] y (x): [0 1] Make y the dimensional coordinate: >>> da.swap_dims({"x": "y"}) <xdas.DataArray (y: 2)> [0 1] Coordinates: x (y): ['a' 'b'] * y (y): [0 1] Assign a new empy coordinate z as dimensional coordinate. Use the ``**kwargs`` syntax this time: >>> da.swap_dims(x="z") <xdas.DataArray (z: 2)> [0 1] Coordinates: x (z): ['a' 'b'] y (z): [0 1] Dimensions without coordinates: z """ if dims_dict is None: dims_dict = {} dims_dict.update(dims_kwargs) for dim in dims_dict: if dim not in self.dims: raise KeyError( f"dimension {dim} not found in current object with dims {self.dims}" ) dims = tuple(dims_dict[dim] if dim in dims_dict else dim for dim in self.dims) coords = {} for name, coord in self.coords.copy(deep=False).items(): if coord.dim in dims_dict: coord.dim = dims_dict[coord.dim] coords[name] = coord return self.__class__(self.data, coords, dims, self.name, self.attrs)
@property def T(self): """Transposed array with dimension order reversed.""" return self.transpose()
[docs] def transpose(self, *dims): """ Return a new DataArray object with transposed dimensions. Parameters ---------- *dims : Hashable, optional By default, reverse the dimensions. Otherwise, reorder the dimensions to this order. The provided `dims` must be a permutation of the original dimensions. If `...` is provided, it is replaced by the missing dimensions. Returns ------- transposed : DataArray The returned DataArray's array is transposed. Notes ----- This operation returns a view of this array's data if this later is a numpy.ndarray object. Otherwise the data is loaded into memory. See Also -------- numpy.transpose Examples -------- >>> import xdas as xd >>> import numpy as np >>> da = xd.DataArray( ... np.arange(2 * 3).reshape(2, 3), {"x": [0, 1], "y": [2, 3, 4]} ... ) >>> da <xdas.DataArray (x: 2, y: 3)> [[0 1 2] [3 4 5]] Coordinates: * x (x): [0 1] * y (y): [2 ... 4] >>> da.transpose("y", "x") # equivalent to not providing any arguments here <xdas.DataArray (y: 3, x: 2)> [[0 3] [1 4] [2 5]] Coordinates: * x (x): [0 1] * y (y): [2 ... 4] >>> assert da.transpose(..., "x").equals(da.transpose("y", ...)) # equivalent """ if not dims: dims = tuple(reversed(self.dims)) if ... in dims: missing_dims = tuple(dim for dim in self.dims if dim not in dims) dims = tuple( item for dim in dims for item in (missing_dims if dim is ... else (dim,)) ) if not (len(dims) == len(self.dims) and set(dims) == set(self.dims)): raise ValueError(f"{dims} must be a permutation of {self.dims}") axes = tuple(self.get_axis_num(dim) for dim in dims) data = np.transpose(self.data, axes) return self.__class__(data, self.coords, dims, self.name, self.attrs)
[docs] def expand_dims(self, dim, axis=0): """ Add an additional dimension at a given axis position. Parameters ---------- dim : str Dimensions to include on the new variable. axis : int Axis position where new axis is to be inserted (position(s) on the result array). Returns ------- expanded : DataArray A copy of this object, but with additional dimension. Notes ----- This operation returns a view of this array's data if this later is a numpy.ndarray object. Otherwise the data is loaded into memory. See Also -------- numpy.expand_dims Examples -------- >>> import xdas as xd >>> da = xd.DataArray([1., 2., 3.], {"x": [0, 1, 2]}) >>> da <xdas.DataArray (x: 3)> [1. 2. 3.] Coordinates: * x (x): [0 ... 2] >>> da.expand_dims("y", 0) <xdas.DataArray (y: 1, x: 3)> [[1. 2. 3.]] Coordinates: * x (x): [0 ... 2] Dimensions without coordinates: y """ if dim in self.dims: raise ValueError(f"cannot expand on existing dimension {dim}") coords = self.coords.copy() if dim in coords: if coords[dim].isscalar(): coords[dim] = [coords[dim].values] else: raise ValueError( f"cannot expand along {dim} because of existing non-dimensional " f"coordinate {dim}. Consider dropping this coordinate." ) data = np.expand_dims(self.data, axis) dims = self.dims[:axis] + (dim,) + self.dims[axis:] return self.__class__(data, coords, dims, self.name, self.attrs)
[docs] def to_xarray(self): """ Convert to the xarray implementation of the DataArray structure. Coordinates are converted to dense arrays and lazy values are loaded in memory. Returns ------- DataArray The converted in-memory DataArray. """ data = self.__array__() coords = { name: (coord.dim if coord.dim else (), coord.values) for name, coord in self.coords.items() } return xr.DataArray(data, coords, self.dims, self.name, self.attrs)
[docs] @classmethod def from_xarray(cls, da): """Build a :class:`DataArray` from an :class:`xarray.DataArray` *da*.""" return cls(da.data, da.coords, da.dims, da.name, da.attrs)
[docs] def to_stream( self, network="NET", station="DAS{:05}", location="00", channel="{:1}N1", dim={"last": "first"}, ): """ Convert a data array into an obspy stream. Parameters ---------- network : str, optional The network code, by default "NET". station : str, optional The station code. Must be a string that can be formatted. By default "DAS{:05}" location : str, optional The location code, by default "00". channel : str, optional The channel code. If the string can be formatted, the band code will be inferred from the sampling rate. By default "{:1}N1" dim : dict, optional A dict with as key the spatial dimension to split into traces, and as value the temporal dimension. By default {"last": "first"}. Returns ------- Stream the obspy stream version of the data array. """ from ..io.miniseed import to_stream return to_stream(self, network, station, location, channel, dim)
[docs] @classmethod def from_stream(cls, st, dims=("channel", "time")): """ Convert an obspy stream into a data array. Traces in the stream must have the same length and must be syncronized. Traces are stacked along the first axis. The trace ids are used as labels along the first dimension. Parameters ---------- st: Stream The stream to convert. dims: (str, str) The name of the dimension respectively given to the trace and time dimensions. Returns ------- DataArray: The consolidated data array. """ from ..io.miniseed import from_stream return from_stream(st, dims)
[docs] def to_netcdf( self, fname, mode="w", group=None, virtual=None, encoding=None, create_dirs=False, ): """ Write DataArray contents to a netCDF file. Parameters ---------- fname : str Path to which to save this dataset. mode : {'w', 'a'}, optional Write ('w') or append ('a') mode. If mode='a', the file must already exist. group : str, optional Path to the netCDF4 group in the given file to open. virtual : bool, optional Whether to write a virtual dataset. The DataArray data must be a VirtualSource or a VirtualLayout. Default (None) is to try to write a virtual dataset if possible. encoding : dict, optional Dictionary of encoding attributes. Because a DataArray contains a unique data variable, the encoding dictionary should not contain the variable name. For more information on encoding, see the `xarray documentation <http://xarray.pydata.org/en/stable/io.html#netcdf>`_. Note that xdas use the `h5netcdf` engine to write the data. If you want to use a specific plugin for compression, you can use the `hdf5plugin` package. For example, to use the ZFP compression, you can use the `hdf5plugin.Zfp` class. create_dirs : bool, optional Whether to create parent directories if they do not exist. Default is False. Examples -------- >>> import os >>> import tempfile >>> import numpy as np >>> import xdas as xd >>> import hdf5plugin Create some sample data array: >>> da = xd.DataArray(np.random.rand(100, 100)) Save the dataset with ZFP compression: >>> encoding = {"chunks": (10, 10), **hdf5plugin.Zfp(accuracy=0.001)} >>> with tempfile.TemporaryDirectory() as tmpdir: ... tmpfile = os.path.join(tmpdir, "path.nc") ... da.to_netcdf(tmpfile, encoding=encoding) """ from ..io.xdas import save_dataarray save_dataarray(self, fname, mode, group, virtual, encoding, create_dirs)
[docs] @classmethod def from_netcdf(cls, fname, group=None): """ Lazily read a data array from a NetCDF file. Parameters ---------- fname: str The path of the file to open. group: str, optional The location of the data array within the file. Root by default Returns ------- DataArray The openend data array. """ from ..io.xdas import open_dataarray return open_dataarray(fname, group)
[docs] def to_dict(self): """Convert the DataArray to a dictionary.""" if isinstance(self.data, VirtualArray): raise NotImplementedError("cannot convert a virtual array to a dictionary") elif isinstance(self.data, np.ndarray): data = self.data.tolist() elif isinstance(self.data, DaskArray): # pragma: no branch data = to_dict(self.data) return { "data": data, "coords": self.coords.to_dict()["coords"], "dims": self.dims, "name": self.name, "attrs": self.attrs, }
[docs] @classmethod def from_dict(cls, dct): """Create a DataArray from a dictionary.""" if isinstance(dct["data"], list): data = np.array(dct["data"]) elif isinstance(dct["data"], dict): data = from_dict(dct["data"]) else: raise ValueError("data must be a list or a dictionary") coords = Coordinates.from_dict({key: dct[key] for key in ["coords", "dims"]}) return cls(data, coords, dct["dims"], dct["name"], dct["attrs"])
[docs] def plot(self, *args, **kwargs): """ Plot a DataArray. This plot function uses the xarray way of plotting depending on the number of dimensions your data has. Please for the args and kwargs refer to the corresponding xarray functions. For a DataArray with one dimension: refer to `xarray.plot.line <https://docs.xarray.dev/en/stable/generated/xarray.plot.line.html>`_. For a DataArray of 2 dimensions or more: refer to `xarray.plot.imshow <https://docs.xarray.dev/en/stable/generated/xarray.plot.imshow.html>`_. For other: refer to `xarray.plot.hist <https://docs.xarray.dev/en/latest/generated/xarray.plot.hist.html#xarray.plot.hist>`_. Parameters ---------- *args: See the corresponding xarray args. **kwargs: See the corresponding xarray kwargs. Returns ------- artist The same type of primitive artist that the wrapped Matplotlib function returns. """ if self.ndim == 1: self.to_xarray().plot.line(*args, **kwargs) elif self.ndim == 2: self.to_xarray().plot.imshow(*args, **kwargs) else: self.to_xarray().plot(*args, **kwargs)
class LocIndexer: """Label-based indexer returned by :attr:`DataArray.loc`.""" def __init__(self, obj): self.obj = obj def __getitem__(self, key): key = self.obj.coords.to_index(key) return self.obj.__getitem__(key) def __setitem__(self, key, value): key = self.obj.coords.to_index(key) self.obj.__setitem__(key, value) class DimSizer(dict): """Dict-like mapping from dimension names to their sizes, returned by :attr:`DataArray.sizes`.""" def __init__(self, obj): super().__init__({dim: size for dim, size in zip(obj.dims, obj.shape)}) def __getitem__(self, key): if key == "first": key = list(self.keys())[0] if key == "last": key = list(self.keys())[-1] return super().__getitem__(key)