Skip to content

Commit

Permalink
Merge pull request #33 from PharmCat/dev14
Browse files Browse the repository at this point in the history
Dev14 (StatsModels 0.7)
  • Loading branch information
PharmCat authored Mar 18, 2023
2 parents 5c9e00e + 4d50073 commit 7d9582a
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 30 deletions.
8 changes: 5 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ uuid = "a1dec852-9fe5-11e9-361f-8d9fde67cfa2"
keywords = ["lenearmodel", "mixedmodel"]
desc = "Mixed-effects models with flexible covariance structure."
authors = ["Vladimir Arnautov <[email protected]>"]
version = "0.14.5"
version = "0.14.6"

[deps]
DiffResults = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
LineSearches = "d3d80556-e9d4-5f37-9878-2ab0fcc64255"
Expand All @@ -19,13 +20,14 @@ StatsModels = "3eaba693-59b7-5ba5-a881-562e759f1c8d"

[compat]
DiffResults = "1"
Distributions = "0.20, 0.21, 0.22, 0.23, 0.24, 0.25"
ForwardDiff = "0.10"
LineSearches = "7"
MetidaBase = "0.10.1, 0.10.2"
MetidaBase = "0.11"
Optim = "1"
ProgressMeter = "1"
StatsBase = "0.29, 0.30, 0.31, 0.32, 0.33"
StatsModels = "0.6"
StatsModels = "0.7"
julia = "1"

[extras]
Expand Down
4 changes: 1 addition & 3 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd"
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
MixedModels = "ff71e718-51f3-5ec2-a782-8ffcbfa3c316"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Expand All @@ -17,8 +16,7 @@ Plots = "≥1"
StatsPlots = "≥0.14"
CSV = "≥0.8"
DataFrames = "≥1"
MixedModels = "3.1.5"
PrettyTables = "0.10, 0.11, 1, 2"
PrettyTables = "1, 2"
StatsBase = "≥0.33"
Distributions = "≥0.25"
CategoricalArrays = "≥0.9, 0.10"
25 changes: 24 additions & 1 deletion docs/src/examples.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
### Example 1 - Continuous and categorical predictors

```@example lmmexample
using Metida, CSV, DataFrames, MixedModels, CategoricalArrays;
using Metida, CSV, DataFrames, CategoricalArrays;
import Pkg
Pkg.activate("MixedModels")
Pkg.add(name="Example", version="3.1.5")
using MixedModels
rds = CSV.File(joinpath(dirname(pathof(Metida)), "..", "test", "csv", "1fptime.csv"); types = [String, String, Float64, Float64]) |> DataFrame
Expand Down Expand Up @@ -156,3 +162,20 @@ MODEL var = sequence period formulation/ DDFM=SATTERTH s;
RANDOM subject/TYPE=VC G V;
RUN;
```

### Example 5 - Working with Effects.jl

```
using Effects, StatsModels
lmm = LMM(@formula(var ~ sequence + period + formulation), df0;
random = VarEffect(@covstr(subject|1), SI)
)
fit!(lmm)
table_model = StatsModels.TableRegressionModel(lmm, lmm.mf, lmm.mm)
emmeans(tm)
effects(Dict(:period => ["1", "2", "3", "4"]), tm)
```
15 changes: 7 additions & 8 deletions src/Metida.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@
__precompile__()
module Metida

using ProgressMeter, LinearAlgebra, ForwardDiff, DiffResults, Random, Optim, LineSearches, MetidaBase#, SparseArrays#, Polyester#, LoopVectorization

import MetidaBase: StatsBase, StatsModels, CategoricalArrays, Distributions
using ProgressMeter, LinearAlgebra, ForwardDiff, DiffResults, Random, Optim, LineSearches, MetidaBase#, SparseArrays#, Polyester#, LoopVectorization
import StatsBase, StatsModels, Distributions

