Raw File
run_GWAS.R
#!/usr/bin/env Rscript
#
# Read csv file, prepare for GEMMA, run GEMMA and plot the results
#
library(mousegwas)
library(argparse)
library(yaml)
library(dplyr)
library(readr)
library(R.utils)
library(data.table)
library(ggplot2)
library(GGally)
library(corrgram)

# create parser object
parser <- ArgumentParser()

# specify our desired options
# by default ArgumentParser will add an help option
parser$add_argument("--downsample", '-d', default=100000, type="integer",
                    help="Downsample strains to have at most this number of representatives. If 0 - average each strain")
parser$add_argument("-i", "--input",
                    help="Input csv file, assume column names. Definitions for which columns to use and how are in the yaml file")
parser$add_argument("-y", "--yaml",
                    help="Yaml file which will include: strain- Name of strain column, phenotypes-phenotypes column names to test, covar-columns to be used as covariates, F1- a dictionary mapping F1 names to strain names (female parent first), translate- translate strain names to names in the genotypes file")
parser$add_argument("--gemma",
                    help="Gemma executable")
parser$add_argument("-g", "--genotypes", nargs = '+',
                    help="The genotypes csv files as downloaded from phenome.jax.org")
parser$add_argument("--basedir", default=".",
                    help="output directory. Will overwrite existing files")
parser$add_argument("--pylmm", "-p", default="pylmmGWAS.py",
                    help="pylmmGWAS.py executable")
parser$add_argument("--pylmmkinship", "-k", default="pylmmKinship.py",
                    help="pylmmKinship.py executable")
parser$add_argument("--method", "-m", default="GEMMA",
                    help="Which GWAS software to use, possible values: GEMMA|pyLMM")
parser$add_argument("--noloco", default=FALSE, action="store_true",
                    help="Do not use LOCO")
parser$add_argument("--missing", default=0.05, type="double",
                    help="Maximal fraction of missing data for marker")
parser$add_argument("--MAF", default=0.1, type="double",
                    help="Minimal value for minor allele frequency")
parser$add_argument("--header", default="GWAS results",
                    help="Manhattan plot header")
parser$add_argument("--shuffle", default=FALSE, action="store_true",
                    help="Shuffle the phenotypes between the different individuals")
parser$add_argument("--seed", type="integer", default=100,
                    help="If shuffle is true, set the seed to avoid repeating the same results but have the option to rerun")
parser$add_argument("--qqnorm", default=FALSE, action="store_true",
                    help="QQNORM each phenotype before analyzing")
parser$add_argument("--genedist", default=1000000, type="integer",
                    help="gene distance from SNP, for gene reporting")
parser$add_argument("--peakdist", default=1000000, type="integer",
                    help="minimal distance between peaks")
parser$add_argument("--snpthr", default=7, type="double",
                    help="P threshold for gene reporting")
parser$add_argument("--namethr", default=10, type="double",
                    help="Print gene names above this threshold")
parser$add_argument("--coat_covar", default=FALSE, action="store_true",
                    help="Use coat color as defined in yaml file as a covariate")
parser$add_argument("--coat_phenotype", default=FALSE, action="store_true",
                    help="Use coat color as defined in yaml file as a phenotype")
parser$add_argument("--coat_fraction", default=0.05,
                    help="Fraction of strains with a coat color to use as a phenotype, default 0.05")
parser$add_argument("--runmetasoft", default=FALSE, action="store_true",
                    help="Run metasoft on the given phenotypes. This assumes the phenotypes have the same effect")
parser$add_argument("--lambda_mean", default=1, type="double",
                    help="lambda_mean for MetaSoft. Should be obtained from an initial run")
parser$add_argument("--lambda_hetero", default=1, type="double",
                    help="lambda_hetero for MetaSoft. Should be obtained from an initial run")
parser$add_argument("--mvalue_method", default="mcmc",
                    help="Either mcmc (default) or exact if the number of phenotypes is small (<10)")
parser$add_argument("--MDA", action="store_true", default=FALSE,
                    help="Use the pre-loaded MDA genotypes files in the package, replaces -g")
parser$add_argument("--nomv",
                    default = FALSE,
                    action = "store_true",
                    help = "Ignore multivariate results and use only single phenotypes")


args <- parser$parse_args()


