{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# MultiPandasIndex\n", "\n", "In many cases an Xarray custom index may be built on top of one or more `PandasIndex` instances. This notebook provides a helper class `MultiPandasIndex` with all the boilerplate, i.e., for each method the input arguments are deferred / dispatched to the encapsulated `PandasIndex` instances." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from __future__ import annotations\n", "\n", "from typing import Any, TYPE_CHECKING, Mapping, Hashable, Iterable, Sequence\n", "\n", "import numpy as np\n", "import pandas as pd\n", "import xarray as xr\n", "\n", "from xarray.core.indexes import Index, PandasIndex, IndexVars, is_scalar\n", "from xarray.core.indexing import IndexSelResult, merge_sel_results\n", "from xarray.core.utils import Frozen\n", "from xarray.core.variable import Variable\n", "\n", "#if TYPE_CHECKING:\n", "from xarray.core.types import ErrorOptions, JoinOptions, T_Index\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "class MultiPandasIndex(Index):\n", " \"\"\"Helper class to implement meta-indexes encapsulating\n", " one or more (single) pandas indexes.\n", " \n", " Each pandas index must relate to a separate dimension.\n", " \n", " This class shoudn't be instantiated directly.\n", "\n", " \"\"\"\n", " indexes: Frozen[Hashable, PandasIndex]\n", " dims: Frozen[Hashable, int]\n", " \n", " __slots__ = (\"indexes\", \"dims\")\n", " \n", " def __init__(self, indexes: Mapping[Hashable, PandasIndex]):\n", " dims = {idx.dim: idx.index.size for idx in indexes.values()}\n", " \n", " seen = set()\n", " dup_dims = [d for d in dims if d in seen or seen.add(d)]\n", " if dup_dims:\n", " raise ValueError(\n", " f\"cannot create a {self.__class__.__name__} from coordinates \"\n", " f\"sharing common dimension(s): {dup_dims}\"\n", " )\n", " \n", " self.indexes = Frozen(indexes)\n", " self.dims = Frozen(dims)\n", " \n", " @classmethod\n", " def from_variables(\n", " cls: type[T_Index], variables: Mapping[Any, Variable], options\n", " ):\n", " indexes = {\n", " k: PandasIndex.from_variables({k: v}, options={})\n", " for k, v in variables.items()\n", " }\n", "\n", " return cls(indexes)\n", " \n", " @classmethod\n", " def concat(\n", " cls: type[T_Index],\n", " indexes: Sequence[T_Index],\n", " dim: Hashable,\n", " positions: Iterable[Iterable[int]] = None,\n", " ) -> T_Index:\n", " new_indexes = {}\n", " \n", " for k, idx in self.indexes.items():\n", " if idx.dim == dim:\n", " new_indexes[k] = PandasIndex.concat(indexes, dim, positions)\n", " else:\n", " new_indexes[k] = idx\n", " \n", " return cls(new_indexes)\n", " \n", " def create_variables(\n", " self, variables: Mapping[Any, Variable] | None = None\n", " ) -> IndexVars:\n", "\n", " idx_variables = {}\n", "\n", " for idx in self.indexes.values():\n", " idx_variables.update(idx.create_variables(variables))\n", "\n", " return idx_variables\n", " \n", " def isel(\n", " self: T_Index, indexers: Mapping[Any, int | slice | np.ndarray | Variable]\n", " ) -> T_Index | PandasIndex | None:\n", " new_indexes = {}\n", " \n", " for k, idx in self.indexes.items():\n", " if k in indexers:\n", " new_idx = idx.isel({k: indexers[k]})\n", " if new_idx is not None:\n", " new_indexes[k] = new_idx\n", " else:\n", " new_indexes[k] = idx\n", " \n", " #\n", " # How should we deal with dropped index(es) (scalar selection)?\n", " # - drop the whole index?\n", " # - always return a MultiPandasIndex with remaining index(es)?\n", " # - return either a MultiPandasIndex or a PandasIndex?\n", " #\n", " \n", " if not len(new_indexes):\n", " return None\n", " elif len(new_indexes) == 1:\n", " return next(iter(new_indexes.values()))\n", " else:\n", " return type(self)(new_indexes)\n", "\n", " def sel(self, labels: dict[Any, Any], **kwargs) -> IndexSelResult:\n", " results: list[IndexSelResult] = []\n", "\n", " for k, idx in self.indexes.items():\n", " if k in labels:\n", " results.append(idx.sel({k: labels[k]}, **kwargs))\n", " \n", " return merge_sel_results(results)\n", " \n", " def _get_unmatched_names(self: T_Index, other: T_Index) -> set:\n", " return set(self.indexes).symmetric_difference(other.indexes)\n", " \n", " def equals(self: T_Index, other: T_Index) -> bool:\n", " # We probably don't need to check for matching coordinate names\n", " # as this is already done during alignment when finding matching indexes.\n", " # This may change in the future, though.\n", " # see https://github.com/pydata/xarray/issues/7002\n", " if self._get_unmatched_names(other):\n", " return False\n", " else:\n", " return all(\n", " [idx.equals(other.indexes[k]) for k, idx in self.indexes.items()]\n", " )\n", " \n", " def join(self: T_Index, other: T_Index, how: JoinOptions = \"inner\") -> T_Index:\n", " new_indexes = {}\n", "\n", " for k, idx in self.indexes.items():\n", " new_indexes[k] = idx.join(other.indexes[k], how=how)\n", " \n", " return type(self)(new_indexes)\n", " \n", " def reindex_like(self: T_Index, other: T_Index) -> dict[Hashable, Any]:\n", " dim_indexers = {}\n", " \n", " for k, idx in self.indexes.items():\n", " dim_indexers.update(idx.reindex_like(other.indexes[k]))\n", " \n", " return dim_indexers\n", " \n", " def roll(self: T_Index, shifts: Mapping[Any, int]) -> T_Index:\n", " new_indexes = {}\n", " \n", " for k, idx in self.indexes.items():\n", " if k in shifts:\n", " new_indexes[k] = idx.roll({k: shifts[k]})\n", " else:\n", " new_indexes[k] = idx\n", "\n", " return type(self)(new_indexes)\n", " \n", " def rename(\n", " self: T_Index,\n", " name_dict: Mapping[Any, Hashable],\n", " dims_dict: Mapping[Any, Hashable],\n", " ) -> T_Index:\n", " new_indexes = {}\n", " \n", " for k, idx in self.indexes.items():\n", " new_indexes[k] = idx.rename(name_dict, dims_dict)\n", " \n", " return type(self)(new_indexes)\n", " \n", " def copy(self: T_Index, deep: bool = True) -> T_Index:\n", " new_indexes = {}\n", " \n", " for k, idx in self.indexes.items():\n", " new_indexes[k] = idx.copy(deep=deep)\n", " \n", " return type(self)(new_indexes)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Issues:\n", " \n", "- How to allow custom `__init__` options in subclasses be passed to all the `type(self)(new_indexes)` calls inside the `MultiPandasIndex` \"base\" class? This could be done via `**kwargs` passed through... However, mypy will certainly complain (Liskov Substitution Principle)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example\n", "\n", "Just to see if it works well. `MultiPandasIndex` shouldn't be used directly in a DataArray or Dataset." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "da = xr.DataArray(\n", " np.random.uniform(size=(4, 5)),\n", " coords={\"x\": range(5), \"y\": range(4)},\n", " dims=(\"y\", \"x\"),\n", ")" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (y: 4, x: 5)>\n",
       "array([[0.43947939, 0.87899004, 0.76420298, 0.99212782, 0.83624422],\n",
       "       [0.75214201, 0.22178014, 0.0969697 , 0.74263207, 0.60629903],\n",
       "       [0.91366429, 0.25963693, 0.20251133, 0.50972423, 0.3037911 ],\n",
       "       [0.95073961, 0.31579758, 0.04704333, 0.81686866, 0.56483109]])\n",
       "Coordinates:\n",
       "  * x        (x) int64 0 1 2 3 4\n",
       "  * y        (y) int64 0 1 2 3
" ], "text/plain": [ "\n", "array([[0.43947939, 0.87899004, 0.76420298, 0.99212782, 0.83624422],\n", " [0.75214201, 0.22178014, 0.0969697 , 0.74263207, 0.60629903],\n", " [0.91366429, 0.25963693, 0.20251133, 0.50972423, 0.3037911 ],\n", " [0.95073961, 0.31579758, 0.04704333, 0.81686866, 0.56483109]])\n", "Coordinates:\n", " * x (x) int64 0 1 2 3 4\n", " * y (y) int64 0 1 2 3" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "da = (\n", " da\n", " .drop_indexes([\"x\", \"y\"])\n", " .set_xindex([\"x\", \"y\"], MultiPandasIndex)\n", ")\n", "\n", "da" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Indexes:\n", "x: <__main__.MultiPandasIndex object at 0x16b5cb580>\n", "y: <__main__.MultiPandasIndex object at 0x16b5cb580>" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "da.xindexes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## sel / isel" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (y: 2, x: 2)>\n",
       "array([[0.43947939, 0.43947939],\n",
       "       [0.91366429, 0.91366429]])\n",
       "Coordinates:\n",
       "  * x        (x) int64 0 0\n",
       "  * y        (y) int64 0 2
" ], "text/plain": [ "\n", "array([[0.43947939, 0.43947939],\n", " [0.91366429, 0.91366429]])\n", "Coordinates:\n", " * x (x) int64 0 0\n", " * y (y) int64 0 2" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "da_sel = da.sel(x=[0, 0], y=[0, 2])\n", "\n", "da_sel" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Indexes:\n", "x: <__main__.MultiPandasIndex object at 0x16b096880>\n", "y: <__main__.MultiPandasIndex object at 0x16b096880>" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "da_sel.xindexes" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (y: 4)>\n",
       "array([0.43947939, 0.75214201, 0.91366429, 0.95073961])\n",
       "Coordinates:\n",
       "  * x        int64 0\n",
       "  * y        (y) int64 0 1 2 3
" ], "text/plain": [ "\n", "array([0.43947939, 0.75214201, 0.91366429, 0.95073961])\n", "Coordinates:\n", " * x int64 0\n", " * y (y) int64 0 1 2 3" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# This is not right!\n", "# \"x\" coordinate should have no index but it has the same PandasIndex than \"y\"\n", "# it doesn't make any sense\n", "# \n", "# To fix this, `Index.isel` should probably return a `dict[Hashable, Index]` instead of `Index | None`\n", "# Or alternatively, we drop the whole `PandasMultiIndex` when this happens\n", "\n", "da_sel = da.sel(x=0)\n", "\n", "da_sel" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Indexes:\n", "x: \n", "y: " ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "da_sel.xindexes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## align" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(\n", " array([[0.43947939, 0.87899004, 0.76420298, 0.99212782, 0.83624422],\n", " [0.75214201, 0.22178014, 0.0969697 , 0.74263207, 0.60629903],\n", " [0.91366429, 0.25963693, 0.20251133, 0.50972423, 0.3037911 ],\n", " [0.95073961, 0.31579758, 0.04704333, 0.81686866, 0.56483109]])\n", " Coordinates:\n", " * x (x) int64 0 1 2 3 4\n", " * y (y) int64 0 1 2 3,\n", " \n", " array([[ nan, nan, nan, nan, nan],\n", " [ nan, nan, nan, nan, nan],\n", " [0.91366429, 0.25963693, nan, nan, nan],\n", " [0.95073961, 0.31579758, nan, nan, nan]])\n", " Coordinates:\n", " * x (x) int64 0 1 2 3 4\n", " * y (y) int64 0 1 2 3)" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xr.align(da, da.isel(x=[0, 1], y=[2, 3]), join=\"outer\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## roll" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (y: 4, x: 5)>\n",
       "array([[0.74263207, 0.60629903, 0.75214201, 0.22178014, 0.0969697 ],\n",
       "       [0.50972423, 0.3037911 , 0.91366429, 0.25963693, 0.20251133],\n",
       "       [0.81686866, 0.56483109, 0.95073961, 0.31579758, 0.04704333],\n",
       "       [0.99212782, 0.83624422, 0.43947939, 0.87899004, 0.76420298]])\n",
       "Coordinates:\n",
       "  * x        (x) int64 3 4 0 1 2\n",
       "  * y        (y) int64 1 2 3 0
" ], "text/plain": [ "\n", "array([[0.74263207, 0.60629903, 0.75214201, 0.22178014, 0.0969697 ],\n", " [0.50972423, 0.3037911 , 0.91366429, 0.25963693, 0.20251133],\n", " [0.81686866, 0.56483109, 0.95073961, 0.31579758, 0.04704333],\n", " [0.99212782, 0.83624422, 0.43947939, 0.87899004, 0.76420298]])\n", "Coordinates:\n", " * x (x) int64 3 4 0 1 2\n", " * y (y) int64 1 2 3 0" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "da_roll = da.roll({\"x\": 2, \"y\": -1}, roll_coords=True)\n", "\n", "da_roll" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Indexes:\n", "x: <__main__.MultiPandasIndex object at 0x16bf6bb80>\n", "y: <__main__.MultiPandasIndex object at 0x16bf6bb80>" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "da_roll.xindexes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## rename" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray (variable: 1, y: 4, z: 5)>\n",
       "array([[[0.43947939, 0.87899004, 0.76420298, 0.99212782, 0.83624422],\n",
       "        [0.75214201, 0.22178014, 0.0969697 , 0.74263207, 0.60629903],\n",
       "        [0.91366429, 0.25963693, 0.20251133, 0.50972423, 0.3037911 ],\n",
       "        [0.95073961, 0.31579758, 0.04704333, 0.81686866, 0.56483109]]])\n",
       "Coordinates:\n",
       "  * z         (z) int64 0 1 2 3 4\n",
       "  * y         (y) int64 0 1 2 3\n",
       "  * variable  (variable) object 'foo'
" ], "text/plain": [ "\n", "array([[[0.43947939, 0.87899004, 0.76420298, 0.99212782, 0.83624422],\n", " [0.75214201, 0.22178014, 0.0969697 , 0.74263207, 0.60629903],\n", " [0.91366429, 0.25963693, 0.20251133, 0.50972423, 0.3037911 ],\n", " [0.95073961, 0.31579758, 0.04704333, 0.81686866, 0.56483109]]])\n", "Coordinates:\n", " * z (z) int64 0 1 2 3 4\n", " * y (y) int64 0 1 2 3\n", " * variable (variable) object 'foo'" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "da_renamed = da.to_dataset(name=\"foo\").rename({\"x\": \"z\"}).to_array()\n", "\n", "da_renamed" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Indexes:\n", "z: <__main__.MultiPandasIndex object at 0x10e512780>\n", "y: <__main__.MultiPandasIndex object at 0x10e512780>\n", "variable: " ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "da_renamed.xindexes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# concat" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "cannot exclude dimension(s) x from alignment because these are used by an index together with non-excluded dimensions y", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m/var/folders/xd/3ls911kd6_n2wphwwd74b1dc0000gn/T/ipykernel_14023/2421416148.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;31m# I've no idea how easy/hard would it be to support that.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m \u001b[0mxr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconcat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mda\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mda\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdim\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"x\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m~/Git/github/benbovy/xarray/xarray/core/concat.py\u001b[0m in \u001b[0;36mconcat\u001b[0;34m(objs, dim, data_vars, coords, compat, positions, fill_value, join, combine_attrs)\u001b[0m\n\u001b[1;32m 229\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 230\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfirst_obj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mDataArray\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 231\u001b[0;31m return _dataarray_concat(\n\u001b[0m\u001b[1;32m 232\u001b[0m \u001b[0mobjs\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 233\u001b[0m \u001b[0mdim\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mdim\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/Git/github/benbovy/xarray/xarray/core/concat.py\u001b[0m in \u001b[0;36m_dataarray_concat\u001b[0;34m(arrays, dim, data_vars, coords, compat, positions, fill_value, join, combine_attrs)\u001b[0m\n\u001b[1;32m 655\u001b[0m \u001b[0mdatasets\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0marr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_to_temp_dataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 656\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 657\u001b[0;31m ds = _dataset_concat(\n\u001b[0m\u001b[1;32m 658\u001b[0m \u001b[0mdatasets\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 659\u001b[0m \u001b[0mdim\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/Git/github/benbovy/xarray/xarray/core/concat.py\u001b[0m in \u001b[0;36m_dataset_concat\u001b[0;34m(datasets, dim, data_vars, coords, compat, positions, fill_value, join, combine_attrs)\u001b[0m\n\u001b[1;32m 464\u001b[0m \u001b[0mdatasets\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mds\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcopy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mds\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mdatasets\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 465\u001b[0m datasets = list(\n\u001b[0;32m--> 466\u001b[0;31m \u001b[0malign\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mdatasets\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mjoin\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcopy\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mexclude\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mdim\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfill_value\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mfill_value\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 467\u001b[0m )\n\u001b[1;32m 468\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/Git/github/benbovy/xarray/xarray/core/alignment.py\u001b[0m in \u001b[0;36malign\u001b[0;34m(join, copy, indexes, exclude, fill_value, *objects)\u001b[0m\n\u001b[1;32m 762\u001b[0m \u001b[0mfill_value\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mfill_value\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 763\u001b[0m )\n\u001b[0;32m--> 764\u001b[0;31m \u001b[0maligner\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0malign\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 765\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0maligner\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mresults\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 766\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/Git/github/benbovy/xarray/xarray/core/alignment.py\u001b[0m in \u001b[0;36malign\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 546\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mresults\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcopy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdeep\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcopy\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 547\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 548\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfind_matching_indexes\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 549\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfind_matching_unindexed_dims\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 550\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0massert_no_index_conflict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/Git/github/benbovy/xarray/xarray/core/alignment.py\u001b[0m in \u001b[0;36mfind_matching_indexes\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 253\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 254\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mobj\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mobjects\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 255\u001b[0;31m \u001b[0mobj_indexes\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mobj_index_vars\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_normalize_indexes\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mxindexes\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 256\u001b[0m \u001b[0mobjects_matching_indexes\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj_indexes\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 257\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mkey\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0midx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mobj_indexes\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mitems\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/Git/github/benbovy/xarray/xarray/core/alignment.py\u001b[0m in \u001b[0;36m_normalize_indexes\u001b[0;34m(self, indexes)\u001b[0m\n\u001b[1;32m 229\u001b[0m \u001b[0mexcl_dims_str\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\", \"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0md\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mexclude_dims\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 230\u001b[0m \u001b[0mincl_dims_str\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\", \"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0md\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0md\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mall_dims\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mexclude_dims\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 231\u001b[0;31m raise ValueError(\n\u001b[0m\u001b[1;32m 232\u001b[0m \u001b[0;34mf\"cannot exclude dimension(s) {excl_dims_str} from alignment because \"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 233\u001b[0m \u001b[0;34m\"these are used by an index together with non-excluded dimensions \"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mValueError\u001b[0m: cannot exclude dimension(s) x from alignment because these are used by an index together with non-excluded dimensions y" ] } ], "source": [ "# Does Xarray support concat along a dimension part of a multi-dimension index? Looks like it doesn't.\n", "# It might require to review some alignment rules...\n", "# I've no idea how easy/hard would it be to support that.\n", "\n", "xr.concat([da.isel(x=[0]), da.isel(x=[1])], dim=\"x\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python [conda env:xarray_dev]", "language": "python", "name": "conda-env-xarray_dev-py" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.6" } }, "nbformat": 4, "nbformat_minor": 4 }