Variant tracks

Introduction

GeneTrackR uses VariantTrack as the common object for VCF-derived genomic variants. A normal variant workflow is:

  1. read a VCF into memory or create a lazy indexed track;
  2. validate the standardized variant records;
  3. summarize the current collection;
  4. retrieve variants by genomic region, gene, transcript, ID, type, or text pattern;
  5. visualize the selected variants;
  6. pass the resulting VariantTrack to haplotype, LD, phenotype, or integrated browser workflows.

The examples below use the same GeneA / TxA1 locus used throughout the GeneTrackR documentation.

library(GeneTrackR)

vcf_file <- system.file(
  "extdata",
  "gtr_demo_variants.vcf",
  package = "GeneTrackR"
)

gp <- read_genepred(
  system.file(
    "extdata",
    "gtr_demo.genePredExt",
    package = "GeneTrackR"
  ),
  format = "genePredExt",
  verbose = FALSE,
  progress = FALSE
)

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.

Read variants

For a small VCF or repeated local analysis, use mode = "memory" to parse all records immediately.

variants <- read_vcf(
  vcf_file,
  mode = "memory",
  keep_genotype = TRUE,
  verbose = FALSE,
  progress = FALSE
)

variants
#> <VariantTrack>
#>   variants  : 56 
#>   format    : VCF 
#>   coordinate: 1-based position

read_vcf() returns a VariantTrack. Standard VCF fields are normalized to columns including chrom, pos, variant_id, ref, alt, qual, filter, info, and variant_type. Genotype fields and sample columns are retained when keep_genotype = TRUE.

head(variants$data[, c(
  "chrom",
  "pos",
  "variant_id",
  "ref",
  "alt",
  "variant_type"
)])
#>     chrom     pos variant_id    ref    alt variant_type
#>    <char>   <int>     <char> <char> <char>       <char>
#> 1:   chr1 1002500     varD01      A      C          SNP
#> 2:   chr1 2500000     varX01      A      G          SNP
#> 3:   chr1 3004500     varE01      T      G          SNP
#> 4:   chr1 5005200     varF01      G      A          SNP
#> 5:   chr1 6500000     varX02      A      G          SNP
#> 6:   chr1 7003300     varG01      C      T          SNP

variants$meta$sample_names
#>  [1] "S01" "S02" "S03" "S04" "S05" "S06" "S07" "S08" "S09" "S10" "S11" "S12"
#> [13] "S13" "S14" "S15" "S16" "S17" "S18" "S19" "S20" "S21" "S22" "S23" "S24"
#> [25] "S25" "S26" "S27" "S28" "S29" "S30" "S31" "S32" "S33" "S34" "S35" "S36"

VariantTrack uses 1-based genomic positions, matching the VCF POS convention.

Large indexed VCF files

For a bgzip-compressed VCF with a .tbi or .csi index, mode = "auto" or mode = "lazy" can avoid parsing the complete file. The lazy object stores source metadata first and reads records only when a genomic interval is requested.

large_vcf <- "/path/to/large.vcf.gz"

variants_lazy <- read_vcf(
  large_vcf,
  mode = "lazy"
)

variants_region <- retrieve_vcf(
  variants_lazy,
  chrom = "chr1",
  start = 12339001,
  end = 12374500,
  as = "VariantTrack"
)

Lazy regional access requires an indexed VCF and Rsamtools. If mode = "lazy" is requested for a non-indexed VCF, GeneTrackR falls back to memory mode with a warning.

Validate and summarize

Use validate_vcf() before downstream analysis when VCF inputs come from heterogeneous pipelines or have been modified outside GeneTrackR.

validation <- validate_vcf(variants)

validation$invalid_summary
#> Empty data.table (0 rows and 2 cols): reason,N
validation$warnings
#> character(0)

The validation report separates record-level failures from general warnings. It checks required identifiers and allele fields, genomic positions, duplicated variant IDs, and consistency among pos, start, and end when those fields are available.

summary_vcf() reports counts for the current variant collection. The default grouping is chromosome plus inferred variant type.

summary_vcf(variants)
#>     chrom variant_type n_variants
#>    <char>       <char>      <int>
#> 1:   chr1          DEL          1
#> 2:   chr1          INS          2
#> 3:   chr1          SNP         33
#> 4:   chr2          SNP         20
summary_vcf(variants, chrom = "chr1")
#>     chrom variant_type n_variants
#>    <char>       <char>      <int>
#> 1:   chr1          DEL          1
#> 2:   chr1          INS          2
#> 3:   chr1          SNP         33