metasoft_args <- paste("-mvalue_p_thres 1e-5", "-lambda_mean", args$lambda_mean, "-lambda_hetero", args$lambda_hetero, "-mvalue_method", args$mvalue_method, sep=" ")
# Load the yaml
yamin <- yaml.load_file(args$yaml)

# Make the output directory
if (!grepl("^/", args$basedir)) args$basedir <- paste(getwd(), args$basedir, sep = "/")
dir.create(args$basedir, recursive = TRUE)
print(paste0("Working directory: ", args$basedir))
# Read the input table
if (length(args$input)){
  complete_table <- read_csv(args$input, col_names = TRUE)
}else{
  complete_table <- tibble()
}
print(complete_table)
# Generate a list of phenotypes and list of groups
phegroups <- list()
pheno_names <- c()
i = 1
for (n in names(yamin$phenotypes)){
  if (n %in% names(complete_table)){
    pheno_names <- c(pheno_names, n)
    if ("group" %in% names(yamin$phenotypes[[n]]))
      phegroups[[yamin$phenotypes[[n]]$group]] <- c(phegroups[[yamin$phenotypes[[n]]$group]], i)
    i <- i+1
  }
}
#pheno_names <- intersect(yamin$phenotypes, names(complete_table))
# A list of covariates
covar_names <- setdiff(intersect(yamin$covar, names(complete_table)), pheno_names)
# Read the coat from the yaml file
coat_table <- tibble(strain=character(), coat=character(), eyes=character())
if (args$coat_phenotype |args$coat_covar){
  for (ct in yamin$coat){
    eyec = "normal"
    coat = unlist(ct)[1]
    if (grepl(",", coat)){
      spl <- strsplit(coat, ",")[[1]]
      eyec <- spl[1]
      coat <- gsub("^ *", "", spl[2])
    }
    coat_table <- add_row(coat_table, strain=names(ct), coat=coat, eyes=eyec)
  }
  if (length(unique(coat_table$eyes)) > 1)
    coat_table_mm <- model.matrix(~coat+eyes+0, coat_table)
  else
    coat_table_mm <- model.matrix(~coat+0, coat_table)
  rownames(coat_table_mm) <- coat_table$strain
  # Remove columns with one strain
  coat_table_mm <- coat_table_mm[,colSums(coat_table_mm)>1, drop=F]
}

# Get the strain names
if (args$coat_phenotype) {
  strains <-
    unique(tibble(
      input_name = rownames(coat_table_mm),
      p1 = rownames(coat_table_mm),
      p2 =
        rownames(coat_table_mm)
    ))
}else{
  strains <-
    complete_table %>% select(input_name = one_of(yamin$strain)) %>% unique() %>% mutate(p1 =
                                                                                           input_name, p2 = input_name)
}
# Translate F1
a <- tibble(input_name=character(), p1=character(), p2=character())
for (fl in yamin$F1){
  a <- add_row(a, input_name=names(fl), p1=unlist(fl)[1], p2=unlist(fl)[2])
}
# Combine the parents in the strains table
strains <- strains %>% left_join(a, by="input_name", suffix=c(".orig","")) %>%
  mutate(p1=ifelse(is.na(p1), p1.orig, p1), p2=ifelse(is.na(p2), p2.orig, p2)) %>% select(input_name, p1, p2)

# Translate names to genotype names
t <- tibble(name=character(), gen_name=character())
for (tr in yamin$translate){
  t <- add_row(t, name=names(tr), gen_name=unlist(tr)[1])
}
# Replace relevant names in the strains table
strains <- strains %>% left_join(t, by=c("p1" = "name")) %>% mutate(p1=ifelse(is.na(gen_name), p1, gen_name)) %>% select(input_name, p1, p2)
strains <- strains %>% left_join(t, by=c("p2" = "name")) %>% mutate(p2=ifelse(is.na(gen_name), p2, gen_name)) %>% select(input_name, p1, p2)

valid_strains <- unique(c(strains$p1, strains$p2))


# For each genotype file read it and add to the long file, use only genotypes in the input
# Read the genotype csv file