import MetidaBase: CategoricalArrays, Tables, MetidaModel, AbstractCovarianceStructure, AbstractCovmatMethod, AbstractCovarianceType, AbstractLMMDataBlocks, MetidaTable, metida_table, PrettyTables, indsdict!
import MetidaBase.CategoricalArrays: CategoricalArray, AbstractCategoricalVector
import MetidaBase.Distributions: Normal, TDist, FDist, Chisq, MvNormal, FullNormal, ccdf, cdf, quantile

import MetidaBase: Tables, MetidaModel, AbstractCovarianceStructure, AbstractCovmatMethod, AbstractCovarianceType, AbstractLMMDataBlocks, MetidaTable, metida_table, PrettyTables, indsdict!
import MetidaBase.PrettyTables: TextFormat, pretty_table, tf_borderless, ft_printf
import LinearAlgebra:checksquare, BlasFloat

import Distributions: Normal, TDist, FDist, Chisq, MvNormal, FullNormal, ccdf, cdf, quantile
import LinearAlgebra: checksquare, BlasFloat
import StatsModels: @formula, termvars, ModelFrame, FunctionTerm, AbstractTerm, CategoricalTerm, AbstractContrasts, ConstantTerm, InterceptTerm, Term, InteractionTerm, FormulaTerm, ModelMatrix, schema, apply_schema, MatrixTerm, modelcols
import StatsBase: fit, fit!, coef, coefnames, confint, nobs, dof_residual, dof, loglikelihood, aic, bic, aicc, isfitted, vcov, mean, var, stderror, modelmatrix, response, responsename, CoefTable, coeftable, crossmodelmatrix
import Base:show, rand, ht_keyindex, getproperty
import Base: show, rand, ht_keyindex, getproperty
import Random: default_rng, AbstractRNG, rand!

export @formula, @covstr, @lmmformula,
Expand Down
6 changes: 6 additions & 0 deletions src/lmm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -288,3 +288,9 @@ function Base.getproperty(x::LMM, s::Symbol)
end
getfield(x, s)
end

