Repository: JuliaData/Tables.jl (julia)

Issue:
support AbstractVecOrMat in matrix.jl
If you have an `AbstractVector`, it would be convenient to be able to treat it as a table with a single column via `table(x)`.   It seems like this could be implemented simply by replacing `AbstractMatrix` with `AbstractVecOrMat` in `src/matrix.jl`.

(Recall that Julia vectors can be indexed like a matrix, with two indices, as long as the second index is `1`, and that `size(x,2)` returns `1` when `x` is a vector.  This is precisely so that vectors can be treated as 1-column matrices.)

Pull request description:
Closes #243.

Patch:
diff --git a/docs/src/index.md b/docs/src/index.md
index 1778d6e..01be1bf 100644
--- a/docs/src/index.md
+++ b/docs/src/index.md
@@ -258,13 +258,13 @@ Tables.AbstractColumns
 ```
 
 ### Implementation Example
-As an extended example, let's take a look at some code defined in Tables.jl for treating `AbstractMatrix`s as tables.
+As an extended example, let's take a look at some code defined in Tables.jl for treating `AbstractVecOrMat`s as tables.
 
-First, we define a special `MatrixTable` type that will wrap an `AbstractMatrix`, and allow easy overloading for the
+First, we define a special `MatrixTable` type that will wrap an `AbstractVecOrMat`, and allow easy overloading for the
 Tables.jl interface.
 
 ```julia
-struct MatrixTable{T <: AbstractMatrix} <: Tables.AbstractColumns
+struct MatrixTable{T <: AbstractVecOrMat} <: Tables.AbstractColumns
     names::Vector{Symbol}
     lookup::Dict{Symbol, Int}
     matrix::T
@@ -280,7 +280,7 @@ Tables.schema(m::MatrixTable{T}) where {T} = Tables.Schema(names(m), fill(eltype
 ```
 
 Here we defined `Tables.istable` for all `MatrixTable` types, signaling that they implement the Tables.jl interfaces.
-We also defined [`Tables.schema`](@ref) by pulling the column names out that we stored, and since `AbstractMatrix` have a single
+We also defined [`Tables.schema`](@ref) by pulling the column names out that we stored, and since `AbstractVecOrMat` have a single
 `eltype`, we repeat it for each column (the call to `fill`). Note that defining [`Tables.schema`](@ref) is optional on tables; by default, `nothing`
 is returned and Tables.jl consumers should account for both known and unknown schema cases. Returning a schema when possible allows consumers
 to have certain optimizations when they can know the types of all columns upfront (and if the # of columns isn't too large)
@@ -320,7 +320,7 @@ Base.length(m::MatrixTable) = size(matrix(m), 1)
 
 Base.iterate(m::MatrixTable, st=1) = st > length(m) ? nothing : (MatrixRow(st, m), st + 1)
 
-# a custom row type; acts as a "view" into a row of an AbstractMatrix
+# a custom row type; acts as a "view" into a row of an AbstractVecOrMat
 struct MatrixRow{T} <: Tables.AbstractRow
     row::Int
     source::MatrixTable{T}
diff --git a/src/matrix.jl b/src/matrix.jl
index c200759..422062a 100644
--- a/src/matrix.jl
+++ b/src/matrix.jl
@@ -1,10 +1,10 @@
 istable(::AbstractMatrix) = false
 istable(::Type{<:AbstractMatrix}) = false
 
-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"))
-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"))
+rows(m::T) where {T <: AbstractMatrix} = throw(ArgumentError("a '$T' is not a table; see `?Tables.table` for ways to treat an AbstractVecOrMat as a table"))
+columns(m::T) where {T <: AbstractMatrix} = throw(ArgumentError("a '$T' is not a table; see `?Tables.table` for ways to treat an AbstractVecOrMat as a table"))
 
-struct MatrixTable{T <: AbstractMatrix} <: AbstractColumns
+struct MatrixTable{T <: AbstractVecOrMat} <: AbstractColumns
     names::Vector{Symbol}
     lookup::Dict{Symbol, Int}
     matrix::T
@@ -43,15 +43,15 @@ getcolumn(m::MatrixTable, nm::Symbol) = getcolumn(m, getfield(m, :lookup)[nm])
 columnnames(m::MatrixTable) = names(m)
 
 """
-    Tables.table(m::AbstractMatrix; [header])
+    Tables.table(m::AbstractVecOrMat; [header])
 
-Wrap an `AbstractMatrix` (`Matrix`, `Adjoint`, etc.) in a `MatrixTable`, which satisfies the
-Tables.jl interface. This allows accessing the matrix via `Tables.rows` and `Tables.columns`.
+Wrap an `AbstractVecOrMat` (`Matrix`, `Vector`, `Adjoint`, etc.) in a `MatrixTable`, which satisfies the
+Tables.jl interface.  (An `AbstractVector` is treated as a 1-column matrix.) This allows accessing the matrix via `Tables.rows` and `Tables.columns`.
 An optional keyword argument iterator `header` can be passed which will be converted to a
-`Vector{Symbol}` to be used as the column names. Note that no copy of the `AbstractMatrix`
+`Vector{Symbol}` to be used as the column names. Note that no copy of the `AbstractVecOrMat`
 is made.
 """
-function table(m::AbstractMatrix; header=[Symbol("Column$i") for i = 1:size(m, 2)])
+function table(m::AbstractVecOrMat; header=[Symbol("Column$i") for i = 1:size(m, 2)])
     symbol_header = header isa Vector{Symbol} ? header : [Symbol(h) for h in header]
     if length(symbol_header) != size(m, 2)
         throw(ArgumentError("provided column names `header` length must match number of columns in matrix ($(size(m, 2)))"))
@@ -88,9 +88,15 @@ function matrix(table; transpose::Bool=false)
     end
     return matrix
 end
-	
-function matrix(table::MatrixTable; transpose::Bool=false) 
+
+function matrix(table::MatrixTable; transpose::Bool=false)
     matrix = getfield(table, :matrix)
-    transpose || return matrix
-    return permutedims(matrix)
+    if transpose
+        return permutedims(matrix)
+    elseif matrix isa AbstractVector
+        # always return a matrix, for type stability
+        return reshape(matrix, :, 1)
+    else
+        return matrix
+    end
 end


Test patch:
diff --git a/test/runtests.jl b/test/runtests.jl
index 330e6f1..281dfc1 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -287,6 +287,14 @@ end
     @test X′ == X
     # test for presence of superfluous wrapping
     @test typeof(X′) == typeof(X)
+
+    # Treating vectors as 1-column tables:
+    x = [1,2,3,4,5]
+    tbl = Tables.Columns(Tables.table(x))
+    @test keys(tbl) == [:Column1]
+    @test keys(Tables.Columns(Tables.table(x, header=["col1"]))) == [:col1]
+    x′ = Tables.matrix(tbl)
+    @test x′ == reshape(x, :, 1) == Tables.matrix(Tables.table(x))
 end
 
 import Base: ==