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.
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)