RNAvigate 1.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.
Files changed (57) hide show
  1. rnavigate/__init__.py +63 -0
  2. rnavigate/analysis/__init__.py +18 -0
  3. rnavigate/analysis/auroc.py +198 -0
  4. rnavigate/analysis/check_sequence.py +171 -0
  5. rnavigate/analysis/deltashape.py +361 -0
  6. rnavigate/analysis/fragmapper.py +463 -0
  7. rnavigate/analysis/logcompare.py +239 -0
  8. rnavigate/analysis/lowss.py +311 -0
  9. rnavigate/data/__init__.py +78 -0
  10. rnavigate/data/alignments.py +927 -0
  11. rnavigate/data/annotation.py +456 -0
  12. rnavigate/data/colors.py +154 -0
  13. rnavigate/data/data.py +620 -0
  14. rnavigate/data/interactions.py +1750 -0
  15. rnavigate/data/pdb.py +271 -0
  16. rnavigate/data/profile.py +1080 -0
  17. rnavigate/data/secondary_structure.py +1433 -0
  18. rnavigate/data_loading.py +214 -0
  19. rnavigate/examples/__init__.py +178 -0
  20. rnavigate/examples/rmrp_data/__init__.py +0 -0
  21. rnavigate/examples/rnasep_data/__init__.py +0 -0
  22. rnavigate/examples/rrna_fragmap_data/__init__.py +0 -0
  23. rnavigate/examples/tpp_data/__init__.py +0 -0
  24. rnavigate/helper_functions.py +223 -0
  25. rnavigate/plots/__init__.py +81 -0
  26. rnavigate/plots/alignment.py +115 -0
  27. rnavigate/plots/arc.py +350 -0
  28. rnavigate/plots/circle.py +221 -0
  29. rnavigate/plots/disthist.py +209 -0
  30. rnavigate/plots/functions/__init__.py +55 -0
  31. rnavigate/plots/functions/circle.py +74 -0
  32. rnavigate/plots/functions/functions.py +337 -0
  33. rnavigate/plots/functions/ss.py +312 -0
  34. rnavigate/plots/functions/tracks.py +227 -0
  35. rnavigate/plots/heatmap.py +245 -0
  36. rnavigate/plots/linreg.py +284 -0
  37. rnavigate/plots/mol.py +280 -0
  38. rnavigate/plots/ntdist.py +131 -0
  39. rnavigate/plots/plots.py +348 -0
  40. rnavigate/plots/profile.py +253 -0
  41. rnavigate/plots/qc.py +262 -0
  42. rnavigate/plots/roc.py +181 -0
  43. rnavigate/plots/skyline.py +287 -0
  44. rnavigate/plots/sm.py +416 -0
  45. rnavigate/plots/ss.py +180 -0
  46. rnavigate/plotting_functions.py +1622 -0
  47. rnavigate/rnavigate.py +363 -0
  48. rnavigate/styles.py +247 -0
  49. rnavigate/transcriptomics/__init__.py +20 -0
  50. rnavigate/transcriptomics/bed.py +185 -0
  51. rnavigate/transcriptomics/eclip.py +262 -0
  52. rnavigate/transcriptomics/transcriptome.py +343 -0
  53. rnavigate-1.1.0.dist-info/METADATA +75 -0
  54. rnavigate-1.1.0.dist-info/RECORD +57 -0
  55. rnavigate-1.1.0.dist-info/WHEEL +5 -0
  56. rnavigate-1.1.0.dist-info/licenses/LICENSE +21 -0
  57. rnavigate-1.1.0.dist-info/top_level.txt +1 -0
rnavigate/__init__.py ADDED
@@ -0,0 +1,63 @@
1
+ """RNAvigate
2
+
3
+ RNA visualization and graphical analysis toolset
4
+
5
+ A Jupyter-compatible toolset for visually exploring RNA structure and chemical
6
+ probing data.
7
+ """
8
+
9
+ from rnavigate.rnavigate import Sample
10
+ from rnavigate.plotting_functions import (
11
+ plot_options,
12
+ plot_alignment,
13
+ plot_arcs,
14
+ plot_arcs_compare,
15
+ plot_circle,
16
+ plot_disthist,
17
+ plot_heatmap,
18
+ plot_linreg,
19
+ plot_mol,
20
+ plot_ntdist,
21
+ plot_profile,
22
+ plot_qc,
23
+ plot_roc,
24
+ plot_shapemapper,
25
+ plot_skyline,
26
+ plot_ss,
27
+ )
28
+ from rnavigate import analysis
29
+ from rnavigate import data
30
+ from rnavigate import plots
31
+ from rnavigate import styles
32
+ from rnavigate import transcriptomics
33
+
34
+ __version__ = "1.0.0"
35
+ __author__ = "Patrick S. Irving"
36
+ __email__ = "psirving@email.unc.edu"
37
+
38
+ __all__ = [
39
+ "Sample",
40
+ # plotting functions
41
+ "plot_options",
42
+ "plot_alignment",
43
+ "plot_arcs",
44
+ "plot_arcs_compare",
45
+ "plot_circle",
46
+ "plot_disthist",
47
+ "plot_heatmap",
48
+ "plot_linreg",
49
+ "plot_mol",
50
+ "plot_ntdist",
51
+ "plot_profile",
52
+ "plot_qc",
53
+ "plot_roc",
54
+ "plot_shapemapper",
55
+ "plot_skyline",
56
+ "plot_ss",
57
+ # modules
58
+ "analysis",
59
+ "data",
60
+ "plots",
61
+ "styles",
62
+ "transcriptomics",
63
+ ]
@@ -0,0 +1,18 @@
1
+ from rnavigate.analysis.logcompare import LogCompare
2
+ from rnavigate.analysis.lowss import LowSS
3
+ from rnavigate.analysis.deltashape import DeltaSHAPE, DeltaSHAPEProfile
4
+ from rnavigate.analysis.auroc import WindowedAUROC
5
+ from rnavigate.analysis.fragmapper import FragMaP, Fragmapper, FragmapperReplicates
6
+ from rnavigate.analysis.check_sequence import SequenceChecker
7
+
8
+ __all__ = [
9
+ "LogCompare",
10
+ "LowSS",
11
+ "DeltaSHAPE",
12
+ "DeltaSHAPEProfile",
13
+ "WindowedAUROC",
14
+ "FragMaP",
15
+ "Fragmapper",
16
+ "FragmapperReplicates",
17
+ "SequenceChecker",
18
+ ]
@@ -0,0 +1,198 @@
1
+ """Windowed AUROC assesses agreement between reactivities and base-pairing."""
2
+
3
+ import numpy as np
4
+ from sklearn.metrics import auc, roc_curve
5
+
6
+ from rnavigate import plots
7
+
8
+
9
+ # TODO: refactor as subclass of rnavigate.Sample
10
+ class WindowedAUROC:
11
+ """Compute and display windowed AUROC analysis.
12
+
13
+ This analysis computes the ROC curve over a sliding window for the
14
+ performance of per-nucleotide data (usually SHAPE-MaP or DMS-MaP Normalized
15
+ reactivity) in predicting the base-pairing status of each nucleotide. The
16
+ area under this curve (AUROC) is displayed compared to the median across
17
+ the RNA. Below, an arc plot displays the secondary structure and
18
+ per-nucleotide profile.
19
+
20
+ AUROC values (should) range from 0.5 (no predictive power) to 1.0
21
+ (perfect predictive power). A value of 0.5 indicates that the reactivity
22
+ profile does not fit the structure prediction well. These regions are good
23
+ candidates for further investigation with ensemble deconvolution.
24
+
25
+ References
26
+ ----------
27
+ Lan, T.C.T., Allan, M.F., Malsick, L.E. et al. Secondary structural
28
+ ensembles of the SARS-CoV-2 RNA genome in infected cells. Nat Commun
29
+ 13, 1128 (2022). https://doi.org/10.1038/s41467-022-28603-2
30
+
31
+ Methods
32
+ -------
33
+ __init__: Computes the AUROC array and AUROC median.
34
+ plot_auroc: Displays the AUROC analysis over the given region.
35
+ Returns Plot object
36
+
37
+ Attributes
38
+ ----------
39
+ sample : rnavigate.Sample
40
+ sample to retrieve profile and secondary structure
41
+ structure : str
42
+ Data keyword of sample pointing to secondary structure
43
+ e.g. sample.data[structure]
44
+ profile : str
45
+ Data keyword of sample pointing to profile
46
+ e.g. sample.data[profile]
47
+ sequence : the sequence string of sample.data[structure]
48
+ window: the size of the windows
49
+ nt_length: the length of sequence string
50
+ auroc: the auroc numpy array, length = nt_length, padded with np.nan
51
+ median_auroc: the median of the auroc array
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ sample,
57
+ window=81,
58
+ profile="default_profile",
59
+ structure="default_structure",
60
+ ):
61
+ """Compute the AUROC for all windows. AUROC is a measure of how well a
62
+ reactivity profile predicts paired vs. unpaired nucleotide status.
63
+
64
+ Parameters
65
+ ----------
66
+ sample : rnav.Sample
67
+ Your rnavigate sample
68
+ window : int, optional
69
+ number of nucleotides to include in window
70
+ Defaults to 81.
71
+ profile (str, optional): data keyword of provided sample pointing
72
+ to a profile.
73
+ Defaults to "default_profile"
74
+ structure (str, optional): data keyword of provided sample pointing
75
+ to a secondary structure.
76
+ Defaults to "default_structure"
77
+ """
78
+ # ensure sample contains profile and structure data
79
+ for data in [profile, structure]:
80
+ assert data in sample.data.keys(), f"Sample missing {data} data"
81
+
82
+ # store basic information
83
+ self.sample = sample
84
+ self.structure = sample.get_data(structure)
85
+ self.profile = sample.get_data(profile)
86
+ self.sequence = self.structure.sequence
87
+ self.window = window
88
+ self.nt_length = self.structure.length
89
+
90
+ # get Norm_profile array and structure array
91
+ profile = self.profile.data["Norm_profile"].values
92
+ pair_nts = self.structure.pair_nts
93
+
94
+ # for each possible window: compute auroc and populate array
95
+ self.auroc = np.full(len(profile), np.nan)
96
+ pad = window // 2
97
+ for i in range(pad, len(profile) - pad):
98
+ # get profile and structure values within window
99
+ win_profile = profile[i - pad : i + pad + 1]
100
+ win_ct = pair_nts[i - pad : i + pad + 1]
101
+ # ignore positions where profile is nan
102
+ valid = ~np.isnan(win_profile)
103
+ # y: classification (paired or unpaired)
104
+ y = win_ct[valid] == 0
105
+ scores = win_profile[valid]
106
+ # skip this window if there are less than 10 paired or unpaired nts
107
+ if (sum(y) < 10) or (sum(~y) < 10):
108
+ continue
109
+ # add window auroc to array
110
+ tpr, fpr, _ = roc_curve(y, scores)
111
+ self.auroc[i] = auc(tpr, fpr)
112
+
113
+ self.auroc_median = np.nanmedian(self.auroc)
114
+
115
+ def plot_auroc(self, region=None):
116
+ """Plot the result of the windowed AUROC analysis, with arc plot of
117
+ structure and reactivity profile.
118
+
119
+ Args:
120
+ region (list of int: length 2, optional): Start and end nucleotide
121
+ positions to plot. Defaults to [1, RNA length].
122
+ """
123
+ if region is None:
124
+ start = 1
125
+ stop = self.nt_length
126
+ region = [start, stop]
127
+ region_length = self.nt_length
128
+ else:
129
+ start, stop = region
130
+ region_length = stop - start + 1
131
+
132
+ plot = plots.AP(1, region_length, cols=1, rows=1, region=region)
133
+ ax = plot.axes[0, 0]
134
+
135
+ # fill between auroc values and median, using secondary ax
136
+ x_values = np.arange(start, stop + 1)
137
+ auc_ax = ax.twinx()
138
+ auc_ax.set_ylim(0.5, 1.6)
139
+ auc_ax.set_yticks([0.5, self.auroc_median, 1.0])
140
+ auc_ax.fill_between(
141
+ x_values,
142
+ self.auroc[start - 1 : stop],
143
+ self.auroc_median,
144
+ fc="0.3",
145
+ lw=0,
146
+ )
147
+ plots.adjust_spines(auc_ax, ["left"])
148
+ plots.clip_spines(auc_ax, ["left"])
149
+
150
+ # add structure and reactivity profile track
151
+ plot.plot_data(
152
+ sequence=self.structure,
153
+ structure=self.structure,
154
+ structure2=None,
155
+ interactions=None,
156
+ interactions2=None,
157
+ profile=self.profile,
158
+ label="label",
159
+ seqbar=False,
160
+ title=False,
161
+ annotations=[],
162
+ plot_error=False,
163
+ )
164
+
165
+ # Place Track Labels
166
+ ax.set_title(
167
+ f"{self.sample.sample}\n{start} - {stop}",
168
+ loc="left",
169
+ fontdict={"fontsize": 48},
170
+ )
171
+ ax.text(
172
+ 1.002,
173
+ 6 / 8,
174
+ "Secondary\nStructure",
175
+ transform=ax.transAxes,
176
+ fontsize=36,
177
+ va="center",
178
+ )
179
+ ax.text(
180
+ 1.002,
181
+ 2 / 8,
182
+ f"{self.window}-nt window\nAUROC",
183
+ transform=ax.transAxes,
184
+ va="center",
185
+ fontsize=36,
186
+ )
187
+
188
+ # limits, ticks, spines, and grid
189
+ ax.set_ylim([-305, 315])
190
+ ax.set_xticks(ticks=[x for x in range(500, stop, 500) if x > start])
191
+ ax.set_xticks(ticks=[x for x in range(100, stop, 100) if x > start], minor=True)
192
+ ax.tick_params(axis="x", which="major", labelsize=36)
193
+ plots.adjust_spines(ax, ["bottom"])
194
+ ax.grid(axis="x")
195
+
196
+ # set figure size so that 100 ax units == 1 inch
197
+ plot.set_figure_size(height_ax_rel=1 / 100, width_ax_rel=1 / 100)
198
+ return plot
@@ -0,0 +1,171 @@
1
+ """SequenceChecker analysis used to inspect sequence differences.
2
+
3
+ Given a list of samples, we can inspect which data keywords belong to the
4
+ samples, which sequences match up perfectly, and inspect the differences
5
+ between sequences.
6
+ """
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+
11
+ from rnavigate import data
12
+
13
+
14
+ class SequenceChecker:
15
+ """Check the sequences stored in a list of samples.
16
+
17
+ Attributes
18
+ ----------
19
+ samples : list
20
+ samples in which to check sequences
21
+ sequences : list
22
+ all unique sequence strings stored in the list of samples. These are converted
23
+ to an all uppercase RNA alphabet.
24
+ keywords : list
25
+ all unique data keywords stored in the list of samples.
26
+ which_sequences : Pandas.DataFrame
27
+ each row is a sample, keyword, and index of self.sequences
28
+ """
29
+
30
+ def __init__(self, samples):
31
+ """Creates an instance of SequenceChecker given a list of samples
32
+
33
+ Parameters
34
+ ----------
35
+ samples : list of rnav.Sample
36
+ samples for which to compare data keywords and sequences.
37
+ """
38
+ self.samples = samples
39
+ self.keywords = self.get_keywords()
40
+ self.sequences = self.get_sequences()
41
+ self.which_sequences = self.get_which_sequences()
42
+
43
+ def reset(self):
44
+ """Reset keywords and sequences from sample list in case of changes."""
45
+ self.keywords = self.get_keywords()
46
+ self.sequences = self.get_sequences()
47
+ self.which_sequences = self.get_which_sequences()
48
+
49
+ def get_keywords(self):
50
+ """A list of all unique data keywords across samples."""
51
+ return list(set([dkw for s in self.samples for dkw in s.data]))
52
+
53
+ def get_sequences(self):
54
+ """A list of all unique sequences (uppercase RNA) across samples."""
55
+ sequences = []
56
+ for sample in self.samples:
57
+ for dkw in self.keywords:
58
+ if dkw not in sample.data:
59
+ continue
60
+ seq = sample.get_data(dkw).sequence
61
+ seq = seq.upper().replace("T", "U")
62
+ if seq not in sequences:
63
+ sequences.append(seq)
64
+ return sequences
65
+
66
+ def get_which_sequences(self):
67
+ """A DataFrame of sequence IDs (integers) for each data keyword."""
68
+ df = {key: [] for key in ["Sample"] + self.keywords}
69
+ for sample in self.samples:
70
+ df["Sample"].append(sample.sample)
71
+ for dkw in self.keywords:
72
+ if dkw not in sample.data:
73
+ df[dkw].append(np.nan)
74
+ else:
75
+ seq = sample.get_data(dkw).sequence
76
+ seq = seq.upper().replace("T", "U")
77
+ df[dkw].append(self.sequences.index(seq))
78
+ return pd.DataFrame(df)
79
+
80
+ def print_which_sequences(self):
81
+ """Print sequence ID (integer) for each data keyword and sample."""
82
+ print("Sequence IDs")
83
+ for _, row in self.which_sequences.iterrows():
84
+ for column in self.which_sequences.columns:
85
+ if column == "Sample":
86
+ print(f" {row[column]}")
87
+ else:
88
+ if np.isnan(row[column]):
89
+ continue
90
+ print(f" {column:<10} {int(row[column])}")
91
+ print()
92
+
93
+ def print_alignments(self, print_format="long", which="all"):
94
+ """Print alignments in the given format for sequence IDs provided.
95
+
96
+ Parameters
97
+ ----------
98
+ print_format : string, defaults to "long"
99
+ What format to print the alignments in:
100
+ "cigar" prints the cigar string
101
+ "short" prints the numbers of mismatches and indels
102
+ "long" prints the location and nucleotide identity of all
103
+ mismatches, insertions and deletions.
104
+ which : tuple of two of integers, defaults to "all" (every pairwise comparison)
105
+ two sequence IDs to compare.
106
+ """
107
+ kwargs = {"print_format": print_format}
108
+ if which == "all":
109
+ num = range(len(self.sequences))
110
+ for s1 in num:
111
+ for s2 in num:
112
+ if s1 < s2:
113
+ self.print_alignments(which=(s1, s2), **kwargs)
114
+ return
115
+ i, j = int(which[0]), int(which[1])
116
+ a = self.sequences[i]
117
+ b = self.sequences[j]
118
+ alignment = data.SequenceAlignment(sequence1=a, sequence2=b, full=True)
119
+ print(f"Sequence {i} -> {j} ({len(a)} nts -> {len(b)} nts)")
120
+ alignment.print(print_format=print_format)
121
+
122
+ def print_mulitple_sequence_alignment(self, base_sequence):
123
+ """Print the multiple sequence alignment with nice formatting.
124
+
125
+ Parameters
126
+ ----------
127
+ base_sequence : string
128
+ a sequence string that represents the longest common sequence.
129
+ Usually, this is the return value from:
130
+ rnav.data.set_multiple_sequence_alignment()
131
+ """
132
+ print("Multiple sequence alignment")
133
+ alignments = [
134
+ data.SequenceAlignment(base_sequence, seq).alignment2
135
+ for seq in self.sequences
136
+ ]
137
+ pos = "".join(f"{n:<20}" for n in range(1, len(alignments[0]), 20))
138
+ misses = "".join("X "[len(set(nts)) == 1] for nts in zip(*alignments))
139
+ print(" ID length alignment")
140
+ for i, (alignment, seq) in enumerate(zip(alignments, self.sequences)):
141
+ print(f" {i:<5} {len(seq):<8} {alignment}")
142
+ print(f" {'mismatches':<14} {misses}")
143
+ print(f" {'positions':<14} {pos}")
144
+ print()
145
+
146
+ def write_fasta(self, filename, which="all"):
147
+ """Write all unique sequences to a fasta file.
148
+
149
+ This is very useful for using external multiple sequence aligners such
150
+ as ClustalOmega.
151
+ 1) go to https://www.ebi.ac.uk/Tools/msa/clustalo/
152
+ 2) upload new fasta file
153
+ 3) under STEP 2 output format, select Pearson/FASTA
154
+ 4) click 'Submit'
155
+ 5) wait for your alignment to finish
156
+ 6) download the alignment fasta file
157
+ 7) use rnav.data.set_multiple_sequence_alignment()
158
+
159
+ Parameters
160
+ ----------
161
+ filename : string
162
+ path to a new file to which fasta entries are written
163
+ which : list of integers, defaults to "all" (every sequence)
164
+ Sequence IDs to write to file.
165
+ """
166
+ if which == "all":
167
+ which = range(len(self.sequences))
168
+ with open(filename, "w") as fasta:
169
+ for i in which:
170
+ fasta.write(f">Sequence_{i}\n")
171
+ fasta.write(f"{self.sequences[i]}\n")