This tutorial implements and explains in detail the DL
composite indicator algorithm in a fully self-contained
way.
All core functions are explicitly defined inside the document to ensure
full reproducibility, although the algorithm is also available in the
public GitHub repository:
https://github.com/E-Jimenez-Fernandez/dl2
The methodological foundations of the DL2 approach were introduced in:
Jiménez-Fernández, E., Sánchez, A., & Ortega-Pérez, M. (2022). Dealing with weighting scheme in composite indicators: An unsupervised distance–machine learning proposal for quantitative data. Socio-Economic Planning Sciences, 83, 101339. https://doi.org/10.1016/j.seps.2022.101339
The theoretical perspective underpinning DL is interpreted as a
norm in a m dimension vector space.
This geometric viewpoint provides a set of desirable axiomatic
properties for the aggregation of indicators.
A mapping \(\| \cdot \| : \mathbb{R}^m \to \mathbb{R}\) is a norm if it satisfies:
Monotonicity
If all indicators improve (componentwise), the composite indicator must
not decrease.
Positive homogeneity
Scaling all indicators by a positive scalar \(\alpha\) scales the composite indicator by
\(\alpha\).
\[
\| \alpha x \| = \alpha \| x \|
\]
Convexity / Subadditivity (triangle
inequality)
The indicator must reward balanced profiles over extreme or compensatory
ones.
\[
\| x + y \| \le \| x \| + \| y \|
\]
Continuity
Small changes in indicators must produce small changes in the composite
index.
Identity of indiscernibles
\[
\| x \| = 0 \iff x = 0
\]
These properties provide a rigorous mathematical foundation ensuring fairness, stability, and interpretability of aggregation.
A crucial insight of the theory is that all ℓₚ norms with
\(p\geq 1\) induce the same
preferences:
if one alternative dominates another in ℓ₂, it will also dominate it in
ℓ₃, ℓ₄, …
This provides:
As highlighted in the paper, changes in the value of \(p\) do affect the shape of the indifference curves but not the ordering of alternatives.
An important theoretical message of the article is that no
composite indicator can preserve all topological properties of the
multidimensional space (Brouwer’s invariance theorem).
This means that every 1-dimensional indicator must necessarily:
The task of a good index is therefore not to avoid distortion (impossible), but to minimize norm-violations and maintain meaningful geometry.
DL follows this theoretical geometry by defining the composite indicator as a weighted ℓ₁ Minkowski norm of powered distances.
A crucial requirement of the DL framework is that all weights must be strictly positive. When the composite indicator is interpreted as a weighted Minkowski norm, the strict positivity of each \(w_j > 0\) is indispensable for preserving the fundamental axioms of a norm. If any weight were equal to zero, the corresponding dimension would become “invisible” to the index, violating the identity of indiscernibles, weakening monotonicity, and destroying the topological equivalence with classical \(\ell_p\) norms. This would produce a degenerate space in which some directions have zero length and the indicator ceases to behave as a proper distance. For this reason, the improved DL2 implementation enforces strictly positive convex weights at every iteration, ensuring that the resulting metric captures the full multidimensional structure of the data and respects the normative properties required for a valid composite indicator.
\[ CI_i = \sum_j w_j |z_{ij}| \]
with the following improvements relative to classical DP2:
Strict convexity of weights \(w_j \ge 0, \sum w_j = 1\)
ensuring that the induced distance is a proper norm.
Positive lower bounds on weights,
avoiding degenerate metrics (zero components break the identity of
indiscernibles).
Machine-learning variable importance (MARS) to
estimate weights,
instead of sequential regression or expert assignment.
Adaptive weight smoothing,
stabilizing convergence of rankings without modifying the indicator
directly.
Direct update of the indicator (no index
relaxation),
preserving the interpretation of each iteration as a genuine
metric.
Under this design, DL2 can be interpreted as an iterative search for a stable norm consistent with the data’s geometry.
The rest of the document proceeds as follows:
The algorithm in this document follows the main conceptual steps of the published DL2 methodology and, in addition, introduces implementation improvements at the algorithmic level, namely:
The algorithm consists of the following main components:
In what follows, each function is defined with detailed comments and then an illustrative example is provided.
# -------------------------------------------------------------------
# 1. Polarity-based Min–Max normalization
# -------------------------------------------------------------------
# This function implements the normalisation scheme described in
# Jiménez-Fernández et al. (2022), Section "Normalisation", where
# each single indicator is rescaled to [0, 1] according to its polarity.
#
# - For indicators with positive polarity (higher = better), we apply:
# (x - min) / (max - min)
# corresponding to eq. (3) in the paper.
#
# - For indicators with negative polarity (higher = worse), we apply:
# (max - x) / (max - min)
# corresponding to eq. (4) in the paper.
#
# Arguments:
# x : numeric data frame of raw single indicators.
# polarity : vector of column indices with positive polarity.
# All other columns are treated as negative by default.
# custom_max : optional vector of user-specified maxima per variable.
# custom_min : optional vector of user-specified minima per variable.
#
# Returns:
# A data frame where each column belongs to [0, 1] and the direction
# of "better performance" has been harmonised across indicators.
#
normalization <- function(x,
polarity = NULL,
custom_max = NULL,
custom_min = NULL) {
x <- as.data.frame(x)
names_var <- colnames(x)
names_regions <- rownames(x)
m <- ncol(x)
columns <- seq_len(m)
if (is.null(polarity)) {
# If no polarity is provided, all variables are treated as negative.
# This is conservative and forces the user to explicitly identify
# the positively oriented indicators when needed.
pospol <- integer(0)
negpol <- columns
} else {
pospol <- polarity
negpol <- setdiff(columns, pospol)
}
normdata <- matrix(0, ncol = m, nrow = nrow(x))
# Min–max transform: positive polarity
norm_minmax <- function(v, min_val, max_val) {
(v - min_val) / (max_val - min_val)
}
# Max–min transform: negative polarity
norm_maxmin <- function(v, min_val, max_val) {
(max_val - v) / (max_val - min_val)
}
# Use empirical or user-provided bounds
max_vals <- if (is.null(custom_max)) apply(x, 2, max) else custom_max
min_vals <- if (is.null(custom_min)) apply(x, 2, min) else custom_min
if (length(max_vals) != m || length(min_vals) != m) {
stop("Length of custom_max and custom_min must match the number of columns in x.")
}
# Apply positive polarity normalization (eq. (3))
for (j in pospol) {
normdata[, j] <- norm_minmax(x[, j], min_vals[j], max_vals[j])
}
# Apply negative polarity normalization (eq. (4))
for (j in negpol) {
normdata[, j] <- norm_maxmin(x[, j], min_vals[j], max_vals[j])
}
normdata <- as.data.frame(normdata)
colnames(normdata) <- names_var
rownames(normdata) <- names_regions
normdata
}
# -------------------------------------------------------------------
# 2. Powered distances (Minkowski components)
# -------------------------------------------------------------------
# This function takes the normalized matrix and raises each entry
# to a given power 'degrees'. In the DL2 framework, this is linked to
# the ℓ_p norm structure underlying the distance:
#
# d_ij = (z_ij)^p
#
# For p = 2, we are in the ℓ2 (Euclidean) case, as discussed in
# Section 3 of Jiménez-Fernández et al. (2022), where DL2 is defined
# as a weighted ℓ2 metric.
#
# Arguments:
# x : matrix or data frame (typically the normalized data).
# degrees : exponent p used in the Minkowski-type transformation.
#
# Returns:
# Matrix of powered distances (same dimension as x).
#
calculateDistance <- function(x, degrees = 2) {
x <- as.matrix(x)
x^degrees
}
# -------------------------------------------------------------------
# 3. Fréchet-type index (initial composite indicator)
# -------------------------------------------------------------------
# The Fréchet distance used in the original paper corresponds to a
# Minkowski-type aggregation of the powered distances:
#
# FD_i = ( sum_j d_ij )^(1/p)
#
# where d_ij are the powered components, typically z_ij^2.
# This index is used as an initial "seed" composite indicator in
# the iterative DL2 algorithm, before learning any weights.
#
# Arguments:
# x : matrix of powered distances.
# degrees : exponent p used in the Minkowski aggregation.
#
# Returns:
# A column matrix with the Fréchet index for each observation.
#
indexFrechet <- function(x, degrees = 2) {
x <- as.matrix(x)
iF <- (rowSums(x))^(1 / degrees)
iF <- matrix(iF, ncol = 1)
colnames(iF) <- "Frechet.Index"
iF
}
# -------------------------------------------------------------------
# 4. DP2-style composite indicator (weighted L1 of powered distances)
# -------------------------------------------------------------------
# In the original DP2 method, and in the DL2 adaptation, the distance
# is constructed by aggregating (normalised) deviations to a reference
# through a weighted structure. In this implementation we work with
# a weighted L1 norm of the powered distance components:
#
# CI_i = sum_j |x_ij * w_j|
#
# where x_ij are the powered distances and w_j are the learned
# weights. Conceptually, this matches the partially compensatory
# nature discussed in the paper.
#
# Arguments:
# x : matrix of powered distances.
# xFactores : numeric vector of weights (one per column in x).
# iteration : integer used for labelling the resulting indicator.
#
# Returns:
# A list with:
# DP1 : column matrix of the composite indicator for each unit.
# mDP : weighted distance matrix.
#
calculateDP2 <- function(x, xFactores, iteration = 1) {
x <- as.matrix(x)
n <- nrow(x)
m <- ncol(x)
# Expand the weight vector into a matrix with n rows and m columns.
# This allows element-wise multiplication between weights and x.
coefs <- matrix(xFactores, n, m, byrow = TRUE)
mDP <- x * coefs
# Weighted L1 norm across columns.
DP1 <- apply(mDP, 1, function(row) sum(abs(row)))
DP1 <- matrix(DP1, ncol = 1)
colnames(DP1) <- paste0("p1distance.", iteration)
list(DP1 = DP1, mDP = mDP)
}
# -------------------------------------------------------------------
# 5. Adaptive smoothing parameter for weights
# -------------------------------------------------------------------
# The original DL2 algorithm uses Kendall's tau to check the stability
# of rankings between consecutive iterations. In this implementation
# we complement that idea with a *numerical* convergence perspective:
#
# delta = RMSE(D^(t), D^(t+1))
#
# and define an adaptive smoothing parameter λ_t that:
#
# - is large when the indicator is already stable (small delta),
# - is small when the indicator changes are still large (big delta).
#
# This modifies the recursion for the weights:
#
# w^(t+1) = (1 - λ_t) * w^(t) + λ_t * w_new^(t+1)
#
# and acts as a stabilizing device for the weighting scheme, reducing
# oscillations and helping convergence of both values and rankings.
#
# Arguments:
# delta : current RMSE between consecutive indicators.
# base_delta : reference RMSE from first iteration.
# lambda_min : lower bound for λ.
# lambda_max : upper bound for λ.
#
# Returns:
# A scalar λ_t used to smooth the new weights.
#
adaptive_lambda <- function(delta,
base_delta,
lambda_min = 0.05,
lambda_max = 0.8) {
if (is.na(delta) || is.na(base_delta) || base_delta == 0) {
# Fallback to mid-range if baseline is degenerate
return((lambda_min + lambda_max) / 2)
}
# Relative size of current variation compared to baseline
ratio <- delta / base_delta
ratio <- max(min(ratio, 1), 0)
# Large delta -> lambda close to lambda_min (conservative updates)
# Small delta -> lambda close to lambda_max (more aggressive updates)
lambda <- lambda_max - (lambda_max - lambda_min) * ratio
lambda
}
# -------------------------------------------------------------------
# 6. Variable weights via MARS (convex weighting scheme)
# -------------------------------------------------------------------
# This function corresponds to the step "Selecting the best set of
# disjoint polynomials" and "Computing the weights of DL2" in
# Jiménez-Fernández et al. (2022).
#
# Conceptual steps:
# 1) Fit a MARS model: Compind ~ z_1 + ... + z_m
# where Compind is either the initial Frechet index or the
# composite indicator from the previous iteration.
# 2) Use earth::evimp() to obtain RSS-based variable importance.
# 3) Transform these importances into a convex vector of weights:
# w_j >= 0, sum_j w_j = 1.
#
# This approximates the role of the Variable Importance (VI/FIRM)
# mechanism described in the paper, but using the RSS-based measure
# provided by the earth package as a practical and efficient proxy.
#
# Importantly, in this implementation we force:
#
# - strictly non-negative weights,
# - renormalisation to sum to one,
# - a small positive weight for any variable with zero importance,
# to preserve the metric property (all ω_j > 0).
#
# Arguments:
# x : normalized data frame of indicators.
# Compind : current composite indicator (response).
# degrees : max interaction degree for MARS.
# nfold : number of CV folds.
# trace : verbosity of earth().
#
# Returns:
# A list with:
# [[1]] : numeric vector of weights aligned with columns of x.
# [[2]] : character vector of variable names in original order.
# [[3]] : evimp object with raw importance measures.
#
weightFactors <- function(x,
Compind,
degrees,
nfold = 5,
trace = .5) {
x <- as.data.frame(x)
dat_os <- x
m <- ncol(x)
# Attach the composite indicator as response
x <- dplyr::mutate(x, Cind = as.numeric(Compind))
# MARS model capturing possibly nonlinear and interaction effects
mymodel <- earth::earth(
Cind ~ .,
data = x,
degree = degrees,
nfold = nfold,
trace = trace
)
# RSS-based importance for each basis variable
p2 <- earth::evimp(mymodel, trim = FALSE)
Variable <- row.names(p2)
weights <- as.numeric(p2[, "rss"])
Variable <- sub("-unused", "", Variable)
# Degenerate case: no meaningful importance structure
if (all(is.na(weights)) || sum(weights, na.rm = TRUE) == 0) {
warning("All calculated weights are 0 or NA. Assigning equal weights.")
weights <- rep(1 / m, m)
res <- data.frame(t(weights))
colnames(res) <- colnames(dat_os)
res.ord <- as.numeric(res[colnames(dat_os)])
return(list(res.ord, colnames(dat_os), p2))
}
# Replace zero importances with a small positive value to avoid
# null weights which would break the metric property (ω_j > 0).
mini <- min(weights[weights > 0], na.rm = TRUE)
for (j in seq_along(weights)) {
if (is.na(weights[j])) {
stop("Missing values were found in the weights.")
} else if (weights[j] == 0) {
weights[j] <- mini / length(weights)
}
}
# Enforce convexity: weights >= 0 and sum(weights) = 1
weights <- pmax(weights, 0)
if (sum(weights) == 0) {
weights <- rep(1 / length(weights), length(weights))
} else {
weights <- weights / sum(weights)
}
# Align weights with the original column order of the indicators
res <- data.frame(t(weights))
colnames(res) <- Variable
faltan <- setdiff(colnames(dat_os), colnames(res))
if (length(faltan) > 0) {
res[, faltan] <- 0
}
res <- res[, colnames(dat_os), drop = FALSE]
res.ord <- as.numeric(res[1, ])
important <- list(res.ord, colnames(dat_os), p2)
important
}
# -------------------------------------------------------------------
# 7. Main DL2 algorithm (improved implementation)
# -------------------------------------------------------------------
# This function orchestrates the entire DL2 procedure:
#
# 1) Optional polarity-based normalization (eqs. (3)–(4)).
# 2) Correlation analysis (diagnostic only, via corrgram).
# 3) Construction of powered distances (eq. (5) components).
# 4) Initial Fréchet-type index (eq. (6)).
# 5) Iterative loop:
# a) MARS-based weight estimation (Section 3, MARS step).
# b) Provisional indicator using raw new weights.
# c) Computation of an RMSE-based delta.
# d) Adaptive selection of λ_t (adaptive_lambda()).
# e) Smoothing of weights ONLY (no relaxation on the index).
# f) Final composite indicator with smoothed weights.
# g) Convergence test based on squared error.
#
# Compared to the baseline algorithm in the paper:
#
# - We enforce convex weights at every iteration, ensuring that
# the induced distance is a proper metric (Section 4 of the paper).
# - We smooth the *weights* rather than the index, which preserves
# interpretability of each composite indicator as a genuine
# distance-based benchmark.
# - We use a numeric squared-error criterion instead of Kendall's τ,
# though both aim at stabilizing the ranking / values of the units.
#
# Arguments:
# x : data frame of raw indicators.
# polarity : numeric vector of columns with positive polarity.
# err : error tolerance for stopping (sum of squared diffs).
# iterations : maximum number of iterations allowed.
# degrees : exponent used in calculateDistance and indexFrechet.
# nfold : number of CV folds in MARS.
# trace : verbosity of earth().
# use_normat : if TRUE, internal normalization is applied.
# lambda_w_fixed : if not NULL, use a fixed smoothing parameter;
# otherwise, use adaptive_lambda().
#
# Returns:
# A list with:
# normat : normalized data (if use_normat = TRUE).
# Weights : final convex weight vector.
# derror : error trajectory along iterations.
# frechet : initial Fréchet index.
# corrgr : corrgram object for diagnostics.
# Comp.Indicator : final composite indicator (distance-based).
# Comp.Indicator_1 : same as above (for compatibility).
# iteration : number of iterations performed.
# dist.in : initial powered distance matrix.
# dist.fin : final weighted distance matrix.
# base_delta : baseline RMSE of the first iteration.
#
dl2 <- function(x,
polarity,
err,
iterations,
degrees,
nfold = 5,
trace = .5,
use_normat = TRUE,
lambda_w_fixed = NULL) {
if (!is.data.frame(x)) {
warning("The argument 'x' must be a data.frame object.")
x <- as.data.frame(x)
}
# Step 1: optional normalization
if (use_normat) {
normat <- normalization(x, polarity)
} else {
normat <- x
}
# Step 2: correlation structure (descriptive)
corrgr <- corrgram::corrgram(
normat,
order = TRUE,
lower.panel = corrgram::panel.shade,
upper.panel = corrgram::panel.pie,
text.panel = corrgram::panel.txt,
main = "Correlation between features"
)
# Step 3: powered distances (Minkowski components)
mDif <- calculateDistance(normat, degrees = degrees)
# Step 4: initial Fréchet-type index (unweighted)
dps <- indexFrechet(mDif, degrees = degrees)
dp2_aux <- dps
iteration <- 1
error.d <- numeric()
weights <- NULL
calcDP2_res <- NULL
base_delta <- NA
# Step 5: iterative refinement of weights and indicator
repeat {
cat("Iteration", iteration, "\n")
# 5a) New weights from MARS using current indicator as response
new_weights <- weightFactors(
x = normat,
Compind = dps,
degrees = degrees,
nfold = nfold,
trace = trace
)[[1]]
# 5b) Provisional indicator using raw new weights
tmp_res <- calculateDP2(mDif, new_weights, iteration = iteration)
d2_prov <- tmp_res$DP1
# RMSE-based delta (used to control smoothing)
error_prov <- sum((dps - d2_prov)^2)
delta <- sqrt(error_prov / length(dps))
# Baseline delta from the first iteration
if (iteration == 1) {
base_delta <- ifelse(delta == 0, .Machine$double.eps, delta)
}
# 5c) Choose smoothing parameter for the weights
if (is.null(lambda_w_fixed)) {
lambda_w_cur <- adaptive_lambda(delta, base_delta)
} else {
lambda_w_cur <- lambda_w_fixed
}
# 5d) Smooth the weights (preserving convexity)
if (is.null(weights)) {
weights <- new_weights
} else {
weights <- (1 - lambda_w_cur) * weights + lambda_w_cur * new_weights
}
# 5e) Recompute the composite indicator with smoothed weights
calcDP2_res <- calculateDP2(mDif, weights, iteration = iteration)
d2_ite <- calcDP2_res$DP1
# 5f) Compute error and apply stopping rule
error <- sum((dps - d2_ite)^2)
error.d[iteration] <- error
if ((error < err) || (iteration >= iterations)) {
break
}
iteration <- iteration + 1
# 5g) Update indicator WITHOUT relaxing it: we directly use the
# new composite indicator as seed for the next iteration. This
# keeps each step interpretable as a proper distance index.
dps <- d2_ite
}
invisible(list(
normat = if (use_normat) normat else NULL,
Weights = weights,
derror = error.d,
frechet = dp2_aux,
corrgr = corrgr,
Comp.Indicator = d2_ite,
Comp.Indicator_1 = dps,
iteration = iteration,
dist.in = mDif,
dist.fin = if (!is.null(calcDP2_res)) calcDP2_res$mDP else NULL,
base_delta = base_delta
))
}
We now illustrate the algorithm with a simple simulated data set comprising five indicators: three with positive polarity (Income, Education, Health) and two with negative polarity (Pollution, Crime).
set.seed(123)
X <- data.frame(
Income = rnorm(50, 30000, 5000),
Education = rnorm(50, 70, 10),
Health = rnorm(50, 0.8, 0.1),
Pollution = rnorm(50, 30, 5),
Crime = rnorm(50, 4, 1)
)
head(X)
## Income Education Health Pollution Crime
## 1 27197.62 72.53319 0.7289593 33.93869 6.198810
## 2 28849.11 69.71453 0.8256884 33.84521 5.312413
## 3 37793.54 69.57130 0.7753308 31.66101 3.734855
## 4 30352.54 83.68602 0.7652457 24.95812 4.543194
## 5 30646.44 67.74229 0.7048381 29.40274 3.585660
## 6 38575.32 85.16471 0.7954972 28.59802 3.523753
polarity_vector <- c(1, 2, 3)
result <- dl2(
x = X,
polarity = polarity_vector,
err = 1e-3,
iterations = 100,
degrees = 2,
nfold = 5,
trace = 0,
use_normat = TRUE
)
## Iteration 1
## Iteration 2
## Iteration 3
## Iteration 4
## Iteration 5
## Iteration 6
## Iteration 7
## Iteration 8
## Iteration 9
## Iteration 10
## Iteration 11
## Iteration 12
## Iteration 13
head(result$normat)
## Income Education Health Pollution Crime
## 1 0.3400113 0.5698846 0.3233146 0.5389689 0.0000000
## 2 0.4198788 0.5071992 0.5562082 0.5430764 0.2490824
## 3 0.8524394 0.5040137 0.4349628 0.6390463 0.6923847
## 4 0.4925860 0.8179183 0.4106810 0.9335599 0.4652370
## 5 0.5067991 0.4633375 0.2652382 0.7382710 0.7343092
## 6 0.8902471 0.8508035 0.4835173 0.7736287 0.7517054
result$Weights
## [1] 0.12048112 0.01922913 0.09614561 0.19650951 0.56763463
df <- data.frame(
Iteration = seq_along(result$derror),
Error = result$derror
)
ggplot(df, aes(Iteration, Error)) +
geom_line(size = 1.2) +
geom_point() +
theme_minimal() +
ggtitle("DL2 Convergence Path (improved implementation)") +
scale_y_log10()
head(result$Comp.Indicator)
## p1distance.13
## [1,] 0.08730744
## [2,] 0.14910581
## [3,] 0.46299542
## [4,] 0.35244026
## [5,] 0.45501776
## [6,] 0.57024263
ci_df <- data.frame(
Observation = seq_len(nrow(X)),
CI = as.numeric(result$Comp.Indicator)
)
ggplot(ci_df, aes(x = Observation, y = CI)) +
geom_point(size = 2) +
geom_line() +
theme_minimal() +
ggtitle("Composite Indicator (DL2 Algorithm - improved version)")
The numerical illustration included in this document provides a complete example of how the DL2 algorithm behaves when applied to a synthetic dataset composed of five indicators: Income, Education, Health, Pollution, and Crime. The first three indicators have positive polarity, meaning that higher values represent better performance, whereas Pollution and Crime have negative polarity, meaning that higher values worsen the composite assessment.
The discussion below interprets the results obtained in the example.
The first output of the algorithm is the normalized data matrix. All variables have been transformed into the interval \([0,1]\) while respecting their polarity:
This harmonization is essential because it ensures that all indicators contribute in the same conceptual direction to the composite indicator. Normalization also removes the effect of heterogeneous units of measurement, which is critical in multidimensional assessment (e.g., euros, years, ppm, crime counts).
The vector returned in result$Weights represents the
final convex weights estimated by the DL2 algorithm. These weights
are:
The DL2-weighting scheme assigns greater weight to indicators that provide more discriminatory power across observations. In the context of the example, one typically observes:
This is consistent with the rationale in Jiménez-Fernández et al. (2022), where DL2 penalizes informational redundancy and rewards variables that meaningfully expand the multidimensional space.
The improved implementation provided here enhances the original methodology by:
The plot titled “DL2 Convergence Path (improved implementation)” displays the squared error between consecutive iterations of the composite indicator on a logarithmic scale.
The key observations are:
This behaviour aligns with the theoretical motivation of DL2, which searches for a stable configuration of distances and weights under the unsupervised learning structure of MARS.
The object result$Comp.Indicator contains the final
composite scores for each of the 50 simulated observations.
The plot of the composite indicator shows:
Because DL2 is distance-based, the indicator values can be interpreted in relation to how far each unit is from an abstract “ideal point” in the normalized space, weighted according to the importance learned through MARS.
Overall, the example demonstrates that:
This small experiment illustrates the core ideas presented in Jiménez-Fernández et al. (2022), showing how the DL2 method integrates normalization theory, distance geometry, and unsupervised machine learning to construct a robust composite indicator.