Skip to content

Latest commit

 

History

History
115 lines (86 loc) · 5.05 KB

File metadata and controls

115 lines (86 loc) · 5.05 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

gwasplot is an R package for visualizing and annotating GWAS summary statistics. It targets output from regenie or SAIGE run on WGS data with many rare variants, so it is built around DuckDB: data is read from parquet/CSV and manipulated lazily in the database rather than collected into R memory. This is the central design constraint — prefer keeping work in DuckDB/dbplyr and only collect() when a step genuinely needs in-memory data (e.g. handing a frame to ggplot).

Common commands

Dependencies are managed with renv (renv.lock). Run from the repo root:

R -q -e "renv::restore()"                  # install locked deps
R -q -e "devtools::load_all()"             # load package for interactive work
R -q -e "devtools::document()"             # regenerate NAMESPACE + man/*.Rd from roxygen
R -q -e "devtools::test()"                 # run the testthat suite
R -q -e "devtools::check()"                # full R CMD check

Run a single test file:

R -q -e 'devtools::load_all(); testthat::test_file("tests/testthat/test-meta_analysis.R")'

C++ (Rcpp) lives in src/ (qqplot.cpp). After editing it, regenerate the glue with R -q -e "Rcpp::compileAttributes()" then devtools::load_all() / devtools::document().

Quick syntax check without loading deps (useful when a needed package is missing from the renv): Rscript --no-save -e "parse('R/foo.R')".

Formatting

Code is formatted with air (config in air.toml: 80-col, 2-space indent). Format before committing:

air format R/            # whole package
air format --check R/    # CI-style check, non-zero exit if unformatted

Keep formatting-only changes in their own commit, separate from behavior changes — a blanket reformat otherwise buries real diffs and churns git blame.

Architecture

GWASFormatter — the DuckDB-backed object

reformat_summary_statistics(file_path) (in R/reformat.R) is the main entry point. It returns a GWASFormatter R6 object that wraps:

  • $con — the DuckDB connection (db_connect() opens local.duckdb)
  • $data — a lazy dbplyr tbl over the current active table
  • $table_name / $source_table_name — the active and original table names

On construction it detects the input format (detect_format()), standardizes column names (reformat_names() via reformat_lookup()), undoes LOG10P, maps chr23chrX, builds an ID (CHROM_POS_REF_ALT), and materializes its own uniquely-named DuckDB table. The unique naming is deliberate: multiple GWASFormatter objects can coexist in one working directory without clobbering a shared summary_stats table.

Standard columns after reformatting: CHROM, POS, REF, ALT, ID, PVALUE, BETA, SE (plus annotation columns added downstream — see the memory index for the full column-provenance map).

The S3 "triality" pattern

Almost every user-facing function is an S3 generic with three methods: .GWASFormatter, .data.frame, and .tbl_df. The convention:

  • .tbl_df delegates to .data.frame.
  • .GWASFormatter either runs the work in DuckDB (for scale) or collect()s and delegates to .data.frame.

When adding functionality, follow this pattern and register all three methods. NAMESPACE is regenerated by devtools::document() from roxygen #' tags — do not hand-edit it (likewise man/*.Rd).

In-place pipeline + materialization

The QC/filter pipeline — filter_variants(), exclude_difficult_regions() — mutates the GWASFormatter in place. Each step builds a lazy dbplyr query (semi/anti-joins against whitelist parquet or packaged BED region datasets) and calls materialize_gwas_tbl_() (R/db.R), which compute()s the result into a new DuckDB table and repoints $data / $table_name. So $table_name always reflects the current filtered state, and filters are cumulative.

write_summary_statistics() (R/write.R) persists the result: the .GWASFormatter method renders the active lazy query with dbplyr::sql_render() and streams to disk via DuckDB COPY (...) TO, so applied filters are captured without collecting into R. In-memory methods write through a throwaway in-memory DuckDB connection (never touching local.duckdb).

Packaged reference data

data/*.rda holds reference datasets (difficult-region BEDs like hg19diff, UCSC_unusual, GRC_exclusions, GIAB_difficult_regions; cytoband ideogram; gene models). They are loaded on demand with data(list = ...). Build scripts live in data-raw/. Note BED regions are 0-based and converted to 1-based (start + 1) before joining.

Annotation & external data

R/annotate.R covers nearest-gene assignment, locus identification, and Open Targets locus-to-gene (annotate_with_l2g()) via either per-variant GraphQL or bulk parquet pulled from the OT FTP with a local cache. R/meta_analysis.R does fixed-effects meta-analysis (meta_analyze_fe()), which requires the DuckDB stochastic community extension for accurate extreme-tail p-values.