allelign 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
allelign/__init__.py ADDED
@@ -0,0 +1,69 @@
1
+ """allelign: read, align and quality-control GWAS summary statistics and PGS weights.
2
+
3
+ One place for the steps every downstream method repeats -- parsing the many
4
+ summary-statistics dialects, getting effect alleles right against a reference,
5
+ and the checks that catch bad input before it becomes a confident wrong
6
+ number. NumPy-only; Numba and SciPy speed up the parser and the LD screen when
7
+ installed.
8
+ """
9
+
10
+ __version__ = "0.1.0"
11
+
12
+ from .alleles import (allele_orientation, is_ambiguous, normalize_chrom,
13
+ reverse_complement)
14
+ from .build import BuildMismatchError, check_build, normalize_build
15
+ from .catalog import resolve_gwas_catalog_columns
16
+ from .download import download_ref, parse_ref, read_keep_ids, resolve_ref
17
+ from .genotypes import Bed, ld_consistency_screen_genotypes
18
+ from .align import (Harmonized, HarmonizedWeights,
19
+ count_positional_only_matches, coverage_message,
20
+ diagnose_match, harmonize, harmonize_weights,
21
+ reanchor_on_identifier, reference_coverage,
22
+ variant_indices)
23
+ from .ldcheck import (chromosome_blocks, dense_blocks, dentist_statistic,
24
+ ld_consistency_screen_selected,
25
+ ld_blocks_from_genotypes, ld_consistency_screen,
26
+ loo_statistic)
27
+ from .qc import (af_concordance_mask, genomic_control_factor,
28
+ genomic_inflation, gwas_af_on_counted_allele,
29
+ implied_sample_size, impute_n_eff, in_long_range_ld,
30
+ qc_sumstats, sd_consistency_mask, should_impute_n)
31
+ from .status import (QC_REASON_NAMES, new_status, status_counts,
32
+ write_status_table)
33
+ from .sumstats import (ColumnMappingError, Sumstats, anchor_n_eff,
34
+ detect_columns, inspect_columns, n_eff_case_control,
35
+ read_sumstats, rescale_n_eff, write_sumstats)
36
+ from .variants import Variants, read_bim, read_pvar, read_variants
37
+ from .weights import (SCORING_COLUMNS, ScoringFile, WeightsTable, read_scoring_file,
38
+ read_weights, scoring_file_id, write_pgs_scoring_file,
39
+ write_weights)
40
+
41
+ __all__ = [
42
+ "__version__", "SCORING_COLUMNS", "resolve_gwas_catalog_columns",
43
+ # reading and writing
44
+ "Sumstats", "read_sumstats", "write_sumstats", "detect_columns",
45
+ "inspect_columns", "ColumnMappingError", "n_eff_case_control",
46
+ "rescale_n_eff", "anchor_n_eff",
47
+ "Variants", "read_bim", "read_pvar", "read_variants", "Bed",
48
+ "WeightsTable", "read_weights", "write_weights", "ScoringFile",
49
+ "read_scoring_file", "scoring_file_id", "write_pgs_scoring_file",
50
+ # alignment
51
+ "Harmonized", "harmonize", "HarmonizedWeights", "harmonize_weights",
52
+ "reanchor_on_identifier", "reference_coverage", "coverage_message",
53
+ "diagnose_match", "count_positional_only_matches", "variant_indices",
54
+ "normalize_chrom", "reverse_complement", "is_ambiguous",
55
+ "allele_orientation", "normalize_build", "check_build",
56
+ "BuildMismatchError",
57
+ # quality control
58
+ "qc_sumstats", "sd_consistency_mask", "af_concordance_mask",
59
+ "gwas_af_on_counted_allele", "should_impute_n", "impute_n_eff",
60
+ "genomic_control_factor", "implied_sample_size", "genomic_inflation",
61
+ "in_long_range_ld", "ld_consistency_screen",
62
+ "ld_consistency_screen_selected", "dentist_statistic",
63
+ "loo_statistic", "ld_blocks_from_genotypes", "dense_blocks",
64
+ "chromosome_blocks", "ld_consistency_screen_genotypes",
65
+ # per-variant status
66
+ "QC_REASON_NAMES", "new_status", "status_counts", "write_status_table",
67
+ # downloading
68
+ "parse_ref", "resolve_ref", "download_ref", "read_keep_ids",
69
+ ]
allelign/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())
allelign/_common.py ADDED
@@ -0,0 +1,95 @@
1
+ """Small shared helpers: control validation, text input and atomic output."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import gzip
6
+ import os
7
+ import tempfile
8
+ from contextlib import contextmanager
9
+ from pathlib import Path
10
+
11
+ import numpy as np
12
+
13
+
14
+ def finite_control(name, value, *, lower=None, upper=None):
15
+ """Coerce a finite scalar control, rejecting booleans and vectors."""
16
+ if isinstance(value, (bool, np.bool_)) or np.ndim(value) != 0:
17
+ raise ValueError(f"{name} must be a finite number")
18
+ try:
19
+ value = float(value)
20
+ except (TypeError, ValueError, OverflowError):
21
+ raise ValueError(f"{name} must be a finite number") from None
22
+ if not np.isfinite(value):
23
+ raise ValueError(f"{name} must be a finite number")
24
+ if lower is not None and value < lower:
25
+ raise ValueError(f"{name} must be >= {lower}")
26
+ if upper is not None and value > upper:
27
+ raise ValueError(f"{name} must be <= {upper}")
28
+ return value
29
+
30
+
31
+ def integer_at_least(name, value, minimum):
32
+ """Return an integer control, rejecting bools and silent coercion."""
33
+ if (isinstance(value, (bool, np.bool_))
34
+ or not isinstance(value, (int, np.integer))
35
+ or int(value) < minimum):
36
+ raise ValueError(f"{name} must be an integer >= {minimum}")
37
+ return int(value)
38
+
39
+
40
+ def validate_boolean(**controls):
41
+ """Reject truthy/falsy surrogates for controls that are genuinely boolean."""
42
+ for name, value in controls.items():
43
+ if not isinstance(value, (bool, np.bool_)):
44
+ raise ValueError(f"{name} must be a boolean")
45
+
46
+
47
+ def validate_seed(seed):
48
+ """Return a seed usable by ``numpy.random.SeedSequence``, or ``None``."""
49
+ if seed is None:
50
+ return None
51
+ if (isinstance(seed, (bool, np.bool_))
52
+ or not isinstance(seed, (int, np.integer))
53
+ or not 0 <= int(seed) <= np.iinfo(np.uint32).max):
54
+ raise ValueError("seed must be an integer in [0, 2**32 - 1] or None")
55
+ return int(seed)
56
+
57
+
58
+ def open_text(path):
59
+ """Open a plain or gzip/bgzip text file for reading.
60
+
61
+ ``utf-8-sig`` strips a UTF-8 byte-order mark, which Excel and several GWAS
62
+ portals emit; left in place it glues itself to the first header token,
63
+ which then matches no alias, so the first column (usually the variant ID)
64
+ is silently never mapped.
65
+ """
66
+ name = os.fspath(path)
67
+ if name.endswith((".gz", ".bgz")):
68
+ return gzip.open(name, "rt", encoding="utf-8-sig")
69
+ return open(name, "r", encoding="utf-8-sig")
70
+
71
+
72
+ @contextmanager
73
+ def atomic_text(path):
74
+ """Write text so that only a complete file is ever published at ``path``.
75
+
76
+ A ``.gz`` destination is gzip-compressed. The temporary file lives on the
77
+ destination filesystem, so the final ``os.replace`` is atomic.
78
+ """
79
+ path = Path(path)
80
+ fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.",
81
+ dir=path.parent or ".")
82
+ try:
83
+ if path.name.endswith(".gz"):
84
+ os.close(fd)
85
+ with gzip.open(temporary, "wt", encoding="utf-8", compresslevel=1) as fh:
86
+ yield fh
87
+ else:
88
+ with os.fdopen(fd, "w", encoding="utf-8") as fh:
89
+ yield fh
90
+ fh.flush()
91
+ os.fsync(fh.fileno())
92
+ os.replace(temporary, path)
93
+ finally:
94
+ if os.path.exists(temporary):
95
+ os.unlink(temporary)
allelign/_numba.py ADDED
@@ -0,0 +1,22 @@
1
+ """Optional Numba support. Everything has a pure-NumPy/Python fallback."""
2
+
3
+ from __future__ import annotations
4
+
5
+ try: # pragma: no cover - depends on the environment
6
+ import numba as _numba
7
+ from numba import config as numba_config
8
+ HAVE_NUMBA = True
9
+ except Exception: # pragma: no cover - a missing or broken install
10
+ _numba = None
11
+ numba_config = None
12
+ HAVE_NUMBA = False
13
+
14
+
15
+ def jit_available():
16
+ """True when compiled kernels can actually run (not disabled by config)."""
17
+ return HAVE_NUMBA and not getattr(numba_config, "DISABLE_JIT", False)
18
+
19
+
20
+ def jit_nogil(fn):
21
+ """Compile ``fn`` in nopython, GIL-releasing mode with an on-disk cache."""
22
+ return _numba.njit(cache=True, nogil=True)(fn)