GeneTrackR treats phenotype analysis as an explicit sample-matching
problem. Phenotype rows are joined to haplotype or variant genotypes by
sample_id, not by row order. The recommended analysis
therefore starts with phenotype QC and sample-overlap checks, then
performs haplotype- or variant-level tests while retaining the returned
statistics together with the figure.
This article follows one continuous workflow:
The bundled phenotype data contain four numeric traits and one categorical trait. Their designed roles are:
| Trait | Type | Demo role |
|---|---|---|
seed_weight |
numeric | strong four-level GeneA haplotype effect with nearest genotype pairs remaining phenotypically closer |
plant_height |
numeric | moderate four-level GeneA haplotype effect with the same hierarchical ordering |
protein_content |
numeric | strong varA03 effect aligned with the
Hap1/Hap2 versus Hap3/Hap4 genotype split |
flowering_time |
numeric | negative control for GeneA haplotypes |
flower_color |
categorical | categorical phenotype example |
Package documentation: https://renscq.github.io/GeneTrackR/.
After Bioconductor acceptance, install GeneTrackR with
BiocManager:
if (!requireNamespace("BiocManager", quietly = TRUE)) {
install.packages("BiocManager")
}
BiocManager::install("GeneTrackR")
library(GeneTrackR)The development version remains available from the GeneTrackR GitHub repository.
Locate the bundled phenotype, VCF, and annotation files:
pheno_file <- system.file(
"extdata",
"gtr_demo_pheno.tsv",
package = "GeneTrackR"
)
vcf_file <- system.file(
"extdata",
"gtr_demo_variants.vcf",
package = "GeneTrackR"
)
gp_file <- system.file(
"extdata",
"gtr_demo.genePredExt",
package = "GeneTrackR"
)Read the three inputs and reconstruct the same GeneA gene-body haplotypes used in the haplotype workflow:
pheno <- read_pheno(
pheno_file,
verbose = FALSE,
progress = FALSE
)
vcf <- read_vcf(
vcf_file,
mode = "memory",
keep_genotype = TRUE,
verbose = FALSE,
progress = FALSE
)
gp <- read_genepred(
gp_file,
format = "genePredExt",
verbose = FALSE,
progress = FALSE
)
hap_gene <- hap_gene_variant(
vcf = vcf,
annotation = gp,
gene_id = "GeneA",
genotype_mode = "string"
)
#> [GeneTrackR] Retrieved variants: 11.read_pheno() returns a data.table. By
default, the first column is treated as the sample column and renamed to
sample_id. If another column stores sample IDs, specify it
explicitly:
Sample IDs must be unique. Duplicate IDs are rejected because they would make genotype-phenotype matching ambiguous.
Start with summary_pheno() before any association
test:
pheno_summary <- summary_pheno(pheno)
pheno_summary
#> trait type sample_n missing_n missing_rate unique_n min
#> <char> <char> <int> <int> <num> <int> <num>
#> 1: seed_weight numeric 36 0 0 32 18.80
#> 2: protein_content numeric 36 0 0 18 37.58
#> 3: plant_height numeric 36 0 0 36 98.20
#> 4: flowering_time numeric 36 0 0 5 43.00
#> 5: flower_color categorical 36 0 0 2 NA
#> mean median max
#> <num> <num> <num>
#> 1: 25 25 31.20
#> 2: 41 41 44.42
#> 3: 104 104 109.80
#> 4: 45 45 47.00
#> 5: NA NA NAThe summary reports:
Plot numeric and categorical phenotype distributions directly from the phenotype table:
pheno_figure <- plot_pheno(
pheno,
traits = c(
"seed_weight",
"protein_content",
"plant_height",
"flowering_time",
"flower_color"
),
text_size = 10
)
#> Warning in melt.data.table(dt, id.vars = sample_col, measure.vars =
#> numeric_traits, : 'measure.vars' [seed_weight, protein_content, plant_height,
#> flowering_time, ...] are not all of the same type. By order of hierarchy, the
#> molten data value column will be of type 'double'. All measure variables not of
#> type 'double' will be coerced too. Check DETAILS in ?melt.data.table for more
#> on coercion.
pheno_figureplot_pheno() returns the figure directly. Numeric traits
are shown as histograms, while categorical traits are shown as count
bars. When both trait classes are requested, the result is a combined
patchwork figure.
plot_hap_pheno() and plot_variant_pheno()
are currently intended for numeric phenotype traits.
Categorical traits such as flower_color should therefore be
summarized or plotted with summary_pheno() /
plot_pheno() unless a categorical association method is
implemented separately.
Phenotype rows do not need to follow the VCF or haplotype sample order. GeneTrackR joins genotype and phenotype records by sample ID.
The demo phenotype table intentionally uses a different row order from the genotype data. Check the overlap before testing:
hap_sample_ids <- as.character(hap_gene$sample_haplotypes$sample_id)
pheno_sample_ids <- as.character(pheno$sample_id)
sample_match <- data.frame(
phenotype_samples = length(unique(pheno_sample_ids)),
haplotype_samples = length(unique(hap_sample_ids)),
matched_samples = length(intersect(pheno_sample_ids, hap_sample_ids)),
phenotype_only = length(setdiff(pheno_sample_ids, hap_sample_ids)),
haplotype_only = length(setdiff(hap_sample_ids, pheno_sample_ids))
)
sample_match
#> phenotype_samples haplotype_samples matched_samples phenotype_only
#> 1 36 36 36 0
#> haplotype_only
#> 1 0For the bundled demo, all 36 phenotype samples match the 36 GeneA haplotype samples even though their row orders differ.
In real datasets, samples present on only one side are not available
for the association test. Checking setdiff() before
plotting is therefore recommended, especially after genotype missingness
filters or phenotype QC have removed samples.
seed_weight is the main deterministic GeneA haplotype
phenotype in the demo. Test the four GeneA haplotypes with
plot_hap_pheno():
hap_seed <- plot_hap_pheno(
hap = hap_gene,
phenotype = pheno,
traits = "seed_weight",
min_hap_samples = 3,
plot_type = "violin_boxplot",
test_method = "t.test",
p_adjust = "BH",
p_value_type = "adjusted",
p_label = "stars",
show_signif_only = TRUE,
fill_palette = "Paired",
text_size = 10
)
hap_seed$figureplot_hap_pheno() returns a
GeneTrackRPhenoPlot, not only a ggplot. Keep the complete
result because it contains the statistical output used to draw the
figure:
hap_seed$pvalue
#> trait group1 group2 p_value p_adj method
#> <char> <char> <char> <num> <num> <char>
#> 1: seed_weight Hap2 Hap1 7.740671e-05 7.740671e-05 t.test
#> 2: seed_weight Hap2 Hap3 1.344894e-14 8.069366e-14 t.test
#> 3: seed_weight Hap2 Hap4 4.343207e-13 8.686413e-13 t.test
#> 4: seed_weight Hap1 Hap3 4.343207e-13 8.686413e-13 t.test
#> 5: seed_weight Hap1 Hap4 3.540311e-11 5.310467e-11 t.test
#> 6: seed_weight Hap3 Hap4 7.740671e-05 7.740671e-05 t.test
hap_seed$summary
#> trait hap_id sample_n median_value mean_value min_value max_value
#> <char> <fctr> <int> <num> <num> <num> <num>
#> 1: seed_weight Hap2 9 20 20 18.8 21.2
#> 2: seed_weight Hap1 9 22 22 20.8 23.2
#> 3: seed_weight Hap3 9 30 30 28.8 31.2
#> 4: seed_weight Hap4 9 28 28 26.8 29.2
hap_seed$bracket
#> trait group1 group2 p_value p_adj method p_display
#> <char> <char> <char> <num> <num> <char> <num>
#> 1: seed_weight Hap2 Hap3 1.344894e-14 8.069366e-14 t.test 8.069366e-14
#> 2: seed_weight Hap1 Hap3 4.343207e-13 8.686413e-13 t.test 8.686413e-13
#> 3: seed_weight Hap2 Hap4 4.343207e-13 8.686413e-13 t.test 8.686413e-13
#> 4: seed_weight Hap1 Hap4 3.540311e-11 5.310467e-11 t.test 5.310467e-11
#> 5: seed_weight Hap2 Hap1 7.740671e-05 7.740671e-05 t.test 7.740671e-05
#> 6: seed_weight Hap3 Hap4 7.740671e-05 7.740671e-05 t.test 7.740671e-05
#> y_max y_min y_range x1 x2 bracket_index y y_tip label_y x_mid
#> <num> <num> <num> <int> <int> <int> <num> <num> <num> <num>
#> 1: 31.2 18.8 12.4 2 3 1 32.192 32.07296 32.2416 2.5
#> 2: 31.2 18.8 12.4 1 3 2 33.184 33.06496 33.2336 2.0
#> 3: 31.2 18.8 12.4 2 4 3 34.176 34.05696 34.2256 3.0
#> 4: 31.2 18.8 12.4 1 4 4 35.168 35.04896 35.2176 2.5
#> 5: 31.2 18.8 12.4 2 1 5 36.160 36.04096 36.2096 1.5
#> 6: 31.2 18.8 12.4 3 4 6 37.152 37.03296 37.2016 3.5
#> label
#> <char>
#> 1: ***
#> 2: ***
#> 3: ***
#> 4: ***
#> 5: ***
#> 6: ***The main components are:
| Component | Content |
|---|---|
$figure |
phenotype distribution figure |
$pvalue |
all pairwise tests, including raw and adjusted p-values |
$summary |
sample number, median, mean, minimum, and maximum by trait and haplotype |
$bracket |
comparisons actually prepared for display on the figure |
$plot_data |
matched long-format phenotype data used for plotting |
Printing the complete GeneTrackRPhenoPlot also prints
its $figure, but use the named components when saving
figures or exporting statistical tables.
Haplotype groups are ordered by retained sample number. The x-axis
label includes the group sample count, for example
Hap1 (9).
The demo provides three useful haplotype-level behaviors in one dataset:
seed_weight: strong four-level differences, while the
genotype-nearest pairs remain phenotypically closer than cross-cluster
pairs;plant_height: the same hierarchical pattern with a more
moderate effect scale;flowering_time: negative control without a designed
GeneA haplotype effect.Plot them together while preserving the requested trait order:
hap_multi <- plot_hap_pheno(
hap = hap_gene,
phenotype = pheno,
traits = c(
"seed_weight",
"plant_height",
"flowering_time"
),
min_hap_samples = 3,
test_method = "t.test",
p_adjust = "BH",
p_value_type = "adjusted",
p_label = "stars",
facet_ncol = 3,
fill_palette = "Paired",
text_size = 10
)
#> Warning in melt.data.table(dt, id.vars = c("sample_id", "hap_id"), measure.vars
#> = traits, : 'measure.vars' [seed_weight, plant_height, flowering_time, ...] are
#> not all of the same type. By order of hierarchy, the molten data value column
#> will be of type 'double'. All measure variables not of type 'double' will be
#> coerced too. Check DETAILS in ?melt.data.table for more on coercion.
hap_multi$figurehap_multi$pvalue
#> trait group1 group2 p_value p_adj method
#> <char> <char> <char> <num> <num> <char>
#> 1: seed_weight Hap2 Hap1 7.740671e-05 7.740671e-05 t.test
#> 2: seed_weight Hap2 Hap3 1.344894e-14 8.069366e-14 t.test
#> 3: seed_weight Hap2 Hap4 4.343207e-13 8.686413e-13 t.test
#> 4: seed_weight Hap1 Hap3 4.343207e-13 8.686413e-13 t.test
#> 5: seed_weight Hap1 Hap4 3.540311e-11 5.310467e-11 t.test
#> 6: seed_weight Hap3 Hap4 7.740671e-05 7.740671e-05 t.test
#> 7: plant_height Hap2 Hap1 2.912466e-03 2.912466e-03 t.test
#> 8: plant_height Hap2 Hap3 2.066800e-10 1.240080e-09 t.test
#> 9: plant_height Hap2 Hap4 1.342207e-08 2.684414e-08 t.test
#> 10: plant_height Hap1 Hap3 1.342207e-08 2.684414e-08 t.test
#> 11: plant_height Hap1 Hap4 2.910692e-06 4.366037e-06 t.test
#> 12: plant_height Hap3 Hap4 2.912466e-03 2.912466e-03 t.test
#> 13: flowering_time Hap2 Hap1 1.000000e+00 1.000000e+00 t.test
#> 14: flowering_time Hap2 Hap3 1.000000e+00 1.000000e+00 t.test
#> 15: flowering_time Hap2 Hap4 1.000000e+00 1.000000e+00 t.test
#> 16: flowering_time Hap1 Hap3 1.000000e+00 1.000000e+00 t.test
#> 17: flowering_time Hap1 Hap4 1.000000e+00 1.000000e+00 t.test
#> 18: flowering_time Hap3 Hap4 1.000000e+00 1.000000e+00 t.testThree pairwise test methods are available:
t.test: compares group means and is appropriate for
approximately continuous, reasonably behaved traits;wilcox.test: rank-based alternative that is less
dependent on normality assumptions;ks.test: tests whether the overall distributions
differ, not specifically their means or medians.For example:
hap_seed_wilcox <- plot_hap_pheno(
hap = hap_gene,
phenotype = pheno,
traits = "seed_weight",
min_hap_samples = 3,
test_method = "wilcox.test",
p_adjust = "BH",
p_value_type = "adjusted",
text_size = 10
)
hap_seed_wilcox$pvalue
#> trait group1 group2 p_value p_adj method
#> <char> <char> <char> <num> <num> <char>
#> 1: seed_weight Hap2 Hap1 2.056767e-04 0.0002056767 wilcox.test
#> 2: seed_weight Hap2 Hap3 4.113534e-05 0.0000617030 wilcox.test
#> 3: seed_weight Hap2 Hap4 4.113534e-05 0.0000617030 wilcox.test
#> 4: seed_weight Hap1 Hap3 4.113534e-05 0.0000617030 wilcox.test
#> 5: seed_weight Hap1 Hap4 4.113534e-05 0.0000617030 wilcox.test
#> 6: seed_weight Hap3 Hap4 2.056767e-04 0.0002056767 wilcox.test$pvalue always retains both p_value and
p_adj. The argument p_value_type controls
which of these is used to filter and label significance brackets; it
does not remove the other statistic from the returned table.
With four haplotypes there are six pairwise comparisons per trait, so
adjusted p-values are generally preferable for the main interpretation.
p_adjust = "BH" is the default multiple-testing adjustment
used in these examples.
show_signif_only = TRUE only controls bracket display.
Non-significant comparisons remain available in
$pvalue.
General GeneTrackR plotting palettes default to Paired;
plot_ld_block() is the deliberate package-level exception
and defaults to Reds. plot_hap_pheno() maps
fill colors according to the median phenotype value within each
trait, so the colors are used as an ordered visual encoding
rather than as permanent haplotype identities.
A more detailed plot can be requested as follows:
hap_seed_styled <- plot_hap_pheno(
hap = hap_gene,
phenotype = pheno,
traits = "seed_weight",
min_hap_samples = 3,
plot_type = "violin_boxplot",
test_method = "t.test",
p_adjust = "BH",
p_value_type = "adjusted",
p_label = "both",
show_points = TRUE,
show_outliers = FALSE,
fill_palette = "Paired",
fill_alpha = 0.75,
x_text_angle = 90,
text_size = 10
)
hap_seed_styled$figureUse plot_type = "violin", "boxplot", or
"violin_boxplot" depending on whether the distribution
shape, compact summary, or both are most useful.
If fixed colors for specific haplotype IDs are required, supply a named vector instead of relying on the median-based palette assignment:
hap_seed_fixed_colors <- plot_hap_pheno(
hap = hap_gene,
phenotype = pheno,
traits = "seed_weight",
min_hap_samples = 3,
fill_colors = c(
Hap1 = "#A6CEE3",
Hap2 = "#1F78B4",
Hap3 = "#B2DF8A",
Hap4 = "#33A02C"
),
text_size = 10
)
hap_seed_fixed_colors$figureUse fixed haplotype colors only after confirming the actual
hap_id values in hap_gene$haplotypes;
haplotype numbering is specific to the variant set used to construct
each HapVariant.
plot_variant_pheno() is the single-variant counterpart
of plot_hap_pheno(). The demo variant varA03
follows the same genotype-cluster split used by refinement:
Hap1/Hap2 carry the REF class, Hap3/Hap4 carry
the ALT class, and ALT carriers have higher protein_content
values.
Select the variant by ID and use the compact binary REF/ALT representation:
variant_protein <- plot_variant_pheno(
variant = vcf,
phenotype = pheno,
variant_id = "varA03",
traits = "protein_content",
genotype_mode = "code",
min_group_samples = 3,
test_method = "t.test",
p_adjust = "BH",
p_value_type = "adjusted",
p_label = "stars",
fill_palette = "Paired",
text_size = 10
)
variant_protein$figurevariant_protein$pvalue
#> trait group1 group2 p_value p_adj method
#> <char> <char> <char> <num> <num> <char>
#> 1: protein_content 0 1 2.008652e-37 2.008652e-37 t.test
variant_protein$summary
#> trait hap_id sample_n median_value mean_value min_value max_value
#> <char> <fctr> <int> <num> <num> <num> <num>
#> 1: protein_content 0 18 38 38 37.58 38.42
#> 2: protein_content 1 18 44 44 43.58 44.42
variant_protein$variant_data
#> chrom pos variant_id ref alt qual filter
#> <char> <int> <char> <char> <char> <num> <char>
#> 1: chr1 12342550 varA03 C G 60 PASS
#> info FORMAT S01 S02 S03 S04 S05
#> <char> <char> <char> <char> <char> <char> <char>
#> 1: ROLE=core_haplotype,protein_effect GT 0/0 0/0 0/0 0/0 0/0
#> S06 S07 S08 S09 S10 S11 S12 S13 S14 S15 S16
#> <char> <char> <char> <char> <char> <char> <char> <char> <char> <char> <char>
#> 1: 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0 0/0
#> S17 S18 S19 S20 S21 S22 S23 S24 S25 S26 S27
#> <char> <char> <char> <char> <char> <char> <char> <char> <char> <char> <char>
#> 1: 0/0 0/0 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1
#> S28 S29 S30 S31 S32 S33 S34 S35 S36 variant_type
#> <char> <char> <char> <char> <char> <char> <char> <char> <char> <char>
#> 1: 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 1/1 SNP
#> start end
#> <int> <int>
#> 1: 12342550 12342550For genotype_mode = "code":
0 means reference-only genotype;1 means at least one alternate allele is present.This is a binary REF/ALT-presence representation, not allele dosage.
Use genotype_mode = "string" when allele labels are more
informative:
variant_protein_string <- plot_variant_pheno(
variant = vcf,
phenotype = pheno,
variant_id = "varA03",
traits = "protein_content",
genotype_mode = "string",
min_group_samples = 3,
p_value_type = "adjusted",
text_size = 10
)
variant_protein_string$figureA variant can also be selected by exact genomic position:
variant_protein_position <- plot_variant_pheno(
variant = vcf,
phenotype = pheno,
chrom = "chr1",
pos = 12342550,
traits = "protein_content",
genotype_mode = "code",
min_group_samples = 3,
p_value_type = "adjusted",
text_size = 10
)
variant_protein_position$variant_data
#> chrom pos variant_id ref alt qual filter info
#> <char> <int> <char> <char> <char> <num> <char> <char>
#> 1: chr1 1002500 varD01 A C 60 PASS ROLE=background_variant
#> FORMAT S01 S02 S03 S04 S05 S06 S07 S08 S09 S10
#> <char> <char> <char> <char> <char> <char> <char> <char> <char> <char> <char>
#> 1: GT 0/0 1/1 0/0 1/1 0/0 1/1 0/0 1/1 0/0 1/1
#> S11 S12 S13 S14 S15 S16 S17 S18 S19 S20 S21
#> <char> <char> <char> <char> <char> <char> <char> <char> <char> <char> <char>
#> 1: 0/0 1/1 0/0 1/1 0/0 1/1 0/0 1/1 0/0 1/1 0/0
#> S22 S23 S24 S25 S26 S27 S28 S29 S30 S31 S32
#> <char> <char> <char> <char> <char> <char> <char> <char> <char> <char> <char>
#> 1: 1/1 0/0 1/1 0/0 1/1 0/0 1/1 0/0 1/1 0/0 1/1
#> S33 S34 S35 S36 variant_type start end
#> <char> <char> <char> <char> <char> <int> <int>
#> 1: 0/0 1/1 0/0 1/1 SNP 1002500 1002500The selected locator must resolve to exactly one variant. If a region
contains several variants, provide variant_id or an exact
chromosome-position locator.
plot_variant_pheno() returns the same
GeneTrackRPhenoPlot structure as
plot_hap_pheno(), with one additional component:
$variant_data, containing the selected variant record.
Keep the same hap_gene and pheno objects
for phenotype-guided refinement and variant-effect prioritization.
Rebuilding the haplotypes with a different region or missing-data rule
would change the biological groups being tested.
# Haplotype refinement
# refined <- refine_haplotype(
# hap_gene,
# phenotype = pheno,
# traits = "protein_content",
# min_hap_samples = 3,
# effect_threshold = 0.5
# )
# Variant-effect prioritization
# effect <- plot_variant_effect(
# hap_gene,
# phenotype = pheno,
# traits = "protein_content",
# min_group_samples = 3
# )The resulting workflow keeps one consistent chain:
VCF + annotation -> HapVariant -> phenotype association -> refinement / variant-effect analysis.
For reproducible analysis, export $pvalue,
$summary, and the matched sample-level
$plot_data together with $figure rather than
saving only the visual result.
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] GeneTrackR_0.99.2 rmarkdown_2.32
#>
#> loaded via a namespace (and not attached):
#> [1] gtable_0.3.6 jsonlite_2.0.0 crayon_1.5.3
#> [4] compiler_4.6.1 zip_3.0.2 Rcpp_1.1.2
#> [7] Biostrings_2.81.9 bitops_1.1-0 Rsamtools_2.29.0
#> [10] GenomicRanges_1.65.4 parallel_4.6.1 jquerylib_0.1.4
#> [13] IRanges_2.47.5 Seqinfo_1.3.2 scales_1.4.0
#> [16] BiocParallel_1.47.0 yaml_2.3.12 fastmap_1.2.0
#> [19] XVector_0.53.0 ggplot2_4.0.3 R6_2.6.1
#> [22] patchwork_1.3.2 labeling_0.4.3 generics_0.1.4
#> [25] openxlsx_4.2.9 knitr_1.52 BiocGenerics_0.59.12
#> [28] maketools_1.3.2 bslib_0.12.0 RColorBrewer_1.1-3
#> [31] rlang_1.3.0 stringi_1.8.9 cachem_1.1.0
#> [34] xfun_0.61 sass_0.4.10 sys_3.4.3
#> [37] S7_0.2.2 otel_0.2.0 cli_3.6.6
#> [40] withr_3.0.3 digest_0.6.39 grid_4.6.1
#> [43] lifecycle_1.0.5 S4Vectors_0.51.10 vctrs_0.7.3
#> [46] evaluate_1.0.5 glue_1.8.1 data.table_1.18.6.1
#> [49] farver_2.1.2 codetools_0.2-20 buildtools_1.0.0
#> [52] stats4_4.6.1 tools_4.6.1 htmltools_0.5.9