Custom grouping is possible when the requested fields exist in the standardized table.

summary_vcf(
  variants,
  by = c("filter", "variant_type")
)
#>    filter variant_type n_variants
#>    <char>       <char>      <int>
#> 1:   PASS          DEL          1
#> 2:   PASS          INS          2
#> 3:   PASS          SNP         53

Retrieve by genomic region

retrieve_vcf() returns a plain data.table, another VariantTrack, or a GenomicRanges::GRanges object for downstream Bioconductor operations.

region_table <- retrieve_vcf(
  variants,
  chrom = "chr1",
  start = 12339001,
  end = 12374500,
  as = "data.table",
  verbose = FALSE
)

region_table[, .(
  chrom,
  pos,
  variant_id,
  ref,
  alt,
  variant_type
)]
#>      chrom      pos variant_id    ref    alt variant_type
#>     <char>    <int>     <char> <char> <char>       <char>
#>  1:   chr1 12339750   varAup01      A      T          SNP
#>  2:   chr1 12340250     varA01      A      G          SNP
#>  3:   chr1 12340600     varA02      T      C          SNP
#>  4:   chr1 12342550     varA03      C      G          SNP
#>  5:   chr1 12342620    varLD01      G      A          SNP
#>  6:   chr1 12342710    varLD02      A      T          SNP
#>  7:   chr1 12342805    varLD03      C      T          SNP
#>  8:   chr1 12342920    varLD04      G      C          SNP
#>  9:   chr1 12343040    varLD05      T      G          SNP
#> 10:   chr1 12343180    varLD06      A      C          SNP
#> 11:   chr1 12344500     varA04      A    ATG          INS
#> 12:   chr1 12351050     varA05    ATG      A          DEL
#> 13:   chr1 12352500    varLD07      G      T          SNP
#> 14:   chr1 12353100    varLD08      A      C          SNP
#> 15:   chr1 12353700    varLD09      C      A          SNP
#> 16:   chr1 12354300    varLD10      T      G          SNP
#> 17:   chr1 12354900    varLD11      G      C          SNP
#> 18:   chr1 12355500    varLD12      A      G          SNP
#> 19:   chr1 12356550     varB01      A      T          SNP
#> 20:   chr1 12359600     varB02      G      C          SNP
#> 21:   chr1 12362550     varB03      C      T          SNP
#> 22:   chr1 12365400     varB04      T      C          SNP
#> 23:   chr1 12366200     varB05      A     AT          INS
#> 24:   chr1 12369500     varC01      G      A          SNP
#> 25:   chr1 12372000     varC02      T      G          SNP
#> 26:   chr1 12374000     varC03      C      A          SNP
#>      chrom      pos variant_id    ref    alt variant_type
#>     <char>    <int>     <char> <char> <char>       <char>

Use as = "VariantTrack" when the subset will be plotted or passed into haplotype, phenotype, or LD functions.

region_variants <- retrieve_vcf(
  variants,
  chrom = "chr1",
  start = 12339001,
  end = 12374500,
  as = "VariantTrack",
  verbose = FALSE
)

region_variants
#> <VariantTrack>
#>   variants  : 26 
#>   format    : VCF 
#>   coordinate: 1-based position

Use as = "GRanges" when the genomic subset should move directly into Bioconductor interval operations.

region_gr <- retrieve_vcf(
  variants,
  chrom = "chr1",
  start = 12339001,
  end = 12374500,
  as = "GRanges",
  verbose = FALSE
)

