Skip to content

Breaking: Materialize DimArray or DimStack From a Table#739

Merged
rafaqz merged 64 commits into
rafaqz:breakingfrom
JoshuaBillson:materialize
Aug 10, 2025
Merged

Breaking: Materialize DimArray or DimStack From a Table#739
rafaqz merged 64 commits into
rafaqz:breakingfrom
JoshuaBillson:materialize

Conversation

@JoshuaBillson

Copy link
Copy Markdown
Contributor

Description:

resolves #335

This PR aims to let users construct either a DimStack or DimArray from a table with one or more coordinate columns.

Unlike the existing contructor, rows may be out of order or even missing altogether.

Performance:

The algorithm is O(n), requiring two forward passes for each dimension to determine the correct order of rows.

1000x1000: 0.005 Seconds

2000x2000: 0.025 Seconds

4000x4000: 0.108 Seconds

8000x8000: 0.376 Seconds

Example:

julia> r = DimArray(rand(UInt8, 1000, 1000), (X, Y));

julia> t = r |> DataFrame |> Random.shuffle!;

julia> restored = DimArray(t, dims(r));

julia> all(r .== restored)
true

Next Steps:

  1. Finalize method signatures.
  2. Decide what to do with missing rows. Currently, users may choose a missing value to fill in (missing by default).
  3. Add support for a :geometry column.
  4. Write test cases.
  5. Update docs.

Comment thread src/table_ops.jl Outdated
Comment thread src/table_ops.jl Outdated
Comment thread src/table_ops.jl
Comment thread src/table_ops.jl Outdated
Comment thread src/table_ops.jl Outdated
Comment thread src/table_ops.jl Outdated
@JoshuaBillson

Copy link
Copy Markdown
Contributor Author

There's still a few questions that need resolving:

  1. What do we do with missing rows? This can be handled explicitly in Rasters.jl, but DimArrays have no concept of missing values. We could let users choose a missing value to write, or we could encode it explicitly with missing. We could also disallow the existence of missing rows, but I don't think this is an ideal solution.
  2. How should we handle a :geometry column? I know that several packages in the Julia ecosystem are using this convention, but I don't have much experience with them. Can we assume :geometry will contain tuples of coordinates, or could they also be some sort of geometry like Meshes.Point?

@asinghvi17

asinghvi17 commented Jun 20, 2024

Copy link
Copy Markdown
Collaborator

I think it's best to test that the geometry column's elements have GI.PointTrait - then you can always extract e.g. GI.x(point) and the same for y, z, and m. You can also interrogate the dimension via GI.is3d, GI.ismeasured.

Going forward to get the geometry column it's probably best to have first(GI.geometrycolumns(table)) - the fallback implementation will give you (:geometry,), but to handle tables with other geometry columns which may be indicated by e.g. metadata, it's better to use this.

@rafaqz

rafaqz commented Jun 20, 2024

Copy link
Copy Markdown
Owner

DimensionalData.jl does not depend on GeoInterface

@JoshuaBillson

Copy link
Copy Markdown
Contributor Author

DimensionalData.jl does not depend on GeoInterface

Being able to interact with geometries in a generic fashion would help us interop with other packages. GeoInterface's only dependency is Extents, which we already depend on. After importing DimensionalData, GeoInterface loads in 0.008 seconds.

I also see that Rasters already depends on GeoInterface. We could simply ignore the :geometry column in DimensionalData, then implement the additional functionality in Rasters. However, I think the problem is general enough to be implemented here.

@rafaqz

rafaqz commented Jun 20, 2024

Copy link
Copy Markdown
Owner

It's not about deps or timing, it's about clean feature scope.

The promise here is that Rasters (and YAX) have all the geo deps and features, so non-geo people don't have to worry about them. Some of the biggest contributors here use DD for unrelated fields.

I would ignore point columns here entirely and instead write the code so it's easy for Rasters to handle them. Taking the underscores off a few functions and documenting them as an real interface will help that.

@JoshuaBillson

Copy link
Copy Markdown
Contributor Author

