Restructure classes and their responsibilities

This commit is contained in:
Elias Projahn 2021-12-16 13:01:44 +01:00
parent 01ec301d6d
commit e2b93babe5
27 changed files with 974 additions and 634 deletions

View file

@ -24,6 +24,7 @@ Imports:
data.table, data.table,
keras, keras,
rlang, rlang,
progress,
tensorflow tensorflow
Suggests: Suggests:
biomaRt, biomaRt,

View file

@ -1,10 +1,19 @@
# Generated by roxygen2: do not edit by hand # Generated by roxygen2: do not edit by hand
S3method(print,geposan_analysis)
S3method(print,geposan_comparison) S3method(print,geposan_comparison)
S3method(print,geposan_method)
S3method(print,geposan_preset) S3method(print,geposan_preset)
S3method(print,geposan_result)
S3method(print,geposan_validation) S3method(print,geposan_validation)
export(adjacency)
export(all_methods)
export(analyze) export(analyze)
export(clustering)
export(compare) export(compare)
export(correlation)
export(method)
export(neural)
export(optimal_weights) export(optimal_weights)
export(plot_boxplot) export(plot_boxplot)
export(plot_chromosomes) export(plot_chromosomes)
@ -12,6 +21,8 @@ export(plot_positions)
export(plot_rankings) export(plot_rankings)
export(plot_scores) export(plot_scores)
export(preset) export(preset)
export(proximity)
export(ranking) export(ranking)
export(result)
export(validate) export(validate)
import(data.table) import(data.table)

View file

@ -1,9 +1,18 @@
# Score genes based on their proximity to the reference genes. #' Score genes based on their proximity to the reference genes.
# #'
# This method finds the distance value with the maximum density for each gene #' This method finds the distance value with the maximum density for each gene
# (i.e. the mode of its estimated distribution). Genes are scored by comparing #' (i.e. the mode of its estimated distribution). Genes are scored by comparing
# those distance values with the values of the reference genes. #' those distance values with the values of the reference genes.
adjacency <- function(preset, progress = NULL) { #'
#' @return An object of class `geposan_method`.
#'
#' @export
adjacency <- function() {
method(
id = "adjacency",
name = "Adjacency",
description = "Adjacency to reference genes",
function(preset, progress) {
species_ids <- preset$species_ids species_ids <- preset$species_ids
gene_ids <- preset$gene_ids gene_ids <- preset$gene_ids
reference_gene_ids <- preset$reference_gene_ids reference_gene_ids <- preset$reference_gene_ids
@ -19,19 +28,21 @@ adjacency <- function(preset, progress = NULL) {
} }
} }
# Filter distances by species and gene and find the distance with the # Filter distances by species and gene and find the distance
# highest density of values for each gene. # with the highest density of values for each gene.
data <- geposan::distances[ data <- geposan::distances[
species %chin% species_ids & gene %chin% gene_ids, species %chin% species_ids & gene %chin% gene_ids,
.(densest_distance = compute_densest_distance(distance)), .(densest_distance = compute_densest_distance(distance)),
by = gene by = gene
] ]
# Compute the absolute value of the difference between the provided # Compute the absolute value of the difference between the
# densest distance value in comparison to the mean of the densest # provided densest distance value in comparison to the mean of
# distances of the comparison genes. # the densest distances of the comparison genes.
compute_difference <- function(densest_distance, comparison_ids) { compute_difference <- function(densest_distance,
# Get the mean of the densest distances of the reference genes. comparison_ids) {
# Get the mean of the densest distances of the reference
# genes.
mean_densest_distance <- data[ mean_densest_distance <- data[
gene %chin% comparison_ids, gene %chin% comparison_ids,
mean(densest_distance) mean(densest_distance)
@ -49,11 +60,10 @@ adjacency <- function(preset, progress = NULL) {
) )
] ]
if (!is.null(progress)) {
progress(0.5) progress(0.5)
}
# Exclude the reference gene itself when computing its difference. # Exclude the reference gene itself when computing its
# difference.
data[ data[
gene %chin% reference_gene_ids, gene %chin% reference_gene_ids,
difference := compute_difference( difference := compute_difference(
@ -66,16 +76,14 @@ adjacency <- function(preset, progress = NULL) {
# Compute the final score by normalizing the difference. # Compute the final score by normalizing the difference.
data[, score := 1 - difference / max(difference)] data[, score := 1 - difference / max(difference)]
if (!is.null(progress)) {
progress(1.0) progress(1.0)
}
structure( result(
list( method = "adjacency",
results = data[, .(gene, score)], scores = data[, .(gene, score)],
details = data details = list(data = data)
),
class = "geposan_method_results"
) )
}) })
} }
)
}

View file