#=
function Base.convert(::Type{StatsModels.TableRegressionModel}, lmm::LMM)
StatsModels.TableRegressionModel(lmm, lmm.mf, lmm.mm)
end
=#
10 changes: 10 additions & 0 deletions src/random.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ function rand!(rng::AbstractRNG, v::AbstractVector, lmm::LMM)
if !lmm.result.fit error("Model not fitted!") end
rand!(rng, v, lmm, lmm.result.theta, lmm.result.beta)
end
"""
rand!(v::AbstractVector, lmm::LMM) = rand!(default_rng(), v, lmm, lmm.result.theta, lmm.result.beta)
Generate random responce vector for fitted 'lmm' model, store results in `v`.
"""
rand!(v::AbstractVector, lmm::LMM) = rand!(default_rng(), v, lmm, lmm.result.theta, lmm.result.beta)
"""
rand(rng::AbstractRNG, lmm::LMM{T}; theta) where T
Expand All @@ -22,6 +27,11 @@ function rand(rng::AbstractRNG, lmm::LMM{T}, theta::AbstractVector) where T
v = Vector{T}(undef, nobs(lmm))
rand!(rng, v, lmm, theta)
end
"""
rand!(rng::AbstractRNG, lmm::LMM{T}; theta) where T
Generate random responce vector 'lmm' model, theta covariance vector, and zero means, store results in `v`.
"""
function rand!(rng::AbstractRNG, v::AbstractVector, lmm::LMM{T}, theta::AbstractVector) where T
n = length(lmm.covstr.vcovblock)
if length(v) != nobs(lmm) error("Wrong v length!") end
Expand Down
2 changes: 1 addition & 1 deletion src/reml.jl
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ function reml_sweep_β_nlopt(lmm, data, θ::Vector{T}; maxthreads::Int = 16) whe
# θ₂ calculation
logdetθ₂ = logdet(Cholesky(ldθ₂, 'U', 0))
# θ₃ calculation
@inbounds @simd for i = 1:n
@inbounds for i = 1:n
r = mul!(copy(data.yv[i]), data.xv[i], βtc, -1, 1)
vr = LinearAlgebra.LAPACK.potrs!('U', A[i], copy(r))
θ₃ += dot(r, vr)
Expand Down
45 changes: 42 additions & 3 deletions src/varstruct.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const CType = Union{FunctionTerm{typeof(+), Vector{Term}}, FunctionTerm{typeof(*), Vector{Term}}, FunctionTerm{typeof(&), Vector{Term}}}

################################################################################
# @covstr macro
################################################################################

"""
@covstr(ex)
Expand All @@ -16,8 +19,8 @@ macro covstr(ex)
return :(@formula(nothing ~ $ex).rhs)
end
function modelparse(term::FunctionTerm{typeof(|)})
eff, subj = term.args_parsed
if !isa(subj, AbstractTerm) throw(FormulaException("Subject term type not <: AbstractTerm. Use `term` or `interaction term` only. Maybe you are using something like this: `@covstr(factor|term1*term2)` or `@covstr(factor|(term1+term2))`. Use only `@covstr(factor|term)` or `@covstr(factor|term1&term2)`.")) end
eff, subj = term.args
if !isa(subj, AbstractTerm) || isa(subj, FunctionTerm{typeof(*), Vector{Term}}) throw(FormulaException("Subject term type not <: AbstractTerm. Use `term` or `interaction term` only. Maybe you are using something like this: `@covstr(factor|term1*term2)` or `@covstr(factor|(term1+term2))`. Use only `@covstr(factor|term)` or `@covstr(factor|term1&term2)`.")) end
eff, subj
end
function modelparse(term)
Expand Down Expand Up @@ -352,6 +355,7 @@ function fill_coding_dict!(t::T, d::Dict, data) where T <: Term
end
d
end
#=
function fill_coding_dict!(t::T, d::Dict, data) where T <: InteractionTerm
for i in t.terms
if typeof(Tables.getcolumn(data, i.sym)) <: AbstractCategoricalVector || !(typeof(Tables.getcolumn(data, i.sym)) <: AbstractVector{V} where V <: Real)
Expand All @@ -360,7 +364,8 @@ function fill_coding_dict!(t::T, d::Dict, data) where T <: InteractionTerm
end
d
end
function fill_coding_dict!(t::T, d::Dict, data) where T <: Tuple{Vararg{AbstractTerm}}
=#
function fill_coding_dict_ct!(t, d, data)
for i in t
if isa(i, Term)
if typeof(Tables.getcolumn(data, i.sym)) <: AbstractCategoricalVector || !(typeof(Tables.getcolumn(data, i.sym)) <: AbstractVector{V} where V <: Real)
Expand All @@ -372,6 +377,40 @@ function fill_coding_dict!(t::T, d::Dict, data) where T <: Tuple{Vararg{Abstract
end
d
end
#=
function fill_coding_dict!(t::T, d::Dict, data) where T <: Tuple{Vararg{AbstractTerm}}
fill_coding_dict_ct!(t, d, data)
end
=#
function fill_coding_dict!(t::T, d::Dict, data) where T <: CType
fill_coding_dict_ct!(t.args, d, data)
end
#=
function fill_coding_dict!(t::T, d::Dict, data) where T <: FunctionTerm{typeof(&), Vector{Term}}
for i in t.args
if isa(i, Term)
if typeof(Tables.getcolumn(data, i.sym)) <: AbstractCategoricalVector || !(typeof(Tables.getcolumn(data, i.sym)) <: AbstractVector{V} where V <: Real)
d[i.sym] = StatsModels.FullDummyCoding()
end
else
fill_coding_dict!(i, d, data)
end
end
d
end
function fill_coding_dict!(t::T, d::Dict, data) where T <: FunctionTerm{typeof(+), Vector{Term}}
for i in t.args
if isa(i, Term)
if typeof(Tables.getcolumn(data, i.sym)) <: AbstractCategoricalVector || !(typeof(Tables.getcolumn(data, i.sym)) <: AbstractVector{V} where V <: Real)
d[i.sym] = StatsModels.FullDummyCoding()
end
else
fill_coding_dict!(i, d, data)
end
end
d
end
=#
################################################################################
# SHOW
################################################################################
Expand Down
8 changes: 8 additions & 0 deletions test/devtest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ random = Metida.VarEffect(Metida.@covstr(1 + time|subject&factor), Metida.CSH),
)
b11 = @benchmark Metida.fit!($lmm, hes = false; maxthreads = 16) seconds = 15

#=
lmm = Metida.LMM(@formula(response ~1 + factor2), ftdf3;
repeated = Metida.VarEffect(Metida.@covstr(p|subject), Metida.CSH),
)
@benchmark Metida.fit!($lmm, hes = false; maxthreads = 16) seconds = 15
=#
#:LN_BOBYQA :LN_NEWUOA
#@benchmark Metida.fit!($lmm, hes = false; maxthreads = 16, solver = :nlopt, optmethod = :LN_NEWUOA) seconds = 15
#@benchmark Metida.fit!($lmm, optmethod = Metida.LBFGS_OM, hes = false; maxthreads = 16) seconds = 15
#@benchmark Metida.fit!($lmm, optmethod = Metida.BFGS_OM, hes = false; maxthreads = 16) seconds = 15
#@benchmark Metida.fit!($lmm, optmethod = Metida.CG_OM, hes = false; maxthreads = 16) seconds = 15
Expand Down
20 changes: 9 additions & 11 deletions test/test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ include("testdata.jl")
Metida.fit!(lmm)
@test Metida.m2logreml(lmm) 25.00077786912235 atol=1E-6



#Missing
lmm = Metida.LMM(@formula(var~sequence+period+formulation), df0m;
random = Metida.VarEffect(Metida.@covstr(formulation|subject), Metida.DIAG),
Expand All @@ -39,7 +37,6 @@ include("testdata.jl")
@test Metida.m2logreml(lmm) 16.636012616466203 atol=1E-6

#milmm = Metida.MILMM(lmm, df0m)

#Basic, Subject block
lmm = Metida.LMM(@formula(var~sequence+period+formulation), df0;
random = Metida.VarEffect(Metida.@covstr(formulation|subject), Metida.DIAG),
Expand All @@ -56,13 +53,11 @@ include("testdata.jl")
random = formulation|subject:Metida.DIAG), df0)
@test Metida.m2logreml(lmm) 16.241112644506067 atol=1E-6


t3table = Metida.typeiii(lmm; ddf = :contain) # NOT VALIDATED
t3table = Metida.typeiii(lmm; ddf = :residual)
t3table = Metida.typeiii(lmm)

############################################################################

############################################################################
# API test
############################################################################
Expand Down Expand Up @@ -99,6 +94,7 @@ include("testdata.jl")
@test sum(Metida.hessian(lmm)) 1118.160713481362 atol=1E-2
@test Metida.nblocks(lmm) == 5
@test length(coefnames(lmm)) == 6
@test Metida.gmatrixipd(lmm)
@test Metida.confint(lmm)[end][1] -0.7630380758015894 atol=1E-4
@test Metida.confint(lmm, 6)[1] -0.7630380758015894 atol=1E-4
@test Metida.confint(lmm; ddf = :residual)[end][1] -0.6740837049617738 atol=1E-4
Expand Down Expand Up @@ -173,7 +169,6 @@ include("testdata.jl")
@test tt.ndf[2] 3.0 atol=1E-5
@test tt.df[2] 3.39086 atol=1E-5
@test tt.pval[2] 0.900636 atol=1E-5

end
################################################################################
# df0
Expand Down Expand Up @@ -292,7 +287,6 @@ end
df0)
Metida.fit!(lmm; rholinkf = :psigm)
@test Metida.m2logreml(lmm) 10.065239006121315 atol=1E-6

