Multivariate Bayesian variable selection regression

Multivariate EBNM based prior for M&M

Here for the simulation benchmark we prepare mixture prior based on a mulrivariate Emperical Bayes Normal Mean model (previously we use Extreme Deconvolution for the task).

Approach

Here is the analysis plan:

  1. Identify up to 20K genes where there is complete phenotype data to make a good / realistic residual variance estimate via FLASH
  2. Simulate 20K data under my phenotypic models (the latest DSC benchmark setting) and generate sumstats for them ; bhat and sbhat
  3. For each data-set, take the strongest gene-snp pair as the strong set
  4. Also select from each data-set perhaps 4 "random" gene-snp pair.
  5. then try to run your estimate of Vhat to get Vhat first, and run Yunqi / Peter's ED

In GTEx we have >35K genes. The reason we want to try using 20K is that 20K seems to have enough information learning about the pattern of sharing between conditions for mixtures I simulated in this notebook.

But we "cheat" a bit by simulating under identity residual variance for all genes, and fit EBNM assuming residual variance is identity, too; or just estimating a global residual variance. This makes the problem easier. Because in practice residual can be different (though maybe similar!) for different genes.

So the simplified plan is to only do 2~5 with 2 using just identity matrix for residual variance.

Workflow

In [ ]:
[global]
parameter: cwd = path('/project2/mstephens/gaow/mvarbvs/dsc/mnm_prototype/mnm_sumstats')
parameter: model = 'artificial_mixture_identity' # 'gtex_mixture_identity'
# handle N = per_chunk data-set in one job
parameter: per_chunk = 1000
import glob
In [1]:
%cd /project2/mstephens/gaow/mvarbvs/dsc/mnm_prototype/mnm_sumstats
/project2/mstephens/gaow/mvarbvs/dsc/mnm_prototype/mnm_sumstats

Get top gene-SNP and random gene-SNP pairs per gene

In [31]:
# extract data for MAHS from summary stats
[extract_1]
parameter: seed = 999
parameter: n_random = 4
input: glob.glob(f'{cwd}/{model}/*.rds'), group_by = per_chunk
output: f"{cwd}/{model}/cache/{model}_{_index+1}.rds"
task: trunk_workers = 1, walltime = '1h', trunk_size = 1, mem = '4G', cores = 1, tags = f'{_output:bn}'
R: expand = "${ }"
    set.seed(${seed})
    matxMax <- function(mtx) {
      return(arrayInd(which.max(mtx), dim(mtx)))
    }
    remove_rownames = function(x) {
        for (name in names(x)) rownames(x[[name]]) = NULL
        return(x)
    }
    extract_one_data = function(infile, n_random) {
        # If cannot read the input for some reason then let it go. I dont care losing one.
        dat = tryCatch(readRDS(infile)$sumstats, error = function(e) return(NULL))
        if (is.null(dat)) return(NULL)
        z = abs(dat$bhat/dat$sbhat)
        max_idx = matxMax(z)
        strong = list(bhat = dat$bhat[max_idx[1],,drop=F], sbhat = dat$sbhat[max_idx[1],,drop=F])
        if (max_idx[1] == 1) {
            sample_idx = 2:nrow(z)
        } else if (max_idx[1] == nrow(z)) {
            sample_idx = 1:(max_idx[1]-1)
        } else {
            sample_idx = c(1:(max_idx[1]-1), (max_idx[1]+1):nrow(z))
        }
        random_idx = sample(sample_idx, n_random, replace = T)
        random = list(bhat = dat$bhat[random_idx,,drop=F], sbhat = dat$sbhat[random_idx,,drop=F])
        return(list(random = remove_rownames(random),  strong = remove_rownames(strong)))
    }
    merge_data = function(res, one_data) {
      if (length(res) == 0) {
          return(one_data)
      } else if (is.null(one_data)) {
          return(res)
      } else {
          for (d in names(one_data)) {
              for (s in names(one_data[[d]])) {
                  res[[d]][[s]] = rbind(res[[d]][[s]], one_data[[d]][[s]])
              }
          }
          return(res)
      }
    }
    res = list()
    for (f in c(${_input:r,})) {
      res = merge_data(res, extract_one_data(f, ${n_random}))
    }
    saveRDS(res, ${_output:r})
  
