Skip to content

Commit 1a71add

Browse files
committed
Added asLogNormal
1 parent 6b22d1d commit 1a71add

9 files changed

Lines changed: 246 additions & 43 deletions

File tree

‎NAMESPACE‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export(SimParam)
1313
export(aa)
1414
export(addSegSite)
1515
export(asCategorical)
16+
export(asLogNormal)
1617
export(attrition)
1718
export(bv)
1819
export(cChr)

‎NEWS.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# AlphaSimR 2.2.0
22

3+
* Added asLogNormal() function.
4+
5+
* Improved examples for asCategorical() function.
6+
7+
* Improved documentation for `SimParam$finalizePop` field.
8+
39
* Consolidated the use of RNG across the package to enable reproducibility. This is an internal change not visible to users.
410

511
* Made meiosis-related C++ RNG reproducible across serial and OpenMP execution by using `dqrng`. This is an internal change not visible to users, but will enable visible reproducibility.

‎R/Class-Pop.R‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ newPop = function(rawPop,simParam=NULL,...){
638638
#' @param maleParentPop optional population of male parents
639639
#' @param hist optional recombination history
640640
#' @param simParam an object of \code{\link{SimParam}}
641-
#' @param ... additional arguments passed to the finalizePop
641+
#' @param ... additional arguments passed to the \code{finalizePop}
642642
#' function in simParam
643643
#'
644644
#' @return Returns an object of \code{\link{Pop-class}}

‎R/Class-SimParam.R‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ SimParam = R6Class(
2727
founderPop = "MapPop",
2828

2929
#' @field finalizePop function applied to newly created populations.
30-
#' Currently does nothing and should only be changed by expert users.
30+
#' The function must satisfy these four requirements:
31+
#' 1) The first argument must be a \code{\link{Pop-class}} object,
32+
#' 2) The second argument must be a SimParam object,
33+
#' 3) The remaining arguments must be passed via \code{...}, and
34+
#' 4) The return must be a \code{\link{Pop-class}} object.
35+
#' See \code{\link{asCategorical}} for an example.
3136
finalizePop = "function",
3237

3338
#' @field allowEmptyPop if true, population arguments with nInd=0 will

‎R/phenotypes.R‎

Lines changed: 159 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -277,37 +277,159 @@ setPheno = function(pop, h2=NULL, H2=NULL, varE=NULL, corE=NULL,
277277
return(pop)
278278
}
279279

280+
#' @title Convert a normal (Gaussian) trait to a log-normal trait
281+
#' @param x matrix, values for one or more traits (if not a matrix,
282+
#' we cast to a matrix).
283+
#' @param meanlog \code{NULL}, numeric or list, trait mean(s) on the log scale;
284+
#' when \code{NULL} mean of 0 is assumed,
285+
#' when numeric means for all traits in \code{x} must be provided, and
286+
#' when list means for all traits in \code{x} must be provided with possibility
287+
#' to pass a \code{NULL} list node to skip the conversion for the trait
288+
#' (see examples).
289+
#' @details If input trait is normal (Gaussian) then this function generates
290+
#' a log-normal trait by applying exponential link function on the input.
291+
#' Note the possible terminological confusion, a log-normal trait is expressed
292+
#' on exponential scale and its underlying (latent) values are on the log scale
293+
#' (see examples). See \code{\link{rlnorm}} on the log-normal distribution,
294+
#' including the expressions for the expected value and variance of observed trait,
295+
#' which can help you tune the parameters to obtain targeted observed trait values.
296+
#' Note that \code{asLogNormal} does not provide the \code{sdlog} argument because
297+
#' latent trait variation is already controlled by other parameters.
298+
#' See examples below.
299+
#' @return matrix of log-normal values.
300+
#' @examples
301+
#' #Simulate a founder pop, set latent trait parameters, and create a population
302+
#' founderPop = quickHaplo(nInd=10, nChr=1, segSites=10)
303+
#' SP = SimParam$new(founderPop)
304+
#' \dontshow{SP$nThreads = 1L}
305+
#' trtMeanLog = c(0, 0)
306+
#' trtVarGLog = c(1, 2)
307+
#' SP$addTraitA(nQtlPerChr = 10, mean = trtMeanLog, var = trtVarGLog,
308+
#' corA = matrix(data = c(1.0, 0.6,
309+
#' 0.6, 1.0), ncol = 2))
310+
#' trtVarELog = c(1, 1)
311+
#' trtVarPLog = trtVarGLog + trtVarELog
312+
#' SP$setVarE(varE = trtVarELog)
313+
#' pop = newPop(founderPop)
314+
#' popLarge = randCross(pop, nCrosses = 1000)
315+
#'
316+
#' meanVarFun = function(x) list(mean = mean(x), var = var(x))
317+
#'
318+
#' #Latent phenotypes and parameters
319+
#' (phenoLog = pheno(pop))
320+
#' phenoLogLarge = pheno(popLarge)
321+
#' apply(X = phenoLog, MARGIN = 2, FUN = meanVarFun)
322+
#' apply(X = phenoLogLarge, MARGIN = 2, FUN = meanVarFun)
323+
#'
324+
#' #Convert a single input trait
325+
#' (phenoExpMeanLog0 = asLogNormal(x = pheno(pop)[, 1]))
326+
#' meanVarFun(phenoExpMeanLog0)
327+
#'
328+
#' #Demonstrate meanlog argument
329+
#' #Here we aim to obtain observed trait values with the mean of 1.
330+
#' #Since E(trtExp)=exp(mean(trtLog)+var(trtLog)/2), trtMeanLog[1]=0, and trtVarPLog[1],
331+
#' #to get E(trtExp)=1=exp(0) we set meanlog to -trtVarPLog[1]/2.
332+
#' #See also ?rlnorm for the expression of variance.
333+
#' (phenoExpMeanExp1 = asLogNormal(x = pheno(pop)[, 1], meanlog = -trtVarPLog[1]/2))
334+
#' meanVarFun(phenoExpMeanExp1)
335+
#' exp(trtVarPLog[1] - 1)
336+
#' cbind(phenoLog = phenoLog[, 1],
337+
#' phenoExpMeanLog0 = phenoExpMeanLog0,
338+
#' phenoExpMeanExp1 = phenoExpMeanExp1)
339+
#'
340+
#' tmp = cbind(phenoLog = phenoLogLarge[, 1],
341+
#' phenoExpMeanLog0 = c(asLogNormal(phenoLogLarge[, 1])),
342+
#' phenoExpMeanExp1 = c(asLogNormal(phenoLogLarge[, 1], meanlog = -trtVarPLog[1]/2)))
343+
#' (tmp2 = apply(X = tmp, MARGIN = 2, FUN = meanVarFun))
344+
#' par(mfrow = c(3, 1))
345+
#' hist(tmp[, "phenoLog"], main = paste0("Mean: ", v=tmp2$phenoLog$mean))
346+
#' abline(v=tmp2$phenoLog$mean, col = "red")
347+
#' hist(tmp[, "phenoExpMeanLog0"], main = paste0("Mean: ", v=tmp2$phenoExpMeanLog0$mean))
348+
#' abline(v = tmp2$phenoExpMeanLog0$mean, col = "red")
349+
#' hist(tmp[, "phenoExpMeanExp1"], main = paste0("Mean: ", v=tmp2$phenoExpMeanExp1$mean))
350+
#' abline(v= tmp2$phenoExpMeanExp1$mean, col = "red")
351+
#'
352+
#' #Convert multiple input traits
353+
#' asLogNormal(x = pheno(pop))
354+
#' try(asLogNormal(x = pheno(pop), meanlog = 0))
355+
#' asLogNormal(x = pheno(pop), meanlog = c(0, 1))
356+
#' asLogNormal(x = pheno(pop), meanlog = list(0, NULL))
357+
#'
358+
#' #Store the recoded trait manually
359+
#' pheno(pop)
360+
#' pop@pheno[, 1] = asLogNormal(x = pheno(pop)[, 1])
361+
#' pheno(pop)
362+
#'
363+
#' #Apply and store the transformation automatically via SimParam$finalizePop()
364+
#' SP$finalizePop = function(pop, simParam = SP) {
365+
#' pop@pheno[, 1] = asLogNormal(x = pheno(pop)[, 1])
366+
#' return(pop)
367+
#' }
368+
#' pop = newPop(founderPop)
369+
#' pheno(pop)
370+
#' @export
371+
asLogNormal <- function(x, meanlog = NULL) {
372+
if (!is.matrix(x)) {
373+
x = as.matrix(x)
374+
}
375+
nTraits = ncol(x)
376+
if (is.null(meanlog)) {
377+
meanlog = rep(x = 0, times = nTraits)
378+
}
379+
if (is.numeric(meanlog)) {
380+
if (length(meanlog) != nTraits) {
381+
stop("You must supply meanlog for all traits in x!")
382+
}
383+
for (trt in 1:nTraits) {
384+
x[, trt] = exp(meanlog[trt] + x[, trt])
385+
}
386+
} else if (is.list(meanlog)) {
387+
if (length(meanlog) != nTraits) {
388+
stop("You must supply meanlog for all traits in x!")
389+
}
390+
for (trt in 1:nTraits) {
391+
if (!is.null(meanlog[[trt]])) {
392+
x[, trt] = exp(meanlog[[trt]] + x[, trt])
393+
}
394+
}
395+
} else {
396+
stop("meanlog must be NULL, numeric, or list!")
397+
}
398+
return(x)
399+
}
400+
280401
#' @title Convert a normal (Gaussian) trait to an ordered categorical (threshold)
281402
#' trait
282403
#' @param x matrix, values for one or more traits (if not a matrix,
283-
#' we cast to a matrix)
284-
#' @param p NULL, numeric or list, when \code{NULL} the \code{threshold} argument
285-
#' takes precedence; when numeric, provide a vector of probabilities of
286-
#' categories to convert continuous values into categories for a single trait
287-
#' (if probabilities do not sum to 1, another category is added and a warning
288-
#' is raised); when list, provide a list of numeric probabilities - list node
289-
#' with \code{NULL} will skip conversion for a specific trait (see examples);
290-
#' internally \code{p} is converted to \code{threshold} hence input
291-
#' \code{threshold} is overwritten
404+
#' we cast to a matrix).
405+
#' @param p \code{NULL}, numeric, or list, when \code{NULL} the \code{threshold}
406+
#' argument takes precedence; when numeric, provide a vector of probabilities
407+
#' of categories to convert continuous values into categories for a single
408+
#' trait (if probabilities do not sum to 1, another category is added and
409+
#' a warning is raised); when list, provide a list of numeric probabilities
410+
#' - list node with \code{NULL} will skip conversion for a specific trait
411+
#' (see examples); internally \code{p} is converted to \code{threshold} hence
412+
#' input \code{threshold} is overwritten.
292413
#' @param mean numeric, assumed mean(s) of the normal (Gaussian) trait(s);
293-
#' used only when \code{p} is given
414+
#' used only when \code{p} is given.
294415
#' @param var numeric, assumed variance(s) of the normal (Gaussian) trait(s);
295-
#' used only when \code{p} is given
296-
#' @param threshold NULL, numeric or list, when numeric, provide a vector of
416+
#' used only when \code{p} is given.
417+
#' @param threshold \code{NULL}, numeric or, list, when numeric, provide a vector of
297418
#' threshold values to convert continuous values into categories for a single trait
298419
#' (the thresholds specify left-closed and right-opened intervals [t1, t2),
299420
#' which can be changed with \code{include.lowest} and \code{right};
300421
#' ensure you add \code{-Inf} and \code{Inf} or min and max to cover the whole
301422
#' range of values; otherwise you will get \code{NA} values);
302423
#' when list, provide a list of numeric thresholds - list node with \code{NULL}
303-
#' will skip conversion for a specific trait (see examples)
304-
#' @param include.lowest logical, see \code{\link{cut}}
305-
#' @param right logical, see \code{\link{cut}}
424+
#' will skip conversion for a specific trait (see examples).
425+
#' @param include.lowest logical, see \code{\link{cut}}.
426+
#' @param right logical, see \code{\link{cut}}.
306427
#' @details If input trait is normal (Gaussian) then this function generates a
307428
#' categorical trait according to the ordered probit model.
308429
#' @return matrix of values with some traits recorded as ordered categories
309-
#' in the form of 1:nC with nC being the number of categories.
430+
#' in the form of \code{1:nC} with \code{nC} being the number of categories.
310431
#' @examples
432+
#' #Simulate a founder pop, set latent trait parameters, and create a population
311433
#' founderPop = quickHaplo(nInd=10, nChr=1, segSites=10)
312434
#' SP = SimParam$new(founderPop)
313435
#' \dontshow{SP$nThreads = 1L}
@@ -316,10 +438,10 @@ setPheno = function(pop, h2=NULL, H2=NULL, varE=NULL, corE=NULL,
316438
#' SP$addTraitA(nQtlPerChr = 10, mean = trtMean, var = trtVarG,
317439
#' corA = matrix(data = c(1.0, 0.6,
318440
#' 0.6, 1.0), ncol = 2))
319-
#' pop = newPop(founderPop)
320441
#' trtVarE = c(1, 1)
321442
#' trtVarP = trtVarG + trtVarE
322-
#' pop = setPheno(pop, varE = trtVarE)
443+
#' SP$setVarE(varE = trtVarE)
444+
#' pop = newPop(founderPop)
323445
#' pheno(pop)
324446
#'
325447
#' #Convert a single input trait
@@ -356,6 +478,19 @@ setPheno = function(pop, h2=NULL, H2=NULL, varE=NULL, corE=NULL,
356478
#' p = list(c(0.5, 0.5),
357479
#' p),
358480
#' mean = trtMean, var = trtVarP)
481+
#'
482+
#' #Store the recoded trait manually
483+
#' pheno(pop)
484+
#' pop@pheno[, 1] = asCategorical(x = pheno(pop)[, 1])
485+
#' pheno(pop)
486+
#'
487+
#' #Apply and store the transformation automatically via SimParam$finalizePop()
488+
#' SP$finalizePop = function(pop, simParam = SP) {
489+
#' pop@pheno[, 1] = asCategorical(x = pheno(pop)[, 1])
490+
#' return(pop)
491+
#' }
492+
#' pop = newPop(founderPop)
493+
#' pheno(pop)
359494
#' @export
360495
asCategorical = function(x, p = NULL, mean = 0, var = 1,
361496
threshold = c(-Inf, 0, Inf),
@@ -384,8 +519,11 @@ asCategorical = function(x, p = NULL, mean = 0, var = 1,
384519
for (trt in 1:nTraits) {
385520
if (!is.null(p[[trt]])) {
386521
pSum = sum(p[[trt]])
387-
if (!(pSum == 1)) { # TODO: how can we do floating point aware comparison?
388-
warning("Probabilities do not sum to 1 - creating one more category!")
522+
if (pSum > 1) {
523+
stop("Probabilities for trait ", trt, " sum to more than 1!")
524+
} else if (pSum < 1) {
525+
warning("Probabilities do not sum to 1 for trait ", trt,
526+
"! Creating one more category!")
389527
p[[trt]] = c(p[[trt]], 1 - pSum)
390528
}
391529
tmp = qnorm(p = cumsum(p[[trt]]), mean = mean[trt], sd = sqrt(var[trt]))
@@ -406,7 +544,7 @@ asCategorical = function(x, p = NULL, mean = 0, var = 1,
406544
threshold = list(threshold)
407545
}
408546
if (length(threshold) != nTraits) {
409-
stop("You must supply thresholds for all traits in x !")
547+
stop("You must supply thresholds for all traits in x!")
410548
}
411549
for (trt in 1:nTraits) {
412550
if (!is.null(threshold[[trt]])) {

‎man/SimParam.Rd‎

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎man/asCategorical.Rd‎

Lines changed: 32 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎man/dot-newPop.Rd‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)