Skip to content

Lazy concatenation of arrays #4628

Open

Description

@nbren12

Is your feature request related to a problem? Please describe.
Concatenating xarray objects forces the data to load. I recently learned about this object allowing lazy indexing into an DataArrays/sets without using dask. Concatenation along a single dimension is the inverse operation of slicing, so it seems natural to also support it. Also, concatenating along dimensions (e.g. "run"/"simulation"/"ensemble") can be a common merging workflow.

Describe the solution you'd like

xr.concat([a, b], dim=...) does not load any data in a or b.

Describe alternatives you've considered
One could rename the variables in a and b to allow them to be merged (e.g. a['air_temperature'] -> "air_temperature_a"), but it's more natural to make a new dimension.

Additional context

This is useful when not using dask for performance reasons (e.g. using another parallelism engine like Apache Beam).

Activity

  1. shoyer commented on May 24, 2021

    @shoyer
    Member

    If you write write something like xarray.concat(..., data_vars='minimal', coords='minimal'), dask should entirely lazy -- the non-laziness only happens with the default value of coords='different'.

    But I agree, it would be nice if Xarray's internal lazy indexing machinery supported concatenation. It currently does not.

  2. LunarLanding commented on Nov 25, 2021

    @LunarLanding

    Any pointers regarding where to start / modules involved to implement this? I would like to have a try.

  3. dcherian commented on May 10, 2022

    @dcherian
    Contributor

    From @rabernat in #6588:

    Right now, if I want to concatenate multiple datasets (e.g. as in open_mfdataset), I have two options:

    • Eagerly load the data as numpy arrays ➡️ xarray will dispatch to np.concatenate
    • Chunk each dataset ➡️ xarray will dispatch to dask.array.concatenate

    In pseudocode:

    ds1 = xr.open_dataset("some_big_lazy_source_1.nc")
    ds2 = xr.open_dataset("some_big_lazy_source_2.nc")
    item1 = ds1.foo[0, 0, 0]  # lazily access a single item
    ds = xr.concat([ds1.chunk(), ds2.chunk()], "time")  # only way to lazily concat
    # trying to access the same item will now trigger loading of all of ds1
    item1 = ds.foo[0, 0, 0]
    # yes I could use different chunks, but the point is that I should not have to 
    # arbitrarily choose chunks to make this work
    

    However, I am increasingly encountering scenarios where I would like to lazily concatenate datasets (without loading into memory), but also without the requirement of using dask. This would be useful, for example, for creating composite datasets that point back to an OpenDAP server, preserving the possibility of granular lazy access to any array element without the requirement of arbitrary chunking at an intermediate stage.

    Describe the solution you'd like

    I propose to extend our LazilyIndexedArray classes to support simple concatenation and stacking. The result of applying concat to such arrays will be a new LazilyIndexedArray that wraps the underlying arrays into a single object.

    The main difficulty in implementing this will probably be with indexing: the concatenated array will need to understand how to map global indexes to the underling individual array indexes. That is a little tricky but eminently solvable.

    Describe alternatives you've considered

    The alternative is to structure your code in a way that avoids needing to lazily concatenate arrays. That is what we do now. It is not optimal.

  4. nbren12 commented on May 10, 2022

    @nbren12
    ContributorAuthor

    @rabernat It seems that great minds think alike ;)

  5. rabernat commented on May 10, 2022

    @rabernat
    Contributor

    Any pointers regarding where to start / modules involved to implement this? I would like to have a try.

    The starting point would be to look at the code in indexing.py and try to understand how lazy indexing works.

    In particular, look at

    class LazilyIndexedArray(ExplicitlyIndexedNDArrayMixin):
    """Wrap an array to make basic and outer indexing lazy."""
    __slots__ = ("array", "key")
    def __init__(self, array, key=None):

    Then you may want to try writing a class that looks like

     class LazilyConcatenatedArray:  # have to decide what to inherit from 
      
        def __init__(self, *arrays: LazilyIndexedArray, concat_axis=0):
            # figure out what you need to keep track of
    
        @property
        def shape(self):
            # figure out how to determine the total shape
    
        def __getitem__(self, indexer) -> LazilyIndexedArray:
            # figure out how to map an indexer to the right piece of data
  6. ilan-gold commented on Sep 23, 2024

    @ilan-gold
    Contributor

    I would +1 your great proposal @dcherian . My use-case would be the extension arrays - being able to concat them without dask is basically a must for us.

    Can't do this without virtual concat machinery (#4628) which someone decided to implement elsewhere 🙄 ;)

    From #9038 (comment) - what is this referring to?

    In short, this is not implemented here: scverse/anndata#1247 (comment) . It is implemented for non-lazy categoricals but not lazy ones

  7. TheGreatAlgo commented on Nov 12, 2025

    @TheGreatAlgo

    This would be really helpful for us as it relates to our massive weather datasets (~3TB in size). We have zarrs of weather data that has different attributes such as in the case of ERA5, finalized or unfinalized data. Finalized meaning no more future changes and unfinalized meaning it may still change. However for someone wanting to run an analysis on a specific location that stretches across the datasets they would need to query individually and concat together.

    ERA5 finalized may end on June for example and then unfinalized they can get data up to November. If you want the full historical weather you need to query both separately and then merge them together.

    Our idea was just to return them both concated together (3TB for finalized reaching back to 1940s and 8GB in this example that might reach back a couple months) and then the user can just query against the whole xarray dataset like normal without much effort.

    I have tried the minimal trick but still seems to silently fail for me after an uptick in cpu usage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions