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/qc.py ADDED
@@ -0,0 +1,133 @@
1
+ from sklearn.decomposition import PCA
2
+ import plotly.express as px
3
+ import pandas as pd
4
+ import plotly
5
+ import plotly.graph_objs as go
6
+
7
+
8
+ def qc_plots(meth_data, window, qcpath=None, outpath=None):
9
+
10
+ if qcpath is None and outpath is None:
11
+ outfile = f"qc_report_{window.string}.html"
12
+ elif qcpath is None:
13
+ from pathlib import Path, PosixPath
14
+ p = Path(outpath.format(region=window.string))
15
+ Path.mkdir(p.parent, exist_ok=True, parents=True)
16
+ outfile = str(p.parent / PosixPath("qc_" + p.stem + ".html"))
17
+ else:
18
+ from pathlib import Path
19
+ p = Path(qcpath)
20
+ Path.mkdir(p.parent, exist_ok=True, parents=True)
21
+ outfile = qcpath
22
+
23
+ with open(outfile, 'w') as qc_report:
24
+ qc_report.write(num_sites_bar(meth_data))
25
+ if len([m for m in meth_data if m.data_type == "nanopolish_freq"]) > 0:
26
+ # only the frequency column, as the Chromosome column of every dataset
27
+ # would collide in the join and end up in the plots below
28
+ data = [m.table[["methylated_frequency"]].rename({"methylated_frequency": m.name},
29
+ axis='columns')
30
+ for m in meth_data if m.data_type == "nanopolish_freq"]
31
+ full = data[0].join(data[1:]).dropna(how="any", axis="index")
32
+ qc_report.write(modified_fraction_histogram(full))
33
+ if len([m for m in meth_data if m.data_type == "nanopolish_freq"]) > 2:
34
+ qc_report.write(pairwise_correlation_plot(full))
35
+ qc_report.write(pca(full))
36
+ qc_report.write(global_box(data))
37
+ if len([m for m in meth_data
38
+ if m.data_type in ["nanopolish_call", "nanopolish_phased"]]) > 2:
39
+ pass
40
+
41
+
42
+ def num_sites_bar(meth_data):
43
+ trace = go.Bar(x=[m.name for m in meth_data],
44
+ y=[m.called_sites for m in meth_data])
45
+ layout = dict(title="Number of called positions")
46
+ return plotly.offline.plot(dict(data=[trace], layout=layout),
47
+ output_type="div",
48
+ show_link=False,
49
+ include_plotlyjs='cdn')
50
+
51
+
52
+ def pairwise_correlation_plot(full):
53
+ trace = go.Splom(dimensions=[dict(label=l, values=full[l]) for l in full.columns],
54
+ marker=dict(size=4,
55
+ line=dict(width=0.5,
56
+ color='rgb(230,230,230)')),
57
+ diagonal=dict(visible=False)
58
+ )
59
+
60
+ layout = go.Layout(
61
+ title='Correlation of modification frequency',
62
+ dragmode='select',
63
+ width=1200,
64
+ height=1200,
65
+ autosize=False,
66
+ hovermode=False,
67
+ plot_bgcolor='rgba(240,240,240, 0.95)')
68
+
69
+ for i in range(1, len(full.columns) + 1):
70
+ layout[f"xaxis{i}"] = dict(showline=True, zeroline=False, gridcolor='#fff', ticklen=4)
71
+ layout[f"yaxis{i}"] = dict(showline=True, zeroline=False, gridcolor='#fff', ticklen=4)
72
+
73
+ return plotly.offline.plot(dict(data=[trace], layout=layout),
74
+ output_type="div",
75
+ show_link=False,
76
+ include_plotlyjs='cdn')
77
+
78
+
79
+ def pca(full):
80
+ sklearn_pca = PCA(n_components=2)
81
+ pca = sklearn_pca.fit_transform(full.transpose())
82
+ data = [dict(type='scatter',
83
+ x=[pca[index, 0]],
84
+ y=[pca[index, 1]],
85
+ mode='markers',
86
+ name=name,
87
+ hoverinfo='name',
88
+ marker=dict(
89
+ size=12,
90
+ line=dict(
91
+ color='rgba(217, 217, 217, 0.14)',
92
+ width=0.5),
93
+ opacity=0.8))
94
+ for index, name in enumerate(full.columns)]
95
+
96
+ layout = dict(xaxis=dict(title='PC1', showline=False),
97
+ yaxis=dict(title='PC2', showline=False),
98
+ title="Principal Component Analysis"
99
+ )
100
+ return plotly.offline.plot(dict(data=data, layout=layout),
101
+ output_type="div",
102
+ show_link=False,
103
+ include_plotlyjs='cdn')
104
+
105
+
106
+ def global_box(data):
107
+ fig = px.box(pd.concat([d.reset_index(drop=True)
108
+ .rename({d.columns[0]: "freq"}, axis="columns")
109
+ .assign(dataset=d.columns[0]) for d in data], ignore_index=True),
110
+ x="dataset", y="freq", title="Global frequency of modification")
111
+ return plotly.offline.plot(fig,
112
+ output_type="div",
113
+ show_link=False,
114
+ include_plotlyjs='cdn')
115
+
116
+
117
+ def modified_fraction_histogram(full):
118
+ traces = [go.Histogram(x=full[dataset].dropna(),
119
+ histnorm='probability density',
120
+ xbins=dict(start=0, size=0.01, end=1),
121
+ name=dataset,
122
+ opacity=0.6
123
+ )
124
+ for dataset in full.columns]
125
+ layout = dict(barmode="overlay",
126
+ title="Histogram of modified fractions",
127
+ xaxis=dict(title="Modified fraction"),
128
+ yaxis=dict(title="Frequency"))
129
+ return plotly.offline.plot(dict(data=traces,
130
+ layout=layout),
131
+ output_type="div",
132
+ show_link=False,
133
+ include_plotlyjs='cdn')
methplotlib/utils.py ADDED
@@ -0,0 +1,409 @@
1
+ from argparse import ArgumentParser, SUPPRESS
2
+ import sys
3
+ from math import ceil
4
+ from methplotlib.version import __version__
5
+ from datetime import datetime as dt
6
+ from time import time
7
+ import logging
8
+ import plotly
9
+ from pathlib import Path
10
+ from itertools import chain
11
+ from plotly import subplots
12
+ from plotly.subplots import make_subplots
13
+
14
+
15
+ class Region(object):
16
+ def __init__(self, region, fasta=None):
17
+ if ":" in region:
18
+ try:
19
+ # the chromosome is deliberately kept as a string,
20
+ # as that is how pyranges and tabix identify chromosomes
21
+ self.chromosome, interval = region.replace(",", "").split(":")
22
+ self.begin, self.end = [int(i) for i in interval.split("-")]
23
+ self.start = self.begin
24
+ except ValueError:
25
+ sys.exit(
26
+ "\n\nERROR: Window (-w/--window) inproperly formatted, "
27
+ "examples of accepted formats are:\n"
28
+ "'chr5:150200605-150423790' or 'ENST00000647408'\n\n"
29
+ )
30
+ self.size = self.end - self.begin
31
+ if not self.size > 0:
32
+ sys.exit(
33
+ "\n\nERROR: Window (-w/--window) inproperly formatted, "
34
+ "begin of the interval has to be smaller than end\n\n"
35
+ )
36
+ self.string = f"{self.chromosome}_{self.begin}_{self.end}"
37
+ else: # When region is an entire chromosome, contig or transcript
38
+ if fasta is None:
39
+ sys.exit(
40
+ "A fasta reference file is required if --window "
41
+ "is an entire chromosome, contig or transcript"
42
+ )
43
+ else:
44
+ from pyfaidx import Fasta
45
+
46
+ self.chromosome = region
47
+ self.begin = 0
48
+ self.string = region
49
+ self.end = len(Fasta(fasta)[region])
50
+ self.size = self.end
51
+
52
+
53
+ def make_windows(full_window, max_size=1e6, fasta=None):
54
+ reg = Region(full_window, fasta)
55
+ if reg.size > max_size:
56
+ chunks = ceil(reg.size / max_size)
57
+ chsize = ceil(reg.size / chunks)
58
+ return [
59
+ Region(f"{reg.chromosome}:{reg.begin + i * chsize}-{reg.begin + (i + 1) * chsize}")
60
+ for i in range(chunks)
61
+ ]
62
+ else:
63
+ return [reg]
64
+
65
+
66
+ def flatten(nested_list):
67
+ return list(chain.from_iterable(nested_list))
68
+
69
+
70
+ def get_args():
71
+ parser = ArgumentParser(description="plotting nanopolish methylation calls or frequency")
72
+ parser.add_argument(
73
+ "-v",
74
+ "--version",
75
+ help="Print version and exit.",
76
+ action="version",
77
+ version=f"methplotlib {__version__}",
78
+ )
79
+ parser.add_argument(
80
+ "-m",
81
+ "--methylation",
82
+ nargs="+",
83
+ help="data in nanopolish, nanocompore, bam/cram with MM/ML tags, bedgraph or bedmethyl format",
84
+ required=True if "--example" not in sys.argv else False,
85
+ )
86
+ parser.add_argument(
87
+ "-n",
88
+ "--names",
89
+ nargs="+",
90
+ help="names of datasets in --methylation",
91
+ required=True if "--example" not in sys.argv else False,
92
+ )
93
+ parser.add_argument(
94
+ "-w",
95
+ "--window",
96
+ help="window (region) to which the visualisation has to be restricted",
97
+ required=True if "--example" not in sys.argv else False,
98
+ )
99
+ parser.add_argument("-g", "--gtf", help="add annotation based on a gtf file")
100
+ parser.add_argument("-b", "--bed", help="add annotation based on a bed file")
101
+ parser.add_argument(
102
+ "-f",
103
+ "--fasta",
104
+ help="required when --window is an entire chromosome, contig or transcript",
105
+ )
106
+ parser.add_argument(
107
+ "--simplify",
108
+ help="simplify annotation track to show genes rather than transcripts",
109
+ action="store_true",
110
+ )
111
+ parser.add_argument(
112
+ "--split",
113
+ help="split, rather than overlay the methylation tracks",
114
+ action="store_true",
115
+ )
116
+ parser.add_argument("--static", help="Make a static image of the browser window")
117
+ parser.add_argument(
118
+ "--binary",
119
+ help="Make the nanopolish plot ignorning log likelihood nuances",
120
+ action="store_true",
121
+ )
122
+ parser.add_argument(
123
+ "--smooth",
124
+ help="Rolling window size for averaging frequency values. "
125
+ "Counted in records, and both strands are separate records in bedmethyl",
126
+ type=int,
127
+ default=5,
128
+ )
129
+ parser.add_argument(
130
+ "--mods",
131
+ help="Comma separated list of modifications to restrict to",
132
+ default=None,
133
+ )
134
+
135
+ parser.add_argument(
136
+ "--dotsize",
137
+ help="Control the size of dots in the per read plots",
138
+ type=int,
139
+ default=4,
140
+ )
141
+ parser.add_argument(
142
+ "--minqual",
143
+ help="The minimal modification probability to show, on the 0-255 scale "
144
+ "of the ML tag [for bam/cram input only]",
145
+ type=int,
146
+ default=20,
147
+ )
148
+ parser.add_argument("--example", action="store_true", help="Show example command and exit.")
149
+ parser.add_argument(
150
+ "-o",
151
+ "--outfile",
152
+ help="File to write results to. "
153
+ "Default: methylation_browser_{chr}_{start}_{end}.html. "
154
+ "Use {region} as a shorthand for {chr}_{start}_{end} in the filename. "
155
+ "Missing paths will be created.",
156
+ )
157
+ parser.add_argument(
158
+ "-q",
159
+ "--qcfile",
160
+ help="File to write the qc report to. "
161
+ "Default: The path in outfile prefixed with qc_, "
162
+ "default is qc_report_methylation_browser_{chr}_{start}_{end}.html. "
163
+ "Use {region} as a shorthand for {chr}_{start}_{end} in the filename. "
164
+ "Missing paths will be created.",
165
+ )
166
+ parser.add_argument("--store", help=SUPPRESS, action="store_true")
167
+ args = parser.parse_args()
168
+ if not args.example and not len(args.names) == len(args.methylation):
169
+ sys.exit("INPUT ERROR: Expecting the same number of names as datasets!")
170
+ return args
171
+
172
+
173
+ def init_logs(args):
174
+ """Initiate log file and log arguments."""
175
+ start_time = dt.fromtimestamp(time()).strftime("%Y%m%d_%H%M")
176
+ logname = "methplotlib_" + start_time + ".log"
177
+ handlers = [logging.FileHandler(logname)]
178
+ logging.basicConfig(format="%(asctime)s %(message)s", handlers=handlers, level=logging.INFO)
179
+ logging.info(f"methplotlib {__version__} started.")
180
+ py_version = sys.version.replace("\n", " ")
181
+ logging.info(f"Python version is: {py_version}")
182
+ logging.info(f"Arguments are: {args}")
183
+
184
+
185
+ def print_example():
186
+ """
187
+ Print an example command
188
+
189
+ The example files are not shipped with the package, to keep it small,
190
+ so the paths are relative to a clone of the repository
191
+ """
192
+ example = """
193
+ methplotlib -m examples/NA19240-methylation_ACTB_calls.tsv.gz \\
194
+ examples/NA19240-methylation_ACTB_frequency.tsv.gz \\
195
+ -n calls frequencies \\
196
+ -w chr7:5,525,542-5,543,028 \\
197
+ -g examples/GRCh38-ACTB-locus.gtf.gz \\
198
+ --simplify \\
199
+ -b examples/DNase_cluster_ACTB.bed.gz \\
200
+ -o '{region}/example.html'""".strip()
201
+ sys.stderr.write(
202
+ "Example command, using the files in the examples folder of\n"
203
+ "https://github.com/wdecoster/methplotlib\n\n"
204
+ )
205
+ print(example)
206
+ sys.exit(0)
207
+
208
+
209
+ def is_gz_file(filepath):
210
+ import binascii
211
+
212
+ with open(filepath, "rb") as test_f:
213
+ return binascii.hexlify(test_f.read(2)) == b"1f8b"
214
+
215
+
216
+ def is_cram_file(filepath):
217
+ with open(filepath, "rb") as test_f:
218
+ return test_f.read(4) == b"CRAM"
219
+
220
+
221
+ def is_bam_file(filepath):
222
+ import gzip
223
+
224
+ try:
225
+ with gzip.open(filepath) as test_f:
226
+ return test_f.read(3) == b"BAM"
227
+ except OSError:
228
+ return False
229
+
230
+
231
+ def open_text(filepath):
232
+ """Open a file for reading text, transparently handling gzip compression."""
233
+ if is_gz_file(filepath):
234
+ import gzip
235
+
236
+ return gzip.open(filepath, "rt")
237
+ else:
238
+ return open(filepath, "r")
239
+
240
+
241
+ def first_data_line(filename):
242
+ """
243
+ Return the first line of a file that contains data,
244
+ skipping empty lines, comments and browser/track lines
245
+ """
246
+ with open_text(filename) as handle:
247
+ for line in handle:
248
+ if line.strip() and not line.startswith(("#", "track", "browser")):
249
+ return line.rstrip("\n").rstrip("\r")
250
+ sys.exit(f"\n\nERROR: File {filename} does not contain any data!\n")
251
+
252
+
253
+ def is_bedmethyl(fields):
254
+ """
255
+ Check if the fields of a line look like bedMethyl,
256
+ which is BED9 with a modification code in column 4, followed by counts
257
+ """
258
+ return (
259
+ len(fields) > 9
260
+ and fields[1].isdigit()
261
+ and fields[2].isdigit()
262
+ and fields[5] in ["+", "-", "."]
263
+ )
264
+
265
+
266
+ def is_bedgraph(fields):
267
+ """
268
+ Check if the fields of a line look like bedgraph:
269
+ chromosome, start, end and a numeric value.
270
+ A fifth column (coverage, as written by `modkit pileup --bedgraph`) is tolerated.
271
+ """
272
+ if len(fields) not in [4, 5] or not (fields[1].isdigit() and fields[2].isdigit()):
273
+ return False
274
+ try:
275
+ float(fields[3])
276
+ return True
277
+ except ValueError:
278
+ return False
279
+
280
+
281
+ def bedmethyl_separator(filename):
282
+ """
283
+ `modkit pileup` writes the first 9 (BED) columns tab separated
284
+ and the remaining columns space separated, unless --only-tabs was used.
285
+ Return the separator to read the file with.
286
+ """
287
+ line = first_data_line(filename)
288
+ return "\t" if len(line.split("\t")) == len(line.split()) else r"\s+"
289
+
290
+
291
+ def unrecognized_file_error(filename, line):
292
+ """Compose an error message explaining why a file could not be used."""
293
+ fields = line.split("\t")
294
+ whitespace_fields = line.split()
295
+ return (
296
+ f"\n\nERROR: Input file {filename} was not recognized!\n\n"
297
+ f"Its first data line has {len(fields)} tab separated fields "
298
+ f"({len(whitespace_fields)} when splitting on any whitespace):\n"
299
+ f"{line[:200]}\n\n"
300
+ "methplotlib accepts:\n"
301
+ " - bedgraph: 4 columns (chromosome, start, end, value), "
302
+ "or the 5 columns of `modkit pileup --bedgraph`\n"
303
+ " - bedMethyl of `modkit pileup`: 18 columns, with or without --only-tabs\n"
304
+ " - bedMethyl of `modbam2bed --extended`: 14 columns\n"
305
+ " - bam or cram with MM/ML tags\n"
306
+ " - nanopolish call-methylation output, optionally phased\n"
307
+ " - the output of nanopolish/calculate_methylation_frequency.py\n"
308
+ " - nanocompore results\n\n"
309
+ "Frequent causes are a header line, a file that is comma or space separated "
310
+ "throughout, or extra columns added by other tools.\n"
311
+ "If your file should be supported, please open an issue at "
312
+ "https://github.com/wdecoster/methplotlib/issues and share these lines.\n"
313
+ )
314
+
315
+
316
+ def file_sniffer(filename):
317
+ """
318
+ Takes in a filename and tries to guess the input file type
319
+ """
320
+ if not Path(filename).is_file():
321
+ sys.exit(f"\n\nERROR: File {filename} does not exist, please check the path!\n")
322
+ if is_cram_file(filename):
323
+ return "cram"
324
+ if is_bam_file(filename):
325
+ return "bam"
326
+ header = first_data_line(filename)
327
+
328
+ # nanocompore renamed GMM_anova_pvalue to GMM_logit_pvalue
329
+ if "GMM_anova_pvalue" in header or "GMM_logit_pvalue" in header:
330
+ return "nanocompore"
331
+ if "log_lik_methylated" in header:
332
+ if "PS" in header:
333
+ return "nanopolish_phased"
334
+ else:
335
+ return "nanopolish_call"
336
+ if "num_motifs_in_group" in header:
337
+ return "nanopolish_freq"
338
+ # bedMethyl files have no header and are tab separated,
339
+ # except for modkit which separates the columns after BED9 by spaces
340
+ # unless it was run with --only-tabs
341
+ whitespace_fields = header.split()
342
+ if is_bedmethyl(whitespace_fields):
343
+ if len(whitespace_fields) == 14: # file generated by `modbam2bed --extended`
344
+ return "bedmethyl_extended"
345
+ if len(whitespace_fields) == 18: # file generated by `modkit pileup`
346
+ return "bedmethyl"
347
+ if is_bedgraph(header.split("\t")):
348
+ return "bedgraph"
349
+ sys.exit(unrecognized_file_error(filename, header))
350
+
351
+
352
+ def create_subplots(num_methrows, split, names=None, annotation=True):
353
+ """
354
+ Prepare the panels (rows * 1 column) for the subplots.
355
+ If splitting: one row for each dataset, taking 90%/len(datasets) for heights
356
+ If not: one row spanning 4 rows and taking 90% of the heights
357
+ if annotation is True (bed or gtf) then add a row with height 10%
358
+ """
359
+ if split:
360
+ return plotly.subplots.make_subplots(
361
+ rows=num_methrows + annotation,
362
+ cols=1,
363
+ shared_xaxes=True,
364
+ specs=[[{}] for i in range(num_methrows + annotation)],
365
+ print_grid=False,
366
+ subplot_titles=names,
367
+ vertical_spacing=0.1 if num_methrows < 10 else 0.01,
368
+ row_heights=[0.9 / num_methrows] * num_methrows + [0.1] * annotation,
369
+ )
370
+ else:
371
+ return plotly.subplots.make_subplots(
372
+ rows=num_methrows + annotation,
373
+ cols=1,
374
+ shared_xaxes=True,
375
+ specs=[[{"rowspan": num_methrows}], [None], [None], [None]] + [[{}]] * annotation,
376
+ print_grid=False,
377
+ vertical_spacing=0.1 if num_methrows < 10 else 0.01,
378
+ row_heights=[0.9, 0, 0, 0] + [0.1] * annotation,
379
+ )
380
+
381
+
382
+ def create_browser_output(fig, outfile, window):
383
+ if outfile is None:
384
+ outfile = f"methylation_browser_{window.string}.html"
385
+ else:
386
+ from pathlib import Path
387
+
388
+ outfile = outfile.format(region=window.string)
389
+ p = Path(outfile)
390
+ Path.mkdir(p.parent, exist_ok=True, parents=True)
391
+
392
+ if outfile.endswith(".html"):
393
+ write_html_output(fig, outfile)
394
+ else:
395
+ try:
396
+ fig.write_image(outfile)
397
+ except ValueError as e:
398
+ sys.stderr.write("\n\nERROR: creating the image in this file format failed.\n")
399
+ sys.stderr.write("ERROR: creating in default html format instead.\n")
400
+ sys.stderr.write("ERROR: additional packages required. Detailed error:\n")
401
+ sys.stderr.write(str(e))
402
+ write_html_output(fig, outfile)
403
+
404
+
405
+ def write_html_output(fig, outfile):
406
+ with open(outfile, "w+") as output:
407
+ output.write(
408
+ plotly.offline.plot(fig, output_type="div", show_link=False, include_plotlyjs="cdn")
409
+ )
methplotlib/version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.23.0"
@@ -0,0 +1,42 @@
1
+ #!python
2
+ from argparse import ArgumentParser
3
+ import pyranges as pr
4
+ from methplotlib.helpers import methylation_pyranges_from_csv
5
+ from methplotlib.differential.differential import main
6
+
7
+
8
+ def get_args():
9
+ parser = ArgumentParser(
10
+ description="Check for modification differences using fisher exact test.")
11
+ parser.add_argument(
12
+ "-b", "--bed", help="Bed file to aggregate modifications on.", required=True)
13
+ parser.add_argument("methylation", nargs=2, help="Haplotype specific frequency files.")
14
+ parser.add_argument("-c", "--cutoff", help="FDR cutoff. Default: 0.05",
15
+ default=0.05, type=float)
16
+ parser.add_argument(
17
+ "-o", "--out", help="File to write results to. Default: stdout.", default="-")
18
+ return parser.parse_args()
19
+
20
+
21
+ if __name__ == '__main__':
22
+ args = get_args()
23
+
24
+ gr = methylation_pyranges_from_csv(args.methylation[0])
25
+ gr2 = methylation_pyranges_from_csv(args.methylation[1])
26
+
27
+ df = pr.read_bed(args.bed, as_df=True)
28
+ bed = pr.PyRanges(df, int64=True).merge()
29
+ result = main(gr, gr2, bed)
30
+
31
+ result = result[result.FDR <= args.cutoff]
32
+
33
+ if args.out == "-":
34
+ import sys
35
+ result.to_csv(sys.stdout, sep="\t") # index=False)
36
+ else:
37
+ from pathlib import Path
38
+
39
+ p = Path(args.out)
40
+ Path.mkdir(p.parent, exist_ok=True, parents=True)
41
+
42
+ result.to_csv(str(p), sep="\t")
@@ -0,0 +1,43 @@
1
+ #!python
2
+ from argparse import ArgumentParser
3
+ import pyranges as pr
4
+ from methplotlib.differential.differential import main
5
+ from methplotlib.helpers import methylation_pyranges_from_csv
6
+
7
+
8
+ def get_args():
9
+ parser = ArgumentParser(
10
+ description="Check for modification differences using fisher exact test.")
11
+ parser.add_argument(
12
+ "-b", "--bed", help="Bed file to aggregate modifications on.", required=True)
13
+ parser.add_argument("-A", "--Agroup", nargs='+', help="Frequencies of group A.")
14
+ parser.add_argument("-B", "--Bgroup", nargs='+', help="Frequencies of group B.")
15
+ parser.add_argument("-c", "--cutoff", help="FDR cutoff. Default: 0.05",
16
+ default=0.05, type=float)
17
+ parser.add_argument(
18
+ "-o", "--out", help="File to write results to. Default: stdout.", default="-")
19
+ return parser.parse_args()
20
+
21
+
22
+ if __name__ == '__main__':
23
+
24
+ args = get_args()
25
+
26
+ a_range = pr.concat([methylation_pyranges_from_csv(f) for f in args.Agroup])
27
+ b_range = pr.concat([methylation_pyranges_from_csv(f) for f in args.Bgroup])
28
+ bed = pr.read_bed(args.bed).merge()
29
+
30
+ gr = main(a_range, b_range, bed)
31
+
32
+ gr = gr[gr.ORFDR <= args.cutoff]
33
+
34
+ if args.out == "-":
35
+ import sys
36
+ gr.to_csv(sys.stdout, sep="\t") # index=False)
37
+ else:
38
+ from pathlib import Path
39
+
40
+ p = Path(args.out)
41
+ Path.mkdir(p.parent, exist_ok=True, parents=True)
42
+
43
+ gr.to_csv(str(p), sep="\t")