I've updated the docstrings for both the DimArray and DimStack constructors. By default, materializers will now use the Contains selector for irregular and non-numeric coordinates, with the option to specify an alternative when desired. We're exporting the restore_array and coords_to_index methods to be used by downstream packages like Rasters.jl. I've also written several test cases to show that we can handle tables with out of order and missing rows. If everything looks good, I think we can go ahead and merge this PR.

@rafaqz rafaqz left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good, but a few minor changes are needed for it to be correct. Constructed selectors should be allowed so At can have atol. We can just construct selector types at the outer level (they're just filled with nothing.

Then there is a bit more dispatch needed to make the fast paths correct for At/Near/Contains with their standard behaviour.

Comment thread src/DimensionalData.jl Outdated
Comment thread src/array/array.jl Outdated
Comment thread src/array/array.jl Outdated
Comment thread src/table_ops.jl Outdated
Comment thread src/table_ops.jl Outdated
Comment thread src/table_ops.jl
Comment thread src/table_ops.jl Outdated
Comment thread src/table_ops.jl Outdated
Comment thread src/table_ops.jl Outdated
Comment thread src/table_ops.jl Outdated
JoshuaBillson and others added 8 commits August 7, 2024 23:55
Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>
Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>
Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>
Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>
Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>
@codecov

codecov Bot commented Jun 28, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.76821% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.18%. Comparing base (886a2b2) to head (55f8017).
⚠️ Report is 1 commits behind head on breaking.

Files with missing lines Patch % Lines
src/table_ops.jl 81.44% 18 Missing ⚠️
src/stack/stack.jl 84.61% 4 Missing ⚠️
src/tree/tree.jl 75.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           breaking     #739      +/-   ##
============================================
- Coverage     84.62%   84.18%   -0.45%     
============================================
  Files            53       54       +1     
  Lines          5307     5456     +149     
============================================
+ Hits           4491     4593     +102     
- Misses          816      863      +47     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tiemvanderdeure

tiemvanderdeure commented Jun 28, 2025

Copy link
Copy Markdown
Collaborator

Okay docs fails because of this:

 Error: 5 docstrings not included in the manual:

    DimensionalData.restore_array :: Tuple{AbstractVector, AbstractVector, Tuple, Any}
    DimensionalData.coords_to_indices :: Tuple{Any, Tuple}
    DimensionalData.guess_dims :: Tuple{Any}
    DimensionalData.data_col_names :: Tuple{Any, Tuple}
    DimensionalData.get_column :: Tuple{Any, Type{<:Dimension}}

But anyway I think all of these functions should be internal. Should I just remove the docstrings - or make them into comments or something?

(Never seen this before so I'm not 100% about what this is testing)

@rafaqz

rafaqz commented Jun 29, 2025

Copy link
Copy Markdown
Owner

Yeah we force docstrings to be in the docs. I think these should just be comments not docstrings

@tiemvanderdeure

tiemvanderdeure commented Jun 30, 2025

Copy link
Copy Markdown
Collaborator

All right let's see if this passes now. I think before merging we should add some documentation, though.

Another thing is that this will only work with exactly DimArray now, not other abstract dimarrays or abstractdimstacks. We probably want this functionality with rasters and maybe yaxarrays as well, without creating dispatch mayhem.

I've tried to solve this now by adding a type argument to dimarray_from_table, so you could call dimarray_from_table(Raster, table, dims; kw...) and the only lines you would have to copy are these:

https://github.com/JoshuaBillson/DimensionalData.jl/blob/aec86a9600d6ceb3af0aaa7dadc0f051c638a87d/src/array/array.jl#L519-L544

@rafaqz

rafaqz commented Jul 1, 2025

Copy link
Copy Markdown
Owner

Yes perfect that's why I removed the underscores, those a_from_b methods can become dev api

@rafaqz

rafaqz commented Aug 10, 2025

Copy link
Copy Markdown
Owner

@tiemvanderdeure this looks good to merge ?

@tiemvanderdeure

Copy link
Copy Markdown
Collaborator

Yeah I think it is! I don't have more, if you've reviewed it and found nothing then do merge :)

@rafaqz rafaqz merged commit cc4928e into rafaqz:breaking Aug 10, 2025
7 of 8 checks passed
@tiemvanderdeure

Copy link
Copy Markdown
Collaborator

I think this got merged into main and released. Was that on purpose? It is very slightly breaking.

@rafaqz

rafaqz commented Aug 12, 2025

Copy link
Copy Markdown
Owner

It got merged into breaking! which I do want to merge now but it will get a breaking bump

@tiemvanderdeure

Copy link
Copy Markdown
Collaborator

Ah I got confused because it is listed in the tag https://github.com/rafaqz/DimensionalData.jl/releases/tag/v0.29.21

Didn't know that it also listed PRs into other branches than main

@rafaqz

rafaqz commented Aug 13, 2025

Copy link
Copy Markdown
Owner

Oh I didn't know that either.. thats actually bad

@felixcremer felixcremer mentioned this pull request Sep 23, 2025
rafaqz added a commit that referenced this pull request Feb 18, 2026
* include DataType in CategoricalEltypes (#876)

* Breaking: `DimVector` of `NamedTuple` is a `NamedTuple` `DimTable` (#839)

* DimVector of NamedTuple is a NamedTuple table

* bugfix

* remove show

* fix ambiguity

* Breaking: add `combine` method for `groupby` output, fixing `similar` for `AbstractDimStack` (#903)

* add combine method

* test groupby and similar

* docs entry

* Breaking: `preservedims` in tables (#917)

* add preservedims keyword to DimTable

* add tests

* Apply suggestions from code review

Co-authored-by: Anshul Singhvi <anshulsinghvi@gmail.com>

* tests, and fix DimSlices

* better table docs

* cleanup

* test

* indexing overhaul

* fix similar and broadcast for basicdimarray

* bugfix rebuildsliced

* more indexing cleanup

* cleanup similar and gubfix indexing

* bugfixes

* uncomment

* fix doctests

* just dont doctest unreproducable failures, for now

* combine new Tables integrations

* bugfix and cleanup show

* bugfix and more tests for preservedims and mergedims

---------

Co-authored-by: Anshul Singhvi <anshulsinghvi@gmail.com>

* Remove deprecations (#1009)

* typo

* add missing reference docs

* fix DimSlices doc

* Breaking: skipmissing on a dimstack (#1041)

* iterate values where no layer is missing

* add tests

* add skipmissing to reference

* Breaking: Materialize `DimArray` or `DimStack` From a Table (#739)

* Table Materializer Methods

* Made col Optional for DimArray

* Apply suggestions from code review

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Handle coordinates with different loci

* replaced At() with Contains() in _coords_to_ords

* Added optional selectors and public methods for table materializer

* Updated table constructors for DimArray and DimStack

* Updated DimArray and DimStack docs to include table materializer methods

* Table materializer test cases

* export table materializer methods

* Added Random to tables.jl test cases

* Update src/array/array.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/table_ops.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Removed exports

* Update src/table_ops.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/table_ops.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/table_ops.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/table_ops.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/table_ops.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Replaced selector type with instance.

* Table materializer can now infer dimensions from the coordinates.

* Update src/stack/stack.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/table_ops.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/table_ops.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/table_ops.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/table_ops.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/table_ops.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/table_ops.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/array/array.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/table_ops.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Added support for guessing the dimension ordering and span for Dates and DateTimes

* Replaced LinRange with StepRangeLen in _build_dim

* Added Tables.istable check to DimArray constructor

* Update src/array/array.jl

* merge materialize2

* fix scuffed merge

* filter instead of indexing in test for clarity

* fix DimSlices doc

* fix ambiguities

* bugfixes

* do checks and call Tables.columns before constructing stack from table

* test dimensions are automatically detected when constructing dimstack

* comments not docstrings for internals

* check for columnaccess if dims are passed

* add type argument to dimarray_from_table

* allow passing name to DimStack

* add a section to the documentation

* use Tables.columnnames instead of keys

* make DimArray work with all tables that are abstractarrays

* do not treat dimvectors as tables

* simplify get_column

---------

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>
Co-authored-by: Tiem van der Deure <tiemvanderdeure@gmail.com>

* start a CHANGELOG

* bump minor version to 0.30.0

* document Changelog.jl usage

* use rebuild for similar of dimarray with new axes (#1082)

* add _similar dispatch for abstractdimarray

* update tests

* Update src/array/array.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update src/array/array.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

---------

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Breaking: standardise interface methods and remove `index` (#1083)

* standardise interface methods and remove index

* update Changelog

* cleanup

* move const

* cleanup

* remove index from test

* dont export index

* last index

* tweaks

* more tweaks

* fix tests

---------

Co-authored-by: Raf Schouten <schoutenr@ull-pf39vwmc.mobility.unimelb.net.au>

* move abstract constructors to DimArray constructors (#1087)

* Forward name keyword in groupby (#1084)

* Forward name keyword in groupby

* Add test for setting groupby name explicitly

* Update src/groupby.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Update test/groupby.jl

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Add Changelog entry

* Mention name keyword in docstring

---------

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Remove rtol from At selector (#1062)

* Remove rtol from At selector

* Remove explicit rtol from test

* Remove unused type parameter

* fix At constructors

---------

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* fix selector tests

* fix At in dimindices

* Breaking: remove methods that are hardly uesd and cause many invalidations (#1113)

* do 0.6, 0.7.2 broke for us (#1099)

* Fix tests on julia 1.12 (#1110)

* use isequal instead of === to compare NaN

* drop all and broadcast

* specify DimensionalData.Dimensions to make reference unique in docs

* drop convert method for name to abstractstring

* remove `merge` method for dimstack with iterators of pairs

* add to changelog

---------

Co-authored-by: Lazaro Alonso <lazarus.alon@gmail.com>

* Implement `Base.instantiate` - take 2 (#1118)

* implement `instantiate` - get rid of BasicDimensionalStyle

* fix setindex! for opaquearray to make some error messages clearer

* fix materialize!

* StandardIndices methods should be last (#1129)

* add `broadcastable` for abstractdimstack (#1127)

* Add the `D` parameter to `AbstractDimStack` (#1128)

* put D parameter in AbstractDimStack

* update CHANGELOG.md

* remove deprecated LookupArrays (#1132)

* Extend matmul with DimUnitRange axes (#1124)

* Add dims fallback

* Update matmul for mixtures of normal arrays and dimarrays

* Fix bugs

* Add and remove some comparedims checks

* Add tests for matmul with DimUnitRange axes

* Test that dims is nothing for an array

* Test that dims returns dims if all axes DimUnitRange

* Check lookup of anon dims

* Breaking: Include refdims as columns in DimTable (#1119)

* Complete unfinished docstring example

* Support refdims in DimTable

* Test refdims in DimTable

* By default add no refdims to Tables

To make feature non-breaking

* Update tables tests

* Test getcolumn for DimStack/DimArray

* Remove unused variable

* Update constructor calls

* Correctly compute dimnums

* Use all dims to compute colnames

* By default include refdims

* Make sure data-array is duplicated if necessary

* Add preservedims/refdims test

* Fix some bugs

* Add more joint compatibility tests with refdims

* Support AoG selection of refdims

* Only extended array with dims if needed

* Test AoG with refdims

* refdims doc line

---------

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* Add a DimStackArray generator (#1131)

* add DimStackArray

* export DimStackArray

* add tests

* add a docstring

* drop inner constructor

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* fix missing }

* fix DimStackArray type definition

* add tests for broadcast over stack

* add to the docs

---------

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>

* uncomment tests

* unccomend dimindices tests

Co-authored-by: Felix Cremer <felix.cremer@dlr.de>

* Swap dims test and value test in == (#1111)

* Swap dims test and value test in ==

* Add isequal test

* Add broken test for dimarray isequal with different axes

* Make the same swap in == also for DimStack

* Add isequal for AbstractDimArray and AbstractDimstack

* Fix stack test

* Update test/stack.jl

Co-authored-by: Tiem van der Deure <tiemvanderdeure@gmail.com>

* Add Changelog entry

---------

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>
Co-authored-by: Tiem van der Deure <tiemvanderdeure@gmail.com>

* Breaking: Make reduction methods error when passed missing dimensions (#1162)

* Fix NearestNeighbor extension

Description of the fix from Claude:

  The issue was in ext/DimensionalDataNearestNeighborsExt.jl at line 35:

  Problem: The distance vector was being created with the wrong element type:
  distvec = Vector{NN.get_T(eltype(points))}(undef, 1)

  NN.get_T(eltype(points)) was returning SVector{2, Float64} (the point type) instead of Float64 (the scalar distance type). This caused knn! to fail with:
  ArgumentError: dists must have eltype Float64, got StaticArraysCore.SVector{2, Float64}

  Fix: Changed to use eltype(eltype(points)) which correctly extracts the scalar type:
  distvec = Vector{eltype(eltype(points))}(undef, 1)

* Disable broken inference tests

Claude blames the implementation of `_sortdims()`, which is called by `_dims()`
and is apparently not type-stable.

* Disable CondaPkg verbosity

This otherwise spams the terminal.

* Revert "fix `sum(da; dims = :notadim)` (#1116)"

This reverts commit 2245221.

* Improve error messages for reduction methods with missing dims

This uses the new `_missingdims()` helper function to select all the missing
dimensions, and we now don't call `basetypeof` in `_extradimsmsg()` since
`extradims` may not all be `Dim`s.

* Fix Makie tests

* Breaking: split `set` into `unsafe_set` and `set` (#926)

* tweaks

* some ambiguities

* more set

* bugfix unsafe and reorder

* set tweaks

* Update src/Dimensions/set.jl

* Update src/set.jl

* Update src/set.jl

* Update src/Dimensions/set.jl

* Add a few test cases

* Try to fix some ambiguities and some wrong variable names

* Fix ambiguities

* Reimport _astuple

* Add roundtrip tests

* more set tests

* merge breaking

* more tests

* checkaxes => checkaxis

* fix doctests

* doc unsafe set

---------

Co-authored-by: Felix Cremer <fcremer@bgc-jena.mpg.de>

* fix ambiguities

* update CHANGELOG.md for set changes

* Breaking: Extent passthrough for multidimensional lookups (#991)

* hasmultipledimensions

* expand the definition of extent

* bump cairomakie compat

* Add tests for hasmultipledimensions trait and extent passthrough (PR #991)

- Add comprehensive tests for hasmultipledimensions trait in merged.jl
- Test that regular lookups return false for hasmultipledimensions
- Test that MergedLookup returns true for hasmultipledimensions
- Test extent passthrough for merged dimensions
- Test mixed regular and merged dimensions
- Test that operations preserve the hasmultipledimensions trait
- Add fallback methods to handle edge cases:
  - hasmultipledimensions(::Any) = false for non-Lookup types
  - bounds(x::AbstractArray) for raw arrays
- Import Extents in merged.jl test file
- Fix bounds ambiguity with explicit module qualification

* Refactor to `hasinternaldimensions`

* Add changelog

* Tweak metadata on dimensions.md docs

* Add yet more tests

* Fix StackOverflow in mergedims for single dimension

Wrap old_dims in _astuple() when constructing MergedLookup so that
a bare Dimension doesn't cause infinite recursion in combinedims.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix BoundsError in bounds for empty MergedLookup

Add isempty guard so that bounds on an empty MergedLookup returns ()
instead of throwing when calling first on an empty collection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix multidimensional_lookups test assertions

- Wrap merged_dims in tuple for Extents.extent call
- Assert ArgumentError for unsupported Near selector
- Use Z type instead of Dim{:Z} for @dim-declared dimension

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update CHANGELOG.md

* Add docstring for `hasinternaldimensions`

* Describe what is going on in new extent code

* Add hasinternaldimensions docs to doc pages

---------

Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* Add removal of LookupArrays into Changelog and remove LookupArrays reference in docs (#1173)

---------

Co-authored-by: Tiem van der Deure <tiemvanderdeure@gmail.com>
Co-authored-by: Anshul Singhvi <anshulsinghvi@gmail.com>
Co-authored-by: Felix Cremer <fcremer@bgc-jena.mpg.de>
Co-authored-by: Joshua Billson <61667893+JoshuaBillson@users.noreply.github.com>
Co-authored-by: Raf Schouten <schoutenr@ull-pf39vwmc.mobility.unimelb.net.au>
Co-authored-by: Lazaro Alonso <lazarus.alon@gmail.com>
Co-authored-by: Seth Axen <seth@sethaxen.com>
Co-authored-by: Felix Cremer <felix.cremer@dlr.de>
Co-authored-by: James Wrigley <JamesWrigley@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a Tables.materializer method

4 participants