end
################################################################################
# ftdf / 1fptime.csv
Expand Down Expand Up @@ -459,20 +453,23 @@ end
random = Metida.VarEffect(Metida.@covstr(1 + r2 * r1|subject), Metida.DIAG; coding=Dict(:r1 => DummyCoding(), :r2 => DummyCoding()))
)
Metida.fit!(lmm)
@test Metida.theta(lmm) [2.796694409004289, 2.900485570555582, 3.354913215348968, 2.0436114769223237, 1.8477830405766895, 2.0436115732330955, 1.0131934233937254] atol=1E-6 # atol=1E-8 !
@test Metida.m2logreml(lmm) 713.0655862252027 atol=1E-8
end
@testset " Model: &, DIAG/SI " begin
lmm = Metida.LMM(@formula(response ~ 1 + factor), ftdf3;
random = Metida.VarEffect(Metida.@covstr(r1&r2|subject), Metida.DIAG),
)
Metida.fit!(lmm)
@test Metida.theta(lmm) [3.0325005960015985, 3.343826588448401, 1.8477830405766895, 1.8477830405766895, 1.8477830405766895, 4.462942536844632, 1.0082345219318216] atol=1E-8
@test Metida.m2logreml(lmm) 719.9413776641368 atol=1E-8
end
@testset " Model: INT, +, TOEPHP(3)/SI " begin
lmm = Metida.LMM(@formula(response ~ 1 + factor), ftdf3;
random = Metida.VarEffect(Metida.@covstr(1 + r1 + r2|subject), Metida.TOEPHP(3); coding = Dict(:r1 => DummyCoding(), :r2 => DummyCoding())),
)
Metida.fit!(lmm)
@test Metida.theta(lmm) [2.843269324925114, 3.3598654954863423, 7.582560427911907e-10, 4.133572859333964, -0.24881591201506625, 0.46067672264107506, 1.0091887333170306] atol=1E-8
@test Metida.m2logreml(lmm) 705.9946274598822 atol=1E-8
end
@testset " Model: TOEP/SI " begin
Expand Down Expand Up @@ -631,7 +628,6 @@ end
)
Metida.fit!(lmm)
@test Metida.m2logreml(lmm) 8.740095378772942 atol=1E-8