[extract_2]
input: group_by = "all"
output: f"{cwd}/{model}.rds"
task: trunk_workers = 1, walltime = '1h', trunk_size = 1, mem = '4G', cores = 1, tags = f'{_output:bn}'
R: expand = "${ }"
    merge_data = function(res, one_data) {
      if (length(res) == 0) {
          return(one_data)
      } else {
          for (d in names(one_data)) {
              for (s in names(one_data[[d]])) {
                  res[[d]][[s]] = rbind(res[[d]][[s]], one_data[[d]][[s]])
              }
          }
          return(res)
      }
    }
    dat = list()
    for (f in c(${_input:r,})) {
      dat = merge_data(dat, readRDS(f))
    }
    # make output consistent in format with 
    # https://github.com/stephenslab/gtexresults/blob/master/workflows/mashr_flashr_workflow.ipynb
    saveRDS(
          list(random.z = dat$random$bhat/dat$random$sbhat,
           strong.z = dat$strong$bhat/dat$strong$sbhat, 
           random.b = dat$random$bhat,
           strong.b = dat$strong$bhat,
           random.s = dat$random$sbhat,
           strong.s = dat$strong$sbhat),
          ${_output:r})

To run it:

for m in artificial_mixture_identity gtex_mixture_identity; do 
    sos run analysis/20200502_Prepare_ED_prior.ipynb extract --model $m -c midway2.yml -q midway2
done

Run extreme deconvolution using mashr

Before this, we need to run the following to generate FLASH mixture,

for m in artificial_mixture_identity gtex_mixture_identity; do
sos run ~/GIT/gtexresults/workflows/mashr_flashr_workflow.ipynb flash \
    --cwd /project2/mstephens/gaow/mvarbvs/dsc/mnm_prototype/mnm_sumstats/ \
    --data /project2/mstephens/gaow/mvarbvs/dsc/mnm_prototype/mnm_sumstats/$m.rds \
    --effect-model EE -c midway2.yml -q midway2
done

We will use simple method to compute the residual variance, as implemented in the pipeline below.

In [ ]:
[mash_ed_1, mash_teem_1, udr_ed_1]
depends: R_library("mashr")
parameter: npc = 3
input: f"{cwd}/{model}.rds", f"{cwd}/{model}.EE.flash.rds"
output: f"{cwd}/{model}.FL_PC{npc}.rds"
R: expand = "${ }", workdir = cwd, stderr = f"{_output:n}.stderr", stdout = f"{_output:n}.stdout"
    library(mashr)
    dat = readRDS(${_input[0]:r})
    vhat = estimate_null_correlation_simple(mash_set_data(dat$random.b, Shat=dat$random.s, zero_Bhat_Shat_reset = 1E3))
    mash_data = mash_set_data(dat$strong.b, Shat=dat$strong.s, alpha=0, V=vhat, zero_Bhat_Shat_reset = 1E3)
    # FLASH matrices
    U.flash = readRDS(${_input[1]:r})
    # SVD matrices
    U.pca = ${"cov_pca(mash_data, %s)" % npc if npc > 0 else "list()"}
    # Emperical cov matrix
    X.center = apply(mash_data$Bhat, 2, function(x) x - mean(x))
    Ulist = c(U.flash, U.pca, list("XX" = t(X.center) %*% X.center / nrow(X.center)))
    saveRDS(list(mash_data = mash_data, Ulist = Ulist), ${_output:r})
In [ ]:
[mash_ed_2]
output: f"{_input:n}.ED.rds"
task: trunk_workers = 1, walltime = '36h', trunk_size = 1, mem = '4G', cores = 14, tags = f'{_output:bn}'
R: expand = "${ }", workdir = cwd, stderr = f"{_output:n}.stderr", stdout = f"{_output:n}.stdout"
    dat = readRDS(${_input:r})
    # Denoised data-driven matrices
    res = mashr:::bovy_wrapper(dat$mash_data, dat$Ulist, logfile=${_output:nr}, tol = 1e-06)
    # format to input for simulation with DSC (current pipeline)
    saveRDS(list(U=res$Ulist, w=res$pi, loglik=scan("${_output:nn}.ED_loglike.log")), ${_output:r})