@ -1,16 +1,17 @@
#' Analyze by applying the specified preset. #' Analyze genes based on position data.
#' #'
#' @param preset The preset to use which should be created using [preset()]. #' @param preset The preset to use which should be created using [preset()].
#' @param progress A function to be called for progress information. The #' @param progress A function to be called for progress information. The
#' function should accept a number between 0.0 and 1.0 for the current #' function should accept a number between 0.0 and 1.0 for the current
#' progress. #' progress. If no function is provided, a simple text progress bar will be
#' shown.
#' #'
#' @returns An object containing the results of the analysis with the following #' @returns An object containing the results of the analysis with the following
#' items: #' items:
#' \describe{ #' \describe{
#' \item{`preset`}{The preset that was used.} #' \item{`preset`}{The preset that was used.}
#' \item{`weights`}{The optimal weights for ranking the reference genes.} #' \item{`scores`}{Table containing all scores for each gene.}
#' \item{`ranking`}{The optimal ranking created using the weights.} #' \item{`results`}{Results from the different methods including details.}
#' } #' }
#' #'
#' @export #' @export
@ -19,80 +20,69 @@ analyze <- function(preset, progress = NULL) {
stop("Preset is invalid. Use geposan::preset() to create one.") stop("Preset is invalid. Use geposan::preset() to create one.")
} }
# Available methods by ID. if (is.null(progress)) {
# progress_bar <- progress::progress_bar$new()
# A method describes a way to perform a computation on gene distance data progress_bar$update(0.0)
# that results in a single score per gene. The function should accept the
# preset to apply (see [preset()]) and an optional progress function (that
# may be called with a number between 0.0 and 1.0) as its parameters.
#
# The function should return a [data.table] with the following columns:
#
# - `gene` Gene ID of the processed gene.
# - `score` Score for the gene between 0.0 and 1.0.
methods <- list(
"clusteriness" = clusteriness,
"correlation" = correlation,
"neural" = neural,
"adjacency" = adjacency,
"proximity" = proximity
)
analysis <- cached("analysis", preset, { progress <- function(progress_value) {
total_progress <- 0.0 if (!progress_bar$finished) {
progress_bar$update(progress_value)
if (progress_value >= 1.0) {
progress_bar$terminate()
}
}
}
}
progress_buffer <- 0.0
method_count <- length(preset$methods) method_count <- length(preset$methods)
results <- data.table(gene = preset$gene_ids)
for (method_id in preset$methods) { method_progress <- function(progress_value) {
method_progress <- if (!is.null(progress)) { progress(progress_buffer + progress_value / method_count)
function(p) {
progress(total_progress + p / method_count)
}
} }
method_results <- methods[[method_id]]( scores <- data.table(gene = preset$gene_id)
preset, results <- list()
progress = method_progress
)$results
setnames(method_results, "score", method_id) for (method in preset$methods) {
method_results <- method$func(preset, method_progress)
results <- merge( scores <- merge(scores, method_results$scores)
results, setnames(scores, "score", method$id)
method_results,
by = "gene"
)
total_progress <- total_progress + 1 / method_count results <- c(results, list(method_results))
progress_buffer <- progress_buffer + 1 / method_count
progress(progress_buffer)
} }
results <- structure(
results,
class = c("geposan_results", class(results))
)
weights <- optimal_weights(
results,
preset$methods,
preset$reference_gene_ids,
target = preset$optimization_target
)
ranking <- ranking(results, weights)
structure( structure(
list( list(
preset = preset, preset = preset,
weights = weights, scores = scores,
ranking = ranking results = results
), ),
class = "geposan_analysis" class = "geposan_analysis"
) )
})
if (!is.null(progress)) {
progress(1.0)
} }
analysis #' Print an analysis object.
#'
#' @param x The analysis to print.
#' @param ... Other parameters.
#'
#' @seealso [analyze()]
#'
#' @export
print.geposan_analysis <- function(x, ...) {
cat("geposan analysis:\n\n")
print(x$preset)
cat("\n")
for (result in x$results) {
print(result)
cat("\n")
}
invisible(x)
} }

View file

@ -1,84 +0,0 @@
# Perform a cluster analysis.
#
# This function will cluster the data using `hclust` and `cutree` (with the
# specified height). Every cluster with at least two members qualifies for
# further analysis. Clusters are then ranked based on their size in relation
# to the number of values. The return value is a final score between zero and
# one. Lower ranking clusters contribute less to this score.
#
# @param data The values that should be scored.
# @param height The maximum span of values considered to be in one cluster.
# @param weight The weight that will be given to the next largest cluster in
# relation to the previous one. For example, if `weight` is 0.7 (the default),
# the first cluster will weigh 1.0, the second 0.7, the third 0.49 etc.
clusteriness_priv <- function(data, height = 1000000, weight = 0.7) {
n <- length(data)
# Return a score of 0.0 if there is just one or no value at all.
if (n < 2) {
return(0.0)
}
# Cluster the data and compute the cluster sizes.
tree <- stats::hclust(stats::dist(data))
clusters <- stats::cutree(tree, h = height)
cluster_sizes <- sort(tabulate(clusters), decreasing = TRUE)
# Compute the "clusteriness" score.
score <- 0.0
for (i in seq_along(cluster_sizes)) {
cluster_size <- cluster_sizes[i]
if (cluster_size >= 2) {
cluster_score <- cluster_size / n
score <- score + weight ^ (i - 1) * cluster_score
}
}
score
}
# Process genes clustering their distance to telomeres.
clusteriness <- function(preset, progress = NULL) {
species_ids <- preset$species_ids
gene_ids <- preset$gene_ids
cached("clusteriness", c(species_ids, gene_ids), {
results <- data.table(gene = gene_ids)
# Prefilter the input data by species.
distances <- geposan::distances[species %chin% species_ids]
# Add an index for quickly accessing data per gene.
setkey(distances, gene)
genes_done <- 0
genes_total <- length(gene_ids)
# Perform the cluster analysis for one gene.
compute <- function(gene_id) {
data <- distances[gene_id, distance]
score <- clusteriness_priv(data)
if (!is.null(progress)) {
genes_done <<- genes_done + 1
progress(genes_done / genes_total)
}
score
}
structure(
list(
results = results[,
score := compute(gene),
by = gene
]
),
class = "geposan_method_results"
)
})
}

93
R/clustering.R Normal file
View file

