Skip to content

Commit

Permalink
Merge pull request #69 from yufongpeng/dev
Browse files Browse the repository at this point in the history
Release 0.8.0
  • Loading branch information
yufongpeng authored Feb 28, 2024
2 parents 683d0a1 + 5f9b8d5 commit e67fce0
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 45 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
*.jl.mem
/Manifest.toml
/docs/build
/jet
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "AnovaBase"
uuid = "946dddda-6a23-4b48-8e70-8e60d9b8d680"
authors = ["Yu-Fong Peng <sciphypar@gmail.com>"]
version = "0.7.5"
version = "0.8.0"

[deps]
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Expand All @@ -16,4 +16,4 @@ Distributions = "0.23, 0.24, 0.25"
Reexport = "0.2, 1"
StatsBase = "0.33, 0.34"
StatsModels = "0.7"
julia = "1.6, 1.7, 1.8, 1.9"
julia = "1.6, 1.7, 1.8, 1.9, 1.10"
62 changes: 41 additions & 21 deletions src/AnovaBase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,27 @@ abstract type AnovaModel{M, N} <: StatisticalModel end
"""
NestedModels{M, N} <: AnovaModel{M, N}
A wrapper of nested models for conducting ANOVA.
A wrapper of nested models of the same types for conducting ANOVA.
* `M` is a type of regression model.
* `N` is the number of models.
# Fields
* `model`: a tuple of models.
# Constructors
NestedModels{M}(model...) where M
NestedModels{M}(model::T) where {M, N, T <: NTuple{N, M}}
NestedModels(model::Vararg{M, N}) where {M, N}
NestedModels(model::NTuple{N, M}) where {M, N}
"""
struct NestedModels{M, N} <: AnovaModel{M, N}
model::Tuple

