diff --git a/docs/src/time_series.md b/docs/src/time_series.md index e3f8e96f..1b2c32b6 100644 --- a/docs/src/time_series.md +++ b/docs/src/time_series.md @@ -27,4 +27,9 @@ ADFTest ## Diebold-Mariano test ```@docs DieboldMarianoTest +``` + +## White test +```@docs +WhiteTest ``` \ No newline at end of file diff --git a/src/HypothesisTests.jl b/src/HypothesisTests.jl index 4a5cf229..c1b56603 100644 --- a/src/HypothesisTests.jl +++ b/src/HypothesisTests.jl @@ -191,5 +191,6 @@ include("wald_wolfowitz.jl") include("f.jl") include("correlation.jl") include("diebold_mariano.jl") +include("white.jl") end diff --git a/src/white.jl b/src/white.jl new file mode 100644 index 00000000..6dcfbf5c --- /dev/null +++ b/src/white.jl @@ -0,0 +1,92 @@ +# white.jl +# White and Breusch-Pagan tests for heteroskedasticity +# +# Copyright (C) 2020 Paul Söderlind +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +export WhiteTest + +struct WhiteTest <: HypothesisTest + dof::Int #degrees of freedom in test + LM::Float64 #test statistic, distributed as Chisq(dof) +end + +""" + WhiteTest(X, e, TestType) + +Compute White's (or Breusch-Pagan's) test for heteroskedasticity. + +`X` is the matrix of regressors from the original model and `e` the vector of residuals. +`TestType` is either `:Linear` for the Breusch-Pagan test, `:LinearAndSquares` for +White's test with linear and squared terms only (no cross-products), or `:White` (the default) +for the full White's test (linear, squared and cross-product terms). `X` should include a +constant. The rest statistic is T*R2 where R2 is from the regression of `e^2` on the terms +mentioned above. Under the null hypothesis it is distributed as `Chisquare(dof)` +where `dof` is the number of independent terms (not counting the constant). + +Implements: [`pvalue`](@ref) + +# References + * H. White, (1980): A heteroskedasticity-consistent covariance matrix estimator and a direct test for heteroskedasticity, Econometrica, 48, 817-838. + * T.S. Breusch & A.R. Pagan (1979), A simple test for heteroscedasticity and random coefficient variation, Econometrica, 47, 1287-1294 + +# External links + * [White's test on Wikipedia](https://en.wikipedia.org/wiki/White_test) +""" +function WhiteTest(X::AbstractVecOrMat{<:Real}, e::AbstractVector{<:Real}, TestType=:White) + (n,K) = (size(X,1),size(X,2)) #nobs,nvars + + n == length(e) || throw(DimensionMismatch("inputs must have the same length")) + + if TestType == :Linear + z = X + elseif TestType == :LinearAndSquares + z = [X X.^2] + else #linear, squares and cross-products + z = fill(NaN,n,round(Int,K*(K+1)/2)) #loop is easier and quicker than index tricks + vv = 1 + for i = 1:K, j = i:K + z[:,vv] = X[:,i].*X[:,j] #eg. x1*x1, x1*x2, x2*x2 + vv = vv + 1 + end + end + + dof = rank(z) - 1 #number of independent regressors in z + e2 = e.^2 + b = z\e2 + res = e2 - z*b + R2 = 1 - var(res)/var(e2) + LM = n*R2 #n*R2 in original papers, could also use n*R2/(1-R2) + return WhiteTest(dof, LM) +end + +testname(t::WhiteTest) = "White's test for heteroskedasticity" +population_param_of_interest(t::WhiteTest) = ("T*R2", 0, t.LM) +default_tail(test::WhiteTest) = :right + +function show_params(io::IO, t::WhiteTest, ident) + println(io, ident, "T*R^2 statistic: ", t.LM) + println(io, ident, "degrees of freedom: ", dof(t)) +end + +StatsBase.dof(t::WhiteTest) = t.dof +pvalue(t::WhiteTest) = pvalue(Chisq(t.dof), t.LM, tail=:right) + diff --git a/test/white.jl b/test/white.jl new file mode 100644 index 00000000..8d484221 --- /dev/null +++ b/test/white.jl @@ -0,0 +1,221 @@ +using HypothesisTests, Test + +@testset "White tests" begin + +X = [ 4.18 0.77 1.00; + -3.41 0.73 1.00; + 5.75 0.81 1.00; + 0.05 0.80 1.00; + -2.18 0.82 1.00; + 3.88 0.81 1.00; + 0.73 0.77 1.00; + 5.70 0.77 1.00; + -0.69 0.83 1.00; + -8.14 0.87 1.00; + 5.37 0.99 1.00; + 1.87 0.95 1.00; + 5.76 0.80 1.00; + -0.79 0.89 1.00; + -13.23 1.21 1.00; + 3.97 1.26 1.00; + 5.20 0.81 1.00; + 3.16 0.61 1.00; + 6.41 0.53 1.00; + 1.72 0.64 1.00; + 2.20 0.75 1.00; + 1.06 0.95 1.00; + 9.54 0.96 1.00; + -4.75 1.31 1.00; + -5.05 1.04 1.00; + 0.48 1.07 1.00; + 3.41 1.21 1.00; + -2.21 1.08 1.00; + 0.21 1.15 1.00; + -2.37 1.35 1.00; + -1.55 1.24 1.00; + -6.91 1.28 1.00; + -7.62 1.24 1.00; + 4.81 1.21 1.00; + 3.51 1.07 1.00; + -3.68 0.87 1.00; + -3.42 0.80 1.00; + -6.03 0.92 1.00; + -1.99 0.98 1.00; + 3.20 1.13 1.00; + -3.88 1.06 1.00; + -3.35 0.96 1.00; + -3.10 1.05 1.00; + 11.14 0.76 1.00; + 1.17 0.51 1.00; + 11.27 0.59 1.00; + 4.56 0.63 1.00; + 0.78 0.67 1.00; + 3.50 0.69 1.00; + 2.40 0.62 1.00; + 2.84 0.63 1.00; + 6.71 0.71 1.00; + 0.63 0.69 1.00; + 3.11 0.67 1.00; + -3.90 0.74 1.00; + -0.41 0.76 1.00; + 0.85 0.76 1.00; + -3.56 0.76 1.00; + 2.26 0.70 1.00; + -1.78 0.73 1.00; + -2.06 0.76 1.00; + -4.62 0.71 1.00; + 0.61 0.73 1.00; + -0.56 0.81 1.00; + -6.01 0.78 1.00; + 1.59 0.75 1.00; + -2.88 0.82 1.00; + 10.44 0.83 1.00; + -0.82 0.86 1.00; + -1.01 1.00 1.00; + -1.80 0.73 1.00; + 1.73 0.64 1.00; + 7.92 0.65 1.00; + 1.11 0.58 1.00; + -0.79 0.62 1.00; + -0.94 0.72 1.00; + 4.92 0.66 1.00; + 1.16 0.55 1.00; + -0.65 0.62 1.00; + -1.03 0.55 1.00; + -4.58 0.60 1.00; + 3.79 0.65 1.00; + 6.31 0.61 1.00; + 3.66 0.65 1.00; + 0.42 0.56 1.00; + 6.72 0.53 1.00; + 4.79 0.60 1.00; + -1.31 0.52 1.00; + 4.59 0.49 1.00; + 0.90 0.52 1.00; + -6.49 0.52 1.00; + 6.16 0.46 1.00; + -8.35 0.45 1.00; + 4.47 0.46 1.00; + 1.12 0.39 1.00; + -3.13 0.49 1.00; + 12.43 0.42 1.00; + 4.36 0.43 1.00; + 1.90 0.47 1.00; + -2.14 0.44 1.00 ] + +e = [ 5.19346; + 2.22296; + 3.82622; + 2.93541; + 0.70453; + 1.98955; + 0.56655; + 1.54306; + 0.57363; + -0.13346; + 1.68565; + 9.24558; + 7.39317; + 2.45484; + -4.83724; + -0.74742; + -0.22633; + 0.51537; + 2.82822; + 9.35943; + 5.38263; + 6.59691; + 1.79080; + 0.54506; + 4.06661; + -4.18808; + 1.69414; + 5.40963; + 2.92762; + -2.67351; + -2.74613; + -3.52752; + -4.27775; + 2.88245; + -8.12566; + 0.08760; + 2.20495; + -0.45509; + -0.06959; + 1.51330; + 1.37878; + -2.33046; + 0.35730; + -11.41189; + -0.48083; + 1.08917; + 2.52333; + 1.73218; + 15.60426; + -0.15961; + -0.22174; + -1.19174; + 13.92022; + 1.16044; + 0.08087; + -5.81547; + -5.14199; + -5.69918; + -0.36585; + -3.70294; + 2.83687; + -1.37631; + -2.55403; + -4.10796; + 1.32092; + -1.64056; + -4.09963; + -4.85728; + -1.45496; + -3.12612; + -4.36648; + -3.23379; + 5.22893; + 5.06730; + -0.63041; + -1.70377; + -5.71335; + -2.76831; + 2.20443; + -0.10174; + -2.36727; + -5.49859; + -4.78093; + -3.36665; + 5.12027; + -3.03180; + -2.52035; + 3.10913; + -3.51388; + -0.70389; + -1.01963; + -8.78989; + 2.95170; + -3.95464; + -4.01258; + -1.92315; + -4.94209; + 5.29138; + 1.28435; + 0.28832 ] + + atol = 1e-4 + w_test = WhiteTest(X, e, :Linear) + @test pvalue(w_test) ≈ 0.1287 atol=atol + + w_test = WhiteTest(X, e, :LinearAndSquares) + @test pvalue(w_test) ≈ 0.2774 atol=atol + + w_test = WhiteTest(X, e) + @test pvalue(w_test) ≈ 0.3458 atol=atol + + show(IOBuffer(), w_test) + + @test_throws DimensionMismatch WhiteTest(rand(3), rand(4)) + +end