locuscomparepy 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.
@@ -0,0 +1,25 @@
1
+ """locuscompare: visualize GWAS-QTL colocalization events.
2
+
3
+ Python port of the R package ``locuscomparer``
4
+ (https://github.com/boxiangliu/locuscomparer).
5
+ """
6
+ from .analysis import assign_color, get_lead_snp
7
+ from .core import locuscompare
8
+ from .datasets import example_path
9
+ from .db import get_position, retrieve_LD
10
+ from .io import read_metal
11
+ from .plot import make_combined_plot, make_locuszoom, make_scatterplot
12
+
13
+ __version__ = "0.1.0"
14
+ __all__ = [
15
+ "read_metal",
16
+ "get_position",
17
+ "retrieve_LD",
18
+ "get_lead_snp",
19
+ "assign_color",
20
+ "make_scatterplot",
21
+ "make_locuszoom",
22
+ "make_combined_plot",
23
+ "locuscompare",
24
+ "example_path",
25
+ ]
@@ -0,0 +1,47 @@
1
+ """Lead-SNP selection and LD colouring (port of R ``get_lead_snp`` and ``assign_color``)."""
2
+ import pandas as pd
3
+
4
+ _LD_BINS = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
5
+ _LD_LABELS = ["blue4", "skyblue", "darkgreen", "orange", "red"]
6
+
7
+
8
+ def get_lead_snp(merged, snp=None):
9
+ """Return the lead SNP rsID.
10
+
11
+ When ``snp`` is ``None``, the lead is ``argmin(pval1 + pval2)`` if p-values are
12
+ usable (no ``NaN`` and all > 0), otherwise ``argmax(logp1 + logp2)`` -- the
13
+ fallback for z-score / logp input or underflowed p-values.
14
+ """
15
+ if snp is None:
16
+ p1, p2 = merged["pval1"], merged["pval2"]
17
+ use_pval = (
18
+ not p1.isna().any()
19
+ and not p2.isna().any()
20
+ and (p1 > 0).all()
21
+ and (p2 > 0).all()
22
+ )
23
+ if use_pval:
24
+ idx = (p1 + p2).idxmin()
25
+ else:
26
+ idx = (merged["logp1"] + merged["logp2"]).idxmax()
27
+ snp = merged.loc[idx, "rsid"]
28
+ else:
29
+ if snp not in set(merged["rsid"]):
30
+ raise ValueError(f"{snp} not found in the intersection of in_fn1 and in_fn2.")
31
+ return str(snp)
32
+
33
+
34
+ def assign_color(rsid, snp, ld):
35
+ """Map each rsID to a colour by its r2 with ``snp``.
36
+
37
+ Returns a ``dict`` {rsid: colour}. The lead SNP is ``'purple'``; SNPs with no
38
+ LD record default to ``'blue4'``. Colours are binned as in the R package:
39
+ (0,0.2]->blue4, (0.2,0.4]->skyblue, (0.4,0.6]->darkgreen, (0.6,0.8]->orange,
40
+ (0.8,1]->red.
41
+ """
42
+ ld_lead = ld[ld["SNP_A"] == snp]
43
+ binned = pd.cut(ld_lead["R2"], bins=_LD_BINS, labels=_LD_LABELS, include_lowest=True)
44
+ b2c = dict(zip(ld_lead["SNP_B"].astype(str), binned.astype(object)))
45
+ colors = {str(r): b2c.get(str(r), "blue4") for r in rsid}
46
+ colors[str(snp)] = "purple"
47
+ return colors
locuscompare/config.py ADDED
@@ -0,0 +1,26 @@
1
+ """Database connection configuration.
2
+
3
+ Mirrors the R package's approach of shipping the (read-only) connection
4
+ parameters with the package so it works out of the box. Any field can be
5
+ overridden with a ``LOCUSCOMPARE_DB_*`` environment variable.
6
+ """
7
+ import os
8
+
9
+ _DEFAULT = {
10
+ "host": "54.254.162.217",
11
+ "user": "locuscompare",
12
+ "password": "voddit-7rubfo-faqxoB",
13
+ "port": 31987,
14
+ "database": "colotool",
15
+ }
16
+
17
+
18
+ def get_db_config():
19
+ """Return the DB connection parameters, allowing ``LOCUSCOMPARE_DB_*`` overrides."""
20
+ return {
21
+ "host": os.environ.get("LOCUSCOMPARE_DB_HOST", _DEFAULT["host"]),
22
+ "user": os.environ.get("LOCUSCOMPARE_DB_USER", _DEFAULT["user"]),
23
+ "password": os.environ.get("LOCUSCOMPARE_DB_PASSWORD", _DEFAULT["password"]),
24
+ "port": int(os.environ.get("LOCUSCOMPARE_DB_PORT", _DEFAULT["port"])),
25
+ "database": os.environ.get("LOCUSCOMPARE_DB_NAME", _DEFAULT["database"]),
26
+ }
locuscompare/core.py ADDED
@@ -0,0 +1,64 @@
1
+ """End-to-end orchestrator (port of R ``locuscompare``)."""
2
+ import warnings
3
+
4
+ import pandas as pd
5
+
6
+ from .analysis import get_lead_snp
7
+ from .db import get_position, retrieve_LD
8
+ from .io import read_metal
9
+ from .plot import make_combined_plot
10
+
11
+
12
+ def locuscompare(
13
+ in_fn1,
14
+ in_fn2,
15
+ marker_col1="rsid",
16
+ pval_col1="pval",
17
+ title1="eQTL",
18
+ marker_col2="rsid",
19
+ pval_col2="pval",
20
+ title2="GWAS",
21
+ snp=None,
22
+ population="EUR",
23
+ combine=True,
24
+ genome="hg19",
25
+ ld=None,
26
+ ):
27
+ """Make a LocusCompare figure from two association summary-statistics inputs.
28
+
29
+ Parameters mirror the R package. Supply ``ld`` (a DataFrame with columns
30
+ ``SNP_A``, ``SNP_B``, ``R2`` where ``SNP_A`` is the lead ``snp``) to bypass the
31
+ reference database; ``snp`` is then required and ``population`` is ignored.
32
+ """
33
+ if ld is not None:
34
+ if snp is None:
35
+ raise ValueError(
36
+ "When supplying 'ld', you must also specify the lead 'snp' the LD is relative to."
37
+ )
38
+ if not isinstance(ld, pd.DataFrame) or not {"SNP_A", "SNP_B", "R2"}.issubset(ld.columns):
39
+ raise ValueError("'ld' must be a DataFrame with columns SNP_A, SNP_B, R2.")
40
+ if snp not in set(ld["SNP_A"]):
41
+ warnings.warn(
42
+ f"None of the supplied LD rows have SNP_A == '{snp}'; "
43
+ "all SNPs will be colored as low-LD.",
44
+ stacklevel=2,
45
+ )
46
+
47
+ d1 = read_metal(in_fn1, marker_col1, pval_col1)
48
+ d2 = read_metal(in_fn2, marker_col2, pval_col2)
49
+ merged = d1.merge(d2, on="rsid", suffixes=("1", "2"))
50
+
51
+ if genome not in ("hg19", "hg38"):
52
+ raise ValueError("genome must be 'hg19' or 'hg38'.")
53
+ merged = get_position(merged, genome)
54
+
55
+ chrs = list(pd.unique(merged["chr"]))
56
+ if len(chrs) != 1:
57
+ raise ValueError("There must be one and only one chromosome.")
58
+ chr = chrs[0]
59
+
60
+ snp = get_lead_snp(merged, snp)
61
+ if ld is None:
62
+ ld = retrieve_LD(chr, snp, population)
63
+
64
+ return make_combined_plot(merged, title1, title2, ld, chr, snp, combine)
@@ -0,0 +1,2 @@
1
+ # Marks locuscompare.data as a package so bundled example files are importable
2
+ # via importlib.resources. See locuscompare.datasets.example_path.