The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
The ROCaggregator package allows you to aggregate multiple ROC (Receiver Operating Characteristic) curves. One of the scenarios where it can be helpful is in federated learning. Evaluating a model using the ROC AUC (Area Under the Curve) in a federated learning scenario will require to evaluating the model against data from different sites. This will eventually lead to partial ROCs from each site which can be aggregated to obtain a global metric to evaluate the model.
library(ROCaggregator)
For this use case, we’ll be using some external packages: - the ROCR
to compute the ROC at each node and to validate the AUC obtained; - the pracma
package to compute the AUC using the trapezoidal method; - the stats
package to create a linear model;
The use case will consist of 3 nodes with horizontally partitioned data. A linear model will be trained with part of the data and tested at each node, generating a ROC curve for each.
To compute the aggregated ROC, each node will have to provide: - the ROC (consisting of the false positive rate and true positive rate); - the thresholds/cutoffs used (in the same order as the ROC); - the total number of negative labels in the dataset; - the total number of samples in the dataset;
library(ROCR)
library(pracma)
library(stats)
set.seed(13)
<- function(n){
create_dataset <- n %/% 2
positive_labels <- n - positive_labels
negative_labels
= c(rep(0, negative_labels), rep(1, positive_labels))
y = rnorm(n, 10, sd = 1)
x1 = c(rnorm(positive_labels, 2.5, sd = 2), rnorm(negative_labels, 2, sd = 2))
x2 = y * 0.3 + rnorm(n, 0.2, sd = 0.3)
x3
data.frame(x1, x2, x3, y)[sample(n, n), ]
}
# Create the dataset for each node
<- create_dataset(sample(300:400, 1))
node_1 <- create_dataset(sample(300:400, 1))
node_2 <- create_dataset(sample(300:400, 1))
node_3
# Train a linear model on a subset
<- glm(
glm.fit ~ x1 + x2 + x3,
y data = rbind(node_1, node_2),
family = binomial,
)
<- function(dataset){
get_roc <- predict(glm.fit,
glm.probs newdata = dataset,
type = "response")
<- prediction(glm.probs, c(dataset$y))
pred <- performance(pred, "tpr", "fpr")
perf <- performance(pred, "prec", "rec")
perf_p_r list(
"fpr" = perf@x.values[[1]],
"tpr" = perf@y.values[[1]],
"prec" = perf_p_r@y.values[[1]],
"thresholds" = perf@alpha.values[[1]],
"negative_count"= sum(dataset$y == 0),
"total_count" = nrow(dataset),
"auc" = performance(pred, measure = "auc")
)
}
# Predict and compute the ROC for each node
<- get_roc(node_1)
roc_node_1 <- get_roc(node_2)
roc_node_2 <- get_roc(node_3) roc_node_3
Obtaining the required inputs from each node will allow us to compute the aggregated ROC and the corresponding AUC.
# Preparing the input
<- list(roc_node_1$fpr, roc_node_2$fpr, roc_node_3$fpr)
fpr <- list(roc_node_1$tpr, roc_node_2$tpr, roc_node_3$tpr)
tpr <- list(
thresholds $thresholds, roc_node_2$thresholds, roc_node_3$thresholds)
roc_node_1<- c(
negative_count $negative_count, roc_node_2$negative_count, roc_node_3$negative_count)
roc_node_1<- c(
total_count $total_count, roc_node_2$total_count, roc_node_3$total_count)
roc_node_1
# Compute the global ROC curve for the model
<- roc_curve(fpr, tpr, thresholds, negative_count, total_count)
roc_aggregated
# Calculate the AUC
<- trapz(roc_aggregated$fpr, roc_aggregated$tpr)
roc_auc
sprintf("ROC AUC aggregated from each node's results: %f", roc_auc)
#> [1] "ROC AUC aggregated from each node's results: 0.778901"
# Calculate the precision-recall
<- precision_recall_curve(
precision_recall_aggregated
fpr, tpr, thresholds, negative_count, total_count)
# Calculate the precision-recall AUC
<- -trapz(
precision_recall_auc $recall, precision_recall_aggregated$pre)
precision_recall_aggregated
sprintf(
"Precision-Recall AUC aggregated from each node's results: %f",
precision_recall_auc
)#> [1] "Precision-Recall AUC aggregated from each node's results: 0.773897"
Using ROCR
we can calculate the ROC and its AUC for the case of having all the data centrally available. The values between this and the aggregated ROC should match.
<- get_roc(rbind(node_1, node_2, node_3))
roc_central_case
# Validate the ROC AUC
sprintf(
"ROC AUC using ROCR with all the data centrally available: %f",
$auc@y.values[[1]]
roc_central_case
)#> [1] "ROC AUC using ROCR with all the data centrally available: 0.778901"
# Validate the precision-recall AUC
<- trapz(
precision_recall_auc $tpr,
roc_central_caseifelse(is.nan(roc_central_case$prec), 1, roc_central_case$prec)
)sprintf(
"Precision-Recall AUC using ROCR with all the data centrally available: %f",
precision_recall_auc
)#> [1] "Precision-Recall AUC using ROCR with all the data centrally available: 0.773897"
The ROC curve obtained can be visualized in the following way:
plot(roc_aggregated$fpr,
$tpr,
roc_aggregatedmain="ROC curve",
xlab = "False Positive Rate",
ylab = "True Positive Rate",
cex=0.3,
col="blue",
)
Another popular package to compute ROC curves is the pROC
. Similarly to the example with the ROCR
package, it’s also possible to aggregate the results from ROC curves computed with the pROC package
.
library(pROC, warn.conflicts = FALSE)
#> Type 'citation("pROC")' for a citation.
<- function(dataset){
get_proc <- predict(glm.fit,
glm.probs newdata = dataset,
type = "response")
<- roc(c(dataset$y), c(glm.probs))
roc_obj list(
"fpr" = 1 - roc_obj$specificities,
"tpr" = roc_obj$sensitivities,
"thresholds" = roc_obj$thresholds,
"negative_count"= sum(dataset$y == 0),
"total_count" = nrow(dataset),
"auc" = roc_obj$auc
)
}
<- get_proc(node_1)
roc_obj_node_1 #> Setting levels: control = 0, case = 1
#> Setting direction: controls < cases
<- get_proc(node_2)
roc_obj_node_2 #> Setting levels: control = 0, case = 1
#> Setting direction: controls < cases
<- get_proc(node_3)
roc_obj_node_3 #> Setting levels: control = 0, case = 1
#> Setting direction: controls < cases
# Preparing the input
<- list(roc_obj_node_1$fpr, roc_obj_node_2$fpr, roc_obj_node_3$fpr)
fpr <- list(roc_obj_node_1$tpr, roc_obj_node_2$tpr, roc_obj_node_3$tpr)
tpr <- list(
thresholds $thresholds, roc_obj_node_2$thresholds, roc_obj_node_3$thresholds)
roc_obj_node_1<- c(
negative_count $negative_count, roc_obj_node_2$negative_count, roc_obj_node_3$negative_count)
roc_obj_node_1<- c(
total_count $total_count, roc_obj_node_2$total_count, roc_obj_node_3$total_count)
roc_obj_node_1
# Compute the global ROC curve for the model
<- roc_curve(fpr, tpr, thresholds, negative_count, total_count)
roc_aggregated
# Calculate the AUC
<- trapz(roc_aggregated$fpr, roc_aggregated$tpr)
roc_auc
sprintf("ROC AUC aggregated from each node's results: %f", roc_auc)
#> [1] "ROC AUC aggregated from each node's results: 0.778901"
# Validate the ROC AUC
<- get_proc(rbind(node_1, node_2, node_3))
roc_central_case #> Setting levels: control = 0, case = 1
#> Setting direction: controls < cases
sprintf(
"ROC AUC using pROC with all the data centrally available: %f",
$auc
roc_central_case
)#> [1] "ROC AUC using pROC with all the data centrally available: 0.778901"
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.