region_gr
#> GRanges object with 26 ranges and 45 metadata columns:
#>        seqnames    ranges strand |       pos  variant_id         ref
#>           <Rle> <IRanges>  <Rle> | <integer> <character> <character>
#>    [1]     chr1  12339750      * |  12339750    varAup01           A
#>    [2]     chr1  12340250      * |  12340250      varA01           A
#>    [3]     chr1  12340600      * |  12340600      varA02           T
#>    [4]     chr1  12342550      * |  12342550      varA03           C
#>    [5]     chr1  12342620      * |  12342620     varLD01           G
#>    ...      ...       ...    ... .       ...         ...         ...
#>   [22]     chr1  12365400      * |  12365400      varB04           T
#>   [23]     chr1  12366200      * |  12366200      varB05           A
#>   [24]     chr1  12369500      * |  12369500      varC01           G
#>   [25]     chr1  12372000      * |  12372000      varC02           T
#>   [26]     chr1  12374000      * |  12374000      varC03           C
#>                alt      qual      filter                   info      FORMAT
#>        <character> <numeric> <character>            <character> <character>
#>    [1]           T        60        PASS ROLE=upstream_missin..          GT
#>    [2]           G        60        PASS ROLE=core_haplotype,..          GT
#>    [3]           C        60        PASS    ROLE=core_haplotype          GT
#>    [4]           G        60        PASS ROLE=core_haplotype,..          GT
#>    [5]           A        60        PASS ROLE=high_ld,ld_grad..          GT
#>    ...         ...       ...         ...                    ...         ...
#>   [22]           C        60        PASS ROLE=geneB_heterozyg..          GT
#>   [23]          AT        60        PASS       ROLE=geneB_indel          GT
#>   [24]           A        60        PASS     ROLE=geneC_variant          GT
#>   [25]           G        60        PASS     ROLE=geneC_variant          GT
#>   [26]           A        60        PASS     ROLE=geneC_variant          GT
#>                S01         S02         S03         S04         S05         S06
#>        <character> <character> <character> <character> <character> <character>
#>    [1]         0/0         0/0         ./.         0/0         0/1         0/0
#>    [2]         0/0         0/0         0/0         0/0         0/0         0/0
#>    [3]         0/0         0/0         0/0         0/0         0/0         0/0
#>    [4]         0/0         0/0         0/0         0/0         0/0         0/0
#>    [5]         0/0         0/0         0/0         0/0         0/0         0/0
#>    ...         ...         ...         ...         ...         ...         ...
#>   [22]         0/0         0/0         0/0         0/0         0/1         0/0
#>   [23]         0/0         0/0         1/1         0/0         0/0         1/1
#>   [24]         1/1         0/0         1/1         0/0         1/1         0/0
#>   [25]         0/0         0/0         1/1         0/0         0/0         1/1
#>   [26]         1/1         1/1         0/0         0/0         1/1         1/1
#>                S07         S08         S09         S10         S11         S12
#>        <character> <character> <character> <character> <character> <character>
#>    [1]         0/0         0/0         0/0         0/0         0/0         ./.
#>    [2]         0/0         0/0         0/0         0/0         0/0         0/0
#>    [3]         0/0         0/0         0/0         1/1         1/1         1/1
#>    [4]         0/0         0/0         0/0         0/0         0/0         0/0
#>    [5]         0/0         0/0         0/0         0/0         0/0         0/0
#>    ...         ...         ...         ...         ...         ...         ...
#>   [22]         0/0         0/0         0/0         1/1         1/1         1/1
#>   [23]         0/0         0/0         1/1         0/0         0/0         1/1
#>   [24]         1/1         0/0         1/1         0/0         1/1         0/0
#>   [25]         0/0         0/0         1/1         0/0         0/0         1/1
#>   [26]         0/0         0/0         1/1         1/1         0/0         0/0
#>                S13         S14         S15         S16         S17         S18
#>        <character> <character> <character> <character> <character> <character>
#>    [1]         0/0         0/1         0/0         0/0         0/0         0/0
#>    [2]         0/0         0/0         0/0         0/0         0/0         0/0
#>    [3]         1/1         1/1         1/1         1/1         1/1         1/1
#>    [4]         0/0         0/0         0/0         0/0         0/0         0/0
#>    [5]         0/0         0/0         0/0         0/0         0/0         0/0
#>    ...         ...         ...         ...         ...         ...         ...
#>   [22]         1/1         0/1         1/1         1/1         1/1         1/1
#>   [23]         0/0         0/0         1/1         0/0         0/0         1/1
#>   [24]         1/1         0/0         1/1         0/0         1/1         0/0
#>   [25]         0/0         0/0         1/1         0/0         0/0         1/1
#>   [26]         1/1         1/1         0/0         0/0         1/1         1/1
#>                S19         S20         S21         S22         S23         S24
#>        <character> <character> <character> <character> <character> <character>
#>    [1]         1/1         1/1         ./.         1/1         0/1         1/1
#>    [2]         1/1         1/1         1/1         1/1         1/1         1/1
#>    [3]         1/1         1/1         1/1         1/1         1/1         1/1
#>    [4]         1/1         1/1         1/1         1/1         1/1         1/1
#>    [5]         1/1         1/1         1/1         1/1         1/1         1/1
#>    ...         ...         ...         ...         ...         ...         ...
#>   [22]         0/0         0/0         0/0         0/0         0/1         0/0
#>   [23]         0/0         0/0         1/1         0/0         0/0         1/1
#>   [24]         1/1         0/0         1/1         0/0         1/1         0/0
#>   [25]         0/0         0/0         1/1         0/0         0/0         1/1
#>   [26]         0/0         0/0         1/1         1/1         0/0         0/0
#>                S25         S26         S27         S28         S29         S30
#>        <character> <character> <character> <character> <character> <character>
#>    [1]         1/1         1/1         1/1         1/1         1/1         ./.
#>    [2]         1/1         1/1         1/1         1/1         1/1         1/1
#>    [3]         1/1         1/1         1/1         0/0         0/0         0/0
#>    [4]         1/1         1/1         1/1         1/1         1/1         1/1
#>    [5]         1/1         1/1         1/1         1/1         1/1         1/1
#>    ...         ...         ...         ...         ...         ...         ...
#>   [22]         0/0         0/0         0/0         1/1         1/1         1/1
#>   [23]         0/0         0/0         1/1         0/0         0/0         1/1
#>   [24]         1/1         0/0         1/1         0/0         1/1         0/0
#>   [25]         0/0         0/0         1/1         0/0         0/0         1/1
#>   [26]         1/1         1/1         0/0         0/0         1/1         1/1
#>                S31         S32         S33         S34         S35         S36
#>        <character> <character> <character> <character> <character> <character>
#>    [1]         1/1         0/1         1/1         1/1         1/1         1/1
#>    [2]         1/1         1/1         1/1         1/1         1/1         1/1
#>    [3]         0/0         0/0         0/0         0/0         0/0         0/0
#>    [4]         1/1         1/1         1/1         1/1         1/1         1/1
#>    [5]         1/1         1/1         1/1         1/1         1/1         1/1
#>    ...         ...         ...         ...         ...         ...         ...
#>   [22]         1/1         0/1         1/1         1/1         1/1         1/1
#>   [23]         0/0         0/0         1/1         0/0         0/0         1/1
#>   [24]         1/1         0/0         1/1         0/0         1/1         0/0
#>   [25]         0/0         0/0         1/1         0/0         0/0         1/1
#>   [26]         0/0         0/0         1/1         1/1         0/0         0/0
#>        variant_type
#>         <character>
#>    [1]          SNP
#>    [2]          SNP
#>    [3]          SNP
#>    [4]          SNP
#>    [5]          SNP
#>    ...          ...
#>   [22]          SNP
#>   [23]          INS
#>   [24]          SNP
#>   [25]          SNP
#>   [26]          SNP
#>   -------
#>   seqinfo: 1 sequence from an unspecified genome; no seqlengths

