You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Explicit indexes in xarray's data-model (Future of MultiIndex) #1603
I think we can continue the discussion we have in #1426 about MultiIndex here.
In comment , @shoyer recommended to remove MultiIndex from public API.
I agree with this, as long as my codes work with this improvement.
I think if we could have a list of possible MultiIndex use cases here,
it would be easier to deeply discuss and arrive at a consensus of the future API.
One API design challenge here is that I think we still want a explicit notation of "indexed" variables. We could possibly allow operations like .sel() on non-indexed variables, but they would be slower, because we would not want to create expensive hash-tables (i.e., pandas.Index) in a non-transparent fashion.
"Indexes" might not even need to be part of the main Dataset.__repr__, but it would certainly be the repr for Dataset.indexes. Other entries could include:
We would promote "Indexes" to a first-class concept in the xarray data model:
(a) The levels of a MultiIndex would have corresponding Variable objects and be found in coords.
(b) In contrast, theMultiIndex would not have a corresponding Variable object or be part of coords, though it could still be returned upon __getitem__ access (computed on demand from .indexes).
(c) Dataset and DataArray would gain an indexes argument in their constructors, which could be used for passing indexes on to new xarray objects.
Coordinates marked with * are part of an index. They can't be modified, unless all corresponding indexes ares removed.
Indexes would still be propagated, like coordinates.
Reacted by Keisuke Fujii, Ryan May, Fabian Hofmann, Florian R. Hölzlwimmer and Luke Davis
I think we currently assume variables[dim] is an Index.
Does your proposal means that Dataset will keep an additional attribute indexes, and indexes[dim] gives a pd.Index (or pd.MultiIndex, KDTree)?
Does your proposal means that Dataset will keep an additional attribute indexes, and indexes[dim] gives a pd.Index (or pd.MultiIndex, KDTree)?
Yes, exactly. We actually already have an attribute that works like this, but it's current computed lazily, from either Dataset._variables or DataArray._coords.
I think that promoting "Indexes" to a first-class concept is indeed a very good idea, at both internal and public levels, even if at the latter level it would be another concept for users (it should be already familiar for pandas users, though). IMHO the "coordinate" and "index" concepts are different enough to consider them separately.
I like the proposed repr for Dataset.indexes. I wouldn't mind if it is not included in Dataset.__repr__, considering that multi-indexes, kdtree, etc. only represent a few use cases. In too many cases it could result in a long, uninformative list of simple pandas.Index.
I have to think a bit more about the details but I like the idea.
@shoyer, could you add more details of this idea?
I think I do not yet fully understand the practical difference between dim and index.
Use cases of the independent Index and dims
Would it be general cases where dimension and index are independent?
(It is the case only for MultiIndex and KDtree)?
MultiIndex implementation
In MultiIndex case, will a xarray object store a MultiIndex object and also the level variables as Variable objects (there will be some duplicates)?
If indexes[dim] returns multiple Variables, which realizes a MultiIndex-like structure without pd.MultiIndex, indexes would be very different from dim,
because a single dimension can have multiple indexes.
Use cases of the independent Index and dims
Would it be general cases where dimension and index are independent?
(It is the case only for MultiIndex and KDtree)?
We would still assign default indexes (using a normal pandas.Index) when you assign a 1D coordinate with matching name and dimension. But in general, yes, it seems like you should be able to make an index even for variables that aren't dimensions, including for a 1D variable whose name does not match a dimension. The rule would be that any coordinates can be part of an index.
Another aspect to consider how to handle alignment when you have indexes along non-dimension coordinates. Probably the most elegant rule would again be to check all indexed variables for exact matches.
Directly assigning indexes rather than using this default or set_index() would be an advanced feature, not recommended for everyday use. The main use case is routines which create a new xarray object based on an existing one, and want to re-use old indexes.
For performance reasons, we probably do not want to actually check the values of manually assigned indexes, although we should verify that the shape matches. (We would have a clear disclaimer that if you manually assign an index with mismatched values the behavior is not well defined.)
In principle, this data model would allow for two mostly equivalent indexing schemes: MultiIndex[time, space] vs two indexes Index[time] and Index[space]. We would need to figure out how to propagate and compare indexes like this. (I suppose if the coordinate values match, the result could have the union of all indexes from input arguments.)
MultiIndex implementation
In MultiIndex case, will a xarray object store a MultiIndex object and also the level variables as Variable objects (there will be some duplicates)?
Yes, this is a little unfortunate. We could potentially make a custom wrapper for use in IndexVariable._data on the level variabless that lazily computes values from the MultiIndex (similar to our LazilyIndexedArray class), but I'm not certain yet that this is necessary.
If indexes[dim] returns multiple Variables, which realizes a MultiIndex-like structure without pd.MultiIndex, indexes would be very different from dim,
because a single dimension can have multiple indexes.
Every entry in indexes should be a single pandas.Index or subclass, including MultiIndex (possibly eventually allowing for index-like objects such as something based on a KDTree).
Thanks for the details.
(Sorry for my late responce. It took a long for me to understand what does it look like.)
I am wondering what the advantageous cases which are realized with this Index concept are.
As far as my understanding is correct,
It will enable more flexible indexing, e.g. more than one Indexes are associated with one dimension and we can select from these coordinate values very flexibly.
It will naturally integrate more advanced Indexes such as KDTree
Are they correct?
Probably the most elegant rule would again be to check all indexed variables for exact matches.
That sounds reasonable.
In principle, this data model would allow for two mostly equivalent indexing schemes: MultiIndex[time, space] vs two indexes Index[time] and Index[space].
I like the latter one, as it is easier to understand even for non-pandas users.
What does the actual implementation look like? xr.Dataset.indexes will be an OrderedDict that maps from variable's name to its associated dimension?
Actual instance of Index will be one of xr.Dataset.variables?
I am wondering what the advantageous cases which are realized with this Index concept are.
The other advantage is that it solves many of the issues with the current MultiIndex implementation. Making MultiIndex levels their own variables considerably simplifies the data model, and means that many features (including serialization) should "just work".
In principle, this data model would allow for two mostly equivalent indexing schemes: MultiIndex[time, space] vs two indexes Index[time] and Index[space].
I like the latter one, as it is easier to understand even for non-pandas users.
I agree, but there are probably some advantages to using a MultiIndex internally. For example, it allows for looking up on multiple levels at the same time.
What does the actual implementation look like?
xr.Dataset.indexes will be an OrderedDict that maps from variable's name to its associated dimension?
Actual instance of Index will be one of xr.Dataset.variables?
I think we could get away with making xr.Dataset.indexes simply a dict, with keys given by index names and values given by a pandas.Index instance. We should enforce that Index.name or MultiIndex.names corresponds to coordinate variables.
For KDTree, this means we'll have to write our own wrapper KDTreeIndex that adds a names property, but we would probably need to add special methods like get_indexer anyways.
Just to say I'm interested in how MultiIndexes are handled also. In our use case, we have two variables conventionally named CHROM (chromosome) and POS (position) which together describe a location in a genome. I want to combine both variables into a multi-index so I can, e.g., select all data from some data variable for chromosome X between positions 100,000-200,000. For all our data variables, this genome location multi-index would be used to index the first dimension.
Will the new API preserve the order of the levels? One of the features that's necessary for MultiIndex to be truly hierarchical is that there is a defined order to the levels.
@jjpr-mit can you explain your use case a little more? What sort of order dependent queries do you want to do? The one that comes to mind for me are range based queries, e.g, [('bar', 1) : ('foo', 9)].
I think it is still relatively easy to ensure a unique ordering between levels, based on the order of coordinate variables in the xarray dataset.
A bigger challenge is that for efficiency, these sorts of queries depend critically on having an actual MultiIndex. This means that if indexes for each of the levels arise from different arguments that were merged together, we might need to "merge" the separate indexes into a joint MultiIndex. This could potentially be slightly expensive.
I'm not sure to fully understand your suggestion, would you mind sharing some illustrative examples?
It is useful to have two distinct coordinate variable vs data variable concepts. Although both are data arrays, the former is used to locate data in the dimensional space(s) defined by all dimensions in the dataset while the latter is used to store field data.
It also helps to have a clear separation between the coordinate variable and index concepts. An index is a specific data structure or object that allows efficient data extraction or alignment based one or more coordinate labels. Sometimes an index object may be handled like a data array (like pandas indexes) but this is not always the case (e.g., a KD-Tree).
Currently in Xarray the index concept is hidden behind "dimension" coordinate variables. The goal of the explicit index refactor is to bring it to the light and make it available to any coordinate (and also open it to custom index structures, not only pandas indexes).
It looks like what you suggest is some kind of implicit (co-)indexes hidden behind any dataset variable(s)? We actually took the opposite direction, trying to make everything explicit.
I'm not sure to fully understand your suggestion, would you mind sharing some illustrative examples?
It is useful to have two distinct coordinate variable vs data variable concepts. Although both are data arrays, the former is used to locate data in the dimensional space(s) defined by all dimensions in the dataset while the latter is used to store field data.
It also helps to have a clear separation between the coordinate variable and index concepts. An index is a specific data structure or object that allows efficient data extraction or alignment based one or more coordinate labels. Sometimes an index object may be handled like a data array (like pandas indexes) but this is not always the case (e.g., a KD-Tree).
Currently in Xarray the index concept is hidden behind "dimension" coordinate variables. The goal of the explicit index refactor is to bring it to the light and make it available to any coordinate (and also open it to custom index structures, not only pandas indexes).
It looks like what you suggest is some kind of implicit (co-)indexes hidden behind any dataset variable(s)? We actually took the opposite direction, trying to make everything explicit.
Thanks for the detailed description @weipeng1999. For the first 4 slides I don't see how this is different from how does S_res = S1.sel(C1=['a', 'b'] and S_res = S2.sel(C1=['a', 'b']) currently? And for the last 2 slides, I don't think that we always want such implicit broadcasting for dimensions that are not involved in the indexed coordinates.
Thanks for the detailed description @weipeng1999. For the first 4 slides I don't see how this is different from how does S_res = S1.sel(C1=['a', 'b'] and S_res = S2.sel(C1=['a', 'b']) currently? And for the last 2 slides, I don't think that we always want such implicit broadcasting for dimensions that are not involved in the indexed coordinates.
thank you for figuring out the wrong things what I done. Well, it' is hard to explain the idea because it is a bit complicated, the last two picture is wrong and make misunderstanding, here are two images explain what I actuarily mean:
Sorry but this is confusing. To me It still looks like you want implicit broadcasting of the A3 variable along the x dimension. In your last comment you depict A3 inconsistently with a 2-d shape but with only the t dimension. I'm also not sure how your suggestion relates to the issue here.
well, here are my ideas on how to define coordinates with multi dims.(because of github's bug, the characters of 1st image are white, I can not fix it)
For such case you could already do ds.stack(z=("t", "x")).set_index(z="C2").sel(z=["a", "e", "h"]).
After the explicit index refactor, we could imagine a custom index that supports multi-dimension coordinates such that you would only need to do something like
For such case you could already do ds.stack(z=("t", "x")).set_index(z="C2").sel(z=["a", "e", "h"]).
After the explicit index refactor, we could imagine a custom index that supports multi-dimension coordinates such that you would only need to do something like
well, both "contain the origin dims" or just "generate another one" have its benefit.
if we contain origin dims, we can ensure that:
less difference between 1d coordinate and multi dims ones, both can run like S1.sel(C1=["a", "e", "h"]) S4.sel(C2=["a", "e", "h"]) and return a new data set with origin dims ( that's why I highly not recommended the implicit one )
return a new data set have original dims which means if you change C1 to C2, and the rest code have S_res.sel(x=[1,2,3]) still work.
well, both "contain the origin dims" or just "generate another one" have its benefit.
Agreed, and both are supported by xarray actually. In case we want to keep the original dimensions like ("x", "y") in the example above, it's better to use masking.
This discussion is broader than the topic covered in this issue so I'd suggest you start a new discussion if you want to further discuss this with the xarray community. Thanks.
Activity
fujiisoup commentedon Oct 4, 2017
I'm using
MultiIndexa lot,but I noticed that it is just a workaround to index along multiple kinds of coordinate.
Consider the following example,
I want to do something like this
but it cannot.
MultiIndexing enables this,
If we could make a selection from a non-index coordinate,
MultiIndexis not necessary for this case.I think there should be other important usecases of
MultiIndex.I would be happy if anyone could list them in this issue.
shoyer commentedon Oct 4, 2017
One API design challenge here is that I think we still want a explicit notation of "indexed" variables. We could possibly allow operations like
.sel()on non-indexed variables, but they would be slower, because we would not want to create expensive hash-tables (i.e.,pandas.Index) in a non-transparent fashion.shoyer commentedon Oct 4, 2017
I sometimes find it helpful to think about what the right
repr()looks right, and then work backwards from there to the right data model.For example, we might imagine that "Indexes" are no longer coordinates, but instead their own entry in the repr:
"Indexes" might not even need to be part of the main
Dataset.__repr__, but it would certainly be the repr forDataset.indexes. Other entries could include:In this model:
(a) The levels of a MultiIndex would have corresponding
Variableobjects and be found incoords.(b) In contrast, the
MultiIndexwould not have a correspondingVariableobject or be part ofcoords, though it could still be returned upon__getitem__access (computed on demand from.indexes).(c) Dataset and DataArray would gain an
indexesargument in their constructors, which could be used for passing indexes on to new xarray objects.*are part of an index. They can't be modified, unless all corresponding indexes ares removed.fujiisoup commentedon Oct 4, 2017
I think we currently assume
variables[dim]is an Index.Does your proposal means that
Datasetwill keep an additional attributeindexes, andindexes[dim]gives apd.Index(orpd.MultiIndex,KDTree)?It sounds a much cleaner data model.
shoyer commentedon Oct 4, 2017
Yes, exactly. We actually already have an attribute that works like this, but it's current computed lazily, from either
Dataset._variablesorDataArray._coords.shoyer commentedon Oct 4, 2017
CC @benbovy @fmaussion
benbovy commentedon Oct 4, 2017
I think that promoting "Indexes" to a first-class concept is indeed a very good idea, at both internal and public levels, even if at the latter level it would be another concept for users (it should be already familiar for pandas users, though). IMHO the "coordinate" and "index" concepts are different enough to consider them separately.
I like the proposed repr for
Dataset.indexes. I wouldn't mind if it is not included inDataset.__repr__, considering that multi-indexes, kdtree, etc. only represent a few use cases. In too many cases it could result in a long, uninformative list of simplepandas.Index.I have to think a bit more about the details but I like the idea.
fujiisoup commentedon Oct 4, 2017
@shoyer, could you add more details of this idea?
I think I do not yet fully understand the practical difference between
dimandindex.Use cases of the independent
IndexanddimsWould it be general cases where
dimensionandindexare independent?(It is the case only for
MultiIndexandKDtree)?MultiIndeximplementationIn
MultiIndexcase, will a xarray object store aMultiIndexobject and also the level variables asVariableobjects (there will be some duplicates)?If
indexes[dim]returns multipleVariables, which realizes aMultiIndex-like structure withoutpd.MultiIndex,indexeswould be very different fromdim,because a single dimension can have multiple
indexes.shoyer commentedon Oct 4, 2017
We would still assign default indexes (using a normal
pandas.Index) when you assign a 1D coordinate with matching name and dimension. But in general, yes, it seems like you should be able to make an index even for variables that aren't dimensions, including for a 1D variable whose name does not match a dimension. The rule would be that any coordinates can be part of an index.Another aspect to consider how to handle alignment when you have indexes along non-dimension coordinates. Probably the most elegant rule would again be to check all indexed variables for exact matches.
Directly assigning indexes rather than using this default or
set_index()would be an advanced feature, not recommended for everyday use. The main use case is routines which create a new xarray object based on an existing one, and want to re-use old indexes.For performance reasons, we probably do not want to actually check the values of manually assigned indexes, although we should verify that the shape matches. (We would have a clear disclaimer that if you manually assign an index with mismatched values the behavior is not well defined.)
In principle, this data model would allow for two mostly equivalent indexing schemes:
MultiIndex[time, space]vs two indexesIndex[time]andIndex[space]. We would need to figure out how to propagate and compare indexes like this. (I suppose if the coordinate values match, the result could have the union of all indexes from input arguments.)Yes, this is a little unfortunate. We could potentially make a custom wrapper for use in
IndexVariable._dataon the level variabless that lazily computes values from the MultiIndex (similar to ourLazilyIndexedArrayclass), but I'm not certain yet that this is necessary.Every entry in
indexesshould be a singlepandas.Indexor subclass, includingMultiIndex(possibly eventually allowing for index-like objects such as something based on a KDTree).fujiisoup commentedon Oct 13, 2017
Thanks for the details.
(Sorry for my late responce. It took a long for me to understand what does it look like.)
I am wondering what the advantageous cases which are realized with this
Indexconcept are.As far as my understanding is correct,
KDTreeAre they correct?
That sounds reasonable.
I like the latter one, as it is easier to understand even for non-pandas users.
What does the actual implementation look like?
xr.Dataset.indexeswill be anOrderedDictthat maps from variable's name to its associated dimension?Actual instance of Index will be one of
xr.Dataset.variables?shoyer commentedon Oct 13, 2017
The other advantage is that it solves many of the issues with the current
MultiIndeximplementation. Making MultiIndex levels their own variables considerably simplifies the data model, and means that many features (including serialization) should "just work".I agree, but there are probably some advantages to using a MultiIndex internally. For example, it allows for looking up on multiple levels at the same time.
I think we could get away with making
xr.Dataset.indexessimply a dict, with keys given by index names and values given by apandas.Indexinstance. We should enforce thatIndex.nameorMultiIndex.namescorresponds to coordinate variables.For KDTree, this means we'll have to write our own wrapper
KDTreeIndexthat adds anamesproperty, but we would probably need to add special methods likeget_indexeranyways.alimanfoo commentedon Oct 23, 2017
Just to say I'm interested in how MultiIndexes are handled also. In our use case, we have two variables conventionally named CHROM (chromosome) and POS (position) which together describe a location in a genome. I want to combine both variables into a multi-index so I can, e.g., select all data from some data variable for chromosome X between positions 100,000-200,000. For all our data variables, this genome location multi-index would be used to index the first dimension.
jjpr-mit commentedon Oct 27, 2017
Will the new API preserve the order of the levels? One of the features that's necessary for
MultiIndexto be truly hierarchical is that there is a defined order to the levels.shoyer commentedon Oct 27, 2017
@jjpr-mit can you explain your use case a little more? What sort of order dependent queries do you want to do? The one that comes to mind for me are range based queries, e.g,
[('bar', 1) : ('foo', 9)].I think it is still relatively easy to ensure a unique ordering between levels, based on the order of coordinate variables in the xarray dataset.
A bigger challenge is that for efficiency, these sorts of queries depend critically on having an actual MultiIndex. This means that if indexes for each of the levels arise from different arguments that were merged together, we might need to "merge" the separate indexes into a joint MultiIndex. This could potentially be slightly expensive.
[-]Future of MultiIndex[/-][+]Indexes as an explicit part of xarray's data-model (Future of MultiIndex)[/+]53 remaining items
benbovy commentedon Oct 19, 2021
Hi @weipeng1999,
I'm not sure to fully understand your suggestion, would you mind sharing some illustrative examples?
It is useful to have two distinct
coordinate variablevsdata variableconcepts. Although both are data arrays, the former is used to locate data in the dimensional space(s) defined by all dimensions in the dataset while the latter is used to store field data.It also helps to have a clear separation between the
coordinate variableandindexconcepts. An index is a specific data structure or object that allows efficient data extraction or alignment based one or more coordinate labels. Sometimes an index object may be handled like a data array (like pandas indexes) but this is not always the case (e.g., a KD-Tree).Currently in Xarray the
indexconcept is hidden behind "dimension" coordinate variables. The goal of the explicit index refactor is to bring it to the light and make it available to any coordinate (and also open it to custom index structures, not only pandas indexes).It looks like what you suggest is some kind of implicit (co-)indexes hidden behind any dataset variable(s)? We actually took the opposite direction, trying to make everything explicit.
weipeng1999 commentedon Oct 20, 2021
Try to explain my idea, I make a PPT.
benbovy commentedon Oct 22, 2021
Thanks for the detailed description @weipeng1999. For the first 4 slides I don't see how this is different from how does
S_res = S1.sel(C1=['a', 'b']andS_res = S2.sel(C1=['a', 'b'])currently? And for the last 2 slides, I don't think that we always want such implicit broadcasting for dimensions that are not involved in the indexed coordinates.weipeng1999 commentedon Oct 22, 2021
thank you for figuring out the wrong things what I done. Well, it' is hard to explain the idea because it is a bit complicated, the last two picture is wrong and make misunderstanding, here are two images explain what I actuarily mean:


benbovy commentedon Oct 22, 2021
Sorry but this is confusing. To me It still looks like you want implicit broadcasting of the
A3variable along thexdimension. In your last comment you depictA3inconsistently with a 2-d shape but with only thetdimension. I'm also not sure how your suggestion relates to the issue here.weipeng1999 commentedon Oct 22, 2021
well, here are my ideas on how to define coordinates with multi dims.(because of github's bug, the characters of 1st image are white, I can not fix it)




benbovy commentedon Oct 22, 2021
For such case you could already do
ds.stack(z=("t", "x")).set_index(z="C2").sel(z=["a", "e", "h"]).After the explicit index refactor, we could imagine a custom index that supports multi-dimension coordinates such that you would only need to do something like
or without explicitly providing the name of the packed dimension:
weipeng1999 commentedon Oct 22, 2021
well, both "contain the origin dims" or just "generate another one" have its benefit.
if we contain origin dims, we can ensure that:
weipeng1999 commentedon Oct 22, 2021
So I think maintain the origin dims may do less broken on current code.
benbovy commentedon Oct 22, 2021
Agreed, and both are supported by xarray actually. In case we want to keep the original dimensions like ("x", "y") in the example above, it's better to use masking.
This discussion is broader than the topic covered in this issue so I'd suggest you start a new discussion if you want to further discuss this with the xarray community. Thanks.
benbovy commentedon Sep 27, 2022
Should we close this issue and continue the discussion in #6293?
For anyone who wants to track the progress on this topic: https://github.com/pydata/xarray/projects/1