diff --git a/.github/workflows/documenter.yml b/.github/workflows/documenter.yml index 03ec3b620..4379603de 100644 --- a/.github/workflows/documenter.yml +++ b/.github/workflows/documenter.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@v1 with: - version: 1.6 + version: 1.8 - uses: julia-actions/julia-buildpkg@latest - uses: julia-actions/julia-docdeploy@latest env: diff --git a/NEWS.md b/NEWS.md index ff90225f5..040e4bb2e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ MixedModels v4.9.0 Release Notes ============================== +* Support `StatsModels` 0.7, drop support for `StatsModels` 0.6. [#664] * Revise code in benchmarks to work with recent Julia and PkgBenchmark.jl [#667] * Julia minimum compat version raised to 1.8 because of BSplineKit [#665] @@ -396,5 +397,6 @@ Package dependencies [#653]: https://github.com/JuliaStats/MixedModels.jl/issues/653 [#657]: https://github.com/JuliaStats/MixedModels.jl/issues/657 [#663]: https://github.com/JuliaStats/MixedModels.jl/issues/663 +[#664]: https://github.com/JuliaStats/MixedModels.jl/issues/664 [#665]: https://github.com/JuliaStats/MixedModels.jl/issues/665 [#667]: https://github.com/JuliaStats/MixedModels.jl/issues/667 diff --git a/Project.toml b/Project.toml index 60d4a4482..ed6f483ef 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "MixedModels" uuid = "ff71e718-51f3-5ec2-a782-8ffcbfa3c316" author = ["Phillip Alday ", "Douglas Bates ", "Jose Bayoan Santiago Calderon "] -version = "4.8.2" +version = "4.9.0" [deps] Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45" @@ -31,7 +31,7 @@ Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" Arrow = "1, 2" DataAPI = "1" Distributions = "0.21, 0.22, 0.23, 0.24, 0.25" -GLM = "1.5.1" +GLM = "1.8.2" JSON3 = "1" LazyArtifacts = "1" NLopt = "0.5, 0.6" @@ -42,7 +42,7 @@ StaticArrays = "0.11, 0.12, 1" StatsAPI = "1.5" StatsBase = "0.31, 0.32, 0.33" StatsFuns = "0.8, 0.9, 1" -StatsModels = "0.6.23" +StatsModels = "0.7" StructTypes = "1" Tables = "1" julia = "1.8" diff --git a/src/randomeffectsterm.jl b/src/randomeffectsterm.jl index fbc1fbc51..a5876d8f3 100644 --- a/src/randomeffectsterm.jl +++ b/src/randomeffectsterm.jl @@ -37,7 +37,7 @@ function StatsModels.apply_schema( schema::MultiSchema{StatsModels.FullRank}, Mod::Type{<:MixedModel}, ) - lhs, rhs = t.args_parsed + lhs, rhs = t.args isempty(intersect(StatsModels.termvars(lhs), StatsModels.termvars(rhs))) || throw(ArgumentError("Same variable appears on both sides of |")) @@ -53,11 +53,21 @@ check_re_group_type(term::GROUPING_TYPE) = true check_re_group_type(terms::Tuple{Vararg{<:GROUPING_TYPE}}) = true check_re_group_type(x) = false +_unprotect(x) = x +for op in StatsModels.SPECIALS + @eval _unprotect(t::FunctionTerm{typeof($op)}) = t.f(_unprotect.(t.args)...) +end + # make a potentially untyped RandomEffectsTerm concrete function StatsModels.apply_schema( t::RandomEffectsTerm, schema::MultiSchema{StatsModels.FullRank}, Mod::Type{<:MixedModel} ) - lhs, rhs = t.lhs, t.rhs + # we need to do this here because the implicit intercept dance has to happen + # _before_ we apply_schema, which is where :+ et al. are normally + # unprotected. I tried to finagle a way around this (using yet another + # schema wrapper type) but it ends up creating way too many potential/actual + # method ambiguities to be a good idea. + lhs, rhs = _unprotect(t.lhs), t.rhs # get a schema that's specific for the grouping (RHS), creating one if needed schema = get!(schema.subs, rhs, StatsModels.FullRank(schema.base.schema)) @@ -134,11 +144,11 @@ Base.:/(a::AbstractTerm, b::AbstractTerm) = a + a & b function StatsModels.apply_schema( t::FunctionTerm{typeof(/)}, sch::StatsModels.FullRank, Mod::Type{<:MixedModel} ) - if length(t.args_parsed) ≠ 2 + if length(t.args) ≠ 2 throw(ArgumentError("malformed nesting term: $t (Exactly two arguments required")) end - first, second = apply_schema.(t.args_parsed, Ref(sch), Mod) + first, second = apply_schema.(t.args, Ref(sch), Mod) if !(typeof(first) <: CategoricalTerm) throw( @@ -188,7 +198,7 @@ end function StatsModels.apply_schema( t::FunctionTerm{typeof(fulldummy)}, sch::StatsModels.FullRank, Mod::Type{<:MixedModel} ) - return fulldummy(apply_schema.(t.args_parsed, Ref(sch), Mod)...) + return fulldummy(apply_schema.(t.args, Ref(sch), Mod)...) end # specify zero correlation @@ -207,11 +217,21 @@ zerocorr(x) = ZeroCorr(x) # for schema extraction (from runtime-created zerocorr) StatsModels.terms(t::ZeroCorr) = StatsModels.terms(t.term) StatsModels.termvars(t::ZeroCorr) = StatsModels.termvars(t.term) +StatsModels.degree(t::ZeroCorr) = StatsModels.degree(t.term) +# dirty rotten no good ugly hack: make sure zerocorr ranef terms sort appropriately +# cf https://github.com/JuliaStats/StatsModels.jl/blob/41b025409af03c0e019591ac6e817b22efbb4e17/src/terms.jl#L421-L422 +StatsModels.degree(t::FunctionTerm{typeof(zerocorr)}) = StatsModels.degree(only(t.args)) + +Base.show(io::IO, t::ZeroCorr) = Base.show(io, MIME"text/plain"(), t) +function Base.show(io::IO, ::MIME"text/plain", t::ZeroCorr) + # ranefterms already show with parens + return print(io, "zerocorr", t.term) +end function StatsModels.apply_schema( t::FunctionTerm{typeof(zerocorr)}, sch::MultiSchema, Mod::Type{<:MixedModel} ) - return ZeroCorr(apply_schema(t.args_parsed..., sch, Mod)) + return ZeroCorr(apply_schema(only(t.args), sch, Mod)) end function StatsModels.apply_schema(t::ZeroCorr, sch::MultiSchema, Mod::Type{<:MixedModel}) diff --git a/test/FactorReTerm.jl b/test/FactorReTerm.jl index e40c76339..32d266c27 100644 --- a/test/FactorReTerm.jl +++ b/test/FactorReTerm.jl @@ -157,6 +157,12 @@ end @test zc.rhs.sym == :subj end + @testset "Amalgamation of ZeroCorr with other terms" begin + f = @formula(reaction ~ 1 + days + (1|subj) + zerocorr(days|subj)) + m = LMM(f, dataset(:sleepstudy), contrasts = Dict(:days => DummyCoding())) + re = only(m.reterms) + @test length(re.cnames) == length(unique(re.cnames)) == 10 + end end @testset "Categorical Blocking Variable" begin