Skip to content

Commit

Permalink
✨ Give priority to Tables.jl API when printing
Browse files Browse the repository at this point in the history
The Tables.jl API is not the priority when checking if a variable
can be printed as a tables.

This fixes the problem related to `StructArray`. This type is an
`AbstractVecOrMat` and also complies with Tables.jl API. However,
the old version gave preference to the `AbstractVecOrMat` type,
leading to a strange printing.

Closes #28
  • Loading branch information
ronisbr committed Jan 30, 2020
1 parent bea68b5 commit 09af865
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 72 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["DataFrames", "Test"]
test = ["DataFrames", "Tables", "Test"]
20 changes: 10 additions & 10 deletions docs/src/man/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,30 @@ DocTestSetup = quote
end
```

The following functions can be used to print data.
The following function can be used to print data.

```julia
function pretty_table([io::IO,] data[, header]; kwargs...) where {T1,T2}
function pretty_table([io::IO,] table[, header::AbstractVecOrMat]; kwargs...)
```

Print to `io` the data `data` with header `header`. If `io` is omitted, then it
defaults to `stdout`. If `header` is empty, then it will be automatically filled
with "Col. i" for the *i*-th column.
Print to `io` the table `table` with header `header`. If `io` is omitted, then
it defaults to `stdout`. If `header` is empty or missing, then it will be
automatically filled with "Col. i" for the *i*-th column.

The `header` can be a `Vector` or a `Matrix`. If it is a `Matrix`, then each row
will be a header line. The first line is called *header* and the others are
called *sub-headers* .

The following types of `data` are currently supported:
When printing, it will be verified if `table` complies with
[**Tables.jl**](https://github.com/JuliaData/Tables.jl) API. If it is is
compliant, then this interface will be used to print the table. If it is not
compliant, then only the following types are supported:

1. `AbstractVector`: any vector can be printed. In this case, the `header`
**must** be a vector, where the first element is considered the header and
the others are the sub-headers.
2. `AbstractMatrix`: any matrix can be printed.
3. `Table`: any object that complies with the API of
[Tables.jl](https://github.com/JuliaData/Tables.jl) is also supported and can
be printed.
4. `Dict`: any `Dict` can be printed. In this case, the special keyword
3. `Dict`: any `Dict` can be printed. In this case, the special keyword
`sortkeys` can be used to select whether or not the user wants to print the
dictionary with the keys sorted. If it is `false`, then the elements will be
printed on the same order returned by the functions `keys` and `values`.
Expand Down
133 changes: 72 additions & 61 deletions src/print.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,30 @@ export pretty_table
################################################################################

"""
function pretty_table([io::IO,] data::AbstractVecOrMat{T1}, header::AbstractVecOrMat{T2}; kwargs...) where {T1,T2}
function pretty_table([io::IO,] table[, header::AbstractVecOrMat]; kwargs...)
Print to `io` the vector or matrix `data` with header `header`. If `io` is
omitted, then it defaults to `stdout`. If `header` is empty, then it will be
Print to `io` the table `table` with header `header`. If `io` is omitted, then
it defaults to `stdout`. If `header` is empty or missing, then it will be
automatically filled with "Col. i" for the *i*-th column.
The `header` can be a `Vector` or a `Matrix`. If it is a `Matrix`, then each row
will be a header line. The first line is called *header* and the others are
called *sub-headers* .
function pretty_table([io::IO,] data::AbstractVecOrMat{T}; ...) where T
Print to `io` the vector or matrix `data`. If `io` is omitted, then it defaults
to `stdout`. The header will be automatically filled with "Col. i" for the
*i*-th column.
function pretty_table([io::IO,] dict::Dict{K,V}; sortkeys = true, ...) where {K,V}
Print to `io` the dictionary `dict` in a matrix form (one column for the keys
and other for the values). If `io` is omitted, then it defaults to `stdout`.
In this case, the keyword `sortkeys` can be used to select whether or not the
user wants to print the dictionary with the keys sorted. If it is `false`, then
the elements will be printed on the same order returned by the functions `keys`
and `values`. Notice that this assumes that the keys are sortable, if they are
not, then an error will be thrown.
function pretty_table([io::IO,] table; ...)
Print to `io` the table `table`. In this case, `table` must comply with the API
of **Tables.jl**. If `io` is omitted, then it defaults to `stdout`.
When printing, it will be verified if `table` complies with **Tables.jl** API.
If it is is compliant, then this interface will be used to print the table. If
it is not compliant, then only the following types are supported:
1. `AbstractVector`: any vector can be printed. In this case, the `header`
**must** be a vector, where the first element is considered the header and
the others are the sub-headers.
2. `AbstractMatrix`: any matrix can be printed.
3. `Dict`: any `Dict` can be printed. In this case, the special keyword
`sortkeys` can be used to select whether or not the user wants to print the
dictionary with the keys sorted. If it is `false`, then the elements will be
printed on the same order returned by the functions `keys` and `values`.
Notice that this assumes that the keys are sortable, if they are not, then an
error will be thrown.
# Keywords
Expand Down Expand Up @@ -402,57 +395,50 @@ all columns that does not have a specific key.
"""
pretty_table(data::AbstractVecOrMat{T1}, header::AbstractVecOrMat{T2};
kwargs...) where {T1,T2} =
pretty_table(stdout, data, header; kwargs...)
pretty_table(data; kwargs...) = pretty_table(stdout, data, []; kwargs...)

function pretty_table(io::IO, data::AbstractVecOrMat{T1},
header::AbstractVecOrMat{T2}; kwargs...) where {T1,T2}

isempty(header) && ( header = ["Col. " * string(i) for i = 1:size(data,2)] )
_pretty_table(io, data, header; kwargs...)
end
pretty_table(data, header::AbstractVecOrMat; kwargs...) =
pretty_table(stdout, data, header; kwargs...)

pretty_table(data::AbstractVecOrMat{T}, kwargs...) where T =
pretty_table(stdout, data; kwargs...)
# This definition is required to avoid ambiguities.
pretty_table(data::AbstractVecOrMat, header::AbstractVecOrMat; kwargs...) =
pretty_table(stdout, data, header; kwargs...)

pretty_table(io::IO, data::AbstractVecOrMat{T}; kwargs...) where T =
# This definition is required to avoid ambiguities.
pretty_table(io::IO, data::AbstractVecOrMat; kwargs...) =
pretty_table(io, data, []; kwargs...)

pretty_table(dict::Dict{K,V}; kwargs...) where {K,V} =
pretty_table(stdout, dict; kwargs...)

function pretty_table(io::IO, dict::Dict{K,V}; sortkeys = false, kwargs...) where {K,V}
header = ["Keys" "Values";
string(K) string(V)]

k = collect(keys(dict))
v = collect(values(dict))
pretty_table(io::IO, data; kwargs...) = pretty_table(io, data, []; kwargs...)

if sortkeys
ind = sortperm(collect(keys(dict)))
vk = @view k[ind]
vv = @view v[ind]
function pretty_table(io::IO, data, header::AbstractVecOrMat; kwargs...)
if Tables.istable(data)
_pretty_table_Tables(io, data, header; kwargs...)
elseif typeof(data) <: AbstractVecOrMat
_pretty_table_VecOrMat(io, data, header; kwargs...)
elseif typeof(data) <: Dict
_pretty_table_Dict(io, data; kwargs...)
else
vk = k
vv = v
error("The type $(typeof(data)) is not supported.")
end

pretty_table(io, [vk vv], header; kwargs...)
end

pretty_table(table; kwargs...) = pretty_table(stdout, table; kwargs...)

function pretty_table(io::IO, table; kwargs...)
################################################################################
# Private Functions
################################################################################

# Function to print data that complies with Tables.jl API.
function _pretty_table_Tables(io::IO, table, header; kwargs...)
# Get the table schema to obtain the columns names.
sch = Tables.schema(table)

# Get the data.
data = Tables.matrix(table)

if sch == nothing
num_cols, num_rows = size(data)
header = ["Col. " * string(i) for i = 1:num_cols]
if isempty(header)
num_cols, num_rows = size(data)
header = ["Col. " * string(i) for i = 1:num_cols]
end
else
names = reshape( [sch.names...], (1,:) )
types = reshape( [sch.types...], (1,:) )
Expand All @@ -469,9 +455,34 @@ function pretty_table(io::IO, table; kwargs...)
_pretty_table(io, data, header; kwargs...)
end

################################################################################
# Private Functions
################################################################################
# Function to print vectors or matrices.
function _pretty_table_VecOrMat(io, matrix, header; kwargs...)
if isempty(header)
header = ["Col. " * string(i) for i = 1:size(matrix,2)]
end

_pretty_table(io, matrix, header; kwargs...)
end

# Function to print dictionaries.
function _pretty_table_Dict(io, dict::Dict{K,V}; sortkeys = false, kwargs...) where {K,V}
header = ["Keys" "Values";
string(K) string(V)]

k = collect(keys(dict))
v = collect(values(dict))

if sortkeys
ind = sortperm(collect(keys(dict)))
vk = @view k[ind]
vv = @view v[ind]
else
vk = k
vv = v
end

pretty_table(io, [vk vv], header; kwargs...)
end

# This is the low level function that prints the table. In this case, `data`
# must be accessed by `[i,j]` and the size of the `header` must be equal to the
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Test
using PrettyTables
using DataFrames
using Tables

println("Text backend")
println("============")
Expand Down
26 changes: 26 additions & 0 deletions test/text_backend.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1398,3 +1398,29 @@ end

@test cresult == expected
end

# Issue #28
# ==============================================================================

@testset "Issue #28 - Tables.api must have priority when printing" begin
# A DataFrame is compliant with Tables.jl API.
df = DataFrame(x=1:3, y='a':'c', z=["String 1";"String 2";"String 3"]);

# Thus, the following 3 calls must provide the same results.
result_1 = sprint(pretty_table, df)
result_2 = sprint(pretty_table, Tables.rowtable(df))
result_3 = sprint(pretty_table, Tables.columns(df))

expected = """
┌───────┬──────┬──────────┐
│ x │ y │ z │
│ Int64 │ Char │ String │
├───────┼──────┼──────────┤
│ 1 │ a │ String 1 │
│ 2 │ b │ String 2 │
│ 3 │ c │ String 3 │
└───────┴──────┴──────────┘
"""

@test result_1 == result_2 == result_3 == expected
end

0 comments on commit 09af865

Please sign in to comment.