Skip to content

Commit 91a144f

Browse files
authored
Fix compat + nested TermTuple (#4)
* compat for combinatorics * support TupleTerm as innermost nesting * slight consistency change
1 parent f3e8daf commit 91a144f

File tree

3 files changed

+50
-4
lines changed

3 files changed

+50
-4
lines changed

Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
88
StatsModels = "3eaba693-59b7-5ba5-a881-562e759f1c8d"
99

1010
[compat]
11+
Combinatorics = "1"
1112
StatsBase = "0.33"
1213
StatsModels = "0.6.7"
1314
julia = "1.6"

src/nesting.jl

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ function _isfulldummy(x::CategoricalTerm)
44
return isa(x.contrasts, StatsModels.ContrastsMatrix{StatsModels.FullDummyCoding})
55
end
66

7+
function _fulldummycheck(outer::InteractionTerm)
8+
all(_isfulldummy, outer.terms[1:end-1]) ||
9+
throw(ArgumentError("Outer interactions in a nesting must consist only " *
10+
" of categorical terms with FullDummyCoding, got $outer"))
11+
return nothing
12+
end
13+
714
"""
815
group / term
916
@@ -14,19 +21,29 @@ function Base.:(/)(outer::CategoricalTerm, inner::AbstractTerm)
1421
return outer + fulldummy(outer) & inner
1522
end
1623

17-
function Base.:(/)(outer::TermTuple, inner::AbstractTerm)
24+
function Base.:(/)(outer::CategoricalTerm, inner::TermTuple)
25+
fd = fulldummy(outer)
26+
return mapfoldl(x -> fd & x, +, inner; init=outer)
27+
end
28+
29+
function Base.:(/)(outer::TermTuple, inner::Union{AbstractTerm, TermTuple})
1830
return outer[1:end-1] + last(outer) / inner
1931
end
2032

2133
function Base.:(/)(outer::InteractionTerm, inner::AbstractTerm)
2234
# we should only get here via expansion where the interaction term,
2335
# but who knows what devious things users will try
24-
all(_isfulldummy, outer.terms[1:end-1]) ||
25-
throw(ArgumentError("Outer interactions in a nesting must consist only " *
26-
" of categorical terms with FullDummyCoding, got $outer"))
36+
_fulldummycheck(outer)
2737
return outer + outer & inner
2838
end
2939

40+
function Base.:(/)(outer::InteractionTerm, inner::TermTuple)
41+
# we should only get here via expansion where the interaction term,
42+
# but who knows what devious things users will try
43+
_fulldummycheck(outer)
44+
return mapfoldl(x -> outer & x, +, inner; init=outer)
45+
end
46+
3047
function Base.:(/)(outer::AbstractTerm, inner::AbstractTerm)
3148
throw(ArgumentError("nesting terms requires categorical grouping term, got $outer / $inner " *
3249
"Manually specify $outer as `CategoricalTerm` in hints/contrasts"))

test/nesting.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ end
3131
m = fit(DummyMod, @formula(y ~ 1 + a / x), dat)
3232
@test coefnames(m) == ["(Intercept)", "a: o", "a: u",
3333
"a: i & x", "a: o & x", "a: u & x"]
34+
35+
m = fit(DummyMod, @formula(y ~ 0 + a / (b + x)), dat)
36+
@test coefnames(m) == ["a: i", "a: o", "a: u",
37+
"a: i & b: q", "a: o & b: q", "a: u & b: q",
38+
"a: i & b: w", "a: o & b: w", "a: u & b: w",
39+
"a: i & x", "a: o & x", "a: u & x"]
40+
m = fit(DummyMod, @formula(y ~ 0 + a / (b * x)), dat)
41+
@test coefnames(m) == ["a: i", "a: o", "a: u",
42+
"a: i & b: q", "a: o & b: q", "a: u & b: q",
43+
"a: i & b: w", "a: o & b: w", "a: u & b: w",
44+
"a: i & x", "a: o & x", "a: u & x",
45+
"a: i & b: q & x", "a: o & b: q & x", "a: u & b: q & x",
46+
"a: i & b: w & x", "a: o & b: w & x", "a: u & b: w & x"]
3447
end
3548

3649
@testset "multiple nesting levels" begin
@@ -68,4 +81,19 @@ end
6881
"a: i & b: q & x", "a: o & b: q & x",
6982
"a: u & b: q & x", "a: i & b: w & x",
7083
"a: o & b: w & x", "a: u & b: w & x"]
84+
85+
m = fit(DummyMod, @formula(y ~ 0 + a / b / (c * x)), dat)
86+
@test coefnames(m) == ["a: i", "a: o", "a: u",
87+
"a: i & b: q", "a: o & b: q", "a: u & b: q",
88+
"a: i & b: w", "a: o & b: w", "a: u & b: w",
89+
"a: i & b: q & c: f", "a: o & b: q & c: f", "a: u & b: q & c: f",
90+
"a: i & b: w & c: f", "a: o & b: w & c: f", "a: u & b: w & c: f",
91+
"a: i & b: q & c: s", "a: o & b: q & c: s", "a: u & b: q & c: s",
92+
"a: i & b: w & c: s", "a: o & b: w & c: s", "a: u & b: w & c: s",
93+
"a: i & b: q & x", "a: o & b: q & x", "a: u & b: q & x",
94+
"a: i & b: w & x", "a: o & b: w & x", "a: u & b: w & x",
95+
"a: i & b: q & c: f & x", "a: o & b: q & c: f & x", "a: u & b: q & c: f & x",
96+
"a: i & b: w & c: f & x", "a: o & b: w & c: f & x", "a: u & b: w & c: f & x",
97+
"a: i & b: q & c: s & x", "a: o & b: q & c: s & x", "a: u & b: q & c: s & x",
98+
"a: i & b: w & c: s & x", "a: o & b: w & c: s & x", "a: u & b: w & c: s & x"]
7199
end

0 commit comments

Comments
 (0)