longfile <- tempfile()
complete.geno <- NULL
if (args$MDA){
  args$genotypes <- c(system.file("extdata", "snp_190703353330.csv.gz", package = "mousegwas"),
                      system.file("extdata", "snp_190703530013.csv.gz", package = "mousegwas"),
                      system.file("extdata", "snp_190703577680.csv.gz", package = "mousegwas"),
                      system.file("extdata", "snp_190703687397.csv.gz", package = "mousegwas"),
                      system.file("extdata", "snp_190703843678.csv.gz", package = "mousegwas"))
}
for (f in args$genotypes){
  geno <- fread(f)
  geno[, c("major", "minor") := tstrsplit(observed, "/", fixed=TRUE, keep=1:2)]
  geno <- geno[rs!="",]

  if (is.null(complete.geno)){
    if (length(intersect(names(geno), valid_strains))>0){
      addnames <- c(intersect(names(geno), valid_strains), "chr", "bp38", "rs", "major", "minor")
      complete.geno <- geno[,..addnames]
    }
  }else{
    addnames <- c(intersect(names(geno), valid_strains), "chr", "bp38", "rs", "major", "minor")
    geno <- geno[,..addnames]
    setkey(geno, rs)
    setkey(complete.geno, rs)
    complete.geno <- merge(complete.geno, geno, all=TRUE, by=c("chr", "bp38", "rs", "major", "minor"))

  }
  valid_strains <- setdiff(valid_strains, names(complete.geno))
}
complete.geno[, chr:=as.character(chr)]
srdata <- complete.geno[, .(rs, major, minor)]
numeric.geno <- complete.geno[, .("chr", "bp38", "rs", "major", "minor")]
for (cn in setdiff(names(complete.geno), c("chr", "bp38", "rs", "major", "minor"))){
  complete.geno[get(cn)=='H',c(cn) := 1]
  complete.geno[get(cn)==major, c(cn) := 0]
  complete.geno[get(cn)==minor, c(cn) := 2]
  complete.geno[,c(cn) := as.numeric(get(cn))]#as.numeric(ifelse(..cn=='H', 1, ifelse(..cn==major, 0, 2)))]
}


# Compute the specific strains genomes
strains_genomes <- srdata
sorder = c()
phenos = data.table()
for (p in pheno_names) phenos <- phenos[, eval(p) := numeric()]
covars = data.table()
for (c in covar_names) covars <- covars[, eval(c) := numeric()]
covars <- covars[, isWild := numeric()]
sexvec <- c()

# Keep old not found strains to not repeat error messages
notfounds = c()

# Read the input phenotypic data, write the genotypes and phenotypes in the proper tables
for (comrow in 1:dim(complete_table)[1]){
  sname <- as.character(complete_table[comrow, yamin$strain])
  rnum <- which(strains$input_name==sname)
  if (length(rnum) == 0){
    print(paste0("Can't find strain data ", sname, " row ",comrow))
    next
  }
  p1n <- strains$p1[rnum]
  p2n <- strains$p2[rnum]
  if (p1n %in% names(complete.geno) & p2n %in% names(complete.geno)){
    sorder <- c(sorder, sname)
    strains_genomes[, eval(paste0('X',comrow)):=(complete.geno[,..p1n] + complete.geno[,..p2n])/2]
    if (p1n!=p2n & !is.null(yamin$sex) & complete_table[comrow, yamin$sex]=="M"){
      # It's an F1 male. Only one X chromosome
      # The first parent is the female. Take its X chromosome only
      strains_genomes[complete.geno$chr=="X", eval(paste0('X',comrow))] = complete.geno[complete.geno$chr=="X",..p1n]
    }
    # Add the phenotypes to the table

    ct <- if (p1n==p2n) as.character(coat_table %>% filter(strain==p1n) %>% select(coat)) else as.character(coat_table %>% filter(strain==sname) %>% select(coat))
    if (is.null(ct) & (args$coat_covar)){
      print(paste0("Can't find coat color for ", p1n, " or ", sname))
    }
    prow <- complete_table[comrow, pheno_names]
    # Add the covariates to the table
    crow <- cbind(complete_table[comrow, covar_names, drop=F], tibble(isWild=as.numeric(p1n %in% yamin$wild | p2n %in% yamin$wild)))
    if (args$coat_covar){
      crow <- cbind(crow, coat=ct)
    }
    if (length(covar_names) == 0 | all(!is.na(crow))) {
      phenos <- rbind(phenos, prow, fill = TRUE)
      covars <- rbind(covars, crow, fill = TRUE)
      sexvec <-
        c(sexvec, if (is.null(yamin$sex))
          1
          else
            complete_table[comrow, yamin$sex])
    }
  }else{
    if (p1n==p2n){
      if (!p1n %in% notfounds){
        print(paste0("Can't find strain ",p1n))
        notfounds <- c(notfounds, p1n)
      }
    }else{
      if (!(p1n %in% notfounds & p2n %in% notfounds)){
        print(paste0("Can't find ", p1n," or ", p2n))
        notfounds <- c(notfounds, p1n, p2n)
      }
    }
  }
}
if (args$coat_phenotype){
  for (rnum in 1:dim(strains)[1]){
    sname <- strains$input_name[rnum]
    p1n <- strains$p1[rnum]
    p2n <- strains$p2[rnum]
    cname <- if (p1n==p2n) p1n else sname
    # Filter to include colors with at least 5% positives
    prow <- coat_table_mm[cname,colSums(coat_table_mm) > args$coat_fraction*nrow(coat_table_mm),drop=F]

    if (p1n %in% names(complete.geno) & p2n %in% names(complete.geno)){
      sorder <- c(sorder, sname)
      strains_genomes[, eval(sname):=(complete.geno[,..p1n] + complete.geno[,..p2n])/2]
      phenos <- rbind(phenos, prow, fill = TRUE)
    }
  }
}
print(dim(strains_genomes))
print(dim(phenos))
# Generate a covar table based on the confounding SNPs provided in the yaml file
snpcovar <- NULL
if (!is.null(yamin$confSNPs)){
  snpcovar <- base::t(as.matrix(strains_genomes)[as.matrix(strains_genomes[,1]) %in% yamin$confSNPs, 4:ncol(strains_genomes), drop=F])
  colnames(snpcovar) <- strains_genomes$rs[strains_genomes$rs %in% yamin$confSNPs]
  for (c in 1:ncol(snpcovar)){
    snpcovar[,c] <- as.numeric(snpcovar[,c])
  }
  covars <- cbind(covars, snpcovar)
  covar_names <- c(covar_names, colnames(snpcovar))
}

