Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make PValue and TestStat behave like Reals #668

Merged
merged 9 commits into from
Mar 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions src/statmodels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ Adjusted pseudo-coefficient of determination (adjusted pseudo R-squared).

For nonlinear models, one of the several pseudo R² definitions must be chosen via `variant`.
The only currently supported variants are `:MacFadden`, defined as ``1 - (\\log (L) - k)/\\log (L0)`` and
`:devianceratio`, defined as ``1 - (D/(n-k))/(D_0/(n-1))``.
`:devianceratio`, defined as ``1 - (D/(n-k))/(D_0/(n-1))``.
In these formulas, ``L`` is the likelihood of the model, ``L0`` that of the null model
(the model including only the intercept), ``D`` is the deviance of the model,
``D_0`` is the deviance of the null model, ``n`` is the number of observations (given by [`nobs`](@ref)) and
Expand Down Expand Up @@ -319,7 +319,7 @@ response(model::RegressionModel) = error("response is not defined for $(typeof(m

"""
responsename(model::RegressionModel)

Return the name of the model response (a.k.a. the dependent variable).
"""
responsename(model::RegressionModel) = error("responsename is not defined for $(typeof(model)).")
Expand Down Expand Up @@ -451,7 +451,7 @@ end
Show a p-value using 6 characters, either using the standard 0.XXXX
representation or as <Xe-YY.
"""
struct PValue
struct PValue <: Real
v::Real
function PValue(v::Real)
0 <= v <= 1 || isnan(v) || error("p-values must be in [0; 1]")
Expand All @@ -477,6 +477,29 @@ struct TestStat <: Real
end

show(io::IO, x::TestStat) = @printf(io, "%.2f", x.v)
TestStat(x::TestStat) = x

float(x::Union{TestStat, PValue}) = float(x.v)

for op in [:(==), :<, :≤, :>, :≥, :(isless), :(isequal)] # isless and < to place nice with NaN
@eval begin
Base.$op(x::Union{TestStat, PValue}, y::Real) = $op(x.v, y)
Base.$op(y::Real, x::Union{TestStat, PValue}) = $op(y, x.v)
Base.$op(x1::Union{TestStat, PValue}, x2::Union{TestStat, PValue}) = $op(x1.v, x2.v)
end
end

# necessary to avoid a method ambiguity with isless(::TestStat, NaN)
palday marked this conversation as resolved.
Show resolved Hide resolved
Base.isless(x::Union{TestStat, PValue}, y::AbstractFloat) = isless(x.v, y)
Base.isless(y::AbstractFloat, x::Union{TestStat, PValue},) = isless(y, x.v)
Base.isequal(y::AbstractFloat, x::Union{TestStat, PValue}) = isequal(y, x.v)
Base.isequal(x::Union{TestStat, PValue}, y::AbstractFloat) = isequal(x.v, y)


Base.isapprox(x::Union{TestStat, PValue}, y::Real; kwargs...) = isapprox(x.v, y; kwargs...)
Base.isapprox(y::Real, x::Union{TestStat, PValue}; kwargs...) = isapprox(y, x.v; kwargs...)
Base.isapprox(x1::Union{TestStat, PValue}, x2::Union{TestStat, PValue}; kwargs...) = isapprox(x1.v, x2.v; kwargs...)


"""Wrap a string so that show omits quotes"""
struct NoQuote
Expand All @@ -493,7 +516,7 @@ function show(io::IO, ct::CoefTable)
rownms = [lpad("[$i]",floor(Integer, log10(nr))+3) for i in 1:nr]
end
mat = [j == 1 ? NoQuote(rownms[i]) :
j-1 == ct.pvalcol ? PValue(cols[j-1][i]) :
j-1 == ct.pvalcol ? NoQuote(sprint(show, PValue(cols[j-1][i]))) :
j-1 in ct.teststatcol ? TestStat(cols[j-1][i]) :
cols[j-1][i] isa AbstractString ? NoQuote(cols[j-1][i]) : cols[j-1][i]
for i in 1:nr, j in 1:nc+1]
Expand Down
69 changes: 68 additions & 1 deletion test/statmodels.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using StatsBase
using StatsBase: PValue
using StatsBase: PValue, TestStat
using Test, Random

v1 = [1.45666, -23.14, 1.56734e-13]
Expand Down Expand Up @@ -63,6 +63,73 @@ end
@test_throws ErrorException PValue(-0.1)
@test_throws ErrorException PValue(1.1)
@test PValue(PValue(0.05)) === PValue(0.05)
@test isless(PValue(0.01), 0.05)
@test isless(PValue(0.01), NaN) == isless(0.01, NaN)
@test (PValue(0.01) < NaN) == (0.01 < NaN)
@test isless(NaN, PValue(0.01)) == isless(NaN, 0.01)
@test (NaN < PValue(0.01)) == (NaN < 0.01)
@test isequal(NaN, PValue(0.01)) == isequal(NaN, 0.01)
@test (NaN == PValue(0.01)) == (NaN == 0.01)
@test isequal(PValue(0.01), NaN) == isequal(0.01, NaN)
palday marked this conversation as resolved.
Show resolved Hide resolved
@test (PValue(0.01) == NaN) == (0.01 == NaN)
@test isequal(PValue(0.05), 0.05)
@test isapprox(PValue(0.05), 0.05)
@test PValue(0.05) <= 0.05
@test PValue(0.1) > 0.05
@test PValue(0.1) >= PValue(0.05)
@test PValue(0.05) <= PValue(0.05)
@test PValue(0.1) > PValue(0.05)
@test PValue(0.1) >= PValue(0.05)
@test 0.1 >= PValue(0.05)
@test 0.05 <= PValue(0.05)
@test 0.1 > PValue(0.05)
@test 0.1 >= PValue(0.05)
# exact equality should hold here since it's the exact same atomic operations
@test float(PValue(Rational(1,3))) == float(1/3)
@test PValue(Rational(1,3)) == Rational(1,3)
@test PValue(Rational(1,3)) ≈ 1/3
@test PValue(Rational(1,3)) == PValue(Rational(1,3))
@test PValue(Rational(1,3)) ≈ PValue(1/3)
@test Rational(1,3) == PValue(Rational(1,3))
@test Rational(1,3) ≈ PValue(1/3) atol=0.01
@test PValue(Rational(1,3)) isa Real

@test sprint(show, TestStat(1e-1)) == "0.10"
@test sprint(show, TestStat(1e-5)) == "0.00"
@test sprint(show, TestStat(π)) == "3.14"
@test TestStat(TestStat(0.05)) === TestStat(0.05)
@test isless(TestStat(0.01), 0.05)
@test isless(TestStat(0.01), NaN) == isless(0.01, NaN)
@test (TestStat(0.01) < NaN) == (0.01 < NaN)
@test isless(NaN, TestStat(0.01)) == isless(NaN, 0.01)
@test (NaN < TestStat(0.01)) == (NaN < 0.01)
@test isequal(TestStat(0.01), NaN) == isequal(0.01, NaN)
@test (TestStat(0.01) == NaN) == (0.01 == NaN)
@test isequal(NaN, TestStat(0.01)) == isequal(NaN, 0.01)
@test (NaN == TestStat(0.01)) == (NaN == 0.01)
@test isequal(TestStat(0.05), 0.05)

@test isapprox(TestStat(0.05), 0.05)
@test TestStat(0.05) <= 0.05
@test TestStat(0.1) > 0.05
@test TestStat(0.1) >= TestStat(0.05)
@test TestStat(0.05) <= TestStat(0.05)
@test TestStat(0.1) > TestStat(0.05)
@test TestStat(0.1) >= TestStat(0.05)
@test 0.1 >= TestStat(0.05)
@test 0.05 <= TestStat(0.05)
@test 0.1 > TestStat(0.05)
@test 0.1 >= TestStat(0.05)
# exact equality should hold here since it's the exact same atomic operations
@test float(TestStat(Rational(1,3))) == float(1/3)
@test TestStat(Rational(1,3)) == Rational(1,3)
@test TestStat(Rational(1,3)) ≈ 1/3
@test TestStat(Rational(1,3)) == TestStat(Rational(1,3))
@test TestStat(Rational(1,3)) ≈ TestStat(1/3)
@test Rational(1,3) == TestStat(Rational(1,3))
@test Rational(1,3) ≈ TestStat(1/3)
@test TestStat(Rational(1,3)) isa Real
@test TestStat(π) ≈ 3.14 atol=0.01

@test sprint(showerror, ConvergenceException(10)) == "failure to converge after 10 iterations."

Expand Down