NestedModels{M}(model::T) where {M, N, T <: NTuple{N, M}} = new{M, N}(model)
model::NTuple{N, M}
end
NestedModels{M}(model...) where M = NestedModels{M}(model)
NestedModels{M}(model::T...) where {M, T <: Tuple} = throw(ArgumentError("Some models in $T are not subtype of $M"))
NestedModels(model::Vararg{M, N}) where {M, N} = NestedModels(model)
NestedModels(model::T) where {T <: Tuple} = throw(ArgumentError("`NestedModels` only accept models of the same type; use `MixedAovModels` instead."))
NestedModels(model...) = throw(ArgumentError("`NestedModels` only accept models of the same type; use `MixedAovModels` instead."))
"""
MixedAovModels{M, N} <: AnovaModel{M, N}
A wrapper of nested models with multiple types for conducting ANOVA.
A wrapper of nested models of multiple types for conducting ANOVA.
* `M` is a union type of regression models.
* `N` is the number of models.
Expand All @@ -94,18 +93,33 @@ A wrapper of nested models with multiple types for conducting ANOVA.
struct MixedAovModels{M, N} <: AnovaModel{M, N}
model::Tuple
end
MixedAovModels{M}(model...) where M = MixedAovModels{M}(model)
MixedAovModels{M}(model::T) where {M, T <: Tuple} = all(m -> isa(m, M), model) ? MixedAovModels{M, length(model)}(model) : throw(ArgumentError("Some models in are not subtype of $M"))
MixedAovModels(model...) = MixedAovModels{Union{typeof.(model)...}, length(model)}(model)
MixedAovModels(model::Vararg{M, N}) where {M, N} = throw(ArgumentError("`MixedAovModels` only accept models of different types; use `NestedModels` instead."))
MixedAovModels(model::T) where {T <: Tuple} = MixedAovModels{Union{T.parameters...}, length(model)}(model)
MixedAovModels(model::NTuple{N, M}) where {M, N} = throw(ArgumentError("`MixedAovModels` only accept models of different types; use `NestedModels` instead."))
"""
const MultiAovModels{M, N} = Union{NestedModels{M, N}, MixedAovModels{M, N}} where {M, N}
Wrappers of mutiple models.
"""
const MultiAovModels{M, N} = Union{NestedModels{M, N}, MixedAovModels{M, N}} where {M, N}
"""
MultiAovModels(model::NTuple{N, M}) where {M, N} -> NestedModels{M, N}
MultiAovModels(model::Vararg{M, N}) where {M, N} -> NestedModels{M, N}
MultiAovModels(model::T) where {T <: Tuple} -> MixedAovModels
MultiAovModels(model...) -> MixedAovModels
Construct `NestedModels` or `MixedAovModels` based on model types.
"""
MultiAovModels(model::NTuple{N, M}) where {M, N} = NestedModels(model)
MultiAovModels(model::Vararg{M, N}) where {M, N} = NestedModels(model)
MultiAovModels(model::T) where {T <: Tuple} = MixedAovModels(model)
MultiAovModels(model...) = MixedAovModels(model)

"""
FullModel{M, N} <: AnovaModel{M, N}
A wrapper of full model for conducting ANOVA.
A wrapper of a regression model for conducting ANOVA.
* `M` is a type of regression model.
* `N` is the number of predictors.
Expand All @@ -130,7 +144,7 @@ end
function FullModel(model::RegressionModel, type::Int, null::Bool, test_intercept::Bool)
err1 = ArgumentError("Invalid set of model specification for ANOVA; not enough variables provided.")
#err2 = ArgumentError("Invalid set of model specification for ANOVA; all coefficents are aliased with 1.")
preds = predictors(model)
preds = predictors(model)::TupleTerm
pred_id = collect(eachindex(preds))
hasintercept(preds) || popfirst!(pred_id)
isempty(pred_id) && throw(err1) # ~ 0
Expand Down Expand Up @@ -174,12 +188,15 @@ Returned object of `anova`.
* `otherstat`: `NamedTuple` contained extra statistics.
# Constructor
AnovaResult{T}(anovamodel::M,
AnovaResult(
anovamodel::M,
::Type{T},
dof::NTuple{N, Int},
deviance::NTuple{N, Float64},
teststat::NTuple{N, Float64},
pval::NTuple{N, Float64},
otherstat::NamedTuple) where {N, M <: AnovaModel{<: RegressionModel, N}, T <: GoodnessOfFit}
otherstat::NamedTuple
) where {N, M <: AnovaModel{<: RegressionModel, N}, T <: GoodnessOfFit}
"""
struct AnovaResult{M, T, N}
anovamodel::M
Expand All @@ -190,12 +207,15 @@ struct AnovaResult{M, T, N}
otherstat::NamedTuple
end

AnovaResult{T}(anovamodel::M,
dof::NTuple{N, Int},
deviance::NTuple{N, Float64},
teststat::NTuple{N, Float64},
pval::NTuple{N, Float64},
otherstat::NamedTuple) where {N, M <: AnovaModel{<: RegressionModel, N}, T <: GoodnessOfFit} =
AnovaResult(
anovamodel::M,
::Type{T},
dof::NTuple{N, Int},
deviance::NTuple{N, Float64},
teststat::NTuple{N, Float64},
pval::NTuple{N, Float64},
otherstat::NamedTuple
) where {N, M <: AnovaModel{<: RegressionModel, N}, T <: GoodnessOfFit} =
AnovaResult{M, T, N}(anovamodel, dof, deviance, teststat, pval, otherstat)

function_arg_error(fn, type) = ErrorException("Arguments are valid for $fn; however, no method match $fn(::$type)")
Expand Down
4 changes: 2 additions & 2 deletions src/fit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function ftest_nested(models::MultiAovModels{M, N}, df, dfr, dev, σ²) where {M
pval = map(zip(Δdf, dfr[2:end], fstat)) do (dof, dofr, fs)
fs > 0 ? ccdf(FDist(dof, dofr), fs) : NaN
end
AnovaResult{FTest}(models, df, dev, (NaN, fstat...), (NaN, pval...), NamedTuple())
AnovaResult(models, FTest, df, dev, (NaN, fstat...), (NaN, pval...), NamedTuple())
end

"""
Expand All @@ -45,7 +45,7 @@ function lrt_nested(models::MultiAovModels{M, N}, df, dev, σ²) where {M <: Reg
pval = map(zip(Δdf, lrstat)) do (dof, lr)
lr > 0 ? ccdf(Chisq(dof), lr) : NaN
end
AnovaResult{LRT}(models, df, dev, (NaN, lrstat...), (NaN, pval...), NamedTuple())
AnovaResult(models, LRT, df, dev, (NaN, lrstat...), (NaN, pval...), NamedTuple())
end

# Calculate dof from assign
Expand Down
6 changes: 3 additions & 3 deletions src/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Degrees of freedom of residuals.
By default, it applies `dof_residual` to models in `aov.anovamodel`.
"""
dof_residual(aov::AnovaResult{M, T, N}) where {M, T, N} = ntuple(x -> dof_residual(aov.anovamodel.model), N)
dof_residual(aov::AnovaResult{<: MultiAovModels}) = dof_residual.(aov.anovamodel.model)
dof_residual(aov::AnovaResult{<: MultiAovModels}) = map(dof_residual, aov.anovamodel.model)

"""
predictors(model::RegressionModel)
Expand All @@ -72,9 +72,9 @@ Return a table with coefficients and related statistics of ANOVA.
When displaying `aov` in repl, `rownames` will be `prednames(aov)` for [`FullModel`](@ref) and `string.(1:N)` for [`MultiAovModels`](@ref).
For `MultiAovModels`, there are two default methods for `FTest` and `LRT`; one can also define new methods dispatching on `::NestedModels{M}` or `::NestedModels{M}` where `M` is a model type.
For `MultiAovModels`, there are two default methods for `FTest` and `LRT`; one can also define new methods dispatching on `::AnovaResult{NestedModels{M}}` or `::AnovaResult{MixedAovModels{M}}` where `M` is a model type.
For a `FullModel`, no default api is implemented.
For `FullModel`, no default api is implemented.
The returned `AnovaTable` object implements the [`Tables.jl`](https://github.com/JuliaData/Tables.jl/) interface, and can be
converted e.g. to a DataFrame via `using DataFrames; DataFrame(anovatable(aov))`.
Expand Down
7 changes: 1 addition & 6 deletions src/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ function show(io::IO, anovamodel::FullModel)
println(io)
println(io, "Coefficients:")
show(io, coeftable(anovamodel.model))
println(io, "\n")
end

function show(io::IO, anovamodel::NestedModels{M, N}) where {M, N}
Expand All @@ -60,7 +59,6 @@ function show(io::IO, anovamodel::NestedModels{M, N}) where {M, N}
println(io)
N > 2 && print(io, " .\n" ^ 3)
show(io, coeftable(last(anovamodel.model)))
println(io, "\n")
end

function show(io::IO, anovamodel::MixedAovModels{M, N}) where {M, N}
Expand All @@ -76,7 +74,6 @@ function show(io::IO, anovamodel::MixedAovModels{M, N}) where {M, N}
println(io)
N > 2 && print(io, " .\n" ^ 3)
show(io, coeftable(last(anovamodel.model)))
println(io, "\n")
end

# Show function that delegates to anovatable
Expand All @@ -90,7 +87,6 @@ function show(io::IO, aov::AnovaResult{<: FullModel, T}) where {T <: GoodnessOfF
println(io)
println(io, "Table:")
show(io, at)
println(io, "\n")
end

function show(io::IO, aov::AnovaResult{<: MultiAovModels, T}) where {T <: GoodnessOfFit}
Expand All @@ -105,7 +101,6 @@ function show(io::IO, aov::AnovaResult{<: MultiAovModels, T}) where {T <: Goodne
println(io)
println(io, "Table:")
show(io, at)
println(io, "\n")
end
# ============================================================================================================================
# AnovaTable, mostly from CoefTable
Expand Down Expand Up @@ -134,7 +129,7 @@ mutable struct AnovaTable
function AnovaTable(cols::Vector, colnms::Vector, rownms::Vector,
pvalcol::Int = 0, teststatcol::Int = 0)
nc = length(cols)
nrs = map(length, cols)
nrs = map(length, cols)::Vector{Int}
nr = nrs[1]
length(colnms) in [0, nc] || throw(ArgumentError("colnms should have length 0 or $nc"))
length(rownms) in [0, nr] || throw(ArgumentError("rownms should have length 0 or $nr"))
Expand Down
30 changes: 19 additions & 11 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,17 @@ anovatable(::AnovaResult{<: FullModel{StatsModels.TableRegressionModel{Int64, Ma
@test @test_error ArgumentError FullModel(model4, 3, true, true)
@test FullModel(model5, 3, false, true).pred_id == ntuple(identity, 4)[2:4]
@test FullModel(model6, 3, false, true).pred_id == ntuple(identity, 4)
@test @test_error ArgumentError NestedModels{Int}(1.5, 2.5, 3)
@test @test_error ArgumentError NestedModels{Int}((1.5, 2.5, 3))
@test @test_error !(MixedAovModels{Number}(1, 2.5, 2//3))
@test @test_error ArgumentError MixedAovModels{Int}((1, 2.5, 2//3))
@test @test_error ArgumentError NestedModels(1.5, 2.5, 3)
@test @test_error ArgumentError NestedModels((1.5, 2.5, 3))
@test @test_error !(MixedAovModels(1, 2.5, 2//3))
@test @test_error ArgumentError MixedAovModels((1, 2, 1))
@test @test_error ArgumentError MixedAovModels(1, 2, 1)
@test MultiAovModels(1, 2, 1) isa NestedModels{Int, 3}
@test MultiAovModels((1, 2, 1)) isa NestedModels{Int, 3}
@test MultiAovModels(1, 2.5, 1) isa MixedAovModels{Union{Int, Float64}, 3}
@test MultiAovModels((1, 2.5, 1)) isa MixedAovModels{Union{Int, Float64}, 3}
end
global ft = AnovaResult{FTest}(NestedModels{StatsModels.TableRegressionModel}(
global ft = AnovaResult(NestedModels(
ntuple(7) do i
StatsModels.TableRegressionModel(
1,
Expand All @@ -134,18 +139,20 @@ anovatable(::AnovaResult{<: FullModel{StatsModels.TableRegressionModel{Int64, Ma
mm)
end
),
FTest,
ntuple(identity, 7),
ntuple(one float, 7),
ntuple(one float, 7),
ntuple(zero float, 7),
NamedTuple())
global lrt = AnovaResult{LRT}(FullModel(model1, ntuple(identity, 8)[2:8], 3),
global lrt = AnovaResult(FullModel(model1, ntuple(identity, 8)[2:8], 3),
LRT,
ntuple(identity, 7),
ntuple(one float, 7),
ntuple(one float, 7),
ntuple(zero float, 7),
NamedTuple())
global lrt2 = AnovaResult{LRT}(NestedModels{StatsModels.TableRegressionModel}(
global lrt2 = AnovaResult(NestedModels(
ntuple(3) do i
StatsModels.TableRegressionModel(
1,
Expand All @@ -155,6 +162,7 @@ anovatable(::AnovaResult{<: FullModel{StatsModels.TableRegressionModel{Int64, Ma
mm)
end
),
LRT,
ntuple(identity, 3),
ntuple(one float, 3),
ntuple(one float, 3),
Expand Down Expand Up @@ -182,8 +190,8 @@ anovatable(::AnovaResult{<: FullModel{StatsModels.TableRegressionModel{Int64, Ma
@test AnovaBase._diffn((1, 2, 3)) == (-1, -1)
@test canonicalgoodnessoffit(Gamma()) == FTest
@test canonicalgoodnessoffit(Binomial()) == LRT
@test AnovaBase.lrt_nested(NestedModels{StatsModels.TableRegressionModel}(model1, model1), (1,2), (1.5, 1.5), 0.1).teststat[2] == 0.0
@test AnovaBase.ftest_nested(NestedModels{StatsModels.TableRegressionModel}((model1, model1)), (1,2), (10, 10), (1.5, 1.5), 0.1).teststat[2] == 0.0
@test AnovaBase.lrt_nested(NestedModels(model1, model1), (1,2), (1.5, 1.5), 0.1).teststat[2] == 0.0
@test AnovaBase.ftest_nested(NestedModels((model1, model1)), (1,2), (10, 10), (1.5, 1.5), 0.1).teststat[2] == 0.0
@test dof_asgn([1, 2, 2, 2, 3]) == [1, 3, 1]
end
global f = FormulaTerm(conterm, MatrixTerm((InterceptTerm{true}(), caterm(), fterm, InteractionTerm((caterm(), fterm)))))
Expand Down Expand Up @@ -220,15 +228,15 @@ anovatable(::AnovaResult{<: FullModel{StatsModels.TableRegressionModel{Int64, Ma
NamedTuple())
)
@test @test_error ErrorException anovatable(AnovaResult{NestedModels{Int, 7}, TestTest, 7}(
NestedModels{Int}(ntuple(identity, 7)),
NestedModels(ntuple(identity, 7)),
ntuple(identity, 7),
ntuple(one float, 7),
ntuple(one float, 7),
ntuple(zero float, 7),
NamedTuple())
)
@test @test_error ErrorException anovatable(AnovaResult{MixedAovModels{Number, 7}, TestTest, 7}(
MixedAovModels{Number}(ntuple(identity, 7)),
MixedAovModels{Number, 7}(ntuple(identity, 7)),
ntuple(identity, 7),
ntuple(one float, 7),
ntuple(one float, 7),
Expand Down

2 comments on commit e67fce0

@yufongpeng
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/101917

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

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.8.0 -m "<description of version>" e67fce080f69515a8d5ad4f64665121e0405a996
git push origin v0.8.0

Please sign in to comment.