From 46a9ad81a1ac17ab1423a7f8d08f965267b2f8ce Mon Sep 17 00:00:00 2001 From: Andreas Brandmaier Date: Fri, 1 Jul 2022 11:11:09 +0200 Subject: [PATCH 1/6] created new loss function for square Hellinger distance --- src/loss/other/hellinger.jl | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/loss/other/hellinger.jl diff --git a/src/loss/other/hellinger.jl b/src/loss/other/hellinger.jl new file mode 100644 index 000000000..cbdd171d5 --- /dev/null +++ b/src/loss/other/hellinger.jl @@ -0,0 +1,61 @@ +############################################################################################ +### Types +############################################################################################ +""" +Squared Hellinger distance loss. + +# Constructor + + Hellinger() + +# Examples +```julia + my_hellinger = Hellinger() +``` + +# Extended help +## Implementation +Subtype of `SemLossFunction`. +""" + +struct Hellinger <: SemLossFunction end + +using LinearAlgebra +import StructuralEquationModels: Σ, μ, obs_cov, obs_mean, objective! + +function objective!(hell::Hellinger, parameters, model::AbstractSem) + + # get model-implied covariance matrices + Σᵢ = Σ(imply(model)) + + # get observed covariance matrix + Σₒ = obs_cov(observed(model)) + + # get model-implued mean vector + μᵢ = μ(imply(model)) + + # get observed mean vector + μₒ = obs_mean(observed(model)) + + Sig = (Σᵢ + Σₒ)/2 + + + + # compute the objective + if isposdef(Symmetric(Σᵢ)) # is the model implied covariance matrix positive definite? + + loss = ( det(Σᵢ)^(1/4) * det(Σₒ)^(1/4) ) / sqrt(det(Sig)) + + if !isnothing(μᵢ) & !isnothing(μₒ) + μd = (μᵢ-μₒ) + loss = loss * exp(0.125 * (μd)*inv(Sig)*(μd)') + end + + loss = 1 - loss + + return loss + + else + return Inf + end +end \ No newline at end of file From 003645dc6aa99633d6980e073d6e40db1ca90aea Mon Sep 17 00:00:00 2001 From: Andreas Brandmaier Date: Wed, 5 Nov 2025 14:25:11 +0100 Subject: [PATCH 2/6] Implement pseudoR2 function for SEM fitted models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a function to compute pseudo-R² for SEM models. --- src/additional_functions/pseudor2.jl | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/additional_functions/pseudor2.jl diff --git a/src/additional_functions/pseudor2.jl b/src/additional_functions/pseudor2.jl new file mode 100644 index 000000000..483036a80 --- /dev/null +++ b/src/additional_functions/pseudor2.jl @@ -0,0 +1,30 @@ + +""" +pseudoR2(fitted) + +Compute pseudo-R² for each observed variable in a fitted SEM. + +Returns a DataFrame with: +- :var — observed variable name (Symbol) +- :r2_implied — 1 - ( residual variance / model-implied variance) + +""" +function pseudoR2(fitted::SemFit) +# 1) Observed variable order as used by the model +obs = Symbol.(fitted.model.observed.observed_vars) + +# 2) Model-implied covariance (diagonal are model-implied total variances) +Σ_imp = fitted.model.implied.Σ +var_imp = diag(Σ_imp) + +# 3) Get model-implied variances of observed variables +resvars = diag(fitted.model.implied.F*fitted.model.implied.S*transpose(fitted.model.implied.F)) + +# 4) Compute R² (model-implied) +r2_imp = 1 .- (resvars ./ var_imp) + +# prepare return object +df = DataFrame(var = obs, r2_implied = r2_imp) +return df + +end From 64f6ae55a0be4d248d55847dded820b1233332e9 Mon Sep 17 00:00:00 2001 From: Andreas Brandmaier Date: Wed, 5 Nov 2025 14:27:01 +0100 Subject: [PATCH 3/6] Delete src/loss/other/hellinger.jl --- src/loss/other/hellinger.jl | 61 ------------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 src/loss/other/hellinger.jl diff --git a/src/loss/other/hellinger.jl b/src/loss/other/hellinger.jl deleted file mode 100644 index cbdd171d5..000000000 --- a/src/loss/other/hellinger.jl +++ /dev/null @@ -1,61 +0,0 @@ -############################################################################################ -### Types -############################################################################################ -""" -Squared Hellinger distance loss. - -# Constructor - - Hellinger() - -# Examples -```julia - my_hellinger = Hellinger() -``` - -# Extended help -## Implementation -Subtype of `SemLossFunction`. -""" - -struct Hellinger <: SemLossFunction end - -using LinearAlgebra -import StructuralEquationModels: Σ, μ, obs_cov, obs_mean, objective! - -function objective!(hell::Hellinger, parameters, model::AbstractSem) - - # get model-implied covariance matrices - Σᵢ = Σ(imply(model)) - - # get observed covariance matrix - Σₒ = obs_cov(observed(model)) - - # get model-implued mean vector - μᵢ = μ(imply(model)) - - # get observed mean vector - μₒ = obs_mean(observed(model)) - - Sig = (Σᵢ + Σₒ)/2 - - - - # compute the objective - if isposdef(Symmetric(Σᵢ)) # is the model implied covariance matrix positive definite? - - loss = ( det(Σᵢ)^(1/4) * det(Σₒ)^(1/4) ) / sqrt(det(Sig)) - - if !isnothing(μᵢ) & !isnothing(μₒ) - μd = (μᵢ-μₒ) - loss = loss * exp(0.125 * (μd)*inv(Sig)*(μd)') - end - - loss = 1 - loss - - return loss - - else - return Inf - end -end \ No newline at end of file From 832beff3e932b23f529e83d10a5ea3b0719289d0 Mon Sep 17 00:00:00 2001 From: Andreas Brandmaier Date: Wed, 5 Nov 2025 20:03:21 +0100 Subject: [PATCH 4/6] Renamed to explained_variance --- .../{pseudor2.jl => explained_variance.jl} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/additional_functions/{pseudor2.jl => explained_variance.jl} (82%) diff --git a/src/additional_functions/pseudor2.jl b/src/additional_functions/explained_variance.jl similarity index 82% rename from src/additional_functions/pseudor2.jl rename to src/additional_functions/explained_variance.jl index 483036a80..0b5dbd863 100644 --- a/src/additional_functions/pseudor2.jl +++ b/src/additional_functions/explained_variance.jl @@ -1,15 +1,15 @@ """ -pseudoR2(fitted) +explained_variance(fitted) -Compute pseudo-R² for each observed variable in a fitted SEM. +Compute explained variance (R²) for each observed variable in a fitted SEM. Returns a DataFrame with: - :var — observed variable name (Symbol) - :r2_implied — 1 - ( residual variance / model-implied variance) """ -function pseudoR2(fitted::SemFit) +function explained_variance(fitted::SemFit) # 1) Observed variable order as used by the model obs = Symbol.(fitted.model.observed.observed_vars) From 2dfd0f4a59caa0f76bfc06230486cfe0915be084 Mon Sep 17 00:00:00 2001 From: Andreas Brandmaier Date: Wed, 5 Nov 2025 20:05:28 +0100 Subject: [PATCH 5/6] Move explained_variance.jl to frontend/fit directory --- src/{additional_functions => frontend/fit}/explained_variance.jl | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{additional_functions => frontend/fit}/explained_variance.jl (100%) diff --git a/src/additional_functions/explained_variance.jl b/src/frontend/fit/explained_variance.jl similarity index 100% rename from src/additional_functions/explained_variance.jl rename to src/frontend/fit/explained_variance.jl From 4fceb70cac6bb02a2122db0ee42e58fc0e6df637 Mon Sep 17 00:00:00 2001 From: Andreas Brandmaier Date: Tue, 11 Nov 2025 16:44:02 +0100 Subject: [PATCH 6/6] Add explained_variance export in SEM file --- src/StructuralEquationModels.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/StructuralEquationModels.jl b/src/StructuralEquationModels.jl index f6068dc50..5d0bb7749 100644 --- a/src/StructuralEquationModels.jl +++ b/src/StructuralEquationModels.jl @@ -83,6 +83,7 @@ include("frontend/fit/fitmeasures/minus2ll.jl") include("frontend/fit/fitmeasures/p.jl") include("frontend/fit/fitmeasures/RMSEA.jl") include("frontend/fit/fitmeasures/fit_measures.jl") +include("frontend/fit/fitmeasures/explained_variance.jl") # standard errors include("frontend/fit/standard_errors/hessian.jl") include("frontend/fit/standard_errors/bootstrap.jl") @@ -178,6 +179,7 @@ export AbstractSem, param_indices, param_labels, fit_measures, + explained_variance, AIC, BIC, χ²,