Skip to content

Commit 0d7599b

Browse files
Add tests to improve coverage (#191)
* some tests for has_schema etc. * missing method for has_schema * test model trait functions drop_intercept and implicit_intercept * fix typo in test * test cases for implicit_intercept(Any) * Apply suggestions from code review Co-authored-by: Milan Bouchet-Valat <[email protected]> Co-authored-by: Milan Bouchet-Valat <[email protected]>
1 parent a966c65 commit 0d7599b

File tree

5 files changed

+154
-2
lines changed

5 files changed

+154
-2
lines changed

src/schema.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ has_schema(t::Term) = false
246246
has_schema(t::Union{ContinuousTerm,CategoricalTerm}) = true
247247
has_schema(t::InteractionTerm) = all(has_schema(tt) for tt in t.terms)
248248
has_schema(t::TupleTerm) = all(has_schema(tt) for tt in t)
249+
has_schema(t::MatrixTerm) = has_schema(t.terms)
249250
has_schema(t::FormulaTerm) = has_schema(t.lhs) && has_schema(t.rhs)
250251

251252
struct FullRank
@@ -263,7 +264,7 @@ function apply_schema(t::FormulaTerm, schema::Schema, Mod::Type{<:StatisticalMod
263264
schema = FullRank(schema)
264265

265266
# Models with the drop_intercept trait do not support intercept terms,
266-
# usually because they include one implicitly.
267+
# usually because one is always necessarily included during fitting
267268
if drop_intercept(Mod)
268269
if hasintercept(t)
269270
throw(ArgumentError("Model type $Mod doesn't support intercept " *

test/modelmatrix.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,24 @@
350350
@test reduce(vcat, last.(modelcols.(Ref(f), Tables.rowtable(d)))') == modelmatrix(f,d)
351351
end
352352

353+
@testset "modelmatrix and response set schema if needed" begin
354+
d = DataFrame(r = rand(8),
355+
w = rand(8),
356+
x = repeat([:a, :b], outer = 4),
357+
y = repeat([:c, :d], inner = 2, outer = 2),
358+
z = repeat([:e, :f], inner = 4))
359+
360+
f = @formula(r ~ 1 + w*x*y*z)
361+
362+
mm1 = modelmatrix(f, d)
363+
mm2 = modelmatrix(apply_schema(f, schema(d)), d)
364+
@test mm1 == mm2
365+
366+
r1 = response(f, d)
367+
r2 = response(apply_schema(f, schema(d)), d)
368+
@test r1 == r2
369+
end
370+
353371
@testset "setcontrasts!" begin
354372
@testset "#95" begin
355373
tbl = (Y = randn(8),

test/runtests.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ my_tests = ["ambiguity.jl",
1717
"modelframe.jl",
1818
"statsmodel.jl",
1919
"contrasts.jl",
20-
"extension.jl"]
20+
"extension.jl",
21+
"traits.jl"]
2122

2223
@testset "StatsModels" begin
2324
for tf in my_tests

test/schema.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,34 @@
3434
@test f3.rhs.terms[end] === hint
3535

3636
end
37+
38+
@testset "has_schema" begin
39+
using StatsModels: has_schema
40+
41+
d = (y = rand(10), a = rand(10), b = repeat([:a, :b], 5))
42+
43+
f = @formula(y ~ a*b)
44+
@test !has_schema(f)
45+
@test !has_schema(f.rhs)
46+
@test !has_schema(StatsModels.collect_matrix_terms(f.rhs))
47+
48+
ff = apply_schema(f, schema(d))
49+
@test has_schema(ff)
50+
@test has_schema(ff.rhs)
51+
@test has_schema(StatsModels.collect_matrix_terms(ff.rhs))
52+
53+
sch = schema(d)
54+
a, b = term.((:a, :b))
55+
@test !has_schema(a)
56+
@test has_schema(sch[a])
57+
@test !has_schema(b)
58+
@test has_schema(sch[b])
59+
60+
@test !has_schema(a & b)
61+
@test !has_schema(a & sch[b])
62+
@test !has_schema(sch[a] & a)
63+
@test has_schema(sch[a] & sch[b])
64+
65+
end
3766

3867
end

test/traits.jl

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using StatsModels: hasintercept, omitsintercept
2+
import StatsModels: drop_intercept, implicit_intercept
3+
4+
struct DroppyMod <: StatisticalModel end
5+
drop_intercept(::Type{DroppyMod}) = true
6+
7+
# define structs for testing implicit intercept trait:
8+
9+
# default for StatisticalModel is true
10+
struct DefaultImplicit <: StatisticalModel end
11+
12+
# override default = true for StatisticalModels
13+
struct NoImplicit <: StatisticalModel end
14+
implicit_intercept(::Type{NoImplicit}) = false
15+
16+
# manual override of default = false
17+
struct YesImplicit end
18+
implicit_intercept(::Type{YesImplicit}) = true
19+
20+
21+
@testset "Model traits" begin
22+
d = (y = rand(10), x = rand(10), z = [:a, :b, :c])
23+
sch = schema(d)
24+
f = @formula(y ~ x)
25+
f1 = @formula(y ~ 1 + x)
26+
f0 = @formula(y ~ 0 + x)
27+
28+
@testset "drop_intercept" begin
29+
@test_throws ArgumentError apply_schema(f1, sch, DroppyMod)
30+
ff = apply_schema(f, sch, DroppyMod)
31+
@test !hasintercept(ff)
32+
@test !omitsintercept(ff)
33+
ff0 = apply_schema(f0, sch, DroppyMod)
34+
@test !hasintercept(ff0)
35+
@test omitsintercept(ff0)
36+
37+
@test drop_intercept(DroppyMod()) == drop_intercept(DroppyMod)
38+
# drop_intercept blocks implicit_intercept == true
39+
@test implicit_intercept(DroppyMod)
40+
41+
@testset "categorical promotion" begin
42+
# drop_intercept == true means that model should always ACT like
43+
# intercept is present even if it's not specified or even ommitted.
44+
# (pushes intercept term to the FullRank already seen terms list).
45+
46+
# full dummy coding
47+
@test width(apply_schema(@formula(y ~ 0 + z), sch, StatisticalModel).rhs) == 3
48+
# droppy regular coding
49+
@test width(apply_schema(@formula(y ~ 0 + z), sch, DroppyMod).rhs) == 2
50+
end
51+
end
52+
53+
@testset "implicit_intercept" begin
54+
@testset "default" begin
55+
ff, ff0, ff1 = apply_schema.((f, f0, f1), Ref(sch), Any)
56+
@test !hasintercept(ff)
57+
@test !hasintercept(ff0)
58+
@test hasintercept(ff1)
59+
@test !omitsintercept(ff)
60+
@test omitsintercept(ff0)
61+
@test !omitsintercept(ff1)
62+
end
63+
64+
@testset "StatisticalModel default" begin
65+
ff, ff0, ff1 = apply_schema.((f, f0, f1), Ref(sch), DefaultImplicit)
66+
@test hasintercept(ff)
67+
@test !hasintercept(ff0)
68+
@test hasintercept(ff1)
69+
@test !omitsintercept(ff)
70+
@test omitsintercept(ff0)
71+
@test !omitsintercept(ff1)
72+
73+
@test implicit_intercept(DefaultImplicit()) == implicit_intercept(DefaultImplicit)
74+
end
75+
76+
@testset "Override StatisticalModel default" begin
77+
ff, ff0, ff1 = apply_schema.((f, f0, f1), Ref(sch), NoImplicit)
78+
@test !hasintercept(ff)
79+
@test !hasintercept(ff0)
80+
@test hasintercept(ff1)
81+
@test !omitsintercept(ff)
82+
@test omitsintercept(ff0)
83+
@test !omitsintercept(ff1)
84+
85+
@test implicit_intercept(NoImplicit()) == implicit_intercept(NoImplicit)
86+
end
87+
88+
@testset "Override Any default" begin
89+
ff, ff0, ff1 = apply_schema.((f, f0, f1), Ref(sch), YesImplicit)
90+
# broken because traits are not checked during apply_schema for
91+
# context that is not <:StatisticalModel
92+
@test_broken hasintercept(ff)
93+
@test !hasintercept(ff0)
94+
@test hasintercept(ff1)
95+
@test !omitsintercept(ff)
96+
@test omitsintercept(ff0)
97+
@test !omitsintercept(ff1)
98+
99+
@test implicit_intercept(YesImplicit()) == implicit_intercept(YesImplicit)
100+
end
101+
102+
end
103+
end

0 commit comments

Comments
 (0)