@ -0,0 +1,93 @@
#' Perform a cluster analysis.
#'
#' This function will cluster the data using [stats::hclust()] and
#' [stats::cutree()]. Every cluster with at least two members qualifies for
#' further analysis. Clusters are then ranked based on their size in relation
#' to the total number of values. The return value is a final score between
#' 0.0 and 1.0. Lower ranking clusters contribute less to this score.
#'
#' @param data The values that should be scored.
#' @param span The maximum span of values considered to be in one cluster.
#' @param weight The weight that will be given to the next largest cluster in
#' relation to the previous one. For example, if `weight` is 0.7 (the
#' default), the first cluster will weigh 1.0, the second 0.7, the third 0.49
#' etc.
clusteriness <- function(data, span = 1000000, weight = 0.7) {
n <- length(data)
# Return a score of 0.0 if there is just one or no value at all.
if (n < 2) {
return(0.0)
}
# Cluster the data and compute the cluster sizes.
tree <- stats::hclust(stats::dist(data))
clusters <- stats::cutree(tree, h = span)
cluster_sizes <- sort(tabulate(clusters), decreasing = TRUE)
# Compute the "clusteriness" score.
score <- 0.0
for (i in seq_along(cluster_sizes)) {
cluster_size <- cluster_sizes[i]
if (cluster_size >= 2) {
cluster_score <- cluster_size / n
score <- score + weight^(i - 1) * cluster_score
}
}
score
}
#' Process genes clustering their distance to telomeres.
#'
#' The result will be cached and can be reused for different presets, because
#' it is independent of the reference genes in use.
#'
#' @return An object of class `geposan_method`.
#'
#' @seealso [clusteriness()]
#'
#' @export
clustering <- function() {
method(
id = "clustering",
name = "Clustering",
description = "Clustering of genes",
function(preset, progress) {
species_ids <- preset$species_ids
gene_ids <- preset$gene_ids
cached("clustering", c(species_ids, gene_ids), {
scores <- data.table(gene = gene_ids)
# Prefilter the input data by species.
distances <- geposan::distances[species %chin% species_ids]
genes_done <- 0
genes_total <- length(gene_ids)
# Perform the cluster analysis for one gene.
compute <- function(gene_id) {
data <- distances[gene == gene_id, distance]
score <- clusteriness(data)
genes_done <<- genes_done + 1
progress(genes_done / genes_total)
score
}
scores[, score := compute(gene), by = gene]
result(
method = "clustering",
scores = scores
)
})
}
)
}

View file