# Compute the covariate matrix
if (length(covar_names) > 0){
  if (args$coat_covar){
    covar_names <- c(covar_names, "coat")
  }
  covars <- model.matrix(as.formula(paste0("~", do.call(paste, c(as.list(covar_names), sep="+")))), covars)
}else{
  covars = NULL
}

# scale to have mean=0 var=1 if there are more than 2 values (0,1)
raw_phenos <- phenos
if (!all(is.na(phenos) | phenos==0 | phenos==1)){
  phenos <- scale(phenos)
}else{
  phenos <- as.matrix(phenos)
}

# Remove columns with NaNs
for (c in ncol(phenos):1){ if (all(is.na(phenos[,c]) | phenos[,c]==0)) {phenos <- phenos[,-c, drop=F]; raw_phenos <- raw_phenos[,-c,drop=F]}}

if (args$shuffle){
  set.seed(args$seed)
  neword <- sample(nrow(phenos))
  phenos <- phenos[neword,, drop=F]
  raw_phenos <- raw_phenos[neword,,drop=F]
}

# Take the betas of each strain and use it to run GEMMA
if (args$coat_phenotype){
  b <- list(phenotypes = phenos, genotypes = strains_genomes, indices = 1:nrow(phenos), covars=NULL)
}else{
  b <- average_strain(strains_genomes, phenos, covars, args$downsample, sexvec, sorder)
}
# Print the phenotypes order
raw_phenos <- raw_phenos[b$indices,,drop=F]
write.csv(raw_phenos, file=paste0(args$basedir, "/raw_phenotypes.csv"))
write.csv(colnames(b$phenotypes), file=paste0(args$basedir, "/phenotypes_order.txt"), quote = FALSE, col.names = FALSE, row.names = FALSE)


# Plot correlations between phenotypes

#print(head(b$phenotypes))
if (dim(b$phenotypes)[2]>1){
  corpheno <- b$phenotypes
  colnames(corpheno) <- gsub("OFA_Groom", "", colnames(corpheno))

  ppr <- ggpairs(as.data.frame(corpheno))
  ggsave(paste0(args$basedir, "/phenotype_correlations.pdf"), plot=ppr, device="pdf", width=16, height=16, units="in")

  pdf(paste0(args$basedir, "/phenotype_correlations_corrgram.pdf"), width = 16, height = 16)
  corrgram(as.data.frame(corpheno), order=TRUE, upper.panel=panel.conf, lower.panel=panel.shade, diag.panel=panel.density)
  dev.off()
}

