methplotlib 0.23.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.
- methplotlib/__init__.py +0 -0
- methplotlib/annotation.py +183 -0
- methplotlib/differential/__init__.py +0 -0
- methplotlib/differential/differential.py +62 -0
- methplotlib/helpers.py +12 -0
- methplotlib/import_methylation.py +583 -0
- methplotlib/methplotlib.py +153 -0
- methplotlib/plots.py +459 -0
- methplotlib/qc.py +133 -0
- methplotlib/utils.py +409 -0
- methplotlib/version.py +1 -0
- methplotlib-0.23.0.data/scripts/allele_specific_modification +42 -0
- methplotlib-0.23.0.data/scripts/differential_modification +43 -0
- methplotlib-0.23.0.dist-info/METADATA +117 -0
- methplotlib-0.23.0.dist-info/RECORD +19 -0
- methplotlib-0.23.0.dist-info/WHEEL +5 -0
- methplotlib-0.23.0.dist-info/entry_points.txt +2 -0
- methplotlib-0.23.0.dist-info/licenses/LICENSE +21 -0
- methplotlib-0.23.0.dist-info/top_level.txt +1 -0
methplotlib/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import pyranges as pr
|
|
3
|
+
import itertools
|
|
4
|
+
import sys
|
|
5
|
+
from plotly.colors import DEFAULT_PLOTLY_COLORS as plcolors
|
|
6
|
+
import gzip
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Transcript(object):
|
|
11
|
+
def __init__(self, transcript, gene, exon_tuples, strand):
|
|
12
|
+
self.transcript = transcript
|
|
13
|
+
self.gene = gene
|
|
14
|
+
self.exon_tuples = list(exon_tuples)
|
|
15
|
+
self.strand = strand
|
|
16
|
+
self.marker = "triangle-right" if self.strand == "+" else "triangle-left"
|
|
17
|
+
self.begin = min(list(itertools.chain.from_iterable(self.exon_tuples)))
|
|
18
|
+
self.end = max(list(itertools.chain.from_iterable(self.exon_tuples)))
|
|
19
|
+
self.color = ""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def open_gtf(gtff):
|
|
23
|
+
"""
|
|
24
|
+
Open the gtf, using gzip if it's compressed
|
|
25
|
+
based on extension
|
|
26
|
+
"""
|
|
27
|
+
return gzip.open(gtff, "rt") if gtff.endswith(".gz") else open(gtff)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def good_record(line, chromosome):
|
|
31
|
+
"""
|
|
32
|
+
Filtering on the gtf lines
|
|
33
|
+
by checking for right chromosome and right feature type
|
|
34
|
+
|
|
35
|
+
The chromosome has to match the entire field, as a prefix match would
|
|
36
|
+
e.g. also accept chr10 up to chr19 and chr1_KI270706v1_random for chr1
|
|
37
|
+
"""
|
|
38
|
+
if line.startswith("#"):
|
|
39
|
+
return False
|
|
40
|
+
fields = line.split("\t")
|
|
41
|
+
return len(fields) > 2 and fields[0] == str(chromosome) and fields[2] in ["exon", "gene"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_features(gtfline, type="gtf"):
|
|
45
|
+
"""
|
|
46
|
+
Extract the desirable features from a gtf record
|
|
47
|
+
"""
|
|
48
|
+
chromosome, _, _, begin, end, _, strand, _, attributes = gtfline.split("\t")
|
|
49
|
+
gene, transcript = parse_attributes(attributes.rstrip(), type=type)
|
|
50
|
+
return [chromosome, int(begin), int(end), strand, gene, transcript]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def parse_attributes(attributes, type="gtf"):
|
|
54
|
+
"""
|
|
55
|
+
Parse the attributes string of gtf record
|
|
56
|
+
Return the values corresponding to gene_name and transcript_id
|
|
57
|
+
"""
|
|
58
|
+
attribute_delimiter = {"gtf": "; ", "gff": ";"}
|
|
59
|
+
kv_delimiter = {"gtf": " ", "gff": "="}
|
|
60
|
+
# the last attribute of a record keeps the trailing delimiter,
|
|
61
|
+
# which would otherwise end up in the name shown when hovering
|
|
62
|
+
info = {
|
|
63
|
+
i.split(kv_delimiter[type])[0]: i.split(kv_delimiter[type])[1].replace('"', "").rstrip(";")
|
|
64
|
+
for i in attributes.split(attribute_delimiter[type])
|
|
65
|
+
if i.startswith(("gene_name", "transcript_id", "locus_tag"))
|
|
66
|
+
}
|
|
67
|
+
if "gene_name" in info.keys():
|
|
68
|
+
return info.get("gene_name"), info.get("transcript_id")
|
|
69
|
+
else:
|
|
70
|
+
return info.get("locus_tag"), info.get("locus_tag")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def transcripts_in_window(df, window, feature="transcript"):
|
|
74
|
+
"""
|
|
75
|
+
Return the transcript names of the exons overlapping the window
|
|
76
|
+
|
|
77
|
+
Testing for overlap rather than for a begin or end within the window,
|
|
78
|
+
as otherwise a feature spanning the entire window would be missed
|
|
79
|
+
"""
|
|
80
|
+
return df.loc[
|
|
81
|
+
(df["begin"] <= window.end) & (df["end"] >= window.begin),
|
|
82
|
+
feature,
|
|
83
|
+
].unique()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def assign_colors_to_genes(transcripts):
|
|
87
|
+
genes = dict.fromkeys([t.gene for t in transcripts])
|
|
88
|
+
colordict = {g: c for g, c in zip(genes, plcolors * 100)}
|
|
89
|
+
for t in transcripts:
|
|
90
|
+
t.color = colordict[t.gene]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def parse_annotation(gtff, window, simplify=False):
|
|
94
|
+
"""
|
|
95
|
+
Parse the gtff and select the relevant region as determined by the window
|
|
96
|
+
return as Transcript objects
|
|
97
|
+
"""
|
|
98
|
+
type = annot_file_sniffer(gtff)
|
|
99
|
+
logging.info(f"Parsing {type} file...")
|
|
100
|
+
chromosomes_seen = set()
|
|
101
|
+
records = []
|
|
102
|
+
for line in open_gtf(gtff):
|
|
103
|
+
if good_record(line, window.chromosome):
|
|
104
|
+
records.append(get_features(line, type=type))
|
|
105
|
+
elif line.strip() and not line.startswith("#"):
|
|
106
|
+
chromosomes_seen.add(line.split("\t")[0])
|
|
107
|
+
if not records and chromosomes_seen:
|
|
108
|
+
sys.stderr.write(
|
|
109
|
+
f"\nWARNING: no exons or genes of chromosome '{window.chromosome}' "
|
|
110
|
+
f"in {gtff}.\n"
|
|
111
|
+
f"The annotation contains {len(chromosomes_seen)} chromosomes, "
|
|
112
|
+
f"such as {', '.join(sorted(chromosomes_seen)[:5])}.\n"
|
|
113
|
+
"Do the chromosome names in --window match those in your annotation "
|
|
114
|
+
"(e.g. '15' versus 'chr15')?\n"
|
|
115
|
+
)
|
|
116
|
+
df = pd.DataFrame(
|
|
117
|
+
data=records,
|
|
118
|
+
columns=["chromosome", "begin", "end", "strand", "gene", "transcript"],
|
|
119
|
+
)
|
|
120
|
+
logging.info("Loaded GTF file, processing...")
|
|
121
|
+
if simplify:
|
|
122
|
+
df.drop_duplicates(subset=["chromosome", "begin", "end", "gene"], inplace=True)
|
|
123
|
+
res = []
|
|
124
|
+
for g in transcripts_in_window(df, window, feature="gene"):
|
|
125
|
+
gtable = df.loc[df["gene"] == g]
|
|
126
|
+
if len(gtable):
|
|
127
|
+
res.append(
|
|
128
|
+
Transcript(
|
|
129
|
+
transcript=gtable["gene"].tolist()[0],
|
|
130
|
+
gene=gtable["gene"].tolist()[0],
|
|
131
|
+
exon_tuples=gtable.loc[:, ["begin", "end"]]
|
|
132
|
+
.sort_values("begin")
|
|
133
|
+
.itertuples(index=False, name=None),
|
|
134
|
+
strand=gtable["strand"].tolist()[0],
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
sys.stderr.write(f"Found {len(res)} gene(s) in the region.\n")
|
|
138
|
+
logging.info(f"Found {len(res)} gene(s) in the region.\n")
|
|
139
|
+
else:
|
|
140
|
+
res = []
|
|
141
|
+
for t in transcripts_in_window(df, window, feature="transcript"):
|
|
142
|
+
tr = df.loc[df["transcript"] == t]
|
|
143
|
+
res.append(
|
|
144
|
+
Transcript(
|
|
145
|
+
transcript=t,
|
|
146
|
+
gene=tr["gene"].tolist()[0],
|
|
147
|
+
exon_tuples=tr.loc[:, ["begin", "end"]]
|
|
148
|
+
.sort_values("begin")
|
|
149
|
+
.itertuples(index=False, name=None),
|
|
150
|
+
strand=tr["strand"].tolist()[0],
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
sys.stderr.write(f"Found {len(res)} transcript(s) in the region.\n")
|
|
154
|
+
logging.info(f"Found {len(res)} transcript(s) in the region.\n")
|
|
155
|
+
assign_colors_to_genes(res)
|
|
156
|
+
return res
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def annot_file_sniffer(annot_file):
|
|
160
|
+
"""
|
|
161
|
+
Figure out type of annotation file
|
|
162
|
+
|
|
163
|
+
Right not just lazily focus on the extension
|
|
164
|
+
"""
|
|
165
|
+
if annot_file.endswith((".gtf", ".gtf.gz")):
|
|
166
|
+
return "gtf"
|
|
167
|
+
elif annot_file.endswith((".gff", ".gff.gz")):
|
|
168
|
+
return "gff"
|
|
169
|
+
else:
|
|
170
|
+
sys.exit(
|
|
171
|
+
"ERROR: unrecognized extension of the annotation file.\n"
|
|
172
|
+
"Supported are gtf, gtf.gz, gff and gff.gz"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def parse_bed(bed, window):
|
|
177
|
+
logging.info("Parsing BED file")
|
|
178
|
+
gr = pr.read_bed(bed)[window.chromosome, window.begin : window.end]
|
|
179
|
+
df = gr.unstrand().df
|
|
180
|
+
df = df.drop(columns=["Chromosome", "Score", "Strand"], errors="ignore")
|
|
181
|
+
if "Name" not in df.columns:
|
|
182
|
+
df["Name"] = "noname"
|
|
183
|
+
return df.itertuples(index=False, name=None)
|
|
File without changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import pyranges as pr
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _methylated_and_freq_to_zero(df):
|
|
8
|
+
|
|
9
|
+
only_in_bed = (df.methylated == -1) & (df.methylated_b == -1)
|
|
10
|
+
df = df[~only_in_bed]
|
|
11
|
+
|
|
12
|
+
a_zero = df.methylated == -1
|
|
13
|
+
df.loc[a_zero, ["methylated", "calls"]] = 0
|
|
14
|
+
b_zero = df.methylated_b == -1
|
|
15
|
+
df.loc[b_zero, ["methylated_b", "calls_b"]] = 0
|
|
16
|
+
|
|
17
|
+
return df
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def count_total_and_methylated(df):
|
|
21
|
+
"""For each region in bed, get sum of calls and methylated"""
|
|
22
|
+
|
|
23
|
+
grpby = df.groupby("ID")
|
|
24
|
+
return pd.DataFrame({"Chromosome": df.Chromosome.iloc[0],
|
|
25
|
+
"Start": grpby.Start.first(),
|
|
26
|
+
"End": grpby.End.first(),
|
|
27
|
+
"ID": grpby.ID.first(),
|
|
28
|
+
"calls": grpby.calls.sum(),
|
|
29
|
+
"methylated": grpby.methylated.sum()})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def merge_regions_with_bed(bed, a, b):
|
|
33
|
+
|
|
34
|
+
a = bed.join(a, how="left").apply(count_total_and_methylated).drop(like="_b")
|
|
35
|
+
b = bed.join(b, how="left").apply(count_total_and_methylated).drop(like="_b")
|
|
36
|
+
|
|
37
|
+
m = a.join(b, how="outer")
|
|
38
|
+
|
|
39
|
+
return m
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main(a, b, bed):
|
|
43
|
+
"""1. Find the regions in bed that overlaps either a or/and b.
|
|
44
|
+
2. Sum methylations over regions in a/b that overlap bed.
|
|
45
|
+
3. Do fisher_exact on the methylation frequencies."""
|
|
46
|
+
|
|
47
|
+
bed.ID = np.arange(len(bed))
|
|
48
|
+
|
|
49
|
+
if "Strand" in bed:
|
|
50
|
+
bed = bed.drop("Strand")
|
|
51
|
+
|
|
52
|
+
m = merge_regions_with_bed(bed, a, b)
|
|
53
|
+
|
|
54
|
+
m = m.apply(_methylated_and_freq_to_zero).drop(like="(Start|End|ID)_b|ID")
|
|
55
|
+
|
|
56
|
+
m1, c1, m2, c2 = m.methylated, m.calls, m.methylated_b, m.calls_b,
|
|
57
|
+
fe = pr.stats.fisher_exact(c1 - m1, m1, c2 - m2, m2, pseudocount=0.01)
|
|
58
|
+
fe.insert(fe.shape[1], "FDR", pr.stats.fdr(fe.P))
|
|
59
|
+
|
|
60
|
+
m = m.insert(fe[['OR', 'P', 'FDR']])
|
|
61
|
+
|
|
62
|
+
return m
|
methplotlib/helpers.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from pyranges import PyRanges
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def methylation_pyranges_from_csv(inputfile):
|
|
6
|
+
colnames = ["Chromosome", "Start", "End", "calls", "methylated"]
|
|
7
|
+
return PyRanges(pd.read_csv(inputfile,
|
|
8
|
+
sep="\t",
|
|
9
|
+
names=colnames,
|
|
10
|
+
header=0,
|
|
11
|
+
usecols=[0, 1, 2, 4, 5]),
|
|
12
|
+
int64=True)
|