Skip to content

Commit ed3b86e

Browse files
authored
Make CoefTable implement the Tables.jl interface (#629)
This allows retrieving the contents of a `CoefTable` object in a convenient form, notably a `DataFrame`. To avoid introducing a dependency on Tables.jl, `CoefTable` has to iterate `NamedTuple`s, so that it implements the row-table interface implicitly. This is inefficient since `CoefTable` uses a column-based storage, but given the typical size of such tables it should not matter.
1 parent 320411b commit ed3b86e

File tree

2 files changed

+55
-5
lines changed

2 files changed

+55
-5
lines changed

src/statmodels.jl

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ coefnames(model::StatisticalModel) = error("coefnames is not defined for $(typeo
1919
"""
2020
coeftable(model::StatisticalModel; level::Real=0.95)
2121
22-
Return a table of class `CoefTable` with coefficients and related statistics.
22+
Return a table with coefficients and related statistics of the model.
2323
`level` determines the level for confidence intervals (by default, 95%).
24+
25+
The returned `CoefTable` object implements the
26+
[Tables.jl](https://github.com/JuliaData/Tables.jl/) interface, and can be
27+
converted e.g. to a `DataFrame` via `using DataFrames; DataFrame(coeftable(model))`.
2428
"""
2529
coeftable(model::StatisticalModel) = error("coeftable is not defined for $(typeof(model)).")
2630

@@ -420,6 +424,29 @@ mutable struct CoefTable
420424
end
421425
end
422426

427+
Base.length(ct::CoefTable) = length(ct.cols[1])
428+
function Base.eltype(ct::CoefTable)
429+
names = isempty(ct.rownms) ?
430+
tuple(Symbol.(ct.colnms)...) :
431+
tuple(Symbol("Name"), Symbol.(ct.colnms)...)
432+
types = isempty(ct.rownms) ?
433+
Tuple{eltype.(ct.cols)...} :
434+
Tuple{eltype(ct.rownms), eltype.(ct.cols)...}
435+
NamedTuple{names, types}
436+
end
437+
438+
function Base.iterate(ct::CoefTable, i::Integer=1)
439+
if i in 1:length(ct)
440+
cols = getindex.(ct.cols, Ref(i))
441+
nt = isempty(ct.rownms) ?
442+
eltype(ct)(tuple(cols...)) :
443+
eltype(ct)(tuple(ct.rownms[i], cols...))
444+
(nt, i+1)
445+
else
446+
nothing
447+
end
448+
end
449+
423450
"""
424451
Show a p-value using 6 characters, either using the standard 0.XXXX
425452
representation or as <Xe-YY.

test/statmodels.jl

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,50 @@ v2 = ["Good", "Great", "Bad"]
77
v3 = [1, 56, 2]
88
v4 = [-12.56, 0.1326, 2.68e-16]
99
v5 = [0.12, 0.3467, 1.345e-16]
10-
@test sprint(show, CoefTable(Any[v1, v2, v3, v4, v5],
11-
["Estimate", "Comments", "df", "t", "p"],
12-
["x1", "x2", "x3"], 5, 4)) == """
10+
ct = CoefTable(Any[v1, v2, v3, v4, v5],
11+
["Estimate", "Comments", "df", "t", "p"],
12+
["x1", "x2", "x3"], 5, 4)
13+
@test sprint(show, ct) == """
1314
───────────────────────────────────────────────
1415
Estimate Comments df t p
1516
───────────────────────────────────────────────
1617
x1 1.45666 Good 1 -12.56 0.1200
1718
x2 -23.14 Great 56 0.13 0.3467
1819
x3 1.56734e-13 Bad 2 0.00 <1e-15
1920
───────────────────────────────────────────────"""
21+
@test length(ct) === 3
22+
@test eltype(ct) ==
23+
NamedTuple{(:Name, :Estimate, :Comments, :df, :t, :p),
24+
Tuple{String,Float64,String,Int,Float64,Float64}}
25+
@test collect(ct) == [
26+
(Name = "x1", Estimate = 1.45666, Comments = "Good", df = 1, t = -12.56, p = 0.12)
27+
(Name = "x2", Estimate = -23.14, Comments = "Great", df = 56, t = 0.1326, p = 0.3467)
28+
(Name = "x3", Estimate = 1.56734e-13, Comments = "Bad", df = 2, t = 2.68e-16, p = 1.345e-16)
29+
]
2030

2131
Random.seed!(10)
2232
m = rand(3,4)
23-
@test sprint(show, CoefTable(m, ["Estimate", "Stderror", "df", "p"], [], 4)) == """
33+
ct = CoefTable(m, ["Estimate", "Stderror", "df", "p"], [], 4)
34+
@test sprint(show, ct) == """
2435
──────────────────────────────────────────
2536
Estimate Stderror df p
2637
──────────────────────────────────────────
2738
[1] 0.112582 0.0566454 0.381813 0.8198
2839
[2] 0.368314 0.120781 0.815104 0.6699
2940
[3] 0.344454 0.179574 0.242208 0.4531
3041
──────────────────────────────────────────"""
42+
@test length(ct) === 3
43+
@test eltype(ct) ==
44+
NamedTuple{(:Estimate, :Stderror, :df, :p),
45+
Tuple{Float64,Float64,Float64,Float64}}
46+
@test collect(ct) == [
47+
(Estimate = 0.11258244478647295, Stderror = 0.05664544616214151,
48+
df = 0.38181274408522614, p = 0.8197779704008801)
49+
(Estimate = 0.36831406658084287, Stderror = 0.12078054506961555,
50+
df = 0.8151038332483567, p = 0.6699313951612162)
51+
(Estimate = 0.3444540231363058, Stderror = 0.17957407667101322,
52+
df = 0.2422083248151139, p = 0.4530583319523316)
53+
]
3154

3255
@test sprint(show, PValue(1.0)) == "1.0000"
3356
@test sprint(show, PValue(1e-1)) == "0.1000"

0 commit comments

Comments
 (0)