| Title: | Computation of Node and Path-Level Risk Scores in Scientific Models |
|---|---|
| Description: | It leverages the network-like architecture of scientific models together with software quality metrics to identify chains of function calls that are more prone to generating and propagating errors. It operates on tbl_graph objects representing call dependencies between functions (callers and callees) and computes risk scores for individual functions and for paths (sequences of function calls) based on cyclomatic complexity, in-degree and betweenness centrality. The package supports variance-based uncertainty and sensitivity analyses after Puy et al. (2022) <doi:10.18637/jss.v102.i05> to assess how risk scores change under alternative risk definitions. |
| Authors: | Arnald Puy [aut, cre] (ORCID: <https://orcid.org/0000-0001-9469-2156>) |
| Maintainer: | Arnald Puy <[email protected]> |
| License: | GPL-3 |
| Version: | 0.3.0 |
| Built: | 2026-10-17 10:26:38 UTC |
| Source: | https://github.com/arnaldpuy/softwarerisk |
Given a directed call graph (tidygraph::tbl_graph) with a node attribute for
cyclomatic complexity, this function:
computes node-level metrics (in-degree, out-degree, betweenness),
calculates a node risk score as a weighted combination of rescaled metrics,
enumerates all simple paths from entry nodes (in-degree = 0) to sink nodes (out-degree = 0),
computes path-level summaries and a path-level risk score.
calculates a gini index and the slope of risk at the path-level.
all_paths_fun( graph, alpha = 0.6, beta = 0.3, gamma = 0.1, p = 1, eps = 1e-12, complexity_col = "cyclo", weight_tol = 1e-08 )all_paths_fun( graph, alpha = 0.6, beta = 0.3, gamma = 0.1, p = 1, eps = 1e-12, complexity_col = "cyclo", weight_tol = 1e-08 )
graph |
A directed |
alpha, beta, gamma
|
Numeric non-negative weights for the risk score,
constrained such that |
p |
Numeric scalar. Power parameter for the weighted power mean.
Must be finite and lie in the interval |
eps |
Numeric. Small positive constant |
complexity_col |
Character scalar. Name of the node attribute containing
cyclomatic complexity. Default |
weight_tol |
Numeric tolerance for enforcing the weight-sum constraint.
Default |
The normalized node metrics are computed using scales::rescale() and denoted
by a tilde .
The risk score for node is computed as
the weighted power mean of normalized metrics:
where is the power-mean parameter. When this reduces to
a weighted sum (additive). In the limit , this reduces
to a weighted geometric mean, implemented with a small constant to
ensure numerical stability:
The path-level risk score is calculated as
where is the risk of the -th function in path and
is the number of functions in that path. The equation behaves like a
saturating OR-operator: is at least as large as the maximum individual
function risk and monotonically increases as more functions on the path become risky,
approaching 1 when several functions have high risk scores.
The Gini index of path is computed as
where is the mean node risk in path .
Finally, the trend of risk is defined by the slope of the regression
where is the risk score of the function at position along
path (ordered from upstream to downstream execution) and is a residual term.
The returned paths tibble includes path_cc, a list-column where each
element is the vector of per-node cyclomatic complexity values along the path.
A named list with two tibbles:
Node-level metrics with columns name, cyclomatic_complexity,
indeg (in-degree), outdeg (out-degree), btw (betweenness), risk_score.
Path-level metrics with columns path_id, path_nodes,
path_str, hops, path_risk_score, path_cc, gini_node_risk,
risk_slope, risk_mean, risk_sum
# synthetic_graph is a tidygraph::tbl_graph with node attribute "cyclo" data(synthetic_graph) # additive risk (p = 1, default) out1 <- all_paths_fun( graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, p = 1, complexity_col = "cyclo" ) # power-mean risk (p = 0 ~ weighted geometric mean) out2 <- all_paths_fun( graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, p = 0, eps = 1e-12, complexity_col = "cyclo" ) out1$nodes out1$paths# synthetic_graph is a tidygraph::tbl_graph with node attribute "cyclo" data(synthetic_graph) # additive risk (p = 1, default) out1 <- all_paths_fun( graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, p = 1, complexity_col = "cyclo" ) # power-mean risk (p = 0 ~ weighted geometric mean) out2 <- all_paths_fun( graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, p = 0, eps = 1e-12, complexity_col = "cyclo" ) out1$nodes out1$paths
Constructs the directed call graph required by all_paths_fun()
automatically, so that no manual preparation of edge lists or complexity
spreadsheets is needed for R code. Each node is a function, each edge
from -> to is a call from function from (caller) to function to
(callee), and each node carries a cyclo attribute with its cyclomatic
complexity.
call_graph_fun(pkg = NULL, dir = NULL, exclude = NULL, keep_self_loops = FALSE)call_graph_fun(pkg = NULL, dir = NULL, exclude = NULL, keep_self_loops = FALSE)
pkg |
Character scalar. Name of an installed package whose namespace
functions (exported and internal) are analyzed. Exactly one of |
dir |
Character scalar. Path to a directory containing |
exclude |
Optional character vector of function names to drop from the graph (e.g., generated or vendored code). |
keep_self_loops |
Logical. Keep self-recursion edges (a function
calling itself)? These edges do not affect the simple-path enumeration
of |
Call detection relies on codetools::findGlobals(), i.e., on static
analysis of each function body. Two limitations follow: (i) calls built
at run time (e.g., do.call(paste0("f", i), ...), get(), method
dispatch) cannot be detected; (ii) a function of the set that is merely
referenced (e.g., passed to lapply()) is treated as called, since
in a dependency sense the caller relies on it.
Cyclomatic complexity is computed internally as plus one unit per
decision point (if, for, while, repeat, &&, ||, and each
switch() alternative beyond the first), following McCabe (1976).
A directed tidygraph::tbl_graph with node attributes name and
cyclo, ready to be passed to all_paths_fun().
McCabe, T. J. (1976). A Complexity Measure. IEEE Transactions on Software Engineering, SE-2(4), 308–320. doi:10.1109/TSE.1976.233837
read_call_graph() to import a call graph prepared outside R
(e.g., for Fortran, C or Python models).
# build the call graph of a directory of R scripts td <- file.path(tempdir(), "cg_example") dir.create(td, showWarnings = FALSE) writeLines(c( "load_data <- function(x) x", "clean_data <- function(x) load_data(x)", "calc_scores <- function(x) if (length(x) > 0) mean(x) else 0", "compute_risk <- function(x) calc_scores(clean_data(x))" ), file.path(td, "model.R")) g <- call_graph_fun(dir = td) g # the result feeds directly into the pipeline out <- all_paths_fun(g, alpha = 0.6, beta = 0.3, gamma = 0.1) out$paths # build the call graph of an installed package g_pkg <- call_graph_fun(pkg = "softwareRisk") g_pkg# build the call graph of a directory of R scripts td <- file.path(tempdir(), "cg_example") dir.create(td, showWarnings = FALSE) writeLines(c( "load_data <- function(x) x", "clean_data <- function(x) load_data(x)", "calc_scores <- function(x) if (length(x) > 0) mean(x) else 0", "compute_risk <- function(x) calc_scores(clean_data(x))" ), file.path(td, "model.R")) g <- call_graph_fun(dir = td) g # the result feeds directly into the pipeline out <- all_paths_fun(g, alpha = 0.6, beta = 0.3, gamma = 0.1) out$paths # build the call graph of an installed package g_pkg <- call_graph_fun(pkg = "softwareRisk") g_pkg
Selects, under a budget of budget refactoring interventions, the set of
nodes whose fixing (risk set to 0) most reduces the path-level risk of the
system. Whereas path_fix_heatmap() shows the effect of fixing single
nodes on single paths, this function answers the budgeted question
directly: given resources to refactor functions, which
?
fix_portfolio_fun( all_paths_out, budget = 5, objective = c("total", "max"), tol = 1e-12 )fix_portfolio_fun( all_paths_out, budget = 5, objective = c("total", "max"), tol = 1e-12 )
all_paths_out |
A list produced by |
budget |
Integer. Maximum number of nodes to fix. Default |
objective |
Character scalar, |
tol |
Numeric. Minimum improvement in the objective required to
continue selecting nodes. Default |
At each step the algorithm evaluates, for every remaining candidate node, the objective obtained by adding that node to the already-selected set, fixes the best node, and repeats. Path risk is recomputed under the independence assumption used throughout the package:
with for fixed nodes. Two objectives are available:
"total": minimize , the total path risk. This
objective is monotone submodular in the set of fixed nodes, so the
greedy solution is guaranteed to achieve at least
of the reduction of the optimal set of the
same size (Nemhauser et al. 1978).
"max": minimize , the risk of the riskiest path.
The greedy search stops early if no remaining node yields an improvement
larger than tol.
A list with two elements:
portfolio: a tibble with one row per selected node, in selection
order, with columns step, node, objective_after (value of the
objective once the node is fixed), delta (improvement contributed by
the node) and cum_reduction (cumulative fraction of the initial
objective removed).
plot: a ggplot2 object showing the objective as a function
of the number of fixed nodes, with the selected nodes labelled.
Nemhauser, G. L., Wolsey, L. A., and Fisher, M. L. (1978). An analysis of approximations for maximizing submodular set functions–I. Mathematical Programming, 14(1), 265–294. doi:10.1007/BF01588971
path_fix_heatmap() for the node-by-path improvement matrix and
node_exposure_fun() for path-aware node criticality.
data(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") res <- fix_portfolio_fun(out, budget = 5) res$portfolio res$plotdata(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") res <- fix_portfolio_fun(out, budget = 5) res$portfolio res$plot
Computes the Gini index (a measure of inequality) for a numeric vector.
Non-finite (NA, NaN, Inf) values are removed prior to computation.
If fewer than two finite values remain, or if all finite values are zero
(no inequality is measurable), the function returns 0.
gini_index_fun(x)gini_index_fun(x)
x |
Numeric vector. |
The Gini index ranges from 0 (perfect equality) to 1 (maximal inequality).
A numeric scalar giving the Gini index of x.
gini_index_fun(c(1, 1, 1, 1)) gini_index_fun(c(1, 2, 3, 4)) gini_index_fun(c(NA, 1, 2, Inf, 3))gini_index_fun(c(1, 1, 1, 1)) gini_index_fun(c(1, 2, 3, 4)) gini_index_fun(c(NA, 1, 2, Inf, 3))
Summarizes, for each node, its participation in the entry-to-sink paths
enumerated by all_paths_fun(). Whereas the node risk score captures how
error-prone a function is in isolation, the risk load captures how many
risky execution chains depend on it: a function of moderate complexity can
still be critical if most high-risk paths route through it.
node_exposure_fun(all_paths_out)node_exposure_fun(all_paths_out)
all_paths_out |
A list produced by |
For node the function computes:
n_paths: the number of paths that contain ,
path_share: n_paths divided by the total number of paths,
risk_load: , the sum of
path_risk_score over the paths containing ,
risk_load_share: risk_load divided by ,
mean_path_risk: the mean path_risk_score of the paths
containing .
The output also reports the rank of each node by risk_load and by its
standalone risk_score, so that structurally exposed functions can be
told apart from merely complex ones: a node ranking much higher on
risk_load than on risk_score is a chokepoint rather than a hotspot.
A tibble with one row per node appearing on at least one path,
sorted by decreasing risk_load, with columns name, risk_score,
n_paths, path_share, risk_load, risk_load_share,
mean_path_risk, rank_risk_load and rank_risk_score. Nodes on no
path (e.g., disconnected helpers) are omitted. If all_paths_out$paths
is empty, an empty tibble with these columns is returned.
path_fix_heatmap() for the per-path effect of fixing single
nodes, and fix_portfolio_fun() for a budgeted selection of fixes.
data(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") node_exposure_fun(out)data(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") node_exposure_fun(out)
Compute how much the risk score of the riskiest paths would decrease if selected high-risk nodes were made perfectly reliable (risk fixed to 0), and visualise the result as a heatmap.
path_fix_heatmap(all_paths_out, n_nodes = 20, k_paths = 20)path_fix_heatmap(all_paths_out, n_nodes = 20, k_paths = 20)
all_paths_out |
A list returned by |
n_nodes |
Integer, number of top-risk nodes (by |
k_paths |
Integer, number of top-risk paths (by |
For each of the top n_nodes nodes ranked by risk_score and
the top k_paths paths ranked by path_risk_score, the function
sets the risk of that node to 0 along the path (for all its occurrences)
and recomputes the path risk score under the independence assumption,
using
The improvement
is used as the fill value in the heatmap cells.
Bright cells indicate nodes that act as chokepoints for a given path. Rows with many bright cells correspond to nodes whose refactoring would improve many risky paths (global chokepoints), while columns with a few very bright cells correspond to paths dominated by a single risky node.
A list with two elements:
delta_tbl: a tibble with columns node, path_id
and deltaR, containing the reduction in path risk score
when fixing the node in that path.
plot: a ggplot2 object containing the heatmap.
data(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") res <- path_fix_heatmap(all_paths_out = out, n_nodes = 20, k_paths = 20) resdata(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") res <- path_fix_heatmap(all_paths_out = out, n_nodes = 20, k_paths = 20) res
Plot the top n_paths paths ranked by their mean risk score,
with horizontal error bars representing the uncertainty range
(minimum and maximum risk) computed from the Monte Carlo samples
stored in uncertainty_analysis.
path_uncertainty_plot(ua_sa_out, n_paths = 20)path_uncertainty_plot(ua_sa_out, n_paths = 20)
ua_sa_out |
A list returned by |
n_paths |
Integer, number of top paths (by mean risk) to include in the plot. Defaults to 20. |
This function is designed to work with the paths component of
the output of uncertainty_fun(). For each path, it summarises the
vector of path risk values by computing the mean, minimum and maximum values, and then displays
these summaries for the n_paths most risky paths.
A ggplot2 object.
data(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") results <- uncertainty_fun(all_paths_out = out, N = 2^10, order = "first") path_uncertainty_plot(ua_sa_out = results, n_paths = 20)data(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") results <- uncertainty_fun(all_paths_out = out, N = 2^10, order = "first") path_uncertainty_plot(ua_sa_out = results, n_paths = 20)
Visualizes the most risky entry-to-sink paths (by decreasing path_risk_score)
computed by all_paths_fun(). Edges that occur on the top paths are
highlighted, with edge colour mapped to the mean path risk and edge width
mapped to the number of top paths using that edge. Nodes on the top paths
are emphasized, with node size mapped to in-degree and node fill mapped to
binned cyclomatic complexity.
plot_top_paths_fun( graph, all_paths_out, model.name = "", language = "", top_n = 10, alpha_non_top = 0.05 )plot_top_paths_fun( graph, all_paths_out, model.name = "", language = "", top_n = 10, alpha_non_top = 0.05 )
graph |
A directed |
all_paths_out |
Output from |
model.name |
Character scalar used in the plot title (e.g., model name). |
language |
Character scalar used in the plot title (e.g., language name). |
top_n |
Integer. Number of highest-risk paths to display (default 10). |
alpha_non_top |
Numeric between 0 and 1. Alpha (transparency) for edges that are not on the top-risk paths. Smaller values fade background edges more. |
The function selects the top_n paths by sorting paths_tbl on
path_risk_score (descending). For those paths, it:
builds an edge list from path_nodes,
marks graph edges that appear on at least one top path,
computes path_freq (how many top paths include each edge),
computes risk_mean_path (mean of risk_sum across top paths that
include each edge),
highlights nodes that appear on any top path.
Node fills are based on cyclomatic_complexity using breaks
(-Inf, 10], (10, 20], (20, 50], (50, Inf] as per Watson & McCabe (1996).
A ggplot object (invisibly). The plot is also printed as a side effect.
Watson, A. H. and McCabe, T. J. (1996). Structured Testing: A Testing Methodology Using the Cyclomatic Complexity Metric. NIST Special Publication 500-235, National Institute of Standards and Technology, Gaithersburg, MD. doi:10.6028/NIST.SP.500-235
data(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") p <- plot_top_paths_fun(synthetic_graph, out, model.name = "MyModel", language = "R", top_n = 10) pdata(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") p <- plot_top_paths_fun(synthetic_graph, out, model.name = "MyModel", language = "R", top_n = 10) p
Quantifies how stable the identification of the top- riskiest paths
(or nodes) is across the uncertainty draws produced by uncertainty_fun().
Every draw corresponds to one plausible definition of risk (one sampled
combination of the weights and the exponent
); ranking the paths within each draw therefore shows which paths
are flagged as risky regardless of how risk is weighted and which ones
enter the top- only under specific assumptions.
rank_robustness_fun(ua_sa_out, top_k = 10, what = c("paths", "nodes"))rank_robustness_fun(ua_sa_out, top_k = 10, what = c("paths", "nodes"))
ua_sa_out |
A list returned by |
top_k |
Integer. Size of the top set whose membership is tracked.
Clamped to the number of items with a message if larger. Default |
what |
Character scalar, |
For each item (path or node) the function reports the probability of
belonging to the top- across draws and the median and the 5th and
95th percentiles of its rank (rank 1 = riskiest; ties share the minimum
rank). As a global summary it also reports the mean Spearman correlation
between the risk values of each draw and the consensus (mean across
draws) risk values: values close to 1 indicate that the ranking barely
responds to the risk definition, values well below 1 that conclusions
depend on it.
Draws that are NA for an item are treated as never placing that item in
the top-.
A list with:
summary: a tibble with one row per item, sorted by decreasing
top_k_prob, with columns id (path path_id or node name),
path_str (paths only), mean_risk, top_k_prob, rank_median,
rank_q05 and rank_q95.
consensus_correlation: mean Spearman correlation between each
draw and the consensus risk values.
top_k, what: the settings used.
rank_robustness_plot() to visualize the result and
uncertainty_fun() to generate the draws.
data(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") results <- uncertainty_fun(all_paths_out = out, N = 2^7, order = "first") rr <- rank_robustness_fun(results, top_k = 10, what = "paths") rr$summary rr$consensus_correlationdata(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") results <- uncertainty_fun(all_paths_out = out, N = 2^7, order = "first") rr <- rank_robustness_fun(results, top_k = 10, what = "paths") rr$summary rr$consensus_correlation
Displays, for the items most often ranked in the top-, the
probability of top- membership across the uncertainty draws
computed by rank_robustness_fun(). Items with probability close to 1
are flagged as risky under essentially any risk definition; intermediate
probabilities identify items whose criticality depends on how the risk
score is weighted.
rank_robustness_plot(rank_out, top_n = 20)rank_robustness_plot(rank_out, top_n = 20)
rank_out |
A list returned by |
top_n |
Integer. Number of items (by decreasing |
A ggplot2 object.
data(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") results <- uncertainty_fun(all_paths_out = out, N = 2^7, order = "first") rr <- rank_robustness_fun(results, top_k = 10, what = "paths") rank_robustness_plot(rr, top_n = 20)data(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") results <- uncertainty_fun(all_paths_out = out, N = 2^7, order = "first") rr <- rank_robustness_fun(results, top_k = 10, what = "paths") rank_robustness_plot(rr, top_n = 20)
Builds the directed call graph required by all_paths_fun() from the two
datasets described in the package vignette: an edge list of function calls
and a table of per-function cyclomatic complexity. The input is validated
so that common preparation errors (misspelled columns, functions missing
from the complexity table, duplicated or non-numeric entries) fail early
with an informative message. This is the recommended entry point for
models written in languages other than R (e.g., Fortran, C, Python),
whose edge lists and complexity values are extracted with external tools.
read_call_graph( edges, metrics, from_col = "from", to_col = "to", name_col = "name", complexity_col = "cyclo" )read_call_graph( edges, metrics, from_col = "from", to_col = "to", name_col = "name", complexity_col = "cyclo" )
edges |
A data.frame (or path to a |
metrics |
A data.frame (or path to a |
from_col, to_col
|
Character scalars. Names of the caller and callee
columns in |
name_col |
Character scalar. Name of the function-name column in
|
complexity_col |
Character scalar. Name of the complexity column in
|
A directed tidygraph::tbl_graph with node attributes name and
complexity_col, ready to be passed to all_paths_fun().
call_graph_fun() to build the graph automatically from R
source code.
calls_df <- data.frame( from = c("clean_data", "compute_risk", "compute_risk", "calc_scores"), to = c("load_data", "clean_data", "calc_scores", "trim_mean") ) cyclo_df <- data.frame( name = c("clean_data", "load_data", "compute_risk", "calc_scores", "trim_mean"), cyclo = c(6, 3, 12, 5, 2) ) g <- read_call_graph(edges = calls_df, metrics = cyclo_df) gcalls_df <- data.frame( from = c("clean_data", "compute_risk", "compute_risk", "calc_scores"), to = c("load_data", "clean_data", "calc_scores", "trim_mean") ) cyclo_df <- data.frame( name = c("clean_data", "load_data", "compute_risk", "calc_scores", "trim_mean"), cyclo = c(6, 3, 12, 5, 2) ) g <- read_call_graph(edges = calls_df, metrics = cyclo_df) g
Visualizes the Sobol' indices stored in the sensitivity_analysis
list-column returned by uncertainty_fun(), showing how much of the
variance in the node risk scores is driven by each parameter of the risk
definition (the weights , , and the
power-mean exponent ).
sensitivity_plot_fun(ua_sa_out, nodes = NULL, index = c("both", "Si", "Ti"))sensitivity_plot_fun(ua_sa_out, nodes = NULL, index = c("both", "Si", "Ti"))
ua_sa_out |
A list returned by |
nodes |
Optional character vector of node names to display
individually. Default |
index |
Character scalar: |
Two displays are available:
With nodes = NULL (default), the distribution of the indices
across all nodes is shown as one boxplot per parameter, answering
the system-level question of which assumption dominates the risk
ranking overall.
With nodes set to a character vector of node names, the indices
of those nodes are shown as dodged bars with their confidence
intervals, one panel per node.
Nodes whose risk score does not vary across draws (e.g., all metrics zero) yield non-finite indices; these are dropped with a message.
A ggplot2 object.
Puy, A., Lo Piano, S., Saltelli, A., and Levin, S. A. (2022). sensobol: An R Package to Compute Variance-Based Sensitivity Indices. Journal of Statistical Software, 102(5), 1–37. doi:10.18637/jss.v102.i05
uncertainty_fun() to generate the indices and the
sensobol package (Puy et al. 2022) for their estimation.
data(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") results <- uncertainty_fun(all_paths_out = out, N = 2^7, order = "first") # system-level view: which assumption drives the risk scores? sensitivity_plot_fun(results) # node-level view sensitivity_plot_fun(results, nodes = results$nodes$name[1:4])data(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") results <- uncertainty_fun(all_paths_out = out, N = 2^7, order = "first") # system-level view: which assumption drives the risk scores? sensitivity_plot_fun(results) # node-level view sensitivity_plot_fun(results, nodes = results$nodes$name[1:4])
Computes the slope of a simple linear regression of a numeric vector
against its index (seq_along(x)). Non-finite (NA, NaN, Inf) values
are removed prior to computation, but the remaining values keep their
original positions as the regressor, so removing values does not distort
the trend. If fewer than two finite values remain, the function returns 0.
slope_fun(x)slope_fun(x)
x |
Numeric vector. |
The slope is estimated from the model
,
where . The function returns the estimated slope
.
This summary is useful for characterizing monotonic trends in ordered risk values along a path.
A numeric scalar giving the slope of the fitted linear trend.
slope_fun(c(1, 2, 3, 4)) slope_fun(c(4, 3, 2, 1)) slope_fun(c(NA, 1, 2, Inf, 3))slope_fun(c(1, 2, 3, 4)) slope_fun(c(4, 3, 2, 1)) slope_fun(c(NA, 1, 2, Inf, 3))
A synthetic directed graph with cyclomatic complexity values to illustrate the functions of the package.
data(synthetic_graph)data(synthetic_graph)
A tbl_graph with:
55 nodes
122 directed edges
The graph is stored as a tbl_graph object with:
Node attributes: name, cyclo
Directed edges defined by from → to
theme_AP() provides a minimalist, publication-ready theme based on
ggplot2::theme_bw(), with grid lines removed, compact legends, and
harmonized text sizes. It is designed for dense network and path-visualization
plots (e.g. call graphs, risk paths).
theme_AP()theme_AP()
The theme:
removes major and minor grid lines,
uses transparent legend backgrounds and keys,
standardizes text sizes for axes, legends, strips, and titles,
reduces legend spacing for compact layouts.
This theme is intended to be composable:
it should be added to a ggplot object using + theme_AP().
A ggplot2::theme object.
ggplot2::ggplot(mtcars, ggplot2::aes(mpg, wt)) + ggplot2::geom_point() + theme_AP()ggplot2::ggplot(mtcars, ggplot2::aes(mpg, wt)) + ggplot2::geom_point() + theme_AP()
Runs a full variance-based uncertainty and sensitivity analysis (UA/SA) for node
risk scores using the results returned by all_paths_fun() and the functions
provided by the sensobol package (Puy et al. 2022).
uncertainty_fun(all_paths_out, N, order, eps = 1e-12)uncertainty_fun(all_paths_out, N, order, eps = 1e-12)
all_paths_out |
A list produced by |
N |
Integer. Base sample size used for Sobol' matrices. |
order |
Passed to |
eps |
Numeric. Small positive constant |
Uncertainty is induced by jointly sampling the weights
(renormalized to sum to 1) and the power parameter
used in the node-risk definition:
For each node, risk scores are repeatedly recalculated using the sampled parameter
combinations, producing a distribution of possible outcomes. Sobol' first-order
and/or total-order sensitivity indices are then computed for all four parameters
(, , , and ), quantifying how much of
the variance in the node risk score is attributable to each parameter.
Parameter labels and the Sobol' design.
Internally the design samples four independent values
(a_raw, b_raw, c_raw, p_raw) because the Sobol' quasi-random sequence
requires independent uniform inputs. Before evaluating the risk model, the raw
draws are transformed: the three weight draws are normalised to sum to one,
yielding , , ; and p_raw is mapped linearly
to . The sensitivity indices are then attributed to the
transformed parameters and the output labels them as alpha, beta, gamma,
and p rather than the internal raw names, so the results are directly
interpretable in terms of the model parameters.
Path-level uncertainty is obtained by propagating node-level uncertainty draws through the path aggregation function:
where are node risks along path .
All uncertainty metrics are computed from the first N Sobol draws (matrix A), while sensitivity indices use the full Sobol' design.
For more information about the uncertainty and sensitivity analysis and the output of this function, see the sensobol package (Puy et al. 2022).
The returned node table includes the following columns:
name: name of the node.
uncertainty_analysis: numeric vector of length giving the
uncertainty draws in the node risk score (from Sobol matrix A).
sensitivity_analysis: object returned by sensobol::sobol_indices()
for that node, containing Sobol' indices labelled alpha, beta, gamma,
and p. These correspond to the normalised weights and the power-mean
exponent respectively. The indices are computed on the transformed
parameters (see Details).
The returned paths table includes:
path_id: path identifier.
path_str: sequence of function calls for each path.
hops: number of edges.
uncertainty_analysis: numeric vector giving the uncertainty draws in the path risk score.
gini_index: numeric vector giving the uncertainty draws in the gini index.
risk_trend: numeric vector giving the uncertainty draws in the risk trend.
A named list with:
A tibble of node results.
A tibble of path results. If all_paths_out$paths is empty
(e.g., the graph has no entry-to-sink paths), an empty tibble with the
same columns is returned.
Puy, A., Lo Piano, S., Saltelli, A., and Levin, S. A. (2022). sensobol: An R Package to Compute Variance-Based Sensitivity Indices. Journal of Statistical Software, 102(5), 1–37. doi:10.18637/jss.v102.i05
data(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") # Power-mean risk (increase N to at least 2^10 for a proper UA/SA) results <- uncertainty_fun(all_paths_out = out, N = 2^2, order = "first") results$nodes results$pathsdata(synthetic_graph) out <- all_paths_fun(graph = synthetic_graph, alpha = 0.6, beta = 0.3, gamma = 0.1, complexity_col = "cyclo") # Power-mean risk (increase N to at least 2^10 for a proper UA/SA) results <- uncertainty_fun(all_paths_out = out, N = 2^2, order = "first") results$nodes results$paths