Skip to content

Commit 81ce1d9

Browse files
authored
Merge pull request #71 from uclahs-cds/nzeltser-add-hexbin
Add hexbinplot
2 parents a36670b + 488c149 commit 81ce1d9

File tree

4 files changed

+190
-35
lines changed

4 files changed

+190
-35
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
## Added
66
* Added hemizygous allele handling to dosage calculation
7+
* Added toggle to hexbinplot at sample size threshold in `create.pgs.with.continuous.phenotype.plot`
78

89
## Changed
910
* Updated INDEL effect switch reporting by strand flip checker

R/plot-pgs.R

Lines changed: 96 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -365,13 +365,23 @@ create.pgs.density.plot <- function(
365365
#' @param pgs.data data.frame PGS data as formatted by \code{apply.polygenic.score()}. Required columns are at least one of PGS, PGS.with.replaced.missing, or PGS.with.normalized.missing, and at least one continuous phenotype column.
366366
#' This function is designed to work with the output of \code{apply.polygenic.score()}.
367367
#' @param phenotype.columns character vector of continuous phenotype column names in pgs.data to plot
368+
#' @param hexbin.threshold numeric threshold (exclusive) for cohort size at which to switch from scatterplot to hexbin plot.
369+
#' @param hexbin.colour.scheme character vector of colors for hexbin plot bins. Default is \code{NULL} which uses gray/black.
370+
#' @param hexbin.colourkey logical whether a legend should be drawn for a hexbinplot, defaults to \code{TRUE}.
371+
#' @param hexbin.colourcut numeric vector of values covering [0, 1] that determine hexagon colour class boundaries and hexagon legend size boundaries.
372+
#' Alternatively, an integer (<= hexbin.maxcnt) specifying the number of equispaced colourcut values in [0,1].
373+
#' @param hexbin.mincnt integer, minimum count for a hexagon to be plotted. Default is 1.
374+
#' @param hexbin.maxcnt integer, maximum count for a hexagon to be plotted. Cells with more counts are not plotted. Default is \code{NULL}.
375+
#' @param hexbin.xbins integer, number of bins in the x direction for hexbin plot. Default is 30.
376+
#' @param hexbin.aspect numeric, aspect ratio of hexbin plot to control plot dimensions. Default is 1.
368377
#' @param output.dir character directory to save output plots
369378
#' @param filename.prefix character prefix for output filenames
370379
#' @param file.extension character file extension for output plots
371380
#' @param tidy.titles logical whether to reformat PGS plot titles to remove periods
372381
#' @param compute.correlation logical whether to compute correlation between PGS and phenotype and display in plot
373382
#' @param corr.legend.corner numeric vector indicating the corner of the correlation legend e.g. \code{c(0,1)} for top left
374383
#' @param corr.legend.cex numeric cex for correlation legend
384+
#' @param include.origin logical whether to include the origin (zero) in plot axes
375385
#' @param width numeric width of output plot in inches
376386
#' @param height numeric height of output plot in inches
377387
#' @param xaxes.cex numeric size for x-axis labels
@@ -425,13 +435,22 @@ create.pgs.density.plot <- function(
425435
create.pgs.with.continuous.phenotype.plot <- function(
426436
pgs.data,
427437
phenotype.columns,
438+
hexbin.threshold = 1000,
439+
hexbin.colour.scheme = NULL,
440+
hexbin.colourkey = TRUE,
441+
hexbin.colourcut = seq(0, 1, length = 11),
442+
hexbin.mincnt = 1,
443+
hexbin.maxcnt = NULL,
444+
hexbin.xbins = 30,
445+
hexbin.aspect = 1,
428446
output.dir = NULL,
429447
filename.prefix = NULL,
430448
file.extension = 'png',
431449
tidy.titles = FALSE,
432450
compute.correlation = TRUE,
433451
corr.legend.corner = c(0,1),
434452
corr.legend.cex = 1.5,
453+
include.origin = FALSE,
435454
width = 10,
436455
height = 10,
437456
xaxes.cex = 1.5,
@@ -467,6 +486,23 @@ create.pgs.with.continuous.phenotype.plot <- function(
467486

468487
for (pgs.column in pgs.columns) {
469488

489+
# handle axes formatting
490+
xaxis.formatting <- BoutrosLab.plotting.general::auto.axis(
491+
pgs.data[ , pgs.column],
492+
log.scaled = FALSE,
493+
num.labels = 5,
494+
include.origin = include.origin
495+
);
496+
scatter.xlimits <- c(min(xaxis.formatting$at), max(xaxis.formatting$at));
497+
498+
yaxis.formatting <- BoutrosLab.plotting.general::auto.axis(
499+
pgs.data[ , phenotype],
500+
log.scaled = FALSE,
501+
num.labels = 5,
502+
include.origin = include.origin
503+
);
504+
scatter.ylimits <- c(min(yaxis.formatting$at), max(yaxis.formatting$at));
505+
470506
# handle tidy titles
471507
if (tidy.titles) {
472508
pgs.column.label <- gsub(pattern = '\\.', replacement = ' ', x = pgs.column);
@@ -494,47 +530,72 @@ create.pgs.with.continuous.phenotype.plot <- function(
494530
)
495531
);
496532
# set y limits that make room for the correlation legend in the top of the plot
497-
scatter.ylimits <- c(min(phenotype.data.for.plotting[ , phenotype], na.rm = TRUE) * 0.1, max(phenotype.data.for.plotting[ , phenotype], na.rm = TRUE) * 1.3);
533+
scatter.ylimits[2] <- scatter.ylimits[2] * 1.3;
534+
498535
} else {
499536
correlation.legend <- NULL;
500537
scatter.ylimits <- NULL;
501538
}
502539

503-
xaxis.formatting <- BoutrosLab.plotting.general::auto.axis(
504-
pgs.data[ , pgs.column],
505-
log.scaled = FALSE, num.labels = 5,
506-
include.origin = FALSE
507-
);
508-
yaxis.formatting <- BoutrosLab.plotting.general::auto.axis(
509-
pgs.data[ , phenotype],
510-
log.scaled = FALSE,
511-
num.labels = 5,
512-
include.origin = FALSE
513-
);
514-
pgs.scatterplots[[paste0(pgs.column,'_',phenotype)]] <- BoutrosLab.plotting.general::create.scatterplot(
515-
formula = as.formula(paste0(phenotype, ' ~ ', pgs.column)),
516-
data = pgs.data,
517-
type = 'p',
518-
cex = point.cex,
519-
xlab.label = pgs.column.label,
520-
ylab.label = phenotype,
521-
main = '',
522-
main.cex = 0,
523-
yat = yaxis.formatting$at,
524-
yaxis.lab = yaxis.formatting$axis.lab,
525-
xat = xaxis.formatting$at,
526-
xaxis.lab = xaxis.formatting$axis.lab,
527-
# Correlation Legend
528-
legend = correlation.legend,
529-
ylimits = scatter.ylimits,
530-
ylab.cex = titles.cex,
531-
xlab.cex = titles.cex,
532-
yaxis.cex = yaxes.cex,
533-
xaxis.cex = xaxes.cex
534-
);
535-
}
536540

537-
}
541+
542+
sample.total <- nrow(pgs.data);
543+
if (sample.total > hexbin.threshold) {
544+
pgs.scatterplots[[paste0(pgs.column,'_',phenotype)]] <- BoutrosLab.plotting.general::create.hexbinplot(
545+
formula = as.formula(paste0(phenotype, ' ~ ', pgs.column)),
546+
data = pgs.data,
547+
colour.scheme = hexbin.colour.scheme,
548+
colourkey = hexbin.colourkey,
549+
colourcut = hexbin.colourcut,
550+
mincnt = hexbin.mincnt,
551+
maxcnt = hexbin.maxcnt,
552+
xbins = hexbin.xbins,
553+
aspect = hexbin.aspect,
554+
xlab.label = pgs.column.label,
555+
ylab.label = phenotype,
556+
main = '',
557+
main.cex = 0,
558+
ylimits = scatter.ylimits,
559+
yat = yaxis.formatting$at,
560+
yaxis.lab = yaxis.formatting$axis.lab,
561+
xlimits = scatter.xlimits,
562+
xat = xaxis.formatting$at,
563+
xaxis.lab = xaxis.formatting$axis.lab,
564+
# Correlation Legend
565+
legend = correlation.legend,
566+
ylab.cex = titles.cex,
567+
xlab.cex = titles.cex,
568+
yaxis.cex = yaxes.cex,
569+
xaxis.cex = xaxes.cex
570+
);
571+
} else {
572+
pgs.scatterplots[[paste0(pgs.column,'_',phenotype)]] <- BoutrosLab.plotting.general::create.scatterplot(
573+
formula = as.formula(paste0(phenotype, ' ~ ', pgs.column)),
574+
data = pgs.data,
575+
type = 'p',
576+
cex = point.cex,
577+
xlab.label = pgs.column.label,
578+
ylab.label = phenotype,
579+
main = '',
580+
main.cex = 0,
581+
ylimits = scatter.ylimits,
582+
yat = yaxis.formatting$at,
583+
yaxis.lab = yaxis.formatting$axis.lab,
584+
xlimits = scatter.xlimits,
585+
xat = xaxis.formatting$at,
586+
xaxis.lab = xaxis.formatting$axis.lab,
587+
# Correlation Legend
588+
legend = correlation.legend,
589+
ylab.cex = titles.cex,
590+
xlab.cex = titles.cex,
591+
yaxis.cex = yaxes.cex,
592+
xaxis.cex = xaxes.cex
593+
);
594+
}
595+
596+
}
597+
598+
}
538599

539600
# organize filename if plot writing requested
540601
if (!is.null(output.dir)) {

man/create.pgs.with.continuous.phenotype.plot.Rd

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

tests/testthat/test-plotting.R

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,38 @@ test_that(
403403
}
404404
);
405405

406+
test_that(
407+
'create.pgs.with.continuous.phenotype.plot runs correctly with include.origin enabled', {
408+
skip.plotting.tests(skip.plots = SKIP.PLOTS || SKIP.COMPREHENSIVE.CASES);
409+
410+
temp.dir <- tempdir();
411+
412+
# add phenotype with non-zero minimum
413+
pgs.test$continuous.phenotype.shift <- pgs.test$continuous.phenotype + 10;
414+
415+
# plot pgs with continuous phenotype
416+
expect_no_error(
417+
create.pgs.with.continuous.phenotype.plot(
418+
pgs.data = pgs.test,
419+
phenotype.columns = 'continuous.phenotype.shift',
420+
output.dir = temp.dir,
421+
filename.prefix = 'TEST-include-origin',
422+
include.origin = TRUE
423+
)
424+
);
425+
426+
test.filename <- generate.filename(
427+
project.stem = 'TEST-include-origin',
428+
file.core = 'pgs-scatter',
429+
extension = 'png'
430+
);
431+
expect_true(
432+
file.exists(file.path(temp.dir, test.filename))
433+
);
434+
435+
}
436+
);
437+
406438
test_that(
407439
'create.pgs.with.continuous.phenotype.plot runs correctly with multiple phenotypes', {
408440
skip.plotting.tests(skip.plots = SKIP.PLOTS);
@@ -434,6 +466,39 @@ test_that(
434466
}
435467
);
436468

469+
test_that(
470+
'create.pgs.with.continuous.phenotypes.plot correctly switches to hexbin plot with default parameters', {
471+
skip.plotting.tests(skip.plots = SKIP.PLOTS || SKIP.COMPREHENSIVE.CASES);
472+
473+
temp.dir <- tempdir();
474+
475+
# generate large test dataset
476+
large.pgs.test <- pgs.test[rep(1:nrow(pgs.test), 100), ];
477+
large.pgs.test$hexbin.phenotype <- rnorm(nrow(large.pgs.test));
478+
479+
expect_no_error(
480+
create.pgs.with.continuous.phenotype.plot(
481+
pgs.data = large.pgs.test,
482+
phenotype.columns = c('hexbin.phenotype'),
483+
output.dir = temp.dir,
484+
filename.prefix = 'TEST-hexbin',
485+
width = 15,
486+
height = 10
487+
)
488+
);
489+
490+
test.filename <- generate.filename(
491+
project.stem = 'TEST-hexbin',
492+
file.core = 'pgs-scatter',
493+
extension = 'png'
494+
);
495+
expect_true(
496+
file.exists(file.path(temp.dir, test.filename))
497+
);
498+
499+
}
500+
);
501+
437502
test_that(
438503
'create.pgs.rank.plot correctly validates inputs', {
439504
# check that input data is a data frame

0 commit comments

Comments
 (0)