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.
- locuscompare/__init__.py +25 -0
- locuscompare/analysis.py +47 -0
- locuscompare/config.py +26 -0
- locuscompare/core.py +64 -0
- locuscompare/data/__init__.py +2 -0
- locuscompare/data/eqtl.tsv +4904 -0
- locuscompare/data/gwas.tsv +6363 -0
- locuscompare/datasets.py +13 -0
- locuscompare/db.py +51 -0
- locuscompare/io.py +75 -0
- locuscompare/plot.py +102 -0
- locuscomparepy-0.1.0.dist-info/METADATA +125 -0
- locuscomparepy-0.1.0.dist-info/RECORD +15 -0
- locuscomparepy-0.1.0.dist-info/WHEEL +4 -0
- locuscomparepy-0.1.0.dist-info/licenses/LICENSE +674 -0
locuscompare/__init__.py
ADDED
|
@@ -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
|
+
]
|
locuscompare/analysis.py
ADDED
|
@@ -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)
|