@@ -11,7 +11,53 @@ import ..Diagnostics: dffits
1111
1212import Distributions: TDist, quantile
1313
14- import LinearAlgebra: det
14+ import LinearAlgebra: ColumnNorm, PosDefException, Symmetric, cholesky, ldiv!, qr
15+
16+ @inline function _rowdot (
17+ X:: AbstractMatrix{Float64} ,
18+ rowindex:: Int ,
19+ betas:: AbstractVector{Float64} ,
20+ ):: Float64
21+ s = 0.0
22+ @inbounds @simd for j in eachindex (betas)
23+ s += X[rowindex, j] * betas[j]
24+ end
25+ return s
26+ end
27+
28+ function _set_membership! (
29+ membership:: AbstractVector{Bool} ,
30+ indices:: AbstractVector{Int} ,
31+ ):: Nothing
32+ fill! (membership, false )
33+ @inbounds for idx in indices
34+ membership[idx] = true
35+ end
36+ return nothing
37+ end
38+
39+ function _gramian! (
40+ target:: AbstractMatrix{Float64} ,
41+ X:: AbstractMatrix{Float64} ,
42+ indices:: AbstractVector{Int} ,
43+ ):: AbstractMatrix{Float64}
44+ fill! (target, 0.0 )
45+ p = size (X, 2 )
46+ @inbounds for idx in indices
47+ for col = 1 : p
48+ xcol = X[idx, col]
49+ for row = 1 : col
50+ target[row, col] += X[idx, row] * xcol
51+ end
52+ end
53+ end
54+ @inbounds for col = 1 : p
55+ for row = (col + 1 ): p
56+ target[row, col] = target[col, row]
57+ end
58+ end
59+ return target
60+ end
1561
1662"""
1763
@@ -112,31 +158,55 @@ function hs93basicsubset(
112158 initialindices:: Array{Int,1} ,
113159):: Array{Int,1}
114160 n, p = size (X)
115- h = floor (( n + p - 1 ) / 2 )
161+ h = fld ( n + p - 1 , 2 )
116162 s = length (initialindices)
117- indices = initialindices
163+ indices = copy (initialindices)
164+ if h > s
165+ resize! (indices, h)
166+ end
118167 betas = zeros (Float64, p)
119168 d = zeros (Float64, n)
120- orderingd = zeros (Int, n)
169+ orderingd = Array {Int} (undef, n)
170+ xtx = Matrix {Float64} (undef, p, p)
171+ insubset = falses (n)
172+ solvework = Vector {Float64} (undef, p)
173+ ymax = maximum (y)
174+
175+ for i in (s + 1 ): h
176+ activeindices = view (indices, 1 : (i - 1 ))
177+ betas .= qr (view (X, activeindices, :), ColumnNorm ()) \ view (y, activeindices)
178+ _set_membership! (insubset, activeindices)
179+ _gramian! (xtx, X, activeindices)
180+
181+ chol = try
182+ cholesky (Symmetric (xtx))
183+ catch err
184+ if err isa PosDefException
185+ nothing
186+ else
187+ rethrow (err)
188+ end
189+ end
121190
122- for i in range (s + 1 , stop = h)
123- betas = X[indices, :] \ y[indices]
124- XM = X[indices, :]
125191 for j = 1 : n
126- if det (XM' XM) > 0
127- xxxx = X[j, :]' * inv (XM' XM) * X[j, :]
128- if j in indices
129- d[j] = abs .(y[j] - sum (X[j, :] .* betas)) / sqrt (abs (1 - xxxx))
192+ if ! isnothing (chol)
193+ @inbounds for k = 1 : p
194+ solvework[k] = X[j, k]
195+ end
196+ ldiv! (chol, solvework)
197+ xxxx = _rowdot (X, j, solvework)
198+ resid = abs (y[j] - _rowdot (X, j, betas))
199+ if insubset[j]
200+ d[j] = resid / sqrt (abs (1 - xxxx))
130201 else
131- d[j] = abs .(y[j] - sum (X[j, :] .* betas)) / sqrt (abs (1 + xxxx))
202+ d[j] = resid / sqrt (abs (1 + xxxx))
132203 end
133204 else
134- # When XM'XM is singular, the corresponding d[j] is set to the maximum of y.
135- d[j] = maximum (y)
205+ d[j] = ymax
136206 end
137207 end
138- orderingd . = sortperm ( abs .(d) )
139- indices = orderingd[ 1 : Int (i)]
208+ sortperm! ( orderingd, d, by = abs)
209+ copyto! ( indices, 1 , orderingd, 1 , i)
140210 end
141211 return indices
142212end
@@ -205,16 +275,41 @@ function hs93(
205275 s = length (indices)
206276 betas = zeros (Float64, p)
207277 d = zeros (Float64, n)
208- orderingd = zeros (Int, n)
278+ orderingd = Array {Int} (undef, n)
279+ if s < n
280+ workingsubset = copy (indices)
281+ resize! (workingsubset, n)
282+ else
283+ workingsubset = copy (indices)
284+ end
285+ indices = workingsubset
286+ xtx = Matrix {Float64} (undef, p, p)
287+ insubset = falses (n)
288+ solvework = Vector {Float64} (undef, p)
209289
210290 while s < n
211- betas .= X[indices, :] \ y[indices]
212- resids = y[indices] - X[indices,:] * betas
213- sigma = sqrt (sum (resids .^ 2.0 ) / (length (resids) - p))
214-
215- XM = X[indices, :]
291+ activeindices = view (indices, 1 : s)
292+ betas .= qr (view (X, activeindices, :), ColumnNorm ()) \ view (y, activeindices)
293+
294+ rss = 0.0
295+ @inbounds for idx in activeindices
296+ resid = y[idx] - _rowdot (X, idx, betas)
297+ rss += resid * resid
298+ end
299+ sigma = sqrt (rss / (s - p))
300+
301+ _gramian! (xtx, X, activeindices)
302+ chol = try
303+ cholesky (Symmetric (xtx))
304+ catch err
305+ if err isa PosDefException
306+ nothing
307+ else
308+ rethrow (err)
309+ end
310+ end
216311
217- if det (XM ' XM) <= 0
312+ if isnothing (chol)
218313 return Dict (
219314 " d" => [],
220315 " t" => [],
@@ -223,25 +318,45 @@ function hs93(
223318 " converged" => false ,
224319 )
225320 end
226-
227- iXmXm = inv (XM' XM)
321+ _set_membership! (insubset, activeindices)
228322 for j = 1 : n
229- xMMx = X[j, :]' * iXmXm * X[j, :]
230- if j in indices
231- d[j] = (y[j] - sum (X[j, :] .* betas)) / (sigma * sqrt (abs (1.0 - xMMx)))
323+ @inbounds for k = 1 : p
324+ solvework[k] = X[j, k]
325+ end
326+ ldiv! (chol, solvework)
327+ xMMx = _rowdot (X, j, solvework)
328+ resid = y[j] - _rowdot (X, j, betas)
329+ if insubset[j]
330+ d[j] = resid / (sigma * sqrt (abs (1.0 - xMMx)))
232331 else
233- d[j] = (y[j] - sum (X[j, :] .* betas)) / (sigma * sqrt (abs (1.0 + xMMx)))
332+ d[j] = resid / (sigma * sqrt (abs (1.0 + xMMx)))
234333 end
235334 end
236- orderingd . = sortperm ( abs .(d) )
335+ sortperm! ( orderingd, d, by = abs)
237336 tdist = TDist (s - p)
238337 tcalc = quantile (tdist, alpha / (2 * (s + 1 )))
239338
240-
241- outlierset = filter (x -> abs (d[x]) > abs (tcalc), 1 : n)
242- inlierset = setdiff (1 : n, outlierset)
243- cleanbetas = X[inlierset, :] \ y[inlierset]
244- if abs (d[orderingd][s+ 1 ]) > abs (tcalc)
339+ if abs (d[orderingd[s + 1 ]]) > abs (tcalc)
340+ outliercount = 0
341+ @inbounds for j = 1 : n
342+ if abs (d[j]) > abs (tcalc)
343+ outliercount += 1
344+ end
345+ end
346+ outlierset = Vector {Int} (undef, outliercount)
347+ inlierset = Vector {Int} (undef, n - outliercount)
348+ outlierpos = 1
349+ inlierpos = 1
350+ @inbounds for j = 1 : n
351+ if abs (d[j]) > abs (tcalc)
352+ outlierset[outlierpos] = j
353+ outlierpos += 1
354+ else
355+ inlierset[inlierpos] = j
356+ inlierpos += 1
357+ end
358+ end
359+ cleanbetas = qr (view (X, inlierset, :), ColumnNorm ()) \ view (y, inlierset)
245360 result = Dict (
246361 " d" => d,
247362 " t" => tcalc,
@@ -252,7 +367,7 @@ function hs93(
252367 return result
253368 end
254369 s += 1
255- indices = orderingd[ 1 : s]
370+ copyto! ( indices, 1 , orderingd, 1 , s)
256371 end
257372
258373 return Dict (
0 commit comments