|
| 1 | +istable(::Type{<:AbstractMatrix}) = false |
| 2 | + |
| 3 | +rows(m::T) where {T <: AbstractMatrix} = throw(ArgumentError("a '$T' is not a table; see `?Tables.table` for ways to treat an AbstractMatrix as a table")) |
| 4 | +columns(m::T) where {T <: AbstractMatrix} = throw(ArgumentError("a '$T' is not a table; see `?Tables.table` for ways to treat an AbstractMatrix as a table")) |
| 5 | + |
| 6 | +struct MatrixTable{T <: AbstractMatrix} |
| 7 | + names::Vector{Symbol} |
| 8 | + lookup::Dict{Symbol, Int} |
| 9 | + matrix::T |
| 10 | +end |
| 11 | + |
| 12 | +istable(::Type{<:MatrixTable}) = true |
| 13 | +names(m::MatrixTable) = getfield(m, :names) |
| 14 | + |
| 15 | +# row interface |
| 16 | +struct MatrixRow{T} |
| 17 | + row::Int |
| 18 | + source::MatrixTable{T} |
| 19 | +end |
| 20 | + |
| 21 | +Base.getproperty(m::MatrixRow, ::Type, col::Int, nm::Symbol) = |
| 22 | + getfield(getfield(m, :source), :matrix)[getfield(m, :row), col] |
| 23 | +Base.getproperty(m::MatrixRow, nm::Symbol) = |
| 24 | + getfield(getfield(m, :source), :matrix)[getfield(m, :row), getfield(getfield(m, :source), :lookup)[nm]] |
| 25 | +Base.propertynames(m::MatrixRow) = names(getfield(m, :source)) |
| 26 | + |
| 27 | +rowaccess(::Type{<:MatrixTable}) = true |
| 28 | +schema(m::MatrixTable{T}) where {T} = Schema(Tuple(names(m)), NTuple{size(getfield(m, :matrix), 2), eltype(T)}) |
| 29 | +rows(m::MatrixTable) = m |
| 30 | +Base.eltype(m::MatrixTable{T}) where {T} = MatrixRow{T} |
| 31 | +Base.length(m::MatrixTable) = size(getfield(m, :matrix), 1) |
| 32 | + |
| 33 | +function Base.iterate(m::MatrixTable, st=1) |
| 34 | + st > length(m) && return nothing |
| 35 | + return MatrixRow(st, m), st + 1 |
| 36 | +end |
| 37 | + |
| 38 | +# column interface |
| 39 | +columnaccess(::Type{<:MatrixTable}) = true |
| 40 | +columns(m::MatrixTable) = m |
| 41 | +Base.getproperty(m::MatrixTable, ::Type{T}, col::Int, nm::Symbol) where {T} = getfield(m, :matrix)[:, col] |
| 42 | +Base.getproperty(m::MatrixTable, nm::Symbol) = getfield(m, :matrix)[:, getfield(m, :lookup)[nm]] |
| 43 | +Base.propertynames(m::MatrixTable) = names(m) |
| 44 | + |
| 45 | +""" |
| 46 | +Tables.table(m::AbstractMatrix; [header::Vector{Symbol}]) |
| 47 | +
|
| 48 | +Wrap an `AbstractMatrix` (`Matrix`, `Adjoint`, etc.) in a `MatrixTable`, which satisfies |
| 49 | +the Tables.jl interface. This allows accesing the matrix via `Tables.rows` and |
| 50 | +`Tables.columns`. An optional keyword argument `header` can be passed as a `Vector{Symbol}` |
| 51 | +to be used as the column names. Note that no copy of the `AbstractMatrix` is made. |
| 52 | +""" |
| 53 | +function table(m::AbstractMatrix; header::Vector{Symbol}=[Symbol("Column$i") for i = 1:size(m, 2)]) |
| 54 | + length(header) == size(m, 2) || throw(ArgumentError("provided column names `header` length must match number of columns in matrix ($(size(m, 2))")) |
| 55 | + lookup = Dict(nm=>i for (i, nm) in enumerate(header)) |
| 56 | + return MatrixTable(header, lookup, m) |
| 57 | +end |
| 58 | + |
| 59 | +""" |
| 60 | +Tables.matrix(table) |
| 61 | +
|
| 62 | +Materialize any table source input as a `Matrix`. If the table column types are not homogenous, |
| 63 | +they will be promoted to a common type in the materialized `Matrix`. Note that column names are |
| 64 | +ignored in the conversion. |
| 65 | +""" |
| 66 | +function matrix(table) |
| 67 | + cols = Tables.columns(table) |
| 68 | + types = schema(cols).types |
| 69 | + T = reduce(promote_type, types) |
| 70 | + n, p = rowcount(cols), length(types) |
| 71 | + mat = Matrix{T}(undef, n, p) |
| 72 | + for (i, col) in enumerate(Tables.eachcolumn(cols)) |
| 73 | + copyto!(mat, n * (i - 1) + 1, col) |
| 74 | + end |
| 75 | + return mat |
| 76 | +end |
0 commit comments