In [ ]:
[mash_teem_2]
output: f"{_input:n}.TEEM.rds"
task: trunk_workers = 1, walltime = '1h', trunk_size = 1, mem = '4G', cores = 1, tags = f'{_output:bn}'
R: expand = "${ }", workdir = cwd, stderr = f"{_output:n}.stderr", stdout = f"{_output:n}.stdout"
    library(mashr)
    dat = readRDS(${_input:r})
    # Denoised data-driven matrices
    res = teem_wrapper(dat$mash_data, dat$Ulist)
    saveRDS(res, ${_output:r})
In [ ]:
[udr_ed_2]
depends: R_library("udr")
output: f"{_input:n}.UD_ED.rds"
task: trunk_workers = 1, walltime = '36h', trunk_size = 1, mem = '4G', cores = 14, tags = f'{_output:bn}'
R: expand = "${ }", workdir = cwd, stderr = f"{_output:n}.stderr", stdout = f"{_output:n}.stdout"
    library(udr) # udr commit 5265079 with changes to set lower bound on the eigenvalues
    dat = readRDS(${_input:r})
    # Denoised data-driven matrices
    f0 = ud_init(X = as.matrix(dat$data), V = dat$S, U_scaled = list(), U_unconstrained = dat$Ulist, n_rank1=0)
    res = ud_fit(f0, control = list(unconstrained.update = "ed", resid.update = 'none', maxiter=5000),
    verbose=FALSE)
    # format to input for simulation with DSC (current pipeline)
    saveRDS(list(U=res$U, w=res$w, loglik=res$loglik), ${_output:r})
sos run analysis/20200502_Prepare_ED_prior.ipynb mash_ed --model artificial_mixture_identity -c midway2.yml -q midway2
sos run analysis/20200502_Prepare_ED_prior.ipynb mash_ed --model gtex_mixture_identity -c midway2.yml -q midway2
sos run analysis/20200502_Prepare_ED_prior.ipynb mash_teem --model artificial_mixture_identity -c midway2.yml -q midway2
sos run analysis/20200502_Prepare_ED_prior.ipynb mash_teem --model gtex_mixture_identity -c midway2.yml -q midway2
sos run analysis/20200502_Prepare_ED_prior.ipynb udr_ed --model artificial_mixture_identity -c midway2.yml -q midway2
sos run analysis/20200502_Prepare_ED_prior.ipynb udr_ed --model gtex_mixture_identity -c midway2.yml -q midway2

It takes many hours to run ED (can be a day depending on number of threads used) but only a few minutes to run TEEM.

In [3]:
%cd ~/GIT/mvarbvs/dsc/mnm_prototype/mnm_sumstats
/project2/mstephens/gaow/mvarbvs/dsc/mnm_prototype/mnm_sumstats

Data-driven prior via ED

For the artifically simulated mixture,

In [4]:
a1 = readRDS('artificial_mixture_identity.FL_PC3.ED.rds')
In [5]:
names(a1)
  1. 'U'
  2. 'w'
  3. 'loglik'