For an in-memory object, as_granges(variants) converts the full VariantTrack and preserves VCF-derived fields as GRanges metadata columns. A lazy VariantTrack should be retrieved by genomic region before conversion.

A direct region query must use one chromosome with both start and end. A chromosome-only query is also valid when coordinates are not required.

Retrieve by gene or transcript

When annotation is available, retrieve_vcf() can resolve a gene or transcript into its genomic interval.

genea_variants <- retrieve_vcf(
  variants,
  annotation = gp,
  gene_id = "GeneA",
  as = "VariantTrack",
  verbose = FALSE
)

genea_variants
#> <VariantTrack>
#>   variants  : 11 
#>   format    : VCF 
#>   coordinate: 1-based position

The deterministic demo contains 11 variants within the GeneA gene body. Flanking sequence can be added with upstream and downstream.

genea_extended <- retrieve_vcf(
  variants,
  annotation = gp,
  gene_id = "GeneA",
  upstream = 1000,
  downstream = 500,
  strand_aware = TRUE,
  as = "VariantTrack",
  verbose = FALSE
)

genea_extended
#> <VariantTrack>
#>   variants  : 13 
#>   format    : VCF 
#>   coordinate: 1-based position

With strand_aware = TRUE, upstream and downstream follow transcriptional direction. This distinction matters for negative-strand genes.

