50a94207 |
## Copyright 2013, 2014, 2015, 2016 Ramon Diaz-Uriarte
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
## You should have received a copy of the GNU General Public License
## along with this program. If not, see <http://www.gnu.org/licenses/>.
## plot.evalAllGenotypes <- plot.evalAllGenotypesMut <-
## plot.genotype_fitness_matrix <- plotFitnessLandscape
## FIXME: show only accessible paths?
|
f9a38e24 |
## FIXME: if using only_accessible, maybe we
## can try to use fast_peaks, and use the slower
## approach as fallback (if identical fitness)
|
50a94207 |
plotFitnessLandscape <- function(x, show_labels = TRUE,
col = c("green4", "red", "yellow"),
lty = c(1, 2, 3), use_ggrepel = FALSE,
log = FALSE, max_num_genotypes = 2000,
|
7c1a570f |
only_accessible = FALSE,
accessible_th = 0,
|
50a94207 |
...) {
## FIXME future:
## - Allow passing order effects. Change "allGenotypes_to_matrix"
## below? Probably not, as we cannot put order effects as a
## matrix. Do something else, like allow only order effects if from
## and allFitnessEffects object.
## - Allow selection only some genotypes or alleles
## Allow selecting only paths that involve a particular genotype in
## adjacency matrix of genotype i go at row i and column i. Follow back
## all entries in row i, and their previous, and forward all column i.
## gfm: genotype fitness matrix
## afe: all fitness effects
## We seem to go back and forth, but we need to ensure the afe and the
## gfm are coherent. Since there are many ways to make them fail
## (e.g., a user passing the wrong order, or incomplete matrices, etc)
## we do it the following way.
## Yes, we set WT to 1 as we call from_genotype_fitness on
## matrices and genotype_fitness_matrix objects.
## We do that because we call evalAllGenotypes to
## get the string representation, etc. And this is for use
## with OncoSimul.
|
7c1a570f |
|
65cb86f3 |
tfm <- to_Fitness_Matrix(x, max_num_genotypes = max_num_genotypes)
mutated <- rowSums(tfm$gfm[, -ncol(tfm$gfm)])
gaj <- genot_to_adj_mat(tfm$gfm)
|
7c1a570f |
if(only_accessible) {
gaj <- filter_inaccessible(gaj, accessible_th)
remaining <- as.numeric(colnames(gaj))
mutated <- mutated[remaining]
tfm$afe <- tfm$afe[remaining, , drop = FALSE]
}
|
50a94207 |
vv <- which(!is.na(gaj), arr.ind = TRUE)
## plot(x = mutated, y = e1$Fitness, ylab = "Fitness",
## xlab = "", type = "n", axes = FALSE)
## box()
## axis(2)
## text(x = mutated, y = x$Fitness, labels = x$Genotype)
## The R CMD CHEKC notes about no visible binding for global variable
x_from <- y_from <- x_to <- y_to <- Change <- muts <-
label <- fitness <- Type <- NULL
dd <- data.frame(muts = mutated,
|
65cb86f3 |
fitness = tfm$afe$Fitness,
label = tfm$afe$Genotype)
|
50a94207 |
cl <- gaj[vv]
sg <- data.frame(x_from = mutated[vv[, 1]],
|
65cb86f3 |
y_from = tfm$afe$Fitness[vv[, 1]],
|
50a94207 |
x_to = mutated[vv[, 2]],
|
65cb86f3 |
y_to = tfm$afe$Fitness[vv[, 2]],
|
50a94207 |
Change = factor(ifelse(cl == 0, "Neutral",
|
4d70b942 |
ifelse(cl > 0, "Gain", "Loss")),
levels = c("Gain", "Loss", "Neutral")))
|
50a94207 |
## From http://stackoverflow.com/a/17257422
number_ticks <- function(n) {function(limits) pretty(limits, n)}
p <- ggplot() +
xlab("") + ylab("Fitness") +
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(),
panel.grid.minor.x = element_blank()) +
geom_segment(data = sg,
aes(x = x_from, y = y_from,
xend = x_to, yend = y_to,
colour = Change,
lty = Change)) + scale_color_manual("Change",
|
4d70b942 |
values = col,
drop = FALSE) +
scale_linetype_manual("Change", values = lty, drop = FALSE) +
scale_x_continuous(breaks = seq(0, max(mutated), 1))
|
50a94207 |
if(log) {
p <- p + scale_y_log10(breaks = number_ticks(5))
} else {
p <- p + scale_y_continuous(breaks = number_ticks(5))
}
peaks_valleys <- peak_valley(gaj)
maxF <- peaks_valleys$peak
minF <- peaks_valleys$valley
ddMM <- dd
ddMM$Type <- NA
ddMM$Type[minF] <- "Sink"
ddMM$Type[maxF] <- "Peak"
ddMM <- ddMM[ddMM$Type %in% c("Sink", "Peak"), ]
if(show_labels && use_ggrepel) {
p <- p + geom_text_repel(data = dd[-c(minF, maxF), ],
aes(x = muts, y = fitness, label = label)) +
geom_label_repel(data = ddMM,
aes(x = muts, y = fitness, label = label, fill = Type),
color = "black",
fontface = "bold")
## geom_label_repel(data = dd[maxF, ], aes(x = muts, y = fitness, label = label),
## color = "black",
## fontface = "bold", fill = col[1]) +
## geom_label_repel(data = dd[minF, ], aes(x = muts, y = fitness, label = label),
## color = "black",
## fontface = "bold", fill = col[2])
}
else {
if(!show_labels) {
## Use same code, but make label empty
ddMM$label <- ""
}
p <- p + geom_text(data = dd[-c(minF, maxF), ],
aes(x = muts, y = fitness, label = label),
vjust = -0.2, hjust = "inward") +
geom_label(data = ddMM,
aes(x = muts, y = fitness, label = label, fill = Type),
vjust = -0.2, hjust = "inward", color = "black",
fontface = "bold")
## geom_label(data = dd[maxF, ], aes(x = muts, y = fitness, label = label),
## vjust = -0.2, hjust = "inward", color = "black",
## fontface = "bold", fill = col[1]) +
## geom_label(data = dd[minF, ], aes(x = muts, y = fitness, label = label),
## vjust = -0.2, hjust = "inward", color = "black",
## fontface = "bold", fill = col[2])
}
p <- p + scale_fill_manual("Local\nmax/min", values = col)
p
}
plot.evalAllGenotypes <- plot.evalAllGenotypesMut <-
plot.genotype_fitness_matrix <-
plot_fitness_landscape <-
plotFitnessLandscape
######################################################################
######################################################################
######################################################################
####
#### Internal functions
####
######################################################################
######################################################################
######################################################################
|
7c1a570f |
## wrap_filter_inaccessible <- function(x, max_num_genotypes, accessible_th) {
## ## wrap it, for my consumption
## tfm <- to_Fitness_Matrix(x, max_num_genotypes = max_num_genotypes)
## mutated <- rowSums(tfm$gfm[, -ncol(tfm$gfm)])
## gaj <- genot_to_adj_mat(tfm$gfm)
## gaj <- filter_inaccessible(gaj, accessible_th)
## remaining <- as.numeric(colnames(gaj))
## mutated <- mutated[remaining]
## tfm$afe <- tfm$afe[remaining, , drop = FALSE]
## return(list(remaining = remaining,
## mutated = mutated,
## tfm = tfm))
## }
|
c95df82d |
## No longer being used. Used to be in rfitness
## count_accessible_g <- function(gfm, accessible_th) {
## gaj <- genot_to_adj_mat(gfm)
## gaj <- filter_inaccessible(gaj, accessible_th)
## return(ncol(gaj) - 1)
## }
|
7c1a570f |
|
c95df82d |
## There is now C++ code to get just the locations/positions of the
## accessible genotypes
|
7c1a570f |
filter_inaccessible <- function(x, accessible_th) {
## Return an adjacency matrix with only accessible paths. x is the gaj
## matrix created in the plots. The difference between genotypes
## separated by a hamming distance of 1
|
c95df82d |
## FIXME: could do the x[, -1] before loop and not add the 1
## inside while, and do that at end
|
7c1a570f |
colnames(x) <- rownames(x) <- 1:ncol(x)
while(TRUE) {
## remove first column
## We use fact that all(na.omit(x) < u) is true if all are NA
## so inaccessible rows are removed and thus destination columns
wrm <- which(apply(x[, -1, drop = FALSE], 2,
function(y) {all(na.omit(y) < accessible_th)})) + 1
if(length(wrm) == 0) break;
x <- x[-wrm, -wrm, drop = FALSE]
}
x[x < 0] <- NA
return(x)
}
|
c95df82d |
|
f9a38e24 |
## wrapper to the C++ code
fast_peaks <- function(x, th) {
## x is the fitness matrix, not adjacency matrix
## Only works if no connected genotypes that form maxima. I.e., no
## identical fitness. Do a sufficient check for it (too inclusive, though)
## And only under no back mutation
original_pos <- 1:nrow(x)
numMut <- rowSums(x[, -ncol(x)])
o_numMut <- order(numMut)
x <- x[o_numMut, ]
numMut <- numMut[o_numMut]
f <- x[, ncol(x)]
## Two assumptions
stopifnot(numMut[1] == 0)
## make sure no repeated in those that could be maxima
if(any(duplicated(f[f >= f[1]])))
stop("There could be several connected maxima genotypes.",
" This function is not safe to use.")
y <- x[, -ncol(x)]
storage.mode(y) <- "integer"
original_pos <- original_pos[o_numMut]
return(sort(original_pos[peaksLandscape(y, f,
as.integer(numMut),
th)]))
}
|
c95df82d |
## wrapper to the C++ code
wrap_accessibleGenotypes <- function(x, th) {
## x is the fitness matrix, not adjacency matrix
|
f9a38e24 |
original_pos <- 1:nrow(x)
|
c95df82d |
numMut <- rowSums(x[, -ncol(x)])
o_numMut <- order(numMut)
x <- x[o_numMut, ]
numMut <- numMut[o_numMut]
|
f9a38e24 |
original_pos <- original_pos[o_numMut]
|
c95df82d |
y <- x[, -ncol(x)]
storage.mode(y) <- "integer"
acc <- accessibleGenotypes(y, x[, ncol(x)],
as.integer(numMut),
th)
|
f9a38e24 |
## return(acc[acc > 0])
return(sort(original_pos[acc[acc > 0]]))
}
## wrapper to the C++ code; the former one, only for testing. Remove
## eventually FIXME
wrap_accessibleGenotypes_former <- function(x, th) {
## x is the fitness matrix, not adjacency matrix
original_pos <- 1:nrow(x)
numMut <- rowSums(x[, -ncol(x)])
o_numMut <- order(numMut)
x <- x[o_numMut, ]
numMut <- numMut[o_numMut]
original_pos <- original_pos[o_numMut]
y <- x[, -ncol(x)]
storage.mode(y) <- "integer"
acc <- accessibleGenotypes_former(y, x[, ncol(x)],
as.integer(numMut),
th)
return(sort(original_pos[acc[acc > 0]]))
|
c95df82d |
}
## A transitional function
faster_accessible_genotypes_R <- function(x, th) {
rs0 <- rowSums(x[, -ncol(x)])
x <- x[order(rs0), ]
rm(rs0)
y <- x[, -ncol(x)]
f <- x[, ncol(x)]
rs <- rowSums(y)
## If 0, not accessible
## adm <- slam::simple_triplet_zero_matrix(nrow = length(rs), ncol = length(rs),
## mode = "integer")
adm <- matrix(0, nrow = length(rs), ncol = length(rs))
storage.mode(adm) <- "integer"
## Most time is gone here
for(i in 1:length(rs)) { ## i is the current genotype
candidates <- which(rs == (rs[i] + 1))
for(j in candidates) {
if( (sum(abs(y[j, ] - y[i, ])) == 1) &&
( (f[j] - f[i]) >= th ) ) {
## actually, this is the largest time sink using slam
adm[i, j] <- 1L
}
}
}
colnames(adm) <- rownames(adm) <- 1:ncol(adm)
admtmp <- adm[, -1, drop = FALSE] ## we do not want the root column.
while(TRUE) {
## We remove inaccessible cols (genotypes) and the corresponding
## rows repeatedly until nothing left to remove; any column left
## is therefore accessible throw at least one path.
## inacc_col <- which(slam::colapply_simple_triplet_matrix(admtmp, FUN = sum) == 0L)
inacc_col <- which(colSums(admtmp) == 0L)
if(length(inacc_col) == 0) break;
inacc_row <- inacc_col + 1 ## recall root row is left
admtmp <- admtmp[-inacc_row, -inacc_col, drop = FALSE]
}
return(as.numeric(c(colnames(adm)[1], colnames(admtmp))))
}
## ## This uses slam, but that is actually slower because
## ## of the assignment
## faster_accessible_genots_slam <- function(x, th = 0) {
## ## Given a genotype matrix, return the genotypes that are accessible
## ## via creating a directed adjacency matrix between genotypes
## ## connected (i.e., those that differ by gaining one mutation). 0
## ## means not connected, 1 means connected.
## ## There is a more general function in OncoSimulR that will give the
## ## fitness difference. But not doing the difference is faster than
## ## just setting a value, say 1, if all we want is to keep track of
## ## accessible ones. And by using only 0/1 we can store only an
## ## integer. And no na.omits, etc. Is too restricted? Yes. But for
## ## simulations and computing just accessible genotypes, probably a
## ## hell of a lot faster.
## ## Well, this is not incredibly fast either.
## ## Make sure sorted, so ancestors always before descendants
## rs0 <- rowSums(x[, -ncol(x)])
## x <- x[order(rs0), ]
## rm(rs0)
## y <- x[, -ncol(x)]
## f <- x[, ncol(x)]
## rs <- rowSums(y)
## ## If 0, not accessible
## adm <- slam::simple_triplet_zero_matrix(nrow = length(rs), ncol = length(rs),
## mode = "integer")
## for(i in 1:length(rs)) { ## i is the current genotype
## candidates <- which(rs == (rs[i] + 1))
## for(j in candidates) {
## ## sumdiff <- sum(abs(y[j, ] - y[i, ]))
## ## if(sumdiff == 1)
## if( (sum(abs(y[j, ] - y[i, ])) == 1) &&
## ( (f[j] - f[i]) >= th ) )
## adm[i, j] <- 1L
## }
## }
## colnames(adm) <- rownames(adm) <- 1:ncol(adm)
## admtmp <- adm[, -1, drop = FALSE] ## we do not want the root column.
## while(TRUE) {
## ## We remove inaccessible cols (genotypes) and the corresponding
## ## rows repeatedly until nothing left to remove; any column left
## ## is therefore accessible throw at least one path.
## ## inacc_col <- which(slam::colapply_simple_triplet_matrix(admtmp, FUN = sum) == 0L)
## inacc_col <- which(slam::col_sums(admtmp) == 0L)
## if(length(inacc_col) == 0) break;
## inacc_row <- inacc_col + 1 ## recall root row is left
## admtmp <- admtmp[-inacc_row, -inacc_col, drop = FALSE]
## }
## return(as.numeric(c(colnames(adm)[1], colnames(admtmp))))
## }
|
50a94207 |
generate_matrix_genotypes <- function(g) {
## FIXME future: do this for order too? Only if rfitness for order.
## Given a number of genes, generate all possible genotypes.
if(g > 20) stop("This would generate more than one million genotypes")
## g: number of genes
f1 <- function(n) {
lapply(seq.int(n), function(x) combinations(n = n, r = x))
}
genotNums <- f1(g)
list.of.vectors <- function(y) {
## there's got to be a simpler way
lapply(unlist(lapply(y, function(x) {apply(x, 1, list)}),
recursive = FALSE),
function(m) m[[1]])
}
genotNums <- list.of.vectors(genotNums)
v <- rep(0, g)
mat <- matrix(unlist(lapply(genotNums,
function(x) {
v[x] <- 1
return(v)
}
)), ncol = g, byrow = TRUE)
mat <- rbind(rep(0, g), mat)
colnames(mat) <- LETTERS[1:g]
return(mat)
}
|
f9a38e24 |
## The R version. See also the C++ one
genot_to_adj_mat_R <- function(x) {
## x is the fitness matrix
|
50a94207 |
## FIXME this can take about 23% of the time of the ggplot call.
## But them, we are quickly constructing a 2000*2000 matrix
## Given a genotype matrix, as given by allGenotypes_to_matrix, produce a
## directed adjacency matrix between genotypes connected (i.e., those
## that differ by gaining one mutation) with value being the
## difference in fitness between destination and origin
|
c95df82d |
## FIXME: code is now in place to do all of this in C++
|
50a94207 |
## Make sure sorted, so ancestors always before descendants
|
f9a38e24 |
original_pos <- 1:nrow(x)
numMut <- rowSums(x[, -ncol(x)])
o_numMut <- order(numMut)
x <- x[o_numMut, ]
original_pos <- original_pos[o_numMut]
rm(numMut)
|
50a94207 |
y <- x[, -ncol(x)]
f <- x[, ncol(x)]
|
f9a38e24 |
rs <- rowSums(y) ## redo for paranoia; could have ordered numMut
|
50a94207 |
## Move this to C++?
adm <- matrix(NA, nrow = length(rs), ncol = length(rs))
for(i in 1:length(rs)) { ## i is the current genotype
candidates <- which(rs == (rs[i] + 1))
for(j in candidates) {
sumdiff <- sum(abs(y[j, ] - y[i, ]))
## if(sumdiff < 0) stop("eh?")
if(sumdiff == 1) adm[i, j] <- (f[j] - f[i])
}
}
|
f9a38e24 |
colnames(adm) <- rownames(adm) <- original_pos
|
50a94207 |
return(adm)
}
|
f9a38e24 |
genot_to_adj_mat <- function(x) {
## x is the fitness matrix
## adding column and row names should rarely be necessary
## as these are internal functions, etc. But just in case
original_pos <- 1:nrow(x)
numMut <- rowSums(x[, -ncol(x)])
o_numMut <- order(numMut)
x <- x[o_numMut, ]
numMut <- numMut[o_numMut]
original_pos <- original_pos[o_numMut]
y <- x[, -ncol(x)]
storage.mode(y) <- "integer"
adm <- genot2AdjMat(y, x[, ncol(x)],
as.integer(numMut))
colnames(adm) <- rownames(adm) <- original_pos
return(adm)
}
## ## to move above to C++ note that loop can be
## for(i in 1:length(rs)) { ## i is the current genotype
## for(j in (i:length(rs))) {
## if(rs[j] > (rs[i] + 1)) break;
## else if(rs[j] == (rs[i] + 1)) {
## ## and use here my HammingDistance function
## ## sumdiff <- sum(abs(y[j, ] - y[i, ]))
## ## if(sumdiff == 1) adm[i, j] <- (f[j] - f[i])
## if(HammingDistance(y[j, ], y[i, ]) == 1) adm[i, j] = (f[j] - f[i]);
## }
## }
## }
## actually, all that is already in accessibleGenotypes except for the
## filling up of adm.
|
50a94207 |
peak_valley <- function(x) {
## FIXME: when there are no identical entries, all this
## can be simplified a lot. Actually, there could be a way
## to only use the slow code on paths with 0.
## But this does not seem to be the CPU hog.
## x is an adjacency matrix where i,j is fitness_j - fitness_i. Thus,
## negative values means j has less fitness than the incoming. And if
## all not NA entries of a column are negative, then column j is is
## sink candidate
## Thus we locate local maxima and minima
## Some of this complicated as we need to detect paths like -3, 0, 0,
## 3. The -3 and the two 0 are local minima with identical values.
## We assume crucially that ancestors always have smaller indexes in
## the matrix than descendants.
## Valleys
bad_back <- vector("integer", nrow(x))
for(i in ncol(x):1) {
if(any(x[i, ] < 0, na.rm = TRUE) || bad_back[i]) {
## this node is bad. Any ancestor with fitness >= is bad
bad_back[i] <- 1
reach_b <- which(x[, i] <= 0)
bad_back[reach_b] <- 1
}
}
bad_fwd <- vector("integer", nrow(x))
for(i in 1:nrow(x)) {
if( any(x[, i] > 0, na.rm = TRUE) || bad_fwd[i] ) {
## this node is bad. Any descendant with fitness >= is bad
bad_fwd[i] <- 1
reach_f <- which(x[i, ] >= 0)
bad_fwd[reach_f] <- 1
}
}
bad <- union(which(bad_back == 1), which(bad_fwd == 1))
candidate <- which(apply(x, 2, function(z) all(z <= 0, na.rm = TRUE)))
valley <- setdiff(candidate, bad)
null <- suppressWarnings(try({rm(bad_back, bad_fwd, reach_b, reach_f, candidate)},
silent = TRUE))
## Peaks
bad_back <- vector("integer", nrow(x))
for(i in ncol(x):1) {
if(any(x[i, ] > 0, na.rm = TRUE) || bad_back[i]) {
## this node is bad. Any ancestor with fitness >= is bad
bad_back[i] <- 1
reach_b <- which(x[, i] >= 0)
bad_back[reach_b] <- 1
}
}
bad_fwd <- vector("integer", nrow(x))
for(i in 1:nrow(x)) {
|
f9a38e24 |
## Eh, why any? All.
## Nope, any: we want peaks in general, not just
## under assumption of "no back mutation"
## We get a different result when we restrict to accessible
## because all < 0 in adjacency are turned to NAs.
|
50a94207 |
if( any(x[, i] < 0, na.rm = TRUE) || bad_fwd[i] ) {
|
f9a38e24 |
## if( all(x[, i] < 0, na.rm = TRUE) ) {
|
50a94207 |
## this node is bad. Any descendant with fitness >= is bad
bad_fwd[i] <- 1
reach_f <- which(x[i, ] <= 0)
bad_fwd[reach_f] <- 1
}
}
bad <- union(which(bad_back == 1), which(bad_fwd == 1))
candidate <- which(apply(x, 2, function(z) all(z >= 0, na.rm = TRUE)))
peak <- setdiff(candidate, bad)
return(list(peak = peak, valley = valley))
}
|
f9a38e24 |
## For the future
## ## data.frame (two columns: genotype with "," and Fitness) -> fitness graph (DAG)
## ## Return an adj matrix of the fitness graph from a fitness
## ## landscape
## ## Based on code in plotFitnessLandscape
## flandscape_to_fgraph <- function(afe) {
## gfm <- OncoSimulR:::allGenotypes_to_matrix(afe)
## ## mutated <- rowSums(gfm[, -ncol(gfm)])
## gaj <- OncoSimulR:::genot_to_adj_mat(gfm)
## gaj2 <- OncoSimulR:::filter_inaccessible(gaj, 0)
## stopifnot(all(na.omit(as.vector(gaj == gaj2))))
## remaining <- as.numeric(colnames(gaj2))
## ## mutated <- mutated[remaining]
## afe <- afe[remaining, , drop = FALSE]
## ## vv <- which(!is.na(gaj2), arr.ind = TRUE)
## gaj2 <- gaj2
## gaj2[is.na(gaj2)] <- 0
## gaj2[gaj2 > 0] <- 1
## colnames(gaj2) <- rownames(gaj2) <- afe[, "Genotype"]
## return(gaj2)
## }
## ## This could be done easily in C++, taking care of row/colnames at end,
## ## without moving around the full adjacency matrix.
## ## Skeleton for C++
## ## a call to accessibleGenotypesPeaksLandscape
## ## (with another argument or changing the returnpeaks by a three value thing)
## ## after done with first loop,
## ## return the matrix adm[accessible > 0, accessible >0]
## ## only need care with row/colnames
|