-
Notifications
You must be signed in to change notification settings - Fork 114
/
linpred.jl
256 lines (218 loc) · 8.31 KB
/
linpred.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
"""
linpred!(out, p::LinPred, f::Real=1.0)
Overwrite `out` with the linear predictor from `p` with factor `f`
The effective coefficient vector, `p.scratchbeta`, is evaluated as `p.beta0 .+ f * p.delbeta`,
and `out` is updated to `p.X * p.scratchbeta`
"""
function linpred!(out, p::LinPred, f::Real=1.)
mul!(out, p.X, iszero(f) ? p.beta0 : broadcast!(muladd, p.scratchbeta, f, p.delbeta, p.beta0))
end
"""
linpred(p::LinPred, f::Read=1.0)
Return the linear predictor `p.X * (p.beta0 .+ f * p.delbeta)`
"""
linpred(p::LinPred, f::Real=1.) = linpred!(Vector{eltype(p.X)}(undef, size(p.X, 1)), p, f)
"""
installbeta!(p::LinPred, f::Real=1.0)
Install `pbeta0 .+= f * p.delbeta` and zero out `p.delbeta`. Return the updated `p.beta0`.
"""
function installbeta!(p::LinPred, f::Real=1.)
beta0 = p.beta0
delbeta = p.delbeta
@inbounds for i = eachindex(beta0,delbeta)
beta0[i] += delbeta[i]*f
delbeta[i] = 0
end
beta0
end
"""
DensePredQR
A `LinPred` type with a dense, unpivoted QR decomposition of `X`
# Members
- `X`: Model matrix of size `n` × `p` with `n ≥ p`. Should be full column rank.
- `beta0`: base coefficient vector of length `p`
- `delbeta`: increment to coefficient vector, also of length `p`
- `scratchbeta`: scratch vector of length `p`, used in [`linpred!`](@ref) method
- `qr`: a `QRCompactWY` object created from `X`, with optional row weights.
"""
mutable struct DensePredQR{T<:BlasReal} <: DensePred
X::Matrix{T} # model matrix
beta0::Vector{T} # base coefficient vector
delbeta::Vector{T} # coefficient increment
scratchbeta::Vector{T}
qr::QRCompactWY{T}
function DensePredQR{T}(X::Matrix{T}, beta0::Vector{T}) where T
n, p = size(X)
length(beta0) == p || throw(DimensionMismatch("length(β0) ≠ size(X,2)"))
new{T}(X, beta0, zeros(T,p), zeros(T,p), qr(X))
end
function DensePredQR{T}(X::Matrix{T}) where T
n, p = size(X)
new{T}(X, zeros(T, p), zeros(T,p), zeros(T,p), qr(X))
end
end
DensePredQR(X::Matrix, beta0::Vector) = DensePredQR{eltype(X)}(X, beta0)
DensePredQR(X::Matrix{T}) where T = DensePredQR{T}(X, zeros(T, size(X,2)))
convert(::Type{DensePredQR{T}}, X::Matrix{T}) where {T} = DensePredQR{T}(X, zeros(T, size(X, 2)))
"""
delbeta!(p::LinPred, r::Vector)
Evaluate and return `p.delbeta` the increment to the coefficient vector from residual `r`
"""
function delbeta! end
function delbeta!(p::DensePredQR{T}, r::Vector{T}) where T<:BlasReal
p.delbeta = p.qr\r
return p
end
"""
DensePredChol{T}
A `LinPred` type with a dense Cholesky factorization of `X'X`
# Members
- `X`: model matrix of size `n` × `p` with `n ≥ p`. Should be full column rank.
- `beta0`: base coefficient vector of length `p`
- `delbeta`: increment to coefficient vector, also of length `p`
- `scratchbeta`: scratch vector of length `p`, used in [`linpred!`](@ref) method
- `chol`: a `Cholesky` object created from `X'X`, possibly using row weights.
- `scratchm1`: scratch Matrix{T} of the same size as `X`
- `scratchm2`: scratch Matrix{T} os the same size as `X'X`
"""
mutable struct DensePredChol{T<:BlasReal,C} <: DensePred
X::Matrix{T} # model matrix
beta0::Vector{T} # base vector for coefficients
delbeta::Vector{T} # coefficient increment
scratchbeta::Vector{T}
chol::C
scratchm1::Matrix{T}
scratchm2::Matrix{T}
end
function DensePredChol(X::StridedMatrix, pivot::Bool)
F = Hermitian(float(X'X))
T = eltype(F)
F = pivot ? cholesky!(F, Val(true), tol = -one(T), check = false) : cholesky!(F)
DensePredChol(AbstractMatrix{T}(X),
zeros(T, size(X, 2)),
zeros(T, size(X, 2)),
zeros(T, size(X, 2)),
F,
similar(X, T),
similar(cholfactors(F)))
end
cholpred(X::StridedMatrix, pivot::Bool=false) = DensePredChol(X, pivot)
cholfactors(c::Union{Cholesky,CholeskyPivoted}) = c.factors
cholesky!(p::DensePredChol{T}) where {T<:FP} = p.chol
cholesky(p::DensePredQR{T}) where {T<:FP} = Cholesky{T,typeof(p.X)}(copy(p.qr.R), 'U', 0)
function cholesky(p::DensePredChol{T}) where T<:FP
c = p.chol
Cholesky(copy(cholfactors(c)), c.uplo, c.info)
end
cholesky!(p::DensePredQR{T}) where {T<:FP} = Cholesky{T,typeof(p.X)}(p.qr.R, 'U', 0)
function delbeta!(p::DensePredChol{T,<:Cholesky}, r::Vector{T}) where T<:BlasReal
ldiv!(p.chol, mul!(p.delbeta, transpose(p.X), r))
p
end
function delbeta!(p::DensePredChol{T,<:CholeskyPivoted}, r::Vector{T}) where T<:BlasReal
ch = p.chol
delbeta = mul!(p.delbeta, adjoint(p.X), r)
rnk = rank(ch)
if rnk == length(delbeta)
ldiv!(ch, delbeta)
else
permute!(delbeta, ch.piv)
for k=(rnk+1):length(delbeta)
delbeta[k] = -zero(T)
end
LAPACK.potrs!(ch.uplo, view(ch.factors, 1:rnk, 1:rnk), view(delbeta, 1:rnk))
invpermute!(delbeta, ch.piv)
end
p
end
function delbeta!(p::DensePredChol{T,<:Cholesky}, r::Vector{T}, wt::Vector{T}) where T<:BlasReal
scr = mul!(p.scratchm1, Diagonal(wt), p.X)
cholesky!(Hermitian(mul!(cholfactors(p.chol), transpose(scr), p.X), :U))
ldiv!(p.chol, mul!(p.delbeta, transpose(scr), r))
p
end
function delbeta!(p::DensePredChol{T,<:CholeskyPivoted}, r::Vector{T}, wt::Vector{T}) where T<:BlasReal
cf = cholfactors(p.chol)
piv = p.chol.piv
cf .= mul!(p.scratchm2, adjoint(LinearAlgebra.mul!(p.scratchm1, Diagonal(wt), p.X)), p.X)[piv, piv]
cholesky!(Hermitian(cf, Symbol(p.chol.uplo)))
ldiv!(p.chol, mul!(p.delbeta, transpose(p.scratchm1), r))
p
end
mutable struct SparsePredChol{T,M<:SparseMatrixCSC,C} <: GLM.LinPred
X::M # model matrix
Xt::M # X'
beta0::Vector{T} # base vector for coefficients
delbeta::Vector{T} # coefficient increment
scratchbeta::Vector{T}
chol::C
scratch::M
end
function SparsePredChol(X::SparseMatrixCSC{T}) where T
chol = cholesky(sparse(I, size(X, 2), size(X,2)))
return SparsePredChol{eltype(X),typeof(X),typeof(chol)}(X,
X',
zeros(T, size(X, 2)),
zeros(T, size(X, 2)),
zeros(T, size(X, 2)),
chol,
similar(X))
end
cholpred(X::SparseMatrixCSC) = SparsePredChol(X)
function delbeta!(p::SparsePredChol{T}, r::Vector{T}, wt::Vector{T}) where T
scr = mul!(p.scratch, Diagonal(wt), p.X)
XtWX = p.Xt*scr
c = p.chol = cholesky(Symmetric{eltype(XtWX),typeof(XtWX)}(XtWX, 'L'))
p.delbeta = c \ mul!(p.delbeta, adjoint(scr), r)
end
LinearAlgebra.cholesky(p::SparsePredChol{T}) where {T} = copy(p.chol)
LinearAlgebra.cholesky!(p::SparsePredChol{T}) where {T} = p.chol
invchol(x::DensePred) = inv(cholesky!(x))
function invchol(x::DensePredChol{T,<: CholeskyPivoted}) where T
ch = x.chol
rnk = rank(ch)
p = length(x.delbeta)
rnk == p && return inv(ch)
fac = ch.factors
res = fill(convert(T, NaN), size(fac))
for j in 1:rnk, i in 1:rnk
res[i, j] = fac[i, j]
end
copytri!(LAPACK.potri!(ch.uplo, view(res, 1:rnk, 1:rnk)), ch.uplo, true)
ipiv = invperm(ch.piv)
res[ipiv, ipiv]
end
invchol(x::SparsePredChol) = cholesky!(x) \ Matrix{Float64}(I, size(x.X, 2), size(x.X, 2))
vcov(x::LinPredModel) = rmul!(invchol(x.pp), dispersion(x, true))
function cor(x::LinPredModel)
Σ = vcov(x)
invstd = inv.(sqrt.(diag(Σ)))
lmul!(Diagonal(invstd), rmul!(Σ, Diagonal(invstd)))
end
stderror(x::LinPredModel) = sqrt.(diag(vcov(x)))
function show(io::IO, obj::LinPredModel)
println(io, "$(typeof(obj)):\n\nCoefficients:\n", coeftable(obj))
end
modelframe(obj::LinPredModel) = obj.fr
modelmatrix(obj::LinPredModel) = obj.pp.X
response(obj::LinPredModel) = obj.rr.y
fitted(m::LinPredModel) = m.rr.mu
predict(mm::LinPredModel) = fitted(mm)
formula(obj::LinPredModel) = modelframe(obj).formula
residuals(obj::LinPredModel) = residuals(obj.rr)
"""
nobs(obj::LinearModel)
nobs(obj::GLM)
For linear and generalized linear models, returns the number of rows, or,
when prior weights are specified, the sum of weights.
"""
function nobs(obj::LinPredModel)
if isempty(obj.rr.wts)
oftype(sum(one(eltype(obj.rr.wts))), length(obj.rr.y))
else
sum(obj.rr.wts)
end
end
coef(x::LinPred) = x.beta0
coef(obj::LinPredModel) = coef(obj.pp)
dof_residual(obj::LinPredModel) = nobs(obj) - dof(obj) + 1