Transcript-aware retrieval uses the same interface.

txa1_variants <- retrieve_vcf(
  variants,
  annotation = gp,
  transcript_id = "TxA1",
  as = "VariantTrack",
  verbose = FALSE
)

txa1_variants
#> <VariantTrack>
#>   variants  : 11 
#>   format    : VCF 
#>   coordinate: 1-based position

Do not mix gene_id or transcript_id with direct chrom / start / end arguments in one query.

Filter by variant attributes

Variant ID, inferred variant type, and text pattern filters can be used independently or combined with a genomic locator.

selected_variants <- retrieve_vcf(
  variants,
  variant_id = c("varA03", "varA04", "varA05"),
  as = "data.table",
  verbose = FALSE
)

selected_variants[, .(
  chrom,
  pos,
  variant_id,
  ref,
  alt,
  variant_type
)]
#>     chrom      pos variant_id    ref    alt variant_type
#>    <char>    <int>     <char> <char> <char>       <char>
#> 1:   chr1 12342550     varA03      C      G          SNP
#> 2:   chr1 12344500     varA04      A    ATG          INS
#> 3:   chr1 12351050     varA05    ATG      A          DEL
genea_indels <- retrieve_vcf(
  genea_variants,
  variant_type = c("INS", "DEL"),
  as = "data.table",
  verbose = FALSE
)

genea_indels[, .(
  pos,
  variant_id,
  ref,
  alt,
  variant_type
)]
#>         pos variant_id    ref    alt variant_type
#>       <int>     <char> <char> <char>       <char>
#> 1: 12344500     varA04      A    ATG          INS
#> 2: 12351050     varA05    ATG      A          DEL

pattern searches the standardized variant ID, REF, ALT, INFO, and variant-type fields. Use fixed = TRUE when the search term should not be interpreted as a regular expression.

high_ld_variants <- retrieve_vcf(
  variants,
  pattern = "high_ld",
  fixed = TRUE,
  as = "VariantTrack",
  verbose = FALSE
)

high_ld_variants
#> <VariantTrack>
#>   variants  : 7 
#>   format    : VCF 
#>   coordinate: 1-based position

Plot a variant track

plot_variant() is the dedicated variant plotting function. It returns a ggplot object and supports lollipop, point, and rug geometries.

variant_figure <- plot_variant(
  genea_variants,
  color_by = "variant_type",
  label_by = "variant_id",
  variant_shape = "lollipop",
  variant_palette = "Paired",
  point_size = 2.5,
  text_size = 10,
  plot_theme = "classic"
)
#> [GeneTrackR] Retrieved variants: 11.

variant_figure

The primary controls are:

Argument Purpose
color_by Color by variant_type, filter, or disable grouping.
label_by Label by variant ID, type, REF, ALT, or hide labels.
variant_shape Draw lollipop, point, or rug geometry.
variant_palette Select an RColorBrewer palette.
variant_colors Supply explicit named or positional colors.

For stable biological classes across multiple figures, prefer explicit named colors.

plot_variant(
  genea_variants,
  color_by = "variant_type",
  label_by = "none",
  variant_shape = "rug",
  variant_colors = c(
    SNP = "#1F78B4",
    INS = "#33A02C",
    DEL = "#E31A1C"
  ),
  plot_theme = "classic",
  text_size = 10
)
#> [GeneTrackR] Retrieved variants: 11.

plot_variant_track() is retained as a backward-compatible wrapper around plot_variant().

Write a standardized VCF subset

write_vcf() writes the standardized site-level VCF fields from a VariantTrack.

write_vcf(
  genea_variants,
  "GeneA.variants.vcf",
  overwrite = TRUE
)

The current writer exports CHROM, POS, ID, REF, ALT, QUAL, FILTER, and INFO. It does not round-trip FORMAT and genotype sample columns. Keep the original genotype-containing VCF as the archival source whenever genotype preservation is required.

Downstream handoff

The same VariantTrack object can be reused directly by later GeneTrackR modules:

  • hap_gene_variant() or hap_region_variant() for haplotype construction;
  • plot_variant_pheno() for single-variant phenotype association;
  • compute_ld_block() and plot_ld_block() for LD analysis;
  • plot_tracks() for integrated browser-style visualization.

Keep genotype columns by reading with keep_genotype = TRUE whenever downstream haplotype or LD analysis is planned.

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