Compute odds ratios between modification and charging.
Source:R/charging-odds-ratios.R
compute_charging_odds_ratios.RdFor each site, use individual reads as the unit of observation to ask whether a read being modified at that site is associated with that same read being charged. Each site yields a 2x2 table of modified/unmodified against charged/uncharged, an odds ratio, and Fisher's exact test.
Usage
compute_charging_odds_ratios(
calls,
charging,
sites = NULL,
refs = NULL,
min_reads = 10,
min_margin = 1,
max_p = 1,
ml_threshold = 200,
dedupe = TRUE,
p_method = "BH"
)Arguments
- calls
Per-read calls, either a path to a
mod_calls.tsv.gz/mismatch_calls.tsv.gzfile or a tibble. Requires columnsread_id,chrom(orref),ref_position(orpos), and eithercall_code(where"-"means no call) or an integermodifiedcolumn. A logicalwithin_alignmentcolumn, if present, is used to drop calls outside the alignment.- charging
Per-read charging status, either a path to a
charging_prob.tsv.gzfile or a tibble fromread_charging_calls().- sites
Optional tibble of sites to test, with columns
refandpos, typically fromcall_bcerror_sites(). IfNULL(default), all positions incallsare tested.- refs
Optional character vector of reference names to include. If
NULL(default), all references are processed.- min_reads
Minimum number of reads with both a call and a charging status required for a site to be tested. Default
10.- min_margin
Minimum count in each margin of the 2x2 table: modified, unmodified, charged and uncharged reads. Default
1, which only requires the odds ratio to be defined. Raising it drops sites too thin to carry power, such as a position where three of ten thousand reads are modified.- max_p
Skip sites where no arrangement of the reads could reach this p-value, given the margins. Default
1, which tests everything. Set it to the significance threshold you intend to use.- ml_threshold
Charging likelihood threshold, used only when
chargingis a path. Default200.- dedupe
Whether to collapse repeated calls for the same read and position before counting. Needed for
modkitoutput, where a read with several modification channels at one position contributes a row per channel. DefaultTRUE. SetFALSEfor per-read mismatch calls, which carry one row per read and position, to skip a grouping pass that is costly on large tables.- p_method
Method for p-value adjustment, passed to
stats::p.adjust(). Default"BH".
Value
A tibble with one row per tested site and columns: ref, pos,
n11 (modified and charged), n10 (modified and uncharged), n01
(unmodified and charged), n00 (unmodified and uncharged), total_obs,
mod_freq, charged_freq, odds_ratio, log_odds_ratio, p_value,
and p_adjusted.
Details
An odds ratio above 1 means modified reads are more likely to be charged than unmodified reads of the same tRNA; below 1 means the opposite. Because both variables are measured on the same read, this is a within-tRNA comparison and is not confounded by differences in abundance or charging between tRNAs or between samples.
Sites can come from either of two sources, set by sites:
Direct modification calls (
sites = NULL, the default): every position present incallsis tested.Base-calling error: pass a site list from
call_bcerror_sites()to restrict testing to positions called from error rates. In this casecallsshould be a per-read mismatch table rather than modification calls, so that the per-read status and the site selection come from the same signal.
Reads with no call at a site are excluded from that site's table rather
than counted as unmodified, so total_obs varies between sites.
Pruning sites before testing
A genome-wide site list is mostly sites that were never testable, and each
one still consumes FDR budget. Two arguments drop them up front. min_margin
requires a minimum count of modified, unmodified, charged and uncharged
reads; max_p drops sites whose margins make the intended significance
threshold unreachable no matter how the reads fall.
Both filter on the margins of the 2x2 table, never on the observed odds
ratio, and that distinction matters. The margins are ancillary, so filtering
on them leaves the null distribution of the surviving p-values intact.
Filtering on effect size would enrich for small p-values and invalidate the
correction applied afterwards, so do not pre-filter sites on an effect
measured from the same reads.
Sites near the 3' end are not interpretable
The amino acid is esterified to the terminal A, and charging is called from the nanopore signal over that CCA end. A site there is therefore not independent of the charging call it is being tested against: the base caller is reading out the same thing the charging model is, and the odds ratio between them is close to tautological.
This dominates the result. Across two zebrafish samples, 82% of terminal-A
sites came back significant with a median log odds ratio of 1.9, and the
effect decayed monotonically with distance from that residue – roughly 50%
of sites one or two nucleotides in, 16-33% from three to ten, and 4.6%
beyond fifty, where it flattens out. By region, cca and discriminator
ran at 63% and 36% against 3-5% in the D-loop and anticodon arms.
Note that the position number of the terminal A varies between references,
since tRNAs differ in length, so filter on distance from the 3' end or on
the region column of a Sprinzl coordinate table rather than on an absolute
position. Dropping cca, discriminator and the 3' acceptor stem removes
the worst of it; the gradient suggests discarding everything within about
twenty nucleotides of the end. This caveat applies whichever site source is
used, since it concerns the charging call rather than the modification call.
Examples
calls <- clover_example("ecoli/mod_calls.tsv.gz")
charging <- clover_example("ecoli/charging_calls.tsv.gz")
compute_charging_odds_ratios(calls, charging, min_reads = 5)
#> # A tibble: 2 × 13
#> ref pos n11 n10 n01 n00 total_obs mod_freq charged_freq odds_ratio
#> <chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 host… 34 3 0 3 9 15 0.2 0.4 19
#> 2 host… 46 1 2 5 7 15 0.2 0.4 0.7
#> # ℹ 3 more variables: log_odds_ratio <dbl>, p_value <dbl>, p_adjusted <dbl>
# Restrict to sites called from base-calling error instead.
bcerr <- read_bcerror(clover_example(
"ecoli/summary/tables/wt-15-ctl-01/wt-15-ctl-01.bcerror.tsv.gz"
))
sites <- call_bcerror_sites(bcerr, min_error = 0.05, min_cov = 10)
mismatches <- clover_example("ecoli/mismatch_calls.tsv.gz")
compute_charging_odds_ratios(
mismatches,
charging,
sites = sites,
min_reads = 5
)
#> # A tibble: 3 × 13
#> ref pos n11 n10 n01 n00 total_obs mod_freq charged_freq odds_ratio
#> <chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 host… 58 6 1 0 8 15 0.467 0.4 73.7
#> 2 host… 34 3 0 3 9 15 0.2 0.4 19
#> 3 host… 55 0 3 6 6 15 0.2 0.4 0.143
#> # ℹ 3 more variables: log_odds_ratio <dbl>, p_value <dbl>, p_adjusted <dbl>