This repository was archived by the owner on Mar 18, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmetrics.R
More file actions
154 lines (138 loc) · 5.26 KB
/
Copy pathmetrics.R
File metadata and controls
154 lines (138 loc) · 5.26 KB
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
#' Calculate Mean Absolute Error
#' @description Computes the Mean Absolute Error (MAE) between the actual and predicted values.
#' @param actual Numeric vector of actual values.
#' @param predicted Numeric vector of predicted values.
#' @return The MAE value (numeric).
#' @examples
#' actual <- c(1, 2, 3)
#' predicted <- c(1, 2, 2)
#' mae(actual, predicted)
mae <- function(actual, predicted) {
if (length(actual) != length(predicted)) {
stop("The lengths of actual and predicted vectors must be the same.")
}
mae <- mean(abs(actual - predicted))
return(mae)
}
#' Calculate Root Mean Square Error
#' @description Computes the Root Mean Square Error (RMSE) between the actual and predicted values.
#' @param actual Numeric vector of actual values.
#' @param predicted Numeric vector of predicted values.
#' @return The RMSE value (numeric).
#' @examples
#' actual <- c(1, 2, 3)
#' predicted <- c(1, 2, 2)
#' rmse(actual, predicted)
rmse <- function(actual, predicted) {
if (length(actual) != length(predicted)) {
stop("The lengths of actual and predicted vectors must be the same.")
}
mse <- mean((actual - predicted)^2)
rmse <- sqrt(mse)
return(rmse)
}
#' Compare Original Data to Imputed Data Using Distribution-based Metrics
#'
#' @description Compares original data with imputed data by calculating various distribution-based metrics,
#' such as the Wasserstein distance and the Jensen–Shannon Divergence (JSD).
#'
#' @param original_data A numeric vector or a data frame containing the original data.
#' @param imputed_data A numeric vector or a data frame of the same dimensions as \code{original_data},
#' containing imputed (or second) values.
#' @param metrics A character vector of metrics to calculate. Possible values include:
#' \itemize{
#' \item \code{"wasserstein"} - 1D Wasserstein distance
#' \item \code{"jsd"} - Jensen–Shannon Divergence via KDE
#' }
#'
#' @return A named list containing the average of each requested metric across all numeric columns.
#'
#' @details
#' If the input is a numeric vector, it will be converted to a single-column data frame internally.
#' Missing or non-numeric columns are skipped.
#'
#' @examples
#' \dontrun{
#' library(transport)
#' library(philentropy)
#'
#' set.seed(123)
#' # Example with vectors
#' original_vals <- rnorm(100, 5, 2)
#' imputed_vals <- rnorm(100, 5.2, 2)
#' compare_distributions(original_vals, imputed_vals)
#'
#' # Example with data frames
#' df_original <- data.frame(
#' x = rnorm(100, 5, 2),
#' y = rnorm(100, 10, 3)
#' )
#' df_imputed <- data.frame(
#' x = rnorm(100, 5.2, 2),
#' y = rnorm(100, 9.8, 3)
#' )
#' compare_distributions(df_original, df_imputed, metrics = c("wasserstein", "jsd"))
#' }
#'
#' @export
compare_distributions <- function(original_data, imputed_data, metrics = c("wasserstein", "jsd")) {
# Check if dimensions match (for data frames)
if (is.data.frame(original_data) && is.data.frame(imputed_data)) {
if (!identical(dim(original_data), dim(imputed_data))) {
warning("Original and imputed data frames must have the same dimensions. Returning NA for all metrics.")
return(NA)
}
}
# Convert vectors to data frames
if (is.vector(original_data)) {
original_data <- data.frame(col1 = original_data)
imputed_data <- data.frame(col1 = imputed_data)
}
# Ensure both are data frames at this point
if (!is.data.frame(original_data) || !is.data.frame(imputed_data)) {
stop("Both 'original_data' and 'imputed_data' must be numeric vectors or data frames.")
}
# Identify numeric columns
numeric_cols <- sapply(original_data, is.numeric)
overall_metrics <- list()
# For each numeric column, calculate requested metrics
for (col in names(original_data)[numeric_cols]) {
orig_col <- original_data[[col]]
imp_col <- imputed_data[[col]]
# Only compare if lengths match
if (length(orig_col) == length(imp_col)) {
for (metric in metrics) {
# Calculate Wasserstein distance
if (metric == "wasserstein") {
wd <- transport::wasserstein1d(orig_col, imp_col)
overall_metrics[[metric]] <- c(overall_metrics[[metric]], wd)
}
# Calculate Jensen–Shannon Divergence
if (metric == "jsd") {
# Estimate density ranges
common_range <- range(c(orig_col, imp_col), na.rm = TRUE)
# Density
orig_kde <- density(orig_col, from = common_range[1], to = common_range[2], n = 1024)
imp_kde <- density(imp_col, from = common_range[1], to = common_range[2], n = 1024)
# Normalize
p <- orig_kde$y / sum(orig_kde$y)
q <- imp_kde$y / sum(imp_kde$y)
distributions <- rbind(p, q)
# Suppress messages during JSD calculation
# jsd_val <- philentropy::JSD(distributions, unit = "log2")
jsd_val <- suppressMessages(philentropy::JSD(distributions, unit = "log2"))
overall_metrics[[metric]] <- c(overall_metrics[[metric]], sqrt(jsd_val))
}
}
}
}
# Compute averages across columns for each metric
for (metric in metrics) {
if (!is.null(overall_metrics[[metric]])) {
overall_metrics[[metric]] <- mean(overall_metrics[[metric]], na.rm = TRUE)
} else {
overall_metrics[[metric]] <- NA
}
}
return(overall_metrics)
}