In [6]:
a1$loglik[length(a1$loglik)]
53.920514
In [10]:
cbind(names(a1$U), a1$w)
FLASH_1 0.0928199835385606
FLASH_2 4.66681026838408e-34
FLASH_3 3.58932690856478e-41
FLASH_4 0.170916160597565
FLASH_5 3.3348060289056e-13
FLASH_6 0.00103070787447895
FLASH_7 0.00355415988483168
FLASH_8 3.49373257500002e-08
FLASH_9 0.00123344516969065
FLASH_10 4.05310561664215e-05
FLASH_11 1.58651158359917e-05
FLASH_12 6.96387520012721e-14
FLASH_13 7.09713951397582e-252
FLASH_14 0.000303771798467122
FLASH_15 0.00771806711741353
FLASH_16 2.82609862930679e-05
FLASH_17 0.000673891478132913
FLASH_18 0.00743608012106805
FLASH_19 8.57092101664386e-55
FLASH_20 7.04580345327831e-05
FLASH_21 3.6285723566672e-131
FLASH_22 0.00713627997477638
FLASH_23 9.87323133685571e-152
FLASH_24 0.000653767355009305
FLASH_25 0.00643943152051095
FLASH_26 4.15821235716626e-96
FLASH_27 0.00673856900028108
FLASH_28 2.22523384016994e-215
FLASH_29 0.00713532869510493
FLASH_30 7.65575053718967e-248
FLASH_42 6.07665358145801e-32
FLASH_43 6.65021812056226e-250
FLASH_44 9.70947519250964e-242
FLASH_45 1.77280612953097e-245
FLASH_46 4.19452309083203e-248
FLASH_47 8.70263840260855e-253
FLASH_48 4.28761448188762e-71
FLASH_49 6.20473152181847e-123
FLASH_50 2.94656246783141e-159
FLASH_51 1.51620214735344e-109
FLASH_52 3.98906367030447e-152
FLASH_53 4.99388822780586e-68
FLASH_54 6.70099398398243e-251
FLASH_55 6.79023576643794e-157
FLASH_56 2.91730007655672e-253
FLASH_57 3.6453881263049e-05
FLASH_58 7.69808159090617e-18
FLASH_59 3.57265620931525e-215
FLASH_60 1.0991104542132e-229
FLASH_61 2.26306695858188e-206
FLASH_62 1.12559259784551e-247
FLASH_63 3.56669069909909e-242
FLASH_64 5.58913658363362e-234
FLASH_65 2.30751593347542e-186
tFLASH 0.287018132738336
PCA_1 0.00381418677387528
PCA_2 0.0154803817130112
PCA_3 0.00113763435850343
tPCA 0.139767896365797
XX 0.238800519912781
In [8]:
tol = 1E-15
In [9]:
names(a1$U)[which(a1$w>tol)]
  1. 'FLASH_1'
  2. 'FLASH_4'
  3. 'FLASH_5'
  4. 'FLASH_6'
  5. 'FLASH_7'
  6. 'FLASH_8'
  7. 'FLASH_9'
  8. 'FLASH_10'
  9. 'FLASH_11'
  10. 'FLASH_12'
  11. 'FLASH_14'
  12. 'FLASH_15'
  13. 'FLASH_16'
  14. 'FLASH_17'
  15. 'FLASH_18'
  16. 'FLASH_20'
  17. 'FLASH_22'
  18. 'FLASH_24'
  19. 'FLASH_25'
  20. 'FLASH_27'
  21. 'FLASH_29'
  22. 'FLASH_57'
  23. 'tFLASH'
  24. 'PCA_1'
  25. 'PCA_2'
  26. 'PCA_3'
  27. 'tPCA'
  28. 'XX'

The component with strongest weight is tFLASH.

In [11]:
plot_sharing = function(X) {
clrs <- colorRampPalette(rev(c("#D73027","#FC8D59","#FEE090","#FFFFBF",
                               "#E0F3F8","#91BFDB","#4575B4")))(64)
lat <- cov2cor(X)
lat[lower.tri(lat)] <- NA
n <- nrow(lat)
print(lattice::levelplot(lat[n:1,],col.regions = clrs,xlab = "",ylab = "",
                colorkey = TRUE,at = seq(0,1,length.out = 64),
                scales = list(cex = 0.6,x = list(rot = 45))))
}
In [12]:
plot_sharing(a1$U$tFLASH)
In [13]:
plot_sharing(a1$U$XX)
In [15]:
plot_sharing(a1$U$PCA_1)

For mixture simulated based on GTEx V8 ED matrices,

In [16]:
g1 = readRDS('gtex_mixture_identity.FL_PC3.ED.rds')
In [17]:
g1$loglik[length(g1$loglik)]
34.631637
In [18]:
g1$w
  1. 0.164536632815805
  2. 1.09798576894701e-95
  3. 0
  4. 0
  5. 0
  6. 0
  7. 0
  8. 0
  9. 4.01300227957019e-05
  10. 0
  11. 0
  12. 1.37325777195672e-24
  13. 4.01303516159582e-05
  14. 0
  15. 7.18704119459428e-150
  16. 0
  17. 0
  18. 0
  19. 0
  20. 0
  21. 0
  22. 0
  23. 0.393424772106715
  24. 6.97386166989233e-07
  25. 0
  26. 0
  27. 0.00164345872118286
  28. 0.440314178595684
In [19]:
names(g1$U)[which(g1$w>tol)]
  1. 'FLASH_1'
  2. 'FLASH_9'
  3. 'FLASH_13'
  4. 'tFLASH'
  5. 'PCA_1'
  6. 'tPCA'
  7. 'XX'

Again, most weights are on tFLASH and tPCA.