# Remove SNPs with more than 5% missing data and 5% MAF
mafc <- rowSums(complete.geno[,-1:-5])/(2*(ncol(complete.geno)-5))
b$genotypes <- b$genotypes[rowSums(is.na(b$genotypes))<=(ncol(b$genotypes)-3)*args$missing &
                             mafc >= args$MAF & mafc <= 1-args$MAF,]

complete.geno <- complete.geno[rs %in% b$genotypes$rs,]
fwrite(complete.geno, file = paste0(args$basedir, "/strains_genotypes_all.csv"))


# Normalize the phenotypes
if (args$qqnorm){
  for (r in 1:ncol(b$phenotypes)){
    b$phenotypes[,r] <- qqnorm(as.data.frame(b$phenotypes)[,r], plot=F)$x
  }
}
# Export order of strains used
write.csv(sorder[b$indices], paste0(args$basedir, "/export_strains_order.csv"), quote = FALSE, col.names = FALSE)

# Run gemma/pylmm using the helper function
if (args$method == "GEMMA"){
  if (args$nomv) phegroups = NULL
  results_file <- execute_lmm(data.table(b$genotypes), data.table(b$phenotypes),
                              as.data.table(complete.geno[,.(rs, bp38, chr)]),
                              b$covars, args$basedir, loco=!args$noloco,
                              groups = phegroups, runmetasoft=args$runmetasoft, metasoft_args = metasoft_args)
  # Run no LOCO to get the unified heritability for each phenotype
  if (!args$noloco){
    all_res <- execute_lmm(data.table(b$genotypes), data.table(b$phenotypes),
                                as.data.table(complete.geno[,.(rs, bp38, chr)]),
                                b$covars, args$basedir, loco=FALSE, runmetasoft=F)
    # Extract the VPE values for each phenotype
  }
  allVPE = data.table(phenotype=character(), PVE=numeric(), PVESE=numeric(), Vg=numeric(), Ve=numeric())
  for (n in 1:dim(b$phenotypes)[2]){
    fname <- paste0(args$basedir, "/output/lmm_all_phenotype_", n, ".log.txt")
    if (file.exists(fname)){
      sigs <- get_sigmas(fname)
      sigs$phenotype <- colnames(b$phenotypes)[n]
      allVPE <- rbind(allVPE, sigs)
    }
  }
  fwrite(allVPE, file=paste0(args$basedir, "/PVE_GEMMA_estimates.txt"))
}else if (args$method == "pyLMM"){
  results_file <- run_pylmm(data.table(b$genotypes), data.table(b$phenotypes),
                                as.data.table(complete.geno[,.(rs, bp38, chr)]),
                                b$covars, args$basedir, args$pylmm, args$pylmmkinship, loco=!args$noloco, metasoft_args = metasoft_args)
}

is.metasoft <- FALSE
if (args$method=="GEMMA" && args$runmetasoft)
  is.metasoft = TRUE

pval_thr <- args$snpthr

# Manhattan plot
if (args$runmetasoft) {
  genotypes = as.data.frame(complete.geno[, -c("chr", "bp38", "minor", "major", "rs")])
  rownames(genotypes) <- complete.geno[, rs]
  p <-
    plot_gemma_lmm(
      results_file,
      name = args$header,
      metasoft = is.metasoft,
      pyLMM = args$method == "pyLMM" && ncol(b$phenotypes) == 1,
      annotations = paste0(args$basedir, "/annotations.csv"),
      namethr = args$namethr,
      redthr = pval_thr,
      genotypes = genotypes,
      maxdist = args$peakdist
    )
  #genotypes = paste0(args$basedir, "/all_genotypes.csv"))
  save(p, file = paste0(args$basedir, "/gwas_object_output.Rdata"))
  ggsave(
    paste0(args$basedir, "/manhattan_plot_p_lrt.pdf"),
    plot = p$plot,
    device = "pdf",
    width = 16,
    height = 8,
    units = "in"
  )

  # Read the significant SNPs and grab their related genes
  affgen <-
    get_genes(p$gwas[p$gwas$ispeak == T &
                       p$gwas$P >= args$snpthr, ], dist = args$genedist)
  fwrite(
    merge(data.table(affgen), data.table(p$gwas), by = "rs"),
    paste0(
      args$basedir,
      "/genes_dist_",
      args$genedist,
      "_pval_",
      args$snpthr,
      ".csv"
    )
  )
}
back to top