end

@testset " Model: Spatial Exponential " begin
Expand Down Expand Up @@ -662,7 +658,6 @@ end
Metida.rand!(v, lmm)
Metida.rand!(v, lmm, [4.54797, 2.82342, 1.05771, 0.576979])
Metida.rand!(v, lmm, [4.54797, 2.82342, 1.05771, 0.576979], [44.3, 5.3, 0.5, 0.29])

end

@testset " Show functions " begin
Expand Down Expand Up @@ -772,8 +767,9 @@ end
@test iAs iAb atol=1E-6
end


@testset " Experimental " begin


io = IOBuffer();
lmm = Metida.LMM(@formula(r2 ~ f), spatdf;
repeated = Metida.VarEffect(Metida.@covstr(x+y|1), Metida.SPEXP),
Expand Down Expand Up @@ -855,10 +851,12 @@ end

@test_throws ErrorException Metida.milmm(lmm; n = 10, verbose = false, rng = StableRNG(1234))


if !(VERSION < v"1.7")
mb = Metida.miboot(mi; n = 10, bootn = 10, double = true, verbose = false, rng = StableRNG(1234))
Base.show(io, mb)
end

# Other
@test Metida.varlinkvecapply([0.1, 0.1], [:var, :rho]; varlinkf = :exp, rholinkf = :sigm) [1.1051709180756477, 0.004999958333749888] atol=1E-6

end

2 comments on commit 7d9582a

@PharmCat
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/79845

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.14.6 -m "<description of version>" 7d9582a18c2bb8762cf029edbcc78c2eeddd9a07
git push origin v0.14.6

Please sign in to comment.