flippr 0.1.1__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.
flippr/__about__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.1"
flippr/__init__.py ADDED
@@ -0,0 +1,172 @@
1
+ from functools import cached_property
2
+ from pathlib import Path
3
+
4
+ import polars as pl
5
+
6
+ from . import spr as _spr
7
+ from . import validate as _validate
8
+ from .parameters import rcParams
9
+ from .uniprot import fasta as _fasta
10
+
11
+
12
+ class Study:
13
+ """
14
+ A FLiPPR Study is used to coordinate Limited Proteolysis (LiP) data processing for a single experiment analyzed with FragPipe.
15
+ Studies can contain LiP data alone or LiP and TrP data; with the inclusion of a FASTA file for automatic metadata generation.
16
+ TrP data should result from the same experimental conditions as the LiP data, but without including a broad-spectrum protease like Proteinase K (PK).
17
+
18
+ Args:
19
+ lip (str | Path): Directory or path to the FragPipe LFQ output data for Limited Proteolysis (LiP) experiment data.
20
+ trp (str | Path, optional): Directory or path to the FragPipe LFQ output data for Trypsin Only (TrP) experiment data. Defaults to None.
21
+ fasta (str | Path, optional): Path to the FASTA file containing the same protein sequences used in the LiP/TrP experiments. Defaults to None.
22
+ method (str): Method used to quantify ion intensities in FragPipe. `lfq` - Label-Free Quantification, `silac` - Stable Isotope Labelling (currently not incorporated). Defaults to `lfq`.
23
+
24
+ Examples:
25
+ Analysis of a single dataset
26
+ >>> flippr.Study(lip = "PXD025926/FragPipe/LiP_LFQ")
27
+
28
+ Analysis of a single dataset with metadata integration
29
+ >>> flippr.Study(lip = "PXD025926/FragPipe/LiP_LFQ", fasta = "UP000000625.fasta")
30
+
31
+ Including protein-level normalization from trypsin-only data
32
+ >>> flippr.Study(lip = "PXD025926/FragPipe/LiP_LFQ", trp = "PXD025926/FragPipe/TrP_LFQ")
33
+
34
+ """
35
+
36
+ def __init__(self, lip: str | Path, trp: str | Path | None = None, fasta: str | Path | None = None, method: str = "lfq") -> None:
37
+
38
+ _lip, _trp, _fasta_path, _method = _validate._validate_study(lip, trp, fasta, method)
39
+
40
+ self.lip: Path = _lip
41
+ self.trp: Path | None = _trp
42
+ self._fasta_path: Path | None = _fasta_path
43
+ self.method: str = _method
44
+
45
+ self._pid: int = 0
46
+ self.processes: dict = dict()
47
+ self.results: dict = dict()
48
+
49
+ @property
50
+ def samples(self) -> dict:
51
+ """
52
+ Returns the names of the samples or experimental annotations found in the FragPipe outputs for LiP and TrP (if include) datasets.
53
+
54
+ Examples:
55
+ Including only LiP data
56
+ >>> e_coli_refolding = flippr.Study(lip = "PXD025926/FragPipe/LiP_LFQ")
57
+ >>> print(e_coli_refolding.samples)
58
+ {"LiP": ("Native", "Refolded_01_min", "Refolded_05_min", "Refolded_30_min")}
59
+
60
+ Including LiP and TrP data
61
+ >>> e_coli_refolding = flippr.Study(lip = "PXD025926/FragPipe/LiP_LFQ", trp = "PXD025926/FragPipe/TrP_LFQ")
62
+ >>> print(e_coli_refolding.samples)
63
+ {"LiP": ("Native", "Refolded_01_min", "Refolded_05_min", "Refolded_30_min"),
64
+ "TrP": ("Native", "Refolded")}
65
+
66
+ """
67
+
68
+ if self.trp is not None:
69
+ return {"LiP": self.lip_samples, "TrP": self.trp_samples}
70
+
71
+ return {"LiP": self.lip_samples}
72
+
73
+ @property
74
+ def lip_samples(self) -> set:
75
+ """
76
+ Returns the names of the samples or experimental annotations found in the FragPipe outputs for LiP datasets.
77
+
78
+ """
79
+
80
+ return self.__read_annotation(self.lip)
81
+
82
+ @property
83
+ def trp_samples(self) -> set:
84
+ """
85
+ Returns the names of the samples or experimental annotations found in the FragPipe outputs for TrP datasets.
86
+
87
+ """
88
+
89
+ return self.__read_annotation(self.trp)
90
+
91
+ @cached_property
92
+ def fasta(self) -> pl.DataFrame | None:
93
+ """
94
+ Returns a three column dataframe containing Protein IDs, Sequences, and Valid ID if the Protein ID is interpretable as an official Uniprot ID.
95
+
96
+ """
97
+ if self._fasta_path is not None:
98
+ return _fasta._read_fasta(self._fasta_path)
99
+
100
+ return None
101
+
102
+ def add_process(
103
+ self, pid: object | None, lip_ctrl: str, lip_test: str, n_rep: int, trp_ctrl: str | None = None, trp_test: str | None = None, trp_n_rep: int | None = None) -> None:
104
+ """
105
+ Adding a process to calculate the fold-change between test and control conditions.
106
+ Normalization is optional and must include a TrP experiment in the `Study` set-up.
107
+
108
+ Args:
109
+ pid (Any): Process ID used to index the `Study().results` dictionary after running `Study().run()`.
110
+ lip_ctrl (str): Control condition sample name from the LiP experiment.
111
+ lip_test (str): Test condition sample name from the LiP experiment.
112
+ n_rep (int): Number of replicates in the LiP experiment.
113
+ trp_ctrl (str, optional): Control condition sample name from the TrP experiment.
114
+ trp_test (str, optional): Test condition sample name from the TrP experiment.
115
+ trp_n_rep (int, optional): Number of replicates in the TrP experiment.
116
+
117
+ Examples:
118
+ Simple fold-change calculation between two conditions
119
+ >>> study.add_process("sample", "WT", "Drug", 3)
120
+
121
+ Fold-change calculation with normalization
122
+ >>> study.add_process("sample", "WT", "Drug", 3, "WT_TrP", "DMSO_TrP", 3)
123
+
124
+ Add multiple processes to the same study
125
+ >>> study.add_process("cond_1", "WT", "Cond1", 5)
126
+ >>> study.add_process("cond_2", "WT", "Cond2", 5, "WT_TrP", "DMSO_TrP", 5)
127
+ """
128
+
129
+ if pid is None:
130
+ pid = self._pid
131
+ self._pid += 1
132
+
133
+ self.processes.update(
134
+ {
135
+ pid: _spr.Process(
136
+ self.fasta,
137
+ self.lip,
138
+ self.trp,
139
+ pid,
140
+ lip_ctrl,
141
+ lip_test,
142
+ n_rep,
143
+ trp_ctrl,
144
+ trp_test,
145
+ trp_n_rep,
146
+ )
147
+ }
148
+ )
149
+
150
+ def run(self) -> dict[object: _spr.Result]:
151
+ """
152
+ Run the processes added to the study.
153
+ Study parameters can be changed by editing the `flippr.rcParams` dictionary.
154
+ """
155
+
156
+ self.results = {pid: proc.run(rcParams) for pid, proc in self.processes.items()}
157
+
158
+ return self.results
159
+
160
+ def __read_annotation(self, liptrp: Path) -> set:
161
+ def __strip_fragpipe_repilicate(sample: str) -> str:
162
+ return "_".join([_ for _ in sample.split("_")[:-1]])
163
+
164
+ annotation = liptrp.joinpath("experiment_annotation.tsv")
165
+
166
+ annotation_df = pl.read_csv(annotation, separator="\t")
167
+
168
+ annotation_dict = annotation_df.select(pl.col("sample")).to_dict(
169
+ as_series=False
170
+ )
171
+
172
+ return set(__strip_fragpipe_repilicate(sample) for sample in annotation_dict["sample"])
flippr/combine.py ADDED
@@ -0,0 +1,107 @@
1
+ import numpy as np
2
+ import polars as pl
3
+ import scipy as sp
4
+
5
+ from .functions import _log2, _log10
6
+ from .parameters import _FLIPPR_COMBINE_KEY
7
+
8
+ COMB_NAME_COLUMN = {
9
+ "CUT SITE": "Cut Site ID",
10
+ "PEPTIDE": "Peptide Sequence",
11
+ "MODIFIED PEPTIDE": "Modified Sequence",
12
+ }
13
+
14
+ def combine_by(df: pl.DataFrame, by: str, fc: str) -> pl.DataFrame:
15
+
16
+ combined = (
17
+ df.group_by(by=["Protein ID", COMB_NAME_COLUMN[by]], maintain_order=True)
18
+ .agg(
19
+ pl.col(_FLIPPR_COMBINE_KEY[by]).first(),
20
+ pl.col(["P-value", "Adj. P-value", "CV", fc])
21
+ .filter(
22
+ pl.col("T-test").sign() == pl.col("T-test").sign().sum().sign()
23
+ )
24
+ )
25
+ .with_columns(
26
+ pl.when(pl.col("P-value").list.len() > 0)
27
+ .then(pl.col("P-value"))
28
+ .otherwise([1.0])
29
+ .alias("P-value"),
30
+ pl.when(pl.col("Adj. P-value").list.len() > 0)
31
+ .then(pl.col("Adj. P-value"))
32
+ .otherwise([1.0])
33
+ .alias("Adj. P-value"),
34
+ pl.when(pl.col("CV").list.len() > 0)
35
+ .then(pl.col("CV"))
36
+ .otherwise([0.0])
37
+ .alias("CV"),
38
+ pl.when(pl.col(fc).list.len() > 0)
39
+ .then(pl.col(fc))
40
+ .otherwise([0.0])
41
+ .alias(fc)
42
+ )
43
+ .with_columns(
44
+ pl.col("P-value")
45
+ .map_elements(lambda x: sp.stats.combine_pvalues(x)[1], return_dtype=pl.Float64)
46
+ .alias("P-value"),
47
+ pl.col("Adj. P-value")
48
+ .map_elements(lambda x: sp.stats.combine_pvalues(x)[1], return_dtype=pl.Float64)
49
+ .alias("Adj. P-value"),
50
+ pl.col("CV")
51
+ .map_elements(lambda x: np.nanmax(x), return_dtype=pl.Float64)
52
+ .alias("CV"),
53
+ pl.col(fc)
54
+ .map_elements(lambda x: np.nanmedian(x), return_dtype=pl.Float64)
55
+ .alias(fc)
56
+ )
57
+ )
58
+
59
+ combined = _log2(combined, fc)
60
+ combined = _log10(combined, "P-value")
61
+ combined = _log10(combined, "Adj. P-value")
62
+
63
+ return combined
64
+
65
+
66
+ def summary_by(df: pl.DataFrame, by: str, fc: str) -> pl.DataFrame:
67
+ val = (
68
+ df.group_by(by="Protein ID", maintain_order=True)
69
+ .agg(pl.col("CV").filter(pl.col("-Log10 P-value") > 0).len())
70
+ .rename({"CV": f"No. of Valid {by}"})
71
+ )
72
+
73
+ sig = (
74
+ df.group_by(by="Protein ID", maintain_order=True)
75
+ .agg(
76
+ pl.col("CV")
77
+ .filter(
78
+ (
79
+ (pl.col(f"Log2 {fc}").abs() >= 1)
80
+ & (pl.col("-Log10 P-value") >= -np.log10(0.01))
81
+ )
82
+ | (
83
+ (pl.col(f"Log2 {fc}").abs() >= 6)
84
+ & (pl.col("-Log10 P-value") >= 1.8)
85
+ )
86
+ )
87
+ .len()
88
+ )
89
+ .rename({"CV": f"No. of Significant {by} (P-value)"})
90
+ )
91
+
92
+ sigsig = (
93
+ df.group_by(by="Protein ID", maintain_order=True)
94
+ .agg(
95
+ pl.col("CV")
96
+ .filter(
97
+
98
+ (pl.col(f"Log2 {fc}").abs() >= 1)
99
+ & (pl.col("-Log10 Adj. P-value") >= -np.log10(0.05))
100
+
101
+ )
102
+ .len()
103
+ )
104
+ .rename({"CV": f"No. of Significant {by} (Adj. P-value)"})
105
+ )
106
+
107
+ return val.join(sig, on="Protein ID").join(sigsig, on="Protein ID")
flippr/functions.py ADDED
@@ -0,0 +1,289 @@
1
+ import numpy as np
2
+ import polars as pl
3
+ import scipy as sp
4
+
5
+
6
+ def _cull_intensities(df: pl.DataFrame,
7
+ ctrl_name: str, test_name: str,
8
+ ctrl_ints: list[str], test_ints: list[str],
9
+ n_rep: int,
10
+ rcParams: dict, **kwargs) -> pl.DataFrame:
11
+
12
+ max_missing = rcParams.get("ion.missing_intensity_thresh", 1)
13
+
14
+ df = \
15
+ df.with_columns(# Count the number of zeros intensity replicates
16
+ pl.concat_list(ctrl_ints).list.count_matches(0).alias(f"{ctrl_name} ZC"),
17
+ pl.concat_list(test_ints).list.count_matches(0).alias(f"{test_name} ZC"),
18
+ ).filter(# Cull based on zero count (ZC)
19
+ (pl.col(f"{ctrl_name} ZC").le(max_missing) & pl.col(f"{test_name} ZC").eq(0))
20
+ | (pl.col(f"{ctrl_name} ZC").eq(0) & pl.col(f"{test_name} ZC").le(max_missing))
21
+ | (pl.col(f"{ctrl_name} ZC").eq(n_rep) & pl.col(f"{test_name} ZC").eq(0))
22
+ | (pl.col(f"{ctrl_name} ZC").eq(0) & pl.col(f"{test_name} ZC").eq(n_rep))
23
+ ).with_columns(# Replace zero values remaining with NaN
24
+ pl.col(ctrl_ints).replace(0.0, float("nan")),
25
+ pl.col(test_ints).replace(0.0, float("nan"))
26
+ )
27
+
28
+ return df
29
+
30
+
31
+ def _add_alt_hypothesis(df: pl.DataFrame,
32
+ ctrl_name: str, test_name: str,
33
+ n_rep: int,
34
+ rcParams: dict, **kwargs) -> pl.DataFrame:
35
+
36
+ df = \
37
+ df.with_columns(# Add alternative hypothesis
38
+ pl.when(pl.col(f"{ctrl_name} ZC").eq(n_rep) & pl.col(f"{test_name} ZC").eq(0))
39
+ .then(pl.lit("greater"))
40
+ .when(pl.col(f"{ctrl_name} ZC").eq(0) & pl.col(f"{test_name} ZC").eq(n_rep))
41
+ .then(pl.lit("less"))
42
+ .otherwise(pl.lit("two-sided"))
43
+ .alias("Alternative Hypothesis")
44
+ )
45
+
46
+ return df
47
+
48
+
49
+ def _impute_aon_intensities(df: pl.DataFrame,
50
+ ctrl_name: str, test_name: str,
51
+ ctrl_ints: list[str], test_ints: list[str],
52
+ n_rep: int,
53
+ rcParams: dict, **kwargs) -> pl.DataFrame:
54
+
55
+
56
+ # Add imputation variables
57
+ loc = rcParams.get("ion.aon_impute_loc", 1e4)
58
+ scale = rcParams.get("ion.aon_impute_scale", 1e4)
59
+ imp_df = pl.from_numpy(np.random.normal(loc=loc, scale=scale, size=(df.height, n_rep)), schema=["IMP"])
60
+ df = df.hstack(imp_df)
61
+
62
+ df = \
63
+ df.with_columns(# Impute only on AON ions in control conditions
64
+ pl.when(pl.col(f"{ctrl_name} ZC").eq(n_rep) & pl.col(f"{test_name} ZC").eq(0))
65
+ .then(pl.concat_list("IMP"))
66
+ .otherwise(pl.concat_list(ctrl_ints))
67
+ .alias(f"{ctrl_name} Intensity"),
68
+ # Impute only on AON ions in test conditions
69
+ pl.when(pl.col(f"{ctrl_name} ZC").eq(0) & pl.col(f"{test_name} ZC").eq(n_rep))
70
+ .then(pl.concat_list("IMP"))
71
+ .otherwise(pl.concat_list(test_ints))
72
+ .alias(f"{test_name} Intensity")
73
+ ).with_columns(# Seperate out imputation (if perfromed) vars for control conditions
74
+ pl.col(f"{ctrl_name} Intensity").list.get(i).alias(col) for i, col in enumerate(ctrl_ints)
75
+ ).with_columns(# Seperate out imputation (if perfromed) vars for test conditions
76
+ pl.col(f"{test_name} Intensity").list.get(i).alias(col) for i, col in enumerate(test_ints)
77
+ ).with_columns(# Drop NaNs for further computations, this column will be droped down-stream
78
+ pl.col(f"{ctrl_name} Intensity").list.eval(pl.element().drop_nans()).alias(f"{ctrl_name} Intensity"),
79
+ pl.col(f"{test_name} Intensity").list.eval(pl.element().drop_nans()).alias(f"{test_name} Intensity")
80
+ ).drop(["IMP", f"{ctrl_name} ZC", f"{test_name} ZC"])
81
+
82
+ return df
83
+
84
+
85
+ def _add_ttest(df: pl.DataFrame,
86
+ ctrl_name: str, test_name: str,
87
+ n_rep: int,
88
+ rcParams: dict, **kwargs) -> pl.DataFrame:
89
+
90
+ df = \
91
+ df.with_columns(# Prepare the descriptive stats for `.ttest_ind_from_stats()`
92
+ pl.col(f"{ctrl_name} Intensity").list.mean().alias(f"{ctrl_name} Mean"),
93
+ pl.col(f"{ctrl_name} Intensity").list.std().alias(f"{ctrl_name} Std"),
94
+
95
+ pl.col(f"{test_name} Intensity").list.mean().alias(f"{test_name} Mean"),
96
+ pl.col(f"{test_name} Intensity").list.std().alias(f"{test_name} Std"),
97
+ ).with_columns(
98
+ pl.struct(# Create struct with `.ttest_ind_from_stats()` var names
99
+ mean1=pl.col(f"{ctrl_name} Mean"),
100
+ std1=pl.col(f"{ctrl_name} Std"),
101
+ nobs1=n_rep,
102
+
103
+ mean2=pl.col(f"{test_name} Mean"),
104
+ std2=pl.col(f"{test_name} Std"),
105
+ nobs2=n_rep,
106
+ alternative=pl.col("Alternative Hypothesis")
107
+ ).map_elements(# Perform T-test, slow step
108
+ lambda x: sp.stats.ttest_ind_from_stats(**x, equal_var=False),
109
+ return_dtype=pl.List(pl.Float64)
110
+ ).alias("Stats")
111
+ ).with_columns(# Seperate out T-test vars
112
+ pl.col("Stats").list.first().alias("T-test"),
113
+ pl.col("Stats").list.last().alias("P-value"),
114
+ ).drop(["Stats", f"{ctrl_name} Intensity", f"{test_name} Intensity"])
115
+
116
+ return df
117
+
118
+
119
+ def _add_fdr(df: pl.DataFrame, rcParams: dict, **kwargs) -> pl.DataFrame:
120
+
121
+ # Sort on P-value
122
+ df = df.sort(by=["Protein ID", "P-value"], descending=[False, False])
123
+
124
+ df = \
125
+ df.hstack(
126
+ df.group_by(by="Protein ID", maintain_order=True)
127
+ .agg(# Calculate Adj. P-value
128
+ pl.map_groups(
129
+ exprs="P-value",
130
+ function=lambda x: sp.stats.false_discovery_control(x[0]),
131
+ return_dtype=pl.List(pl.Float64)
132
+ ).alias("Adj. P-value"),
133
+ )# Add Adj. P-value to the original `DataFrame`
134
+ .explode(pl.col("Adj. P-value"))
135
+ .select(pl.col("Adj. P-value"))
136
+ )
137
+
138
+ return df
139
+
140
+
141
+ def _add_ratio(df: pl.DataFrame,
142
+ ctrl_name: str, test_name: str,
143
+ rcParams: dict, **kwargs) -> pl.DataFrame:
144
+
145
+ df = \
146
+ df.with_columns(# Calculate FC and CV
147
+ (pl.col(f"{test_name} Mean").mean() / pl.col(f"{ctrl_name} Mean")).alias("FC"),
148
+ (pl.col(f"{test_name} Std").mean() / pl.col(f"{test_name} Mean")).alias("CV")
149
+ ).drop([f"{ctrl_name} Mean", f"{ctrl_name} Std", f"{test_name} Mean", f"{test_name} Std"])
150
+
151
+ return df
152
+
153
+
154
+ def _normalize_ratios(df: pl.DataFrame, trp_norm: pl.DataFrame, rcParams: dict, **kwargs) -> pl.DataFrame:
155
+
156
+ trp_prot_fc = rcParams.get("trp_protein.fc_sig_tresh", 1.0)
157
+ trp_prot_pval = rcParams.get("trp_protein.pval_sig_tresh", 0.01)
158
+
159
+ trp_norm = trp_norm.select(pl.col("Protein ID", "P-value", "FC"))
160
+ trp_norm = trp_norm.select(# Select only the significantly different proteins
161
+ pl.col("Protein ID"),
162
+ pl.when(pl.col("P-value").le(trp_prot_pval) & pl.col("FC").log(base=2).abs().ge(trp_prot_fc))
163
+ .then(pl.col("FC"))
164
+ .otherwise(pl.lit(0.0))
165
+ .alias("Normalization Factor")
166
+ )
167
+
168
+ df = df.join(trp_norm, on="Protein ID", how="left")
169
+
170
+ df = df.with_columns(# Apply normalization
171
+ pl.col("Normalization Factor").fill_null(strategy="zero"),
172
+ pl.when(pl.col("Normalization Factor").gt(0))
173
+ .then(pl.col("FC") / pl.col("Normalization Factor"))
174
+ .otherwise(pl.col("FC"))
175
+ .alias("Normalized FC")
176
+ )
177
+
178
+ return df
179
+
180
+
181
+ def _add_start_end_aa(df: pl.DataFrame, rcParams: dict, **kwargs) -> pl.DataFrame:
182
+
183
+ df = \
184
+ df.with_columns(# Parse the Starting and Ending AA of the peptide
185
+ pl.col("Peptide Sequence").str.head(1).alias("Start AA"),
186
+ pl.col("Peptide Sequence").str.tail(1).alias("End AA"),
187
+ )
188
+
189
+ return df
190
+
191
+
192
+ def _add_half_trpytic(df: pl.DataFrame, rcParams: dict, **kwargs) -> pl.DataFrame:
193
+
194
+ df = \
195
+ df.with_columns(
196
+ pl.when(# [K/R/-].~~~~~(K/R).[X]
197
+ pl.col("Prev AA").is_in(["K", "R", "-"])
198
+ & pl.col("End AA").is_in(["K", "R"])
199
+ & ~pl.col("Next AA").is_in(["-"]))
200
+ .then(pl.lit("FULL_TRP"))
201
+ .when(# [K/R/-].~~~~~(X).[-]
202
+ pl.col("Prev AA").is_in(["K", "R"])
203
+ & pl.col("Next AA").is_in(["-"]))
204
+ .then(pl.lit("FULL_TRP"))
205
+ .when(# [M].2~~~~~(K/R).[X]
206
+ pl.col("Prev AA").is_in(["M"])
207
+ & pl.col("Start").eq(2)
208
+ & pl.col("End AA").is_in(["K", "R"]))
209
+ .then(pl.lit("FULL_TRP"))
210
+ .when(# [K/R/-].~~~~~(X).[X]
211
+ pl.col("Prev AA").is_in(["K", "R", "-"])
212
+ & ~pl.col("End AA").is_in(["K", "R"])
213
+ & ~pl.col("Next AA").is_in(["-"]))
214
+ .then(pl.lit("C_SEMI"))
215
+ .when(# [M].2~~~~~(X).[X]
216
+ pl.col("Prev AA").is_in(["M"])
217
+ & pl.col("Start").eq(2)
218
+ & ~pl.col("End AA").is_in(["K", "R"]))
219
+ .then(pl.lit("C_SEMI"))
220
+ .when(# [X].~~~~~(K/R).[X]
221
+ ~pl.col("Prev AA").is_in(["K", "R", "-"])
222
+ & pl.col("End AA").is_in(["K", "R"])
223
+ & ~pl.col("Next AA").is_in(["-"]))
224
+ .then(pl.lit("N_SEMI"))
225
+ .when(# [X].~~~~~(X).[-]
226
+ ~pl.col("Prev AA").is_in(["K", "R", "-"])
227
+ & pl.col("Next AA").is_in(["-"]))
228
+ .then(pl.lit("N_SEMI"))
229
+ .otherwise(pl.lit(None))
230
+ .alias("Cleavage Type")
231
+ ).filter(# Filter out over-digested peptides
232
+ ~pl.col("Cleavage Type").is_null()
233
+ ).with_columns(# Fill in `Half Tryptic`
234
+ pl.col("Cleavage Type").ne("FULL_TRP").alias("Half Tryptic")
235
+ )
236
+
237
+ return df
238
+
239
+
240
+ def _add_cut_sites(df: pl.DataFrame, rcParams: dict, **kwargs) -> pl.DataFrame:
241
+
242
+ df = \
243
+ df.with_columns(# Adding unique identifier for all `Cleavage Type`
244
+ pl.when(pl.col("Cleavage Type").eq("C_SEMI"))
245
+ .then(pl.format("{}{}", pl.col("Next AA"), pl.col("End") + 1))
246
+ .when(pl.col("Cleavage Type").eq("N_SEMI"))
247
+ .then(pl.format("{}{}", pl.col("Start AA"), pl.col("Start")))
248
+ .when((pl.col("Cleavage Type").eq("FULL_TRP")) & (pl.col("Prev AA").ne("-")))
249
+ .then(
250
+ pl.format(
251
+ "{}{}-{}{}",
252
+ pl.col("Prev AA"),
253
+ pl.col("Start") - 1,
254
+ pl.col("End AA"),
255
+ pl.col("End"),
256
+ )
257
+ )
258
+ .when((pl.col("Cleavage Type").eq("FULL_TRP")) & (pl.col("Prev AA").eq("-")))
259
+ .then(
260
+ pl.format(
261
+ "{}{}-{}{}",
262
+ pl.col("Start AA"),
263
+ pl.col("Start"),
264
+ pl.col("End AA"),
265
+ pl.col("End"),
266
+ )
267
+ )
268
+ .alias("Cut Site")
269
+ ).with_columns(
270
+ pl.format("{}_{}", pl.col("Protein ID"), pl.col("Cut Site")).alias("Cut Site ID")
271
+ )
272
+
273
+ return df
274
+
275
+
276
+ def _log2(df: pl.DataFrame, col: str) -> pl.DataFrame:
277
+
278
+ df = df.with_columns(
279
+ pl.when(pl.col(col).eq(0))
280
+ .then(0.0)
281
+ .otherwise(pl.col(col).log(base=2))
282
+ .alias(f"Log2 {col}")
283
+ )
284
+
285
+ return df
286
+
287
+
288
+ def _log10(df: pl.DataFrame, col: str) -> pl.DataFrame:
289
+ return df.with_columns(-pl.col(col).log10().alias(f"-Log10 {col}"))
File without changes