In [20]:
plot_sharing(g1$U$tFLASH)
In [21]:
plot_sharing(g1$U$tPCA)
In [22]:
plot_sharing(g1$U$XX)

Data-driven prior via TEEM

Currently two caveats:

  1. we don't have interface for bhat/sbhat so the result is based on z-score without transforming back to original scale bhat.
  2. we don't have interface for non-identity residual covariance so the result is based on assuming identity covariance, although identity covariance is indeed the oracle covariance for the residual.
In [36]:
a2 = readRDS('artificial_mixture_identity.FLASH_PC3.TEEM.rds')
In [37]:
names(a2)
  1. 'w'
  2. 'U'
  3. 'objective'
  4. 'maxd'
In [38]:
a2$objective[length(a2$objective)]
-1514460.34911569
In [39]:
a2$w
  1. 0.000150168583502527
  2. 0.063706313615437
  3. 0.00173940219644464
  4. 2.25625779570696e-05
  5. 2.7441573335457e-05
  6. 0.0004013500687758
  7. 0.000100004694516837
  8. 0.0176210839689777
  9. 0.59431789907055
  10. 0.321461600239074
  11. 0.000452173411430355
In [40]:
names(a2$U)[which(a2$w>tol)]
  1. 'FLASH_1'
  2. 'FLASH_2'
  3. 'FLASH_3'
  4. 'FLASH_4'
  5. 'FLASH_5'
  6. 'tFLASH'
  7. 'PCA_1'
  8. 'PCA_2'
  9. 'PCA_3'
  10. 'tPCA'
  11. 'XX'

In TEEM since it does not preserve the rank of input, these names are not informative. Just showing how many are there compared to before.

In [41]:
g2 = readRDS('gtex_mixture_identity.FLASH_PC3.TEEM.rds')
In [42]:
g2$objective[length(g2$objective)]
-1525155.70712717
In [43]:
g2$w
  1. 1.71955676871705e-63
  2. 1.39777288788456e-143
  3. 1.68845728397329e-142
  4. 7.84691317226551e-93
  5. 3.24486241601681e-107
  6. 1.31455241438438e-135
  7. 1.33653510640492e-138
  8. 5.87864927988118e-88
  9. 1.57715878286267e-134
  10. 2.62148379045725e-134
  11. 1.98384163132785e-132
  12. 1.19591756153686e-134
  13. 1.09691032521019e-133
  14. 2.11605318408162e-141
  15. 6.15309550806711e-125
  16. 2.0494773131503e-139
  17. 1.67913095261693e-114
  18. 5.63191011607809e-123
  19. 5.66285674684209e-139
  20. 8.03469169954033e-87
  21. 1.6871925107188e-63
  22. 1.7426880628207e-146
  23. 1.64210838312768e-146
  24. 1
  25. 9.35210690850578e-87
In [45]:
names(g2$U)[which(g2$w>tol)]
'tPCA'

with 100% weights making it a single component.

TEEM with oracle initialization

Here I initialize TEEM with the true mixture prior and weights under which the true b (effect size) was simulated from,

In [3]:
prior = readRDS("../data/prior_simulation.rds")
In [17]:
setwd('~/tmp/07-May-2020/')
In [10]:
a_data = readRDS('artificial_mixture_identity.FLASH_PC3.rds')
g_data = readRDS('gtex_mixture_identity.FLASH_PC3.rds')
In [5]:
names(prior)
  1. 'gtex_mixture'
  2. 'artificial_mixture_50'
In [6]:
length(prior$gtex_mixture$U)
34
In [7]:
length(prior$gtex_mixture$w)
34
In [13]:
a_fit = mashr::teem_wrapper(a_data$mash_data, prior$artificial_mixture_50$U, w_init = prior$artificial_mixture_50$w)
In [14]:
g_fit = mashr::teem_wrapper(g_data$mash_data, prior$gtex_mixture$U, w_init = prior$gtex_mixture$w)

Compare the objectives with previous run using data driven initialization, loglik is higher for oracle init with GTEx based simulation, but not with the artificial simulation.