@ -1,18 +1,29 @@
# Compute the mean correlation coefficient comparing gene distances with a set #' Compute the mean correlation coefficient comparing gene distances with a set
# of reference genes. #' of reference genes.
correlation <- function(preset, progress = NULL) { #'
#' @return An object of class `geposan_method`.
#'
#' @export
correlation <- function() {
method(
id = "correlation",
name = "Correlation",
description = "Correlation with reference genes",
function(preset, progress) {
species_ids <- preset$species_ids species_ids <- preset$species_ids
gene_ids <- preset$gene_ids gene_ids <- preset$gene_ids
reference_gene_ids <- preset$reference_gene_ids reference_gene_ids <- preset$reference_gene_ids
cached( cached(
"correlation", c(species_ids, gene_ids, reference_gene_ids), { "correlation",
c(species_ids, gene_ids, reference_gene_ids),
{ # nolint
# Prefilter distances by species. # Prefilter distances by species.
distances <- geposan::distances[species %chin% species_ids] distances <- geposan::distances[species %chin% species_ids]
# Tranform data to get species as rows and genes as columns. We # Tranform data to get species as rows and genes as columns.
# construct columns per species, because it requires fewer # We construct columns per species, because it requires
# iterations, and transpose the table afterwards. # fewer iterations, and transpose the table afterwards.
data <- data.table(gene = gene_ids) data <- data.table(gene = gene_ids)
@ -30,7 +41,7 @@ correlation <- function(preset, progress = NULL) {
# Transpose to the desired format. # Transpose to the desired format.
data <- transpose(data, make.names = "gene") data <- transpose(data, make.names = "gene")
if (!is.null(progress)) progress(0.33) progress(0.33)
# Take the reference data. # Take the reference data.
reference_data <- data[, ..reference_gene_ids] reference_data <- data[, ..reference_gene_ids]
@ -46,18 +57,20 @@ correlation <- function(preset, progress = NULL) {
results <- data.table(results, keep.rownames = TRUE) results <- data.table(results, keep.rownames = TRUE)
setnames(results, "rn", "gene") setnames(results, "rn", "gene")
# Remove correlations between the reference genes themselves. # Remove correlations between the reference genes
# themselves.
for (reference_gene_id in reference_gene_ids) { for (reference_gene_id in reference_gene_ids) {
column <- quote(reference_gene_id) column <- quote(reference_gene_id)
results[gene == reference_gene_id, eval(column) := NA] results[gene == reference_gene_id, eval(column) := NA]
} }
if (!is.null(progress)) progress(0.66) progress(0.66)
# Compute the final score as the mean of known correlation scores. # Compute the final score as the mean of known correlation
# Negative correlations will correctly lessen the score, which will # scores. Negative correlations will correctly lessen the
# be clamped to zero as its lower bound. Genes with no possible # score, which will be clamped to zero as its lower bound.
# correlations at all will be assumed to have a score of 0.0. # Genes with no possible correlations at all will be assumed
# to have a score of 0.0.
compute_score <- function(scores) { compute_score <- function(scores) {
score <- mean(scores, na.rm = TRUE) score <- mean(scores, na.rm = TRUE)
@ -77,12 +90,12 @@ correlation <- function(preset, progress = NULL) {
results[, .(gene, score)] results[, .(gene, score)]
structure( result(
list( method = "correlation",
results = results[, .(gene, score)], scores = results[, .(gene, score)],
all_correlations = results details = list(all_correlations = results)
), )
class = "geposan_method_results" }
) )
} }
) )

67
R/method.R Normal file
View file

@ -0,0 +1,67 @@
#' Describe a new method for analyzing gene position data.
#'
#' @param id Unique identifier for the method.
#' @param name Human readable name.
#' @param description Slightly longer description.
#' @param func Function to apply the method. The function should accept two
#' parameters: an object of class `geposan_preset` as input and a function to
#' report progress information to as a numeric value. The return value should
#' be an object of class `geposan_result`.
#'
#' @return An object of class `geposan_method`.
#'
#' @export
method <- function(id, name, description, func) {
stopifnot(is.character(id) & length(id) == 1)
stopifnot(is.character(name) & length(name) == 1)
stopifnot(is.character(description) & length(description) == 1)
stopifnot(is.function(func))
structure(
list(
id = id,
name = name,
description = description,
func = func
),
class = "geposan_method"
)
}
#' Get a list of all available methods.
#'
#' @export
all_methods <- function() {
list(
clustering(),
correlation(),
neural(),
adjacency(),
proximity()
)
}
#' Print a method object.
#'
#' @param x The method to print.
#' @param ... Other parameters.
#'
#' @seealso [method()]
#'
#' @export
print.geposan_method <- function(x, ...) {
cat(sprintf(
paste0(
"geposan method:",
"\n Method ID: %s",
"\n Name: %s",
"\n Description: %s",
"\n"
),
x$id,
x$name,
x$description
))
invisible(x)
}

View file

@ -1,12 +1,22 @@
# Find genes by training and applying a neural network. #' Find genes by training and applying a neural network.
# #'
# @param seed The seed will be used to make the results reproducible. #' @param seed The seed will be used to make the results reproducible.
# @param n_models This number specifies how many sets of training data should #' @param n_models This number specifies how many sets of training data should
# be created. For each set, there will be a model trained on the remaining #' be created. For each set, there will be a model trained on the remaining
# training data and validated using this set. For non-training genes, the #' training data and validated using this set. For non-training genes, the
# final score will be the mean of the result of applying the different #' final score will be the mean of the result of applying the different
# models. #' models. There should be at least two training sets. The analysis will only
neural <- function(preset, progress = NULL, seed = 751833, n_models = 5) { #' work, if there is at least one reference gene per training set.
#'
#' @return An object of class `geposan_method`.
#'
#' @export
neural <- function(seed = 180199, n_models = 5) {
method(
id = "neural",
name = "Neural",
description = "Assessment by neural network",
function(preset, progress) {
species_ids <- preset$species_ids species_ids <- preset$species_ids
gene_ids <- preset$gene_ids gene_ids <- preset$gene_ids
reference_gene_ids <- preset$reference_gene_ids reference_gene_ids <- preset$reference_gene_ids
@ -16,12 +26,7 @@ neural <- function(preset, progress = NULL, seed = 751833, n_models = 5) {
c(species_ids, gene_ids, reference_gene_ids, seed, n_models), c(species_ids, gene_ids, reference_gene_ids, seed, n_models),
{ # nolint { # nolint
reference_count <- length(reference_gene_ids) reference_count <- length(reference_gene_ids)
if (!n_models %in% 2:reference_count) { stopifnot(n_models %in% 2:reference_count)
stop(paste0(
"n_models has to be between 2 and the number of reference ",
"genes."
))
}
# Make results reproducible. # Make results reproducible.
tensorflow::set_random_seed(seed) tensorflow::set_random_seed(seed)
@ -32,9 +37,9 @@ neural <- function(preset, progress = NULL, seed = 751833, n_models = 5) {
# Prefilter distances by species. # Prefilter distances by species.
distances <- geposan::distances[species %chin% species_ids] distances <- geposan::distances[species %chin% species_ids]
# Input data for the network. This contains the gene ID as an # Input data for the network. This contains the gene ID as
# identifier as well as the per-species gene distances as input # an identifier as well as the per-species gene distances as
# variables. # input variables.
data <- data.table(gene = gene_ids) data <- data.table(gene = gene_ids)
# Buffer to keep track of the names of the input variables. # Buffer to keep track of the names of the input variables.
@ -48,24 +53,27 @@ neural <- function(preset, progress = NULL, seed = 751833, n_models = 5) {
.(gene, distance) .(gene, distance)
] ]
# Only include species with at least 25% known values. As # Only include species with at least 25% known values.
# positions and distances always coexist, we don't loose any # As positions and distances always coexist, we don't
# data here. # loose any data here.
species_data <- stats::na.omit(species_data) species_data <- stats::na.omit(species_data)
if (nrow(species_data) >= 0.25 * length(gene_ids)) { if (nrow(species_data) >= 0.25 * length(gene_ids)) {
data <- merge(data, species_data, all.x = TRUE) data <- merge(data, species_data, all.x = TRUE)
# Replace missing data with mean values. The neural network # Replace missing data with mean values. The neural
# can't handle NAs in a meaningful way. Choosing extreme # network can't handle NAs in a meaningful way.
# values here would result in heavily biased results. # Choosing extreme values here would result in
# Therefore, the mean value is chosen as a compromise. # heavily biased results. Therefore, the mean value
# However, this will of course lessen the significance of # is chosen as a compromise. However, this will of
# the results. # course lessen the significance of the results.
mean_distance <- round(species_data[, mean(distance)]) mean_distance <- round(
data[is.na(distance), `:=`(distance = mean_distance)] species_data[, mean(distance)]
)
data[is.na(distance), distance := mean_distance]
# Name the new column after the species. # Name the new column after the species.
setnames(data, "distance", species_id) setnames(data, "distance", species_id)
@ -75,9 +83,7 @@ neural <- function(preset, progress = NULL, seed = 751833, n_models = 5) {
} }
} }
if (!is.null(progress)) {
progress(0.1) progress(0.1)
}
# Step 2: Prepare training data. # Step 2: Prepare training data.
# ------------------------------ # ------------------------------
@ -87,13 +93,14 @@ neural <- function(preset, progress = NULL, seed = 751833, n_models = 5) {
reference_data <- data[gene %chin% reference_gene_ids] reference_data <- data[gene %chin% reference_gene_ids]
reference_data[, score := 1.0] reference_data[, score := 1.0]
# Take out random samples from the remaining genes. This is another # Take out random samples from the remaining genes. This is
# compromise with a negative impact on significance. Because there # another compromise with a negative impact on
# is no information on genes with are explicitely *not* TPE-OLD # significance. We assume that a random gene is not likely
# genes, we have to assume that a random sample of genes has a low # to match the reference genes.
# probability of including TPE-OLD genes.
without_reference_data <- data[!gene %chin% reference_gene_ids] without_reference_data <- data[
!gene %chin% reference_gene_ids
]
control_data <- without_reference_data[ control_data <- without_reference_data[
sample( sample(
@ -104,8 +111,8 @@ neural <- function(preset, progress = NULL, seed = 751833, n_models = 5) {
control_data[, score := 0.0] control_data[, score := 0.0]
# Split the training data into random sets to have validation data # Split the training data into random sets to have
# for each model. # validation data for each model.
# Scramble the source tables. # Scramble the source tables.
reference_data <- reference_data[sample(reference_count)] reference_data <- reference_data[sample(reference_count)]
@ -153,8 +160,9 @@ neural <- function(preset, progress = NULL, seed = 751833, n_models = 5) {
output_vars <- NULL output_vars <- NULL
for (i in seq_along(networks)) { for (i in seq_along(networks)) {
# Create a new model for each training session, because the # Create a new model for each training session, because
# model would keep its state across training sessions otherwise. # the model would keep its state across training
# sessions otherwise.
model <- keras::keras_model_sequential() |> model <- keras::keras_model_sequential() |>
keras::layer_dense( keras::layer_dense(
units = layer1, units = layer1,
@ -218,10 +226,8 @@ neural <- function(preset, progress = NULL, seed = 751833, n_models = 5) {
networks[[i]]$model <- keras::serialize_model(model) networks[[i]]$model <- keras::serialize_model(model)
networks[[i]]$fit <- fit networks[[i]]$fit <- fit
if (!is.null(progress)) {
progress(0.1 + i * (0.9 / n_models)) progress(0.1 + i * (0.9 / n_models))
} }
}
# Compute the final score as the mean score. # Compute the final score as the mean score.
data[, data[,
@ -230,19 +236,19 @@ neural <- function(preset, progress = NULL, seed = 751833, n_models = 5) {
by = gene by = gene
] ]
if (!is.null(progress)) {
progress(1.0) progress(1.0)
}
structure( result(
list( method = "neural",
results = data[, .(gene, score)], scores = data[, .(gene, score)],
details = list(
seed = seed, seed = seed,
n_models = n_models, n_models = n_models,
all_results = data[, !..input_vars], all_results = data[, !..input_vars],
networks = networks networks = networks
), )
class = "geposan_method_results" )
}
) )
} }
) )

View file

@ -5,46 +5,22 @@
#' reference genes to be able to assess the results later. The genes will be #' reference genes to be able to assess the results later. The genes will be
#' filtered based on how many species have data for them. Genes which only have #' filtered based on how many species have data for them. Genes which only have
#' orthologs for less than 25% of the input species will be excluded from the #' orthologs for less than 25% of the input species will be excluded from the
#' preset and the analyis. #' preset and the analyis. See the different method functions for the available
#' methods: [clustering()], [correlation()], [neural()], [adjacency()] and
#' [proximity()].
#' #'
#' Available methods are: #' @param methods List of methods to apply.
#'
#' - `clusteriness` How much the gene distances to the nearest telomere
#' cluster across species.
#' - `correlation` The mean correlation of gene distances to the nearest
#' telomere across species.
#' - `neural` Assessment by neural network trained on the reference genes.
#' - `adjacency` Proximity to reference genes.
#' - `proximity` Mean proximity to telomeres.
#'
#' Available optimization targets are:
#'
#' - `mean` Mean rank of the reference genes.
#' - `median` Median rank of the reference genes.
#' - `max` First rank of the reference genes.
#' - `min` Last rank of the reference genes.
#'
#' @param methods Methods to apply.
#' @param species_ids IDs of species to include. #' @param species_ids IDs of species to include.
#' @param gene_ids IDs of genes to screen. #' @param gene_ids IDs of genes to screen.
#' @param reference_gene_ids IDs of reference genes to compare to. #' @param reference_gene_ids IDs of reference genes to compare to.
#' @param optimization_target Parameter of the reference genes that the ranking
#' should be optimized for.
#' #'
#' @return The preset to use with [analyze()]. #' @return The preset to use with [analyze()].
#' #'
#' @export #' @export
preset <- function(methods = c( preset <- function(methods = all_methods(),
"clusteriness", species_ids = geposan::species$id,
"correlation", gene_ids = geposan::genes$id,
"neural", reference_gene_ids) {
"adjacency",
"proximity"
),
species_ids = NULL,
gene_ids = NULL,
reference_gene_ids = NULL,
optimization_target = "mean_rank") {
# Count included species per gene. # Count included species per gene.
genes_n_species <- geposan::distances[ genes_n_species <- geposan::distances[
species %chin% species_ids, species %chin% species_ids,
@ -63,11 +39,10 @@ preset <- function(methods = c(
# for the object later. # for the object later.
structure( structure(
list( list(
methods = sort(methods), methods = methods,
species_ids = sort(species_ids), species_ids = sort(species_ids),
gene_ids = sort(gene_ids_filtered), gene_ids = sort(gene_ids_filtered),
reference_gene_ids = sort(reference_gene_ids), reference_gene_ids = sort(reference_gene_ids)
optimization_target = optimization_target
), ),
class = "geposan_preset" class = "geposan_preset"
) )
@ -82,25 +57,20 @@ preset <- function(methods = c(
#' #'
#' @export #' @export
print.geposan_preset <- function(x, ...) { print.geposan_preset <- function(x, ...) {
cat("geposan preset:")
cat("\n Included methods: ")
cat(x$methods, sep = ", ")
cat(sprintf( cat(sprintf(
"\n Input data: %i species, %i genes", paste0(
"geposan preset:",
"\n Included methods: %s",
"\n Number of species: %i",
"\n Number of genes: %i",
"\n Reference genes: %i",
"\n"
),
paste(sapply(x$methods, function(m) m$id), collapse = ", "),
length(x$species_ids), length(x$species_ids),
length(x$gene_ids) length(x$gene_ids),
))
cat(sprintf(
"\n Comparison data: %i reference genes",
length(x$reference_gene_ids) length(x$reference_gene_ids)
)) ))
cat(sprintf(
"\n Optimization target: %s\n",
x$optimization_target
))
invisible(x) invisible(x)
} }

View file

@ -1,15 +1,25 @@
# Score the mean distance of genes to the telomeres across species. #' Score the mean distance of genes to the telomeres across species.
# #'
# A score will be given to each gene such that 0.0 corresponds to the maximal #' A score will be given to each gene such that 0.0 corresponds to the maximal
# mean distance across all genes and 1.0 corresponds to a distance of 0. #' mean distance across all genes and 1.0 corresponds to a distance of 0.
proximity <- function(preset, progress = NULL) { #'
#' @return An object of class `geposan_method`.
#'
#' @export
proximity <- function() {
method(
id = "proximity",
name = "Proximity",
description = "Proximity to telomeres",
function(preset, progress) {
species_ids <- preset$species_ids species_ids <- preset$species_ids
gene_ids <- preset$gene_ids gene_ids <- preset$gene_ids
cached("proximity", c(species_ids, gene_ids), { cached("proximity", c(species_ids, gene_ids), {
# Prefilter distances by species and gene. # Prefilter distances by species and gene.
data <- geposan::distances[ data <- geposan::distances[
species %chin% preset$species_ids & gene %chin% preset$gene_ids species %chin% preset$species_ids &
gene %chin% preset$gene_ids
] ]
# Compute the score as described above. # Compute the score as described above.
@ -17,18 +27,13 @@ proximity <- function(preset, progress = NULL) {
max_distance <- data[, max(mean_distance)] max_distance <- data[, max(mean_distance)]
data[, score := 1 - mean_distance / max_distance] data[, score := 1 - mean_distance / max_distance]
if (!is.null(progress)) {
# We do everything in one go, so it's not possible to report
# detailed progress information. As the method is relatively quick,
# this should not be a problem.
progress(1.0) progress(1.0)
}
structure( result(
list( method = "proximity",
results = data[, .(gene, score)] scores = data[, .(gene, score)]
),
class = "geposan_method_results"
) )
}) })
} }
)
}

View file

@ -13,10 +13,10 @@
#' #'
#' @export #' @export
ranking <- function(analysis, weights) { ranking <- function(analysis, weights) {
if (inherits(analysis, "geposan_analysis")) { ranking <- if (inherits(analysis, "geposan_analysis")) {
ranking <- copy(analysis$ranking) copy(analysis$scores)
} else if (inherits(analysis, "geposan_results")) { } else if (inherits(analysis, "geposan_ranking")) {
ranking <- copy(analysis) copy(analysis)
} else { } else {
stop("Invalid analyis. Use geposan::analyze().") stop("Invalid analyis. Use geposan::analyze().")
} }
@ -39,7 +39,7 @@ ranking <- function(analysis, weights) {
structure( structure(
ranking, ranking,
class = c("geposan_ranking", "geposan_results", class(ranking)) class = c("geposan_ranking", class(ranking))
) )
} }
@ -60,7 +60,7 @@ ranking <- function(analysis, weights) {
#' @export #' @export
optimal_weights <- function(analysis, methods, reference_gene_ids, optimal_weights <- function(analysis, methods, reference_gene_ids,
target = "mean") { target = "mean") {
if (!inherits(analysis, c("geposan_analysis", "geposan_results"))) { if (!inherits(analysis, c("geposan_analysis", "geposan_ranking"))) {
stop("Invalid analyis. Use geposan::analyze().") stop("Invalid analyis. Use geposan::analyze().")
} }

50
R/result.R Normal file
View file

@ -0,0 +1,50 @@
#' Result of applying a method on gene position data.
#'
#' @param method_id ID of the method that produced this result.
#' @param scores A `data.frame` mapping gene IDs (`gene`) to computed scores
#' between 0.0 and 1.0 (`score`).
#' @param details Optional details that may contain intermediate results as
#' well as other information on the method application.
#'
#' @return An object of class `geposan_result`.
#'
#' @export
result <- function(method_id, scores, details = list()) {
stopifnot(is.data.frame(scores) &
c("gene", "score") %chin% colnames(scores))
stopifnot(is.list(details))
structure(
list(
method_id = method_id,
scores = scores,
details = details
),
class = "geposan_result"
)
}
#' Print a result object.
#'
#' @param x The result to print.
#' @param ... Other parameters.
#'
#' @seealso [result()]
#'
#' @export
print.geposan_result <- function(x, ...) {
cat(sprintf(
paste0(
"geposan result:",
"\n Method: %s",
"\n Number of genes: %i",
"\n Available details: %s",
"\n"
),
x$method_id,
nrow(x$scores),
paste(names(x$details), collapse = ", ")
))
invisible(x)
}

16
man/adjacency.Rd Normal file
View file

@ -0,0 +1,16 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/adjacency.R
\name{adjacency}
\alias{adjacency}
\title{Score genes based on their proximity to the reference genes.}
\usage{
adjacency()
}
\value{
An object of class \code{geposan_method}.
}
\description{
This method finds the distance value with the maximum density for each gene
(i.e. the mode of its estimated distribution). Genes are scored by comparing
those distance values with the values of the reference genes.
}

11
man/all_methods.Rd Normal file
View file

@ -0,0 +1,11 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/method.R
\name{all_methods}
\alias{all_methods}
\title{Get a list of all available methods.}
\usage{
all_methods()
}
\description{
Get a list of all available methods.
}

View file

@ -2,7 +2,7 @@
% Please edit documentation in R/analyze.R % Please edit documentation in R/analyze.R
\name{analyze} \name{analyze}
\alias{analyze} \alias{analyze}
\title{Analyze by applying the specified preset.} \title{Analyze genes based on position data.}
\usage{ \usage{
analyze(preset, progress = NULL) analyze(preset, progress = NULL)
} }
@ -11,17 +11,18 @@ analyze(preset, progress = NULL)
\item{progress}{A function to be called for progress information. The \item{progress}{A function to be called for progress information. The
function should accept a number between 0.0 and 1.0 for the current function should accept a number between 0.0 and 1.0 for the current
progress.} progress. If no function is provided, a simple text progress bar will be
shown.}
} }
\value{ \value{
An object containing the results of the analysis with the following An object containing the results of the analysis with the following
items: items:
\describe{ \describe{
\item{\code{preset}}{The preset that was used.} \item{\code{preset}}{The preset that was used.}
\item{\code{weights}}{The optimal weights for ranking the reference genes.} \item{\code{scores}}{Table containing all scores for each gene.}
\item{\code{ranking}}{The optimal ranking created using the weights.} \item{\code{results}}{Results from the different methods including details.}
} }
} }
\description{ \description{
Analyze by applying the specified preset. Analyze genes based on position data.
} }

25
man/clusteriness.Rd Normal file
View file

@ -0,0 +1,25 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/clustering.R
\name{clusteriness}
\alias{clusteriness}
\title{Perform a cluster analysis.}
\usage{
clusteriness(data, span = 1e+06, weight = 0.7)
}
\arguments{
\item{data}{The values that should be scored.}
\item{span}{The maximum span of values considered to be in one cluster.}
\item{weight}{The weight that will be given to the next largest cluster in
relation to the previous one. For example, if \code{weight} is 0.7 (the
default), the first cluster will weigh 1.0, the second 0.7, the third 0.49
etc.}
}
\description{
This function will cluster the data using \code{\link[stats:hclust]{stats::hclust()}} and
\code{\link[stats:cutree]{stats::cutree()}}. Every cluster with at least two members qualifies for
further analysis. Clusters are then ranked based on their size in relation
to the total number of values. The return value is a final score between
0.0 and 1.0. Lower ranking clusters contribute less to this score.
}

18
man/clustering.Rd Normal file
View file

@ -0,0 +1,18 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/clustering.R
\name{clustering}
\alias{clustering}
\title{Process genes clustering their distance to telomeres.}
\usage{
clustering()
}
\value{
An object of class \code{geposan_method}.
}
\description{
The result will be cached and can be reused for different presets, because
it is independent of the reference genes in use.
}
\seealso{
\code{\link[=clusteriness]{clusteriness()}}
}

16
man/correlation.Rd Normal file
View file

@ -0,0 +1,16 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/correlation.R
\name{correlation}
\alias{correlation}
\title{Compute the mean correlation coefficient comparing gene distances with a set
of reference genes.}
\usage{
correlation()
}
\value{
An object of class \code{geposan_method}.
}
\description{
Compute the mean correlation coefficient comparing gene distances with a set
of reference genes.
}

26
man/method.Rd Normal file
View file

@ -0,0 +1,26 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/method.R
\name{method}
\alias{method}
\title{Describe a new method for analyzing gene position data.}
\usage{
method(id, name, description, func)
}
\arguments{
\item{id}{Unique identifier for the method.}
\item{name}{Human readable name.}
\item{description}{Slightly longer description.}
\item{func}{Function to apply the method. The function should accept two
parameters: an object of class \code{geposan_preset} as input and a function to
report progress information to as a numeric value. The return value should
be an object of class \code{geposan_result}.}
}
\value{
An object of class \code{geposan_method}.
}
\description{
Describe a new method for analyzing gene position data.
}

24
man/neural.Rd Normal file
View file

@ -0,0 +1,24 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/neural.R
\name{neural}
\alias{neural}
\title{Find genes by training and applying a neural network.}
\usage{
neural(seed = 180199, n_models = 5)
}
\arguments{
\item{seed}{The seed will be used to make the results reproducible.}
\item{n_models}{This number specifies how many sets of training data should
be created. For each set, there will be a model trained on the remaining
training data and validated using this set. For non-training genes, the
final score will be the mean of the result of applying the different
models. There should be at least two training sets. The analysis will only
work, if there is at least one reference gene per training set.}
}
\value{
An object of class \code{geposan_method}.
}
\description{
Find genes by training and applying a neural network.
}

View file

@ -5,24 +5,20 @@
\title{Create a new preset.} \title{Create a new preset.}
\usage{ \usage{
preset( preset(
methods = c("clusteriness", "correlation", "neural", "adjacency", "proximity"), methods = all_methods(),
species_ids = NULL, species_ids = geposan::species$id,
gene_ids = NULL, gene_ids = geposan::genes$id,
reference_gene_ids = NULL, reference_gene_ids
optimization_target = "mean_rank"
) )
} }
\arguments{ \arguments{
\item{methods}{Methods to apply.} \item{methods}{List of methods to apply.}
\item{species_ids}{IDs of species to include.} \item{species_ids}{IDs of species to include.}
\item{gene_ids}{IDs of genes to screen.} \item{gene_ids}{IDs of genes to screen.}
\item{reference_gene_ids}{IDs of reference genes to compare to.} \item{reference_gene_ids}{IDs of reference genes to compare to.}
\item{optimization_target}{Parameter of the reference genes that the ranking
should be optimized for.}
} }
\value{ \value{
The preset to use with \code{\link[=analyze]{analyze()}}. The preset to use with \code{\link[=analyze]{analyze()}}.
@ -33,25 +29,7 @@ analysis. Note that the genes to process should normally include the
reference genes to be able to assess the results later. The genes will be reference genes to be able to assess the results later. The genes will be
filtered based on how many species have data for them. Genes which only have filtered based on how many species have data for them. Genes which only have
orthologs for less than 25\% of the input species will be excluded from the orthologs for less than 25\% of the input species will be excluded from the
preset and the analyis. preset and the analyis. See the different method functions for the available
} methods: \code{\link[=clustering]{clustering()}}, \code{\link[=correlation]{correlation()}}, \code{\link[=neural]{neural()}}, \code{\link[=adjacency]{adjacency()}} and
\details{ \code{\link[=proximity]{proximity()}}.
Available methods are:
\itemize{
\item \code{clusteriness} How much the gene distances to the nearest telomere
cluster across species.
\item \code{correlation} The mean correlation of gene distances to the nearest
telomere across species.
\item \code{neural} Assessment by neural network trained on the reference genes.
\item \code{adjacency} Proximity to reference genes.
\item \code{proximity} Mean proximity to telomeres.
}
Available optimization targets are:
\itemize{
\item \code{mean} Mean rank of the reference genes.
\item \code{median} Median rank of the reference genes.
\item \code{max} First rank of the reference genes.
\item \code{min} Last rank of the reference genes.
}
} }

View file

@ -0,0 +1,19 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/analyze.R
\name{print.geposan_analysis}
\alias{print.geposan_analysis}
\title{Print an analysis object.}
\usage{
\method{print}{geposan_analysis}(x, ...)
}
\arguments{
\item{x}{The analysis to print.}
\item{...}{Other parameters.}
}
\description{
Print an analysis object.
}
\seealso{
\code{\link[=analyze]{analyze()}}
}

View file

@ -0,0 +1,19 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/method.R
\name{print.geposan_method}
\alias{print.geposan_method}
\title{Print a method object.}
\usage{
\method{print}{geposan_method}(x, ...)
}
\arguments{
\item{x}{The method to print.}
\item{...}{Other parameters.}
}
\description{
Print a method object.
}
\seealso{
\code{\link[=method]{method()}}
}

View file

@ -0,0 +1,19 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/result.R
\name{print.geposan_result}
\alias{print.geposan_result}
\title{Print a result object.}
\usage{
\method{print}{geposan_result}(x, ...)
}
\arguments{
\item{x}{The result to print.}
\item{...}{Other parameters.}
}
\description{
Print a result object.
}
\seealso{
\code{\link[=result]{result()}}
}

15
man/proximity.Rd Normal file
View file

@ -0,0 +1,15 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/proximity.R
\name{proximity}
\alias{proximity}
\title{Score the mean distance of genes to the telomeres across species.}
\usage{
proximity()
}
\value{
An object of class \code{geposan_method}.
}
\description{
A score will be given to each gene such that 0.0 corresponds to the maximal
mean distance across all genes and 1.0 corresponds to a distance of 0.
}

23
man/result.Rd Normal file
View file

@ -0,0 +1,23 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/result.R
\name{result}
\alias{result}
\title{Result of applying a method on gene position data.}
\usage{
result(method_id, scores, details = list())
}
\arguments{
\item{method_id}{ID of the method that produced this result.}
\item{scores}{A \code{data.frame} mapping gene IDs (\code{gene}) to computed scores
between 0.0 and 1.0 (\code{score}).}
\item{details}{Optional details that may contain intermediate results as
well as other information on the method application.}
}
\value{
An object of class \code{geposan_result}.
}
\description{
Result of applying a method on gene position data.
}