From a69583674e697a3fc329d2ec405d8ffdf29d6c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Tue, 19 Sep 2023 20:01:46 +0200 Subject: [PATCH] Add nrow and ncol fallback implementations (#343) --- Project.toml | 2 +- docs/src/index.md | 28 ++++++++++++----------- src/dicts.jl | 5 ++++ src/fallbacks.jl | 9 ++++++++ src/matrix.jl | 5 ++++ src/namedtuples.jl | 8 +++++++ test/runtests.jl | 57 +++++++++++++++++++++++++++++++++++++++++++--- 7 files changed, 97 insertions(+), 17 deletions(-) diff --git a/Project.toml b/Project.toml index 25d6cb2..d330373 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Tables" uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" authors = ["quinnj "] -version = "1.10.1" +version = "1.11.0" [deps] DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" diff --git a/docs/src/index.md b/docs/src/index.md index 4765d69..49ae82f 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -229,19 +229,21 @@ make my custom type valid for Tables.jl consumers? For a type `MyTable`, the interface to becoming a proper table is straightforward: -| Required Methods | Default Definition | Brief Description | -|----------------------------------------|------------------------------|---------------------------------------------------------------------------------------------------------------------------------| -| `Tables.istable(::Type{MyTable})` | | Declare that your table type implements the interface | -| **One of:** | | | -| `Tables.rowaccess(::Type{MyTable})` | | Declare that your table type defines a `Tables.rows(::MyTable)` method | -| `Tables.rows(x::MyTable)` | | Return an `Tables.AbstractRow`-compatible iterator from your table | -| **Or:** | | | -| `Tables.columnaccess(::Type{MyTable})` | | Declare that your table type defines a `Tables.columns(::MyTable)` method | -| `Tables.columns(x::MyTable)` | | Return an `Tables.AbstractColumns`-compatible object from your table | -| **Optional methods** | | | -| `Tables.schema(x::MyTable)` | `Tables.schema(x) = nothing` | Return a [`Tables.Schema`](@ref) object from your `Tables.AbstractRow` iterator or `Tables.AbstractColumns` object; or `nothing` for unknown schema | -| `Tables.materializer(::Type{MyTable})` | `Tables.columntable` | Declare a "materializer" sink function for your table type that can construct an instance of your type from any Tables.jl input | -| `Tables.subset(x::MyTable, inds; viewhint)` | | Return a row or a sub-table of the original table +| Required Methods | Default Definition | Brief Description | +|----------------------------------------------|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| `Tables.istable(::Type{MyTable})` | | Declare that your table type implements the interface | +| **One of:** | | | +| `Tables.rowaccess(::Type{MyTable})` | | Declare that your table type defines a `Tables.rows(::MyTable)` method | +| `Tables.rows(x::MyTable)` | | Return an `Tables.AbstractRow`-compatible iterator from your table | +| **Or:** | | | +| `Tables.columnaccess(::Type{MyTable})` | | Declare that your table type defines a `Tables.columns(::MyTable)` method | +| `Tables.columns(x::MyTable)` | | Return an `Tables.AbstractColumns`-compatible object from your table | +| **Optional methods** | | | +| `Tables.schema(x::MyTable)` | `Tables.schema(x) = nothing` | Return a [`Tables.Schema`](@ref) object from your `Tables.AbstractRow` iterator or `Tables.AbstractColumns` object; or `nothing` for unknown schema | +| `Tables.materializer(::Type{MyTable})` | `Tables.columntable` | Declare a "materializer" sink function for your table type that can construct an instance of your type from any Tables.jl input | +| `Tables.subset(x::MyTable, inds; viewhint)` | | Return a row or a sub-table of the original table | +| `DataAPI.nrow(x::MyTable)` | | Return number of rows of table `x` | +| `DataAPI.ncol(x::MyTable)` | | Return number of columns of table `x` | Based on whether your table type has defined `Tables.rows` or `Tables.columns`, you then ensure that the `Tables.AbstractRow` iterator or `Tables.AbstractColumns` object satisfies the respective interface. diff --git a/src/dicts.jl b/src/dicts.jl index d5d1bf9..addc2fd 100644 --- a/src/dicts.jl +++ b/src/dicts.jl @@ -197,3 +197,8 @@ function dictrowtable(x) end return DictRowTable(names, types, out) end + +# implement default nrow and ncol methods for DataAPI.jl + +DataAPI.nrow(table::DictRowTable) = length(table) +DataAPI.ncol(table::DictRowTable) = length(getfield(table, :names)) diff --git a/src/fallbacks.jl b/src/fallbacks.jl index f0621c0..d911ac1 100644 --- a/src/fallbacks.jl +++ b/src/fallbacks.jl @@ -275,3 +275,12 @@ columnnames(x::CopiedColumns) = columnnames(source(x)) end throw(ArgumentError("no default `Tables.columns` implementation for type: $T")) end + +# implement default nrow and ncol methods for DataAPI.jl + +# this covers also MatrixTable +DataAPI.nrow(table::AbstractColumns) = rowcount(table) +DataAPI.ncol(table::AbstractColumns) = length(columnnames(table)) + +DataAPI.nrow(table::AbstractRowTable) = length(table) +DataAPI.ncol(table::AbstractRowTable) = isempty(table) ? 0 : length(columnnames(first(table))) diff --git a/src/matrix.jl b/src/matrix.jl index b8d0fdb..e4d98b3 100644 --- a/src/matrix.jl +++ b/src/matrix.jl @@ -111,3 +111,8 @@ function matrix(table::MatrixTable; transpose::Bool=false) return matrix end end + +# implement default nrow and ncol methods for DataAPI.jl + +DataAPI.nrow(table::MatrixRowTable) = length(table) +DataAPI.ncol(table::MatrixRowTable) = size(getfield(table, :matrix), 2) # this is correct even if m is a vector diff --git a/src/namedtuples.jl b/src/namedtuples.jl index 309b669..8bdc793 100644 --- a/src/namedtuples.jl +++ b/src/namedtuples.jl @@ -190,3 +190,11 @@ function columntable(itr::T) where {T} return columntable(schema(cols), cols) end columntable(x::ColumnTable) = x + +# implement default nrow and ncol methods for DataAPI.jl + +DataAPI.nrow(table::ColumnTable) = isempty(table) ? 0 : length(first(table)) +DataAPI.ncol(table::ColumnTable) = length(table) + +DataAPI.nrow(table::RowTable) = length(table) +DataAPI.ncol(table::RowTable) = isempty(table) ? 0 : length(first(table)) diff --git a/test/runtests.jl b/test/runtests.jl index 25d03c8..7eef20a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -using Test, Tables, OrderedCollections, TableTraits, DataValues, QueryOperators, IteratorInterfaceExtensions, SparseArrays +using Test, Tables, OrderedCollections, TableTraits, DataValues, QueryOperators, IteratorInterfaceExtensions, SparseArrays, DataAPI @testset "utils.jl" begin @@ -957,7 +957,7 @@ end @test Tables.istable(Vector{Dict{String}}) @test Tables.istable(Vector{Dict{SubString}}) @test Tables.istable(Vector{Dict{AbstractString}}) - + @test Set(Tables.columnnames(Dict(:a=>1, :b=>2))) == Set([:a, :b]) @test Set(Tables.columnnames(Dict("a"=>1, "b"=>2))) == Set([:a, :b]) @test Set(Tables.columnnames(Dict("a"=>1, SubString("b")=>2))) == Set([:a, :b]) @@ -976,4 +976,55 @@ end @test Tables.getcolumn(Dict("a"=>1, SubString("b")=>2), Int, 1, :a) == 1 @test Tables.getcolumn(Dict(SubString("a")=>1, SubString("b")=>2), Int, 1, :a) == 1 end - + +@testset "test nrow and ncol" begin + # AbstractColumns + @test DataAPI.nrow(Tables.CopiedColumns(NamedTuple())) == 0 + @test DataAPI.ncol(Tables.CopiedColumns(NamedTuple())) == 0 + @test DataAPI.nrow(Tables.CopiedColumns((a=1:3, b=2:4))) == 3 + @test DataAPI.ncol(Tables.CopiedColumns((a=1:3, b=2:4))) == 2 + + # ColumnTable + @test DataAPI.nrow(NamedTuple()) == 0 + @test DataAPI.ncol(NamedTuple()) == 0 + @test DataAPI.nrow((a=1:3, b=2:4)) == 3 + @test DataAPI.ncol((a=1:3, b=2:4)) == 2 + + # AbstractRowTable + @test DataAPI.nrow(collect(Tables.rows(Tables.table(ones(0, 0))))) == 0 + @test DataAPI.ncol(collect(Tables.rows(Tables.table(ones(0, 0))))) == 0 + @test DataAPI.nrow(collect(Tables.rows(Tables.table(ones(2, 3))))) == 2 + @test DataAPI.ncol(collect(Tables.rows(Tables.table(ones(2, 3))))) == 3 + + # RowTable + @test DataAPI.nrow(NamedTuple[]) == 0 + @test DataAPI.ncol(NamedTuple[]) == 0 + @test DataAPI.nrow([(a=1,b=2), (a=3, b=4), (a=5, b=6)]) == 3 + @test DataAPI.ncol([(a=1, b=2), (a=3, b=4), (a=5, b=6)]) == 2 + + # MatrixTable + @test DataAPI.nrow(Tables.table(ones(0, 0))) == 0 + @test DataAPI.ncol(Tables.table(ones(0, 0))) == 0 + @test DataAPI.nrow(Tables.table(ones(2, 3))) == 2 + @test DataAPI.ncol(Tables.table(ones(2, 3))) == 3 + @test DataAPI.nrow(Tables.table([])) == 0 + @test DataAPI.ncol(Tables.table([])) == 1 + @test DataAPI.nrow(Tables.table([1, 2])) == 2 + @test DataAPI.ncol(Tables.table([1, 2])) == 1 + + # MatrixRowTable + @test DataAPI.nrow(Tables.rows(Tables.table(ones(0, 0)))) == 0 + @test DataAPI.ncol(Tables.rows(Tables.table(ones(0, 0)))) == 0 + @test DataAPI.nrow(Tables.rows(Tables.table(ones(2, 3)))) == 2 + @test DataAPI.ncol(Tables.rows(Tables.table(ones(2, 3)))) == 3 + @test DataAPI.nrow(Tables.rows(Tables.table([]))) == 0 + @test DataAPI.ncol(Tables.rows(Tables.table([]))) == 1 + @test DataAPI.nrow(Tables.rows(Tables.table([1, 2]))) == 2 + @test DataAPI.ncol(Tables.rows(Tables.table([1, 2]))) == 1 + + # DictRowTable + @test DataAPI.nrow(Tables.dictrowtable(NamedTuple[])) == 0 + @test DataAPI.ncol(Tables.dictrowtable(NamedTuple[])) == 0 + @test DataAPI.nrow(Tables.dictrowtable([(a=1, b=2), (a=3, b=4), (a=5, b=6)])) == 3 + @test DataAPI.ncol(Tables.dictrowtable([(a=1, b=2), (a=3, b=4), (a=5, b=6)])) == 2 +end \ No newline at end of file