In [23]:
print(c(a_fit$objective[length(a_fit$objective)], a2$objective[length(a2$objective)], a_fit$objective[length(a_fit$objective)] - a2$objective[length(a2$objective)]))
[1] -1.514531e+06 -1.514460e+06 -7.093301e+01
In [24]:
print(c(g_fit$objective[length(g_fit$objective)], g2$objective[length(g2$objective)], g_fit$objective[length(g_fit$objective)] - g2$objective[length(g2$objective)]))
[1] -1521579.038 -1525155.707     3576.669
In [46]:
names(a_fit$U)[which(a_fit$w>tol)]
  1. 'singleton_1'
  2. 'singleton_2'
  3. 'singleton_3'
  4. 'singleton_4'
  5. 'singleton_6'
  6. 'singleton_7'
  7. 'singleton_8'
  8. 'singleton_9'
  9. 'singleton_10'
  10. 'singleton_12'
  11. 'singleton_13'
  12. 'singleton_14'
  13. 'singleton_15'
  14. 'singleton_16'
  15. 'singleton_17'
  16. 'singleton_18'
  17. 'singleton_20'
  18. 'singleton_21'
  19. 'singleton_22'
  20. 'singleton_23'
  21. 'singleton_24'
  22. 'singleton_25'
  23. 'singleton_26'
  24. 'singleton_27'
  25. 'singleton_28'
  26. 'singleton_29'
  27. 'singleton_30'
  28. 'singleton_31'
  29. 'singleton_32'
  30. 'singleton_33'
  31. 'singleton_35'
  32. 'singleton_36'
  33. 'singleton_37'
  34. 'singleton_38'
  35. 'singleton_39'
  36. 'singleton_41'
  37. 'singleton_43'
  38. 'singleton_44'
  39. 'singleton_45'
  40. 'singleton_46'
  41. 'singleton_47'
  42. 'singleton_49'
  43. 'singleton_50'
  44. 'shared_1'
  45. 'shared_2'
  46. 'shared_3'
  47. 'shared_4'
  48. 'shared_5'
  49. 'paired_1'
  50. 'blocked_1'
In [48]:
a_fit$w[which(a_fit$w>tol)]
  1. 1.65374168726002e-06
  2. 2.03559896229902e-07
  3. 1.11779321992023e-07
  4. 2.34047454138079e-06
  5. 6.92496577578445e-07
  6. 6.22950539875304e-07
  7. 1.56719300468396e-07
  8. 4.79182667110522e-07
  9. 0.00250280781301374
  10. 7.51691953161936e-07
  11. 2.86284456790016e-07
  12. 1.31793245769535e-07
  13. 6.93431949172888e-08
  14. 1.85319263627321e-07
  15. 1.91405587513994e-05
  16. 6.70707865618999e-07
  17. 9.33734979340282e-08
  18. 2.65747044601569e-08
  19. 7.46126840819198e-06
  20. 1.30459137385524e-07
  21. 2.73099141544342e-07
  22. 6.81166633073959e-08
  23. 4.23663783949518e-07
  24. 1.92089497708736e-06
  25. 2.20906018233115e-06
  26. 3.57875446050413e-06
  27. 1.64351790281843e-07
  28. 1.30213206966351e-08
  29. 3.83980373693296e-07
  30. 3.89337813917774e-08
  31. 1.8882814891134e-07
  32. 4.86584240983071e-08
  33. 1.72399744096178e-07
  34. 3.27747177064877e-07
  35. 3.8521549692085e-07
  36. 4.68448756350232e-07
  37. 1.03717164960131e-07
  38. 7.58256680442448e-08
  39. 5.46292495883693e-07
  40. 6.6679370149707e-08
  41. 6.29069697539576e-06
  42. 2.95853328949598e-06
  43. 2.30045387090013e-06
  44. 0.182002182445188
  45. 0.0637563754903778
  46. 0.716203376739764
  47. 0.0348349357099656
  48. 0.000500050067876397
  49. 4.17738799640564e-05
  50. 0.000100264189718715
In [47]:
names(g_fit$U)[which(g_fit$w>tol)]
  1. 'ED_tFLASH'
  2. 'ED_tPCA'
  3. 'ED_XX'
  4. 'equal_effects'
  5. 'simple_het_2'
  6. 'simple_het_3'
In [49]:
g_fit$w[which(g_fit$w>tol)]
  1. 5.00032351801603e-05
  2. 5.00025001249668e-05
  3. 0.895430427201832
  4. 0.104369562062606
  5. 5.00022492857371e-05
  6. 5.00025001290434e-05

Copyright © 2016-2020 Gao Wang et al at Stephens Lab, University of Chicago