-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmouse-diet-upto-filtering.qmd
More file actions
275 lines (200 loc) · 9.61 KB
/
Copy pathmouse-diet-upto-filtering.qmd
File metadata and controls
275 lines (200 loc) · 9.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
---
title: Normalisation wrapup - Mouse Diet
code-tools: true
---
```{r, echo = FALSE}
source("R/knitr_setup.R")
```
# Background
With the PXD059421 data deposited on ProteomeXchange researchers study the molecular effects of dietary DINCH exposure, on the proteome, phosphoproteome and acetylome profiles of visceral (VIS) and subcutaneous (SC) adipose tissue in a model of diet-induced obesity in male and female C57BL/6N mice. This study includes data on visceral and subcutaneous adipose tissue of female and male mice that were either fed a standard plant-based diet (chow), a standard high-fat diet (HFD) or two HFD diets including doses of DINCH (4,500 ppm and 15,000 ppm). Three female and three male mice were used for each diet [@AldehoffEtAl2025].
The data were downloaded from Pride and reprocessed using spectronaut.
Here, we will focus on the data from the proteome MS runs.
# Load packages
We load the `msqrob2` package, along with additional packages for
data manipulation and visualisation.
```{r load_libraries}
library("QFeatures")
library("dplyr")
library("tidyr")
library("ggplot2")
library("msqrob2")
library("stringr")
library("ExploreModelMatrix")
library("MsCoreUtils")
library("matrixStats")
library("patchwork")
library("kableExtra")
library("ComplexHeatmap")
library("purrr")
library("tibble")
library("scater")
```
# Data
## Precursor table
We load the output from Spectronaut parquet file. Can be file path to local file or url to file that lives on the web.
```{r import_data}
precursorFile = "https://github.com/statOmics/PDA-DIA/raw/refs/heads/main/data/mouseDiet-spectronaut.parquet"
```
We can import the report.parquet file using the `read_parquet` function from the `arrow` package.
```{r}
precursors <- arrow::read_parquet(precursorFile) # function from the arrow package
#precursors <- data.table::fread(precursorFile) # For older versions the results are stored as tsv files.
```
Note, that by default the spectronaut output is stored as a tsv file. In that case a `.` occurs in the column variables instead of a "_" upon importing with fread.
Each row in the precursor data table is in "long format" and contains information about one precursor in a specific run (the table below shows the first 6 rows).
The columns contains various descriptors about the precursor, such as its sequence, its charge, run, etc.
```{r, echo=FALSE}
knitr::kable(head(precursors))
```
(Note, that by default the spectronaut output is stored as a tsv file. In that case a `.` occurs in the column variables instead of a "_" upon importing with fread.)
No precursor Id and norm factors are present in the file.
We can make the former using the EG_ModifiedSequence and FG_Charge, and the latter using FG_MS2RawQuantity and FG_Quantity.
```{r}
precursors <- precursors |>
mutate(EG_PrecursorId = paste0(EG_ModifiedSequence, FG_Charge),
EG_NormalizationFactor = FG_Quantity/FG_MS2RawQuantity, #Normalisation.Factor
)
```
```{r eval = FALSE}
precursors <- precursors |>
select(
R_FileName, #Run,
EG_PrecursorId,
EG_ModifiedSequence, #Modified.Sequence,
PEP_StrippedSequence, #Stripped.Sequence,
FG_Charge, #PrecursoR_Charge,
PG_ProteinGroups, #Protein.Group,
PG_Genes, #Genes,
FG_MS2RawQuantity, #PrecursoR_Quantity,
FG_Quantity,
EG_NormalizationFactor,
EG_Qvalue, #Q.Value,
#No spectronaut counterpart #Lib.Q.Value,
PG_Qvalue, # PG_Q.Value,
#No spectronaut counterpart #Lib.PG_Q.Value
PEP_IsProteotypic, #Proteotypic,
EG_IsDecoy, #Decoy,
EG_ApexRT,#RT
EG_IsImputed)
```
Quick check on distribution of precursors MS2 intensities.
```{r}
precursors |>
ggplot(aes(x = log2(FG_MS2RawQuantity))) +
geom_density() +
theme_minimal()
```
Seems no imputation has been done.
## Sample annotation table
The [sample annotation table](#sec-annotation_table)) is not available
and can be generated from the run labels, as the researchers included information on the design in the filenames.
```{r create_metadata}
annot <- precursors |>
dplyr::distinct(R_FileName) |>
separate(R_FileName, into = c('f1','tissue','f2','diet','rep'), sep = '_', remove=FALSE) |>
mutate(runCol=R_FileName,
sex = stringr::str_sub(diet,1,1),
diet = stringr::str_sub(diet,2),
rep = paste(diet, sex, rep, sep='_'),
sampleGroup = paste(tissue,diet,sex,sep='_'),
sampleId = paste(tissue,rep, sep="_")) |>
dplyr::select(-c(R_FileName,f1,f2)) |>
relocate("runCol")
annot
```
## Convert to QFeatures
First, recall that the precursor table is file in long format.
Every quantitative column in the precursor table contains
information for multiple runs. Therefore, the function split the table
based on the run identifier, given by the `runCol` argument (for
Spectronaut, that identifier is contained in `run`).
So, the
`QFeatures` object after import will contain as many sets as there are
runs.
Next, the function links the annotation table with the PSM data.
To achieve this, the annotation table must contain a `runCol` column
that provides the run identifier in which each sample has been
acquired, and this information will be used to match the identifiers
in the `Run` column of the precursor table.
Here, we will use the `FG_MS2Quantity` column as quantification input.
Note, that we filter a number of variables to reduce the footprint of the QFeatures object.
```{r}
(qf <- readQFeatures(assayData = precursors,
colData = annot,
quantCols = "FG_MS2RawQuantity",
runCol = "R_FileName",
fnames = "EG_PrecursorId"))
```
# Data preprocessing{#sec-basic_preprocess}
The data preprocessing workflow for DIA data is similar to the workflow for DDA-LFQ data, but there are suble differences as we start from precursor level data and have additional columns.
## Encoding missing values
We first replace any zero in the quantitative data
with an `NA`.
```{r}
qf <- zeroIsNA(qf, names(qf))
```
Note that `msqrob2` can handle missing data without having to rely on
hard-to-verify imputation assumptions, which is our general recommendation. However, `msqrob2` does not
prevent users from using imputation, which can be performed with
`impute()` from the `QFeatures` package.
## Precursor Filtering
Filtering removes low-quality and unreliable precursors that would otherwise introduce noise and artefacts in the data.
### Remove questionable identifications
We apply standard filtering:
1. q-value threshold of 0.01 for the identification of precursors (`EG_Qvalue`) and protein groups (`PG_Qvalue`).
2. Remove precursors that could not be mapped, i.e. when `EG_PrecursorId` column is an empty string.
3. Filter decoys, i.e. only keep precursors for which the `EG_IsDecoy` column equals 0.
4. Keeping only proteotypic peptides, which map uniquely to a specific protein.
5. Only keep non-imputed intensities: `EG_IsImputed` equals 0.
```{r}
qf <- qf |>
filterFeatures(~ EG_Qvalue <= 0.01 & #1.
PG_Qvalue <= 0.01 & #1.
# Lib.Q.Value <= 0.01 & #1.
# Lib.PG_Q.Value <= 0.01 & #1.
EG_PrecursorId != "" & #2.
EG_IsDecoy == 0 & #3.
PEP_IsProteotypic == 1 & #4
EG_IsImputed == 0) #5
```
Note, that it is important that the filtering criteria are not distorting the distribution of the test statistics in the downstream analysis for features that are non-DA.
It can be shown that filtering will not induce bias results when the filtering criterion is independent of test statistic. The criteria that we proposed above are all based on the results of the identification step, hence, they are independent of the downstream test statistics that will be used to prioritize DA proteins.
### Assay joining
Up to now, the data from different runs were kept in separate assays. We can now join the normalised sets into an precursor set using joinAssays(). Sets are joined by stacking the columns (samples) in a matrix and rows (features) are matched according to a row identifier, here the `EG_PrecursorId`.
We will store the result in the assay with name: `precursor`.
```{r}
(qf <- joinAssays(
x = qf,
i = names(qf),
fcol = "EG_PrecursorId",
name = "precursors"
))
```
### Filtering: Remove highly missing precursors
We keep peptides that were observed at last 6 times out of the $n
= 48$ samples, so that we can estimate the peptide characteristics.
```{r}
nObs <- 6
n <- ncol(qf[["precursors"]])
(qf <- filterNA(qf, i = "precursors", pNA = (n - nObs) / n))
```
### Filter one-hit wonders
Here, we remove proteins that can only be found by one peptide, as such proteins may not be trustworthy.
1. We first calculate how many distinct peptides map to each protein group (`PG_ProteinGroups`). We use the stripped precursor sequence, i.e. sequence of the base peptide for this purpose.
2. We store this information in the row data of the precursors assay
3. We filter precursors of one-hit wonder proteins.
```{r}
# Filter for peptides per protein
pepsPerProtDf <- qf[["precursors"]] |>
rowData() |>
data.frame() |>
dplyr::select("PEP_StrippedSequence", "PG_ProteinGroups") |>
group_by(PG_ProteinGroups) |>
mutate(pepsPerProt = PEP_StrippedSequence |>
unique() |> length()
) #1.
rowData(qf[["precursors"]])$pepsPerProt <- pepsPerProtDf$pepsPerProt #2.
qf <- filterFeatures(qf,
~ pepsPerProt > 1,
keep = TRUE) #3.
```