Skip to content

Commit a83d1e7

Browse files
authored
Merge pull request #39 from GregFa/main
- added and fixed contrasts testing set - added calc_sigma_test - updated calc_sigma_test - updated ci julia lts 1.6 -> 1.10.10 - fixed typo in calc_sigma_test
2 parents 34515ac + 39e538b commit a83d1e7

10 files changed

Lines changed: 516 additions & 92 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
fail-fast: false
1515
matrix:
1616
version:
17-
- '1.6'
17+
- '1.10.10'
1818
- '1' # automatically expands to the latest stable 1.x release of Julia
1919
- 'nightly'
2020
os:

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "MatrixLM"
22
uuid = "37290134-6146-11e9-0c71-a5c489be1f53"
33
authors = ["Jane Liang, Gregory Farage, Chenhao Zhao, Saunak Sen"]
4-
version = "0.3.0"
4+
version = "0.3.1"
55

66
[deps]
77
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"

src/contr.jl

Lines changed: 63 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,87 @@
11
"""
22
get_dummy(df::DataFrames.DataFrame, cVar::Symbol,
3-
cType::String, trtRef::Nothing)
3+
cType::String, trtRef::Union{Nothing,String}=nothing)
44
5-
Convert categorical variable to dummy indicators using specified contrast
6-
type. This covers all cases except for treatment contrasts with a specified
7-
reference level.
5+
Convert a categorical variable to dummy indicators using the specified
6+
contrast type.
87
9-
# Arguments
8+
# Arguments
109
11-
- `df::DataFrames.DataFrame`: DataFrame of variables
12-
- `cVar::Symbol`: symbol for the categorical variable in df to be converted
13-
- `cType::String`: character string indicating the type of contrast to use for `cVar`
14-
- `trtRef::Nothing`: nothing
10+
- `df::DataFrames.DataFrame`: DataFrame containing the variables.
11+
- `cVar::Symbol`: column name in `df` for the categorical variable.
12+
- `cType::String`: contrast type to use. Supported values are
13+
"treat", "sum", "noint", and "sumnoint".
14+
- `trtRef::Union{Nothing,String}`: optional reference level for "treat"
15+
or "sum" contrasts. When omitted (`nothing` or an empty string),
16+
the reference defaults to the last sorted level for "treat" contrasts
17+
and the first sorted level for "sum" contrasts.
1518
1619
# Value
1720
18-
DataFrame of dummy variables for the specified categorical variable
21+
DataFrame containing the dummy variables for the specified categorical
22+
variable.
1923
2024
"""
21-
function get_dummy(df::DataFrames.DataFrame, cVar::Symbol, cType::String,
22-
trtRef::Nothing)
23-
# Obtain the levels to use for the dummy indicators, depending on
24-
# contrast type
25-
thisVar = string.(df[:,cVar])
26-
if cType=="treat"
27-
levs = unique(thisVar)[2:end]
28-
elseif (cType=="sum")
29-
levs = unique(thisVar)[(2:end).-1]
30-
elseif cType=="noint" || (cType=="sumnoint")
31-
levs = unique(thisVar)
32-
else
33-
error(string("Did not recognize contrast type for ", cVar))
34-
end
35-
36-
# Iterate through levels to make dummy indicators
37-
namedict = Dict(zip(levs, 1:length(levs)))
38-
dummies = zeros(length(thisVar), length(namedict))
39-
for i=1:length(thisVar)
40-
if haskey(namedict, thisVar[i])
41-
dummies[i, namedict[thisVar[i]]] = 1
42-
end
43-
end
44-
45-
# Some additional modifications for sum contrasts
46-
if (cType=="sum")
47-
dummies[thisVar.==(unique(thisVar)[end]),:] .= -1
48-
end
49-
if cType=="sumnoint"
50-
dummies = dummies - (1/length(unique(thisVar)))
51-
end
52-
53-
# Convert results to a DataFrame and rename columns
54-
# newDf = convert(DataFrame, dummies)
55-
newDf = DataFrame(dummies ,:auto)
56-
rename!(newDf, [Symbol("$(cVar)_$k") for k in levs])
57-
return newDf
58-
end
59-
60-
61-
"""
62-
get_dummy(df::DataFrames.DataFrame, cVar::Symbol,
63-
cType::String, trtRef::String)
25+
function get_dummy(df::DataFrames.DataFrame, cVar::Symbol, cType::String,
26+
trtRef::Union{Nothing,String}=nothing)
6427

65-
Convert categorical variables to for treatment contrasts with a specified
66-
reference level.
28+
# Convert levels to strings and compute sorted unique values once
29+
thisVar = string.(df[:, cVar])
30+
sortedLevs = sort(unique(thisVar))
6731

68-
# Arguments
69-
70-
- `df::DataFrames.DataFrame`: DataFrame of variables
71-
- `cVar::Symbol`: symbol for the categorical variable in df to be converted
72-
- `cType::String`: character string indicating the type of contrast to use for `cVar`
73-
- `trtRef::String`: character string specifying the level in cVar to use as the reference
32+
isempty(sortedLevs) && return DataFrame()
7433

75-
# Value
34+
# Normalise the optional reference argument
35+
refProvided = !(trtRef === nothing || (isa(trtRef, String) && isempty(trtRef)))
36+
reference = nothing
7637

77-
DataFrame of dummy variables for the specified categorical variable
38+
if cType == "treat" || cType == "sum"
39+
if refProvided
40+
reference = trtRef::String
41+
if !(reference in sortedLevs)
42+
error("Reference level '" * reference * "' not found in variable " * string(cVar) *
43+
". Available levels: " * join(sortedLevs, ", "))
44+
end
45+
else
46+
reference = cType == "treat" ? sortedLevs[1] : sortedLevs[end]
47+
end
48+
elseif refProvided
49+
error("A reference level can only be provided for \"treat\" or \"sum\" contrasts.")
50+
end
7851

79-
"""
80-
function get_dummy(df::DataFrames.DataFrame, cVar::Symbol, cType::String,
81-
trtRef::String)
82-
83-
# Obtain the levels to use for the dummy indicators.
84-
thisVar = string.(df[:,cVar])
85-
if cType=="treat"
86-
levs = unique(thisVar)[unique(thisVar) .!= trtRef]
52+
# Determine levels used to create indicator columns
53+
levs = if cType == "treat"
54+
filter(x -> x != reference, sortedLevs)
55+
elseif cType == "sum"
56+
filter(x -> x != reference, sortedLevs)
57+
elseif cType == "noint" || cType == "sumnoint"
58+
sortedLevs
8759
else
88-
error("Can only specify trtRef for treatment contrasts.")
60+
error("Did not recognize contrast type '" * cType * "' for " * string(cVar) *
61+
". Valid options: \"treat\", \"sum\", \"noint\", \"sumnoint\"")
8962
end
90-
91-
# Iterate through levels to make dummy indicators
63+
9264
namedict = Dict(zip(levs, 1:length(levs)))
93-
dummies = zeros(length(thisVar), length(namedict))
94-
for i=1:length(thisVar)
65+
dummies = zeros(Float64, length(thisVar), length(namedict))
66+
67+
for i in 1:length(thisVar)
9568
if haskey(namedict, thisVar[i])
96-
dummies[i, namedict[thisVar[i]]] = 1
69+
dummies[i, namedict[thisVar[i]]] = 1.0
9770
end
9871
end
99-
100-
# Convert results to a DataFrame and rename columns
101-
newDf = DataFrame(dummies ,:auto)
72+
73+
if cType == "sum" && reference !== nothing
74+
refMask = thisVar .== reference
75+
if any(refMask) && !isempty(levs)
76+
dummies[refMask, :] .= -1.0
77+
end
78+
elseif cType == "sumnoint"
79+
dummies .= dummies .- (1.0 / length(sortedLevs))
80+
end
81+
82+
newDf = DataFrame(dummies, :auto)
10283
rename!(newDf, [Symbol("$(cVar)_$k") for k in levs])
84+
10385
return newDf
10486
end
10587

@@ -175,3 +157,4 @@ function contr(df::DataFrames.DataFrame, cVars::AbstractArray{Symbol,1},
175157

176158
return newDf
177159
end
160+

src/misc_helpers.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,6 @@ end
9999

100100
function check_Z_rank(A::AbstractArray{Float64,2})
101101
if !is_full_rank(A)
102-
@warn "The rank of Z matrix is not full, and this may generate errores."
102+
@warn "The rank of Z matrix is not full, and this may generate errors."
103103
end
104104
end

src/mlm_helpers.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ function calc_var(X::AbstractArray{Float64,2}, Z::AbstractArray{Float64,2},
125125
# LHS of covariance matrix
126126
varLeft = inv(XTX)
127127
# RHS of covariance matrix
128-
varRight = transpose(ZTZ\(transpose((ZTZ\(transpose(Z)*sigma))*Z)))
128+
A = ZTZ\Z';
129+
varRight = A*sigma*transpose(A);
129130

130131
# Diagonal of covariance matrix, aka the variance
131132
varDiag = transpose(kron_diag(varLeft, varRight))

test/calc_sigma_test.jl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
# Tests for calc_sigma
3+
4+
# Tolerance for tests
5+
tol = 10.0^(-7)
6+
7+
# Small deterministic example
8+
Random.seed!(1)
9+
n = 5
10+
m = 3
11+
rsdl = randn(n,m)
12+
13+
@testset "calcSigmaBasic" begin
14+
# When targetType is nothing, should compute sample covariance
15+
# Sigma = RSS / (n - 1)
16+
RSS = transpose(rsdl) * rsdl
17+
expected_sigma = RSS ./ (n - 1)
18+
19+
sigma, lambda = MatrixLM.calc_sigma(rsdl, nothing)
20+
21+
@test size(sigma) == (m, m)
22+
@test isapprox(sigma, expected_sigma, atol=tol)
23+
@test lambda == 0.0
24+
end
25+
26+
@testset "calcSigmaDelegation" begin
27+
# When targetType is a string, calc_sigma should delegate to shrink_sigma
28+
s1, l1 = MatrixLM.calc_sigma(rsdl, "A")
29+
s2, l2 = MatrixLM.shrink_sigma(rsdl, "A")
30+
31+
@test size(s1) == (m, m)
32+
@test size(s2) == (m, m)
33+
@test isapprox(s1, s2, atol=tol)
34+
@test isapprox(l1, l2, atol=tol)
35+
@test 0.0 <= l1 <= 1.0
36+
end

0 commit comments

Comments
 (0)