Phenotype association

Introduction

library(GeneTrackR)

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:

  1. prepare phenotype, VCF, annotation, and the standard GeneA haplotypes;
  2. summarize phenotype types, missingness, and distributions;
  3. verify sample matching before association analysis;
  4. test haplotype-phenotype associations;
  5. inspect multiple traits and statistical-test settings;
  6. control phenotype-association plot appearance;
  7. test a single variant against phenotype;
  8. pass the same phenotype and haplotype objects to refinement and variant-effect workflows.

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/.

Installation

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.

Step 1. Prepare phenotype and genotype inputs

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:

# pheno <- read_pheno(
#   "phenotype.tsv",
#   sample_col = "Taxa"
# )

Sample IDs must be unique. Duplicate IDs are rejected because they would make genotype-phenotype matching ambiguous.

Step 2. Summarize phenotype quality and distributions

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     NA

The summary reports:

  • numeric versus categorical trait type;
  • total sample number;
  • missing number and missing rate;
  • number of unique non-missing values;
  • minimum, mean, median, and maximum for numeric traits.

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_figure

plot_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.

Step 3. Verify sample matching explicitly

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              0

For 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.

Step 4. Test the primary haplotype-phenotype association

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$figure

plot_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).

Step 5. Compare traits and choose the statistical settings

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$figure

hap_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.test

Three 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.

Step 6. Control phenotype-association plot appearance

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$figure

Use 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$figure

Use 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.

Step 7. Test a single variant against phenotype

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$figure

variant_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 12342550

For 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$figure

A 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 1002500

The 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.

Step 8. Pass the same objects downstream

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.

Session information

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