| Title: | Optimisation of the Analysis of AND-OR Decision Trees |
|---|---|
| Description: | A decision support tool to strategically prioritise evidence gathering in complex, hierarchical AND-OR decision trees. It is designed for situations with incomplete or uncertain information where the goal is to reach a confident conclusion as efficiently as possible (responding to the minimum number of questions, and only spending resources on generating improved evidence when it is of significant value to the final decision). The framework excels in complex analyses with multiple potential successful pathways to a conclusion ('OR' nodes). Key features include a dynamic influence index to guide users to the most impactful question, a system for propagating answers and semi-quantitative confidence scores (0-5) up the tree, and post-conclusion guidance to identify the best actions to increase the final confidence. These components are brought together in an interactive command-line workflow that guides the analysis from start to finish. |
| Authors: | Angus R Cameron [aut, cre] (ORCID: <https://orcid.org/0000-0001-8801-0366>), EpiMundi [cph, fnd] |
| Maintainer: | Angus R Cameron <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.3.1 |
| Built: | 2026-05-21 08:22:06 UTC |
| Source: | https://github.com/epimundi/andorr |
Iteratively prompts the user to answer questions to solve a decision tree. The function first presents the most impactful unanswered questions. Once the tree's root is solved, it presents questions that can increase the overall confidence of the conclusion.
andorR_interactive(tree, sort_by = "BOTH")andorR_interactive(tree, sort_by = "BOTH")
tree |
The |
sort_by |
A character string indicating how the prioritised questions should be sorted. Options are:
|
This function provides a command-line interface (CLI) for working with the
tree. It uses the cli package for formatted output and handles user input
for quitting, saving, printing the tree state, or providing answers to
specific questions (either by number or by name). All tree modifications are
performed by calling the package's existing API functions:
set_answer()
update_tree()
get_highest_influence()
get_confidence_boosters()
The following key commands may be used during interactive mode:
h : Show the help screen
p : Print the current state of the tree
s : Save the current state of the tree to an .rds file
q : Quit (exit interactive mode)
n : Specify a node to edit by name (case sensitive)
1, 2, ... : Specify a node to edit from the numbered list
The final, updated data.tree object.
# Load a tree ethical_tree <- load_tree_df(ethical) # Start interactive mode if(interactive()){ andorR_interactive(ethical_tree) }# Load a tree ethical_tree <- load_tree_df(ethical) # Start interactive mode if(interactive()){ andorR_interactive(ethical_tree) }
This function performs a full, bottom-up recalculation of the decision tree's
state. It takes the user-provided answers and confidences at the leaf level
and propagates the logical outcomes (answer) and aggregate confidence scores
up to the parent nodes based on their AND/OR rules.
calculate_tree(tree)calculate_tree(tree)
tree |
The |
This function is one of three called by update_tree(), which does a full
recalculation of the decision tree result and optimisation indices.
The function first resets the answer and confidence of all non-leaf nodes
to NA to ensure a clean calculation.
It then uses a post-order traversal, which is critical as it guarantees that a parent node is only processed after all of its children have been processed.
The logical rules are applied with short-circuiting:
Become TRUE if any child is TRUE. Become
FALSE only if all children are answered and none are TRUE.
Become FALSE if any child is FALSE.
Become TRUE only if all children are answered and none are FALSE.
The confidence calculation is based on the confidences of the children that
determined the outcome (e.g., only the TRUE children for a resolved OR node).
The modified tree object (returned invisibly).
# Load the data ethical_tree <- load_tree_df(ethical) # Answer some questions set_answer(ethical_tree, "FIN2", TRUE, 4) set_answer(ethical_tree, "ENV2", TRUE, 3) set_answer(ethical_tree, "SOC2", TRUE, 4) set_answer(ethical_tree, "GOV2", FALSE, 1) # Calculate the tree ethical_tree <- calculate_tree(ethical_tree) # View the result print_tree(ethical_tree)# Load the data ethical_tree <- load_tree_df(ethical) # Answer some questions set_answer(ethical_tree, "FIN2", TRUE, 4) set_answer(ethical_tree, "ENV2", TRUE, 3) set_answer(ethical_tree, "SOC2", TRUE, 4) set_answer(ethical_tree, "GOV2", FALSE, 1) # Calculate the tree ethical_tree <- calculate_tree(ethical_tree) # View the result print_tree(ethical_tree)
This dataframe represents a decision tree in relational format, in which hierarchical relationships are indicated by a value indicating the parent of each node.
The decision tree is a hypothetical tool to standardise the process of making ethical investments. It was developed to illustrate the functionality of this package.
ethicalethical
A data frame with 5 variables and 34 rows
Each row represents a node or leaf in the tree and the columns represent attributes of those nodes. The columns are:
A unique sequential numeric identifier for each node
A short, unique alphanumeric code or name for nodes. For leaf nodes (questions), a short code is used. For higher nodes, a descriptive phrase is used.
The full text of the question for leaves, or NA for higher nodes.
The logical rule for nodes, either AND or OR, and NA for leaves.
The numeric id of the parent node, and NA for the root node.
A data.tree object is created from the dataframe using the read_tree_df()
function.
This is a simple hypothetical decision tree created solely to illustrate the use of the analytical approach.
# Read the data into a data.tree object for analysis tree <- load_tree_df(ethical) # View the tree print_tree(tree)# Read the data into a data.tree object for analysis tree <- load_tree_df(ethical) # View the tree print_tree(tree)
This dataframe represents a decision tree in hierarchical format, in which hierarchical relationships are indicated by a nested list
The decision tree is a hypothetical tool to standardise the process of making ethical investments. It was developed to illustrate the functionality of this package.
ethical_nlethical_nl
A nested node list of the ethical investment dataset, in which hierarchical relationships are indicated by a nested list
Each list element represents a node or leaf in the tree and has the following members:
A short, unique alphanumeric code or name for nodes. For leaf nodes (questions), a short code is used. For higher nodes, a descriptive phrase is used.
The logical rule for nodes, either AND or OR, and NA for leaves.
(Optional) For leaf nodes, the associated question.
A list of nested nodes
A data.tree object is created from the nested list using the
read_tree_node_list() function.
This is a simple hypothetical decision tree created solely to illustrate the use of the analytical approach.
# Read the data into a data.tree object for analysis tree <- load_tree_node_list(ethical_nl) # View the tree print_tree(tree)# Read the data into a data.tree object for analysis tree <- load_tree_node_list(ethical_nl) # View the tree print_tree(tree)
Performs a sensitivity analysis on the tree to find which actions (answering a new question or increasing confidence in an old one) will have the greatest positive impact on the root node's final confidence score.
get_confidence_boosters(tree, top_n = 5, verbose = TRUE)get_confidence_boosters(tree, top_n = 5, verbose = TRUE)
tree |
The current data.tree object, typically after a conclusion is reached. |
top_n |
The number of suggestions to return. |
verbose |
Logical value (default TRUE) determining the level of output. |
A data.frame of the top_n suggested actions, ranked by potential gain.
# Load a tree ethical_tree <- load_tree_df(ethical) # Answer some questions set_answer(ethical_tree, "FIN2", TRUE, 4) set_answer(ethical_tree, "FIN4", TRUE, 3) set_answer(ethical_tree, "FIN5", TRUE, 2) set_answer(ethical_tree, "ENV5", TRUE, 3) set_answer(ethical_tree, "SOC2", TRUE, 4) set_answer(ethical_tree, "GOV1", TRUE, 1) set_answer(ethical_tree, "GOV2", TRUE, 2) set_answer(ethical_tree, "GOV3", TRUE, 1) set_answer(ethical_tree, "GOV4", TRUE, 1) set_answer(ethical_tree, "GOV5", TRUE, 1) # Updated tree ethical_tree <- update_tree(ethical_tree) # View the tree print_tree(ethical_tree) # Get guidance on how to improve the confidence --- guidance <- get_confidence_boosters(ethical_tree, verbose = FALSE) print(guidance)# Load a tree ethical_tree <- load_tree_df(ethical) # Answer some questions set_answer(ethical_tree, "FIN2", TRUE, 4) set_answer(ethical_tree, "FIN4", TRUE, 3) set_answer(ethical_tree, "FIN5", TRUE, 2) set_answer(ethical_tree, "ENV5", TRUE, 3) set_answer(ethical_tree, "SOC2", TRUE, 4) set_answer(ethical_tree, "GOV1", TRUE, 1) set_answer(ethical_tree, "GOV2", TRUE, 2) set_answer(ethical_tree, "GOV3", TRUE, 1) set_answer(ethical_tree, "GOV4", TRUE, 1) set_answer(ethical_tree, "GOV5", TRUE, 1) # Updated tree ethical_tree <- update_tree(ethical_tree) # View the tree print_tree(ethical_tree) # Get guidance on how to improve the confidence --- guidance <- get_confidence_boosters(ethical_tree, verbose = FALSE) print(guidance)
Scans all leaf nodes in the tree to find the questions that
currently have the highest influence_index.
get_highest_influence(tree, top_n = 5, sort_by = "BOTH")get_highest_influence(tree, top_n = 5, sort_by = "BOTH")
tree |
The main |
top_n |
The number of top-ranked questions to return. |
sort_by |
A character string indicating how the prioritised questions should be sorted. Options are:
|
A data.frame (tibble) containing the name, question, the
components of the influence index (influence_if_true, influence_if_false),
and the total influence_index for the highest-influence leaf/leaves,
sorted by influence.
Traverses the tree to find all leaf nodes (questions) and compiles their key attributes into a single, tidy data frame. This is useful for getting a complete overview of the analysis state or for creating custom reports.
get_questions(tree)get_questions(tree)
tree |
The |
A data.frame with one row for each leaf node and the following
columns: name, question, answer, confidence (on a 0-5 scale),
and influence_index.
# Load the example 'ethical' dataset data(ethical) # Build and initialise the tree object ethical_tree <- load_tree_df(ethical) ethical_tree <- update_tree(ethical_tree) # Get the summary data frame of all questions questions_df <- get_questions(ethical_tree) # Display the first few rows head(questions_df)# Load the example 'ethical' dataset data(ethical) # Build and initialise the tree object ethical_tree <- load_tree_df(ethical) ethical_tree <- update_tree(ethical_tree) # Get the summary data frame of all questions questions_df <- get_questions(ethical_tree) # Display the first few rows head(questions_df)
Reads a CSV file from a given path and constructs a tree. This
function expects the CSV to define the tree in a relational
format with id and parent columns defining the hierarchy and name,
question (for leaves) and rule (for nodes) columns for the decision
tree attributes.
load_tree_csv(file_path)load_tree_csv(file_path)
file_path |
The path to the .csv file. |
A data.tree object, fully constructed and initialised with answer
and confidence attributes set to NA.
load_tree_df() for the underlying constructor function.
# Load data from the `ethical.csv` file included with this package path <- system.file("extdata", "ethical.csv", package = "andorR") ethical_tree <- load_tree_csv(path) # View the tree print_tree(ethical_tree)# Load data from the `ethical.csv` file included with this package path <- system.file("extdata", "ethical.csv", package = "andorR") ethical_tree <- load_tree_csv(path) # View the tree print_tree(ethical_tree)
Reads a CSV file from a given path and constructs a tree. This
function expects the CSV to define the tree in a path string format, with
each node's hierarchy defined in a column named path.
load_tree_csv_path(file_path, delim = "/")load_tree_csv_path(file_path, delim = "/")
file_path |
The path to the .csv file. |
delim |
The character used to separate nodes in the path string. Defaults to "/". |
A data.tree object.
load_tree_df_path() for the underlying constructor function.
#' # Load data from the `ethical_path.csv` file included with this package path <- system.file("extdata", "ethical_path.csv", package = "andorR") ethical_tree <- load_tree_csv_path(path) # View the tree print_tree(ethical_tree)#' # Load data from the `ethical_path.csv` file included with this package path <- system.file("extdata", "ethical_path.csv", package = "andorR") ethical_tree <- load_tree_csv_path(path) # View the tree print_tree(ethical_tree)
Constructs and initialises a tree from a data frame that is already in memory, where the hierarchy is defined in a relational (ID/parent) format.
load_tree_df(df)load_tree_df(df)
df |
A data frame with columns: id, name, question, rule, parent. |
This is a core constructor function. It may be used to load one of
the example datasets in relational format. It is called by the
load_tree_csv() wrapper, which handles reading the data from a file.
A data.tree object, fully constructed and initialised with answer
and confidence attributes set to NA.
load_tree_csv() to read this format from a file.
# Load a tree from the 'ethical' dataframe included in this package ethical_tree <- load_tree_df(ethical) # View the tree structure ## Not run: print_tree(ethical_tree) ## End(Not run)# Load a tree from the 'ethical' dataframe included in this package ethical_tree <- load_tree_df(ethical) # View the tree structure ## Not run: print_tree(ethical_tree) ## End(Not run)
Constructs a tree from a data frame that is already in memory, where the hierarchy is defined using a path string for each node (e.g., "Root/Branch/Leaf").
load_tree_df_path(df, delim = "/")load_tree_df_path(df, delim = "/")
df |
A data frame with a column named |
delim |
The character used to separate nodes in the path string. Defaults to "/". |
This is a core constructor function, typically called by a wrapper
like load_tree_csv_path(), which handles reading the data from a file.
The node's name is inferred from the last element of its path.
A data.tree object.
load_tree_csv_path() to read this format from a file.
# Create a sample data frame in path format path_df <- data.frame( path = c("Root", "Root/Branch1", "Root/Branch1/LeafA", "Root/Branch2"), rule = c("AND", "OR", NA, NA), question = c(NA, "Is Branch1 relevant?", "Is LeafA true?", "Is Branch2 true?") ) # Build the tree my_tree <- load_tree_df_path(path_df) print(my_tree)# Create a sample data frame in path format path_df <- data.frame( path = c("Root", "Root/Branch1", "Root/Branch1/LeafA", "Root/Branch2"), rule = c("AND", "OR", NA, NA), question = c(NA, "Is Branch1 relevant?", "Is LeafA true?", "Is Branch2 true?") ) # Build the tree my_tree <- load_tree_df_path(path_df) print(my_tree)
Reads a JSON file from a given path and constructs a tree. This
function expects the JSON to define the tree in a hierarchical (nested)
format. It uses load_tree_node_list to construct the tree object.
load_tree_json(file_path)load_tree_json(file_path)
file_path |
The path to the .jsn or .json file. |
A data.tree object, fully constructed and initialised with answer
and confidence attributes set to NA.
load_tree_node_list() for the underlying constructor function.
#' # Load data from the `ethical.json` file included with this package path <- system.file("extdata", "ethical.json", package = "andorR") ethical_tree <- load_tree_json(path) # View the tree print_tree(ethical_tree)#' # Load data from the `ethical.json` file included with this package path <- system.file("extdata", "ethical.json", package = "andorR") ethical_tree <- load_tree_json(path) # View the tree print_tree(ethical_tree)
Constructs a tree from a nested R list, where the hierarchy is
defined by the list's structure. It also initialises the answer and
confidence attributes required for the analysis.
load_tree_node_list(data_list)load_tree_node_list(data_list)
data_list |
A nested R list representing the tree structure. Each list
element should have a |
This is a core constructor function, typically called by the
load_tree_yaml() wrapper, which handles parsing the YAML file into a list.
A data.tree object, fully constructed and initialised with answer
and confidence attributes set to NA.
load_tree_yaml() to read this format from a file.
# 1. Define the tree structure as a nested list my_data_list <- list( name = "Root", rule = "OR", nodes = list( list(name = "Leaf A", question = "Is A true?"), list(name = "Branch B", rule = "AND", nodes = list( list(name = "Leaf B1", question = "Is B1 true?"), list(name = "Leaf B2", question = "Is B2 true?") ) ) ) ) # 2. Build the tree from the list my_tree <- load_tree_node_list(my_data_list) # 3. Print the resulting tree print_tree(my_tree)# 1. Define the tree structure as a nested list my_data_list <- list( name = "Root", rule = "OR", nodes = list( list(name = "Leaf A", question = "Is A true?"), list(name = "Branch B", rule = "AND", nodes = list( list(name = "Leaf B1", question = "Is B1 true?"), list(name = "Leaf B2", question = "Is B2 true?") ) ) ) ) # 2. Build the tree from the list my_tree <- load_tree_node_list(my_data_list) # 3. Print the resulting tree print_tree(my_tree)
Reads a YAML file from a given path and constructs a tree. This
function expects the YAML to define the tree in a hierarchical (nested)
format. It uses load_tree_node_list to construct the tree object.
load_tree_yaml(file_path)load_tree_yaml(file_path)
file_path |
The path to the .yml or .yaml file. |
A data.tree object, fully constructed and initialised with answer
and confidence attributes set to NA.
load_tree_node_list() for the underlying constructor function.
#' # Load data from the `ethical.yml` file included with this package path <- system.file("extdata", "ethical.yml", package = "andorR") ethical_tree <- load_tree_yaml(path) # View the tree print_tree(ethical_tree)#' # Load data from the `ethical.yml` file included with this package path <- system.file("extdata", "ethical.yml", package = "andorR") ethical_tree <- load_tree_yaml(path) # View the tree print_tree(ethical_tree)
Displays a clean, perfectly aligned, color-coded summary of the tree's
current state, based on pre-calculated answer attributes.
print_tree(tree)print_tree(tree)
tree |
The |
An alternative approach to inspect internal attributes is to use the
data.tree print() function with named attributes. See the example below.
Available attributes include:
rule : AND or OR for a node
name : The name of the node or leaf
question : The question for leaves
answer : The response provided for leaves or the calculated status of nodes
confidence : The confidence score provided for leaves (0 - 5) or the probability that the answer is correct (50% to 100%) for nodes
true_index : Influence the node has on the overall conclusion, if the response is TRUE
false_index : Influence the node has on the overall conclusion, if the response is FALSE
influence_if_true: Influence the leaf has on the overall conclusion, if the response is TRUE. This is the product of the ancestor values of true_index
influence_if_false: Influence the leaf has on the overall conclusion, if the response is FALSE. This is the product of the ancestor values of false_index
influence_index : The sum of influence_if_true and influence_if_false for each unanswered leaf
The original tree object (returned invisibly).
# Load a tree ethical_tree <- load_tree_df(ethical) # View the tree - initially all 'plain' as no answers print_tree(ethical_tree) # Set an answer for leaf 'FIN2' and update the tree ethical_tree <- set_answer(ethical_tree, "FIN2", TRUE, 3) ethical_tree <- update_tree(ethical_tree) # Crucial: update the tree to propagate answers print_tree(ethical_tree) # Alternative approach to inspect internal attributes using `data.tree::print() # First, recalculate the internal indices update_tree(ethical_tree) # Then print the tree, renaming column headings if required print(ethical_tree, "rule", "true_index", "false_index", influence = "influence_index")# Load a tree ethical_tree <- load_tree_df(ethical) # View the tree - initially all 'plain' as no answers print_tree(ethical_tree) # Set an answer for leaf 'FIN2' and update the tree ethical_tree <- set_answer(ethical_tree, "FIN2", TRUE, 3) ethical_tree <- update_tree(ethical_tree) # Crucial: update the tree to propagate answers print_tree(ethical_tree) # Alternative approach to inspect internal attributes using `data.tree::print() # First, recalculate the internal indices update_tree(ethical_tree) # Then print the tree, renaming column headings if required print(ethical_tree, "rule", "true_index", "false_index", influence = "influence_index")
This is the primary function for providing evidence to the tree. It finds a
specific leaf node by its name and updates its answer and confidence
attributes based on user input.
set_answer(tree, node_name, response, confidence_level, verbose = TRUE)set_answer(tree, node_name, response, confidence_level, verbose = TRUE)
tree |
The |
node_name |
A character string specifying the |
response |
A logical value, |
confidence_level |
A numeric value from 0 to 5 representing the user's confidence in the answer. Confidence levels are semi-quantitative and map to the following probabilities:
|
verbose |
An optional logical value controlling output. Default is TRUE. |
The function takes a 0-5 confidence level from the user and converts it to an
internal score between 0.5 (uncertain) and 1.0 (certain) using the formula:
score = 0.5 + (confidence_level / 10).
It includes validation to ensure the target node exists, is a leaf, and that the provided response is a valid logical value. A confirmation message is printed to the console upon successful update.
Returns the modified tree object invisibly, which allows for function chaining.
# Load a tree ethical_tree <- load_tree_df(ethical) # View the tree print_tree(ethical_tree) # Set an answer for leaf 'A1' ethical_tree <- set_answer(ethical_tree, "FIN2", TRUE, 3) print_tree(ethical_tree)# Load a tree ethical_tree <- load_tree_df(ethical) # View the tree print_tree(ethical_tree) # Set an answer for leaf 'A1' ethical_tree <- set_answer(ethical_tree, "FIN2", TRUE, 3) print_tree(ethical_tree)
Propagate the results up to the tree nodes based on the answers provided, and update the influence index to identify most important questions.
update_tree(tree)update_tree(tree)
tree |
The |
Returns the modified tree object invisibly, which allows for function chaining.
# Load a tree ethical_tree <- load_tree_df(ethical) # Internal indices before update print(ethical_tree, "rule", "true_index", "false_index", influence = "influence_index") ethical_tree <- update_tree(ethical_tree) # Updated indices print(ethical_tree, "rule", "true_index", "false_index", influence = "influence_index") # Answer some questions set_answer(ethical_tree, "FIN2", TRUE, 4) set_answer(ethical_tree, "ENV2", TRUE, 3) set_answer(ethical_tree, "SOC2", TRUE, 4) set_answer(ethical_tree, "GOV2", FALSE, 1) # Updated again ethical_tree <- update_tree(ethical_tree) # Updated indices print(ethical_tree, "rule", "true_index", "false_index", influence = "influence_index") # Updated results print_tree(ethical_tree)# Load a tree ethical_tree <- load_tree_df(ethical) # Internal indices before update print(ethical_tree, "rule", "true_index", "false_index", influence = "influence_index") ethical_tree <- update_tree(ethical_tree) # Updated indices print(ethical_tree, "rule", "true_index", "false_index", influence = "influence_index") # Answer some questions set_answer(ethical_tree, "FIN2", TRUE, 4) set_answer(ethical_tree, "ENV2", TRUE, 3) set_answer(ethical_tree, "SOC2", TRUE, 4) set_answer(ethical_tree, "GOV2", FALSE, 1) # Updated again ethical_tree <- update_tree(ethical_tree) # Updated indices print(ethical_tree, "rule", "true_index", "false_index", influence = "influence_index") # Updated results print_tree(ethical_tree)