Step 1: Import and validate proteomics data

library(readxl)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(janitor)
## 
## Attaching package: 'janitor'
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
proteomics_raw <- read_excel("Copy_of_Proteomics_data_original_1.xlsx", 
                              sheet = "proteins_imputed_80 percent_V3") %>%
  clean_names()

dim(proteomics_raw)
## [1]  178 1224

Pathology groups

proteomics_raw %>% tabyl(pathology)
##  pathology  n   percent
##      Bmass 62 0.3483146
##         HC 35 0.1966292
##       PDAC 81 0.4550562

Proteomics uses three pathology categories (Bmass, HC, PDAC) rather than the six categories found in the clinical data (BBP, RPC, HC, LAPC, MPC, CP). This suggests a coarser classification, likely collapsing benign groups into “Bmass” and cancer subtypes into “PDAC” — the exact mapping needs to be confirmed once the crosswalk file is available.

Sample ID inspection

proteomics_raw %>%
  select(file_name, pathology, linked_id) %>%
  slice_head(n = 15)
## # A tibble: 15 × 3
##    file_name          pathology linked_id
##    <chr>              <chr>     <chr>    
##  1 B1_S2-D3_1_3370.d  PDAC      P067     
##  2 B1_S2-D4_1_3396.d  PDAC      P068     
##  3 B2_S3-D11_1_3463.d PDAC      P069     
##  4 B2_S3-D12_1_3447.d PDAC      P070     
##  5 B1_S2-D5_1_3403.d  PDAC      P072     
##  6 B2_S3-E1_1_3480.d  PDAC      P073     
##  7 B2_S3-E2_1_3495.d  PDAC      P074     
##  8 B2_S3-E3_1_3457.d  PDAC      P075     
##  9 B1_S2-D7_1_3416.d  PDAC      P076     
## 10 B3_S4-D10_1_3520.d PDAC      P077     
## 11 B3_S4-D11_1_3518.d PDAC      P078     
## 12 B1_S2-D8_1_3377.d  PDAC      P079     
## 13 B3_S4-D12_1_3513.d PDAC      P081     
## 14 B3_S4-E1_1_3528.d  PDAC      P082     
## 15 B3_S4-E2_1_3546.d  PDAC      P083

file_name contains raw mass spectrometer output filenames, not diagnostic codes — this is expected metadata about the instrument run, not a data quality issue. pathology and linked_id both contain proper, expected values.

Missingness check

proteomics_raw %>%
  select(-file_name, -pathology, -linked_id) %>%
  summarise(across(everything(), ~ sum(is.na(.)))) %>%
  rowSums()
## [1] 0

Zero missing values across all 1221 proteins, consistent with this dataset already being imputed (per the sheet name, “proteins_imputed_80 percent_V3”).

Duplicate patient check

proteomics_raw %>%
  count(linked_id) %>%
  filter(n > 1)
## # A tibble: 3 × 2
##   linked_id     n
##   <chr>     <int>
## 1 HC14          2
## 2 P067          2
## 3 P070          2
proteomics_raw %>%
  filter(linked_id %in% c("HC14", "P067", "P070")) %>%
  select(file_name, pathology, linked_id, a1bg, a2m)
## # A tibble: 6 × 5
##   file_name          pathology linked_id   a1bg     a2m
##   <chr>              <chr>     <chr>      <dbl>   <dbl>
## 1 B1_S2-D3_1_3370.d  PDAC      P067       8664. 167513.
## 2 B2_S3-D12_1_3447.d PDAC      P070       9282. 113251.
## 3 B1_S2-A12_1_3376.d PDAC      P067      13383. 348350.
## 4 B2_S3-A8_1_3454.d  PDAC      P070       6233. 199897.
## 5 B1_S2-D11_1_3424.d HC        HC14       1682.  63159.
## 6 B2_S3-E9_1_3450.d  HC        HC14       3567. 305892.

Three patients (HC14, P067, P070) each have two technical replicate runs, confirmed by different file_name values per pair. Replicate values agree reasonably well for P067 and P070 (~1.5-2x spread), but HC14 shows a larger ~5-fold difference between runs.

Note: replicates are averaged below as a provisional approach; this is pending confirmation from the supervisor, particularly for HC14, where the 5-fold spread is notably larger than the other two patients.

proteomics_clean <- proteomics_raw %>%
  group_by(linked_id, pathology) %>%
  summarise(across(where(is.numeric), mean), .groups = "drop")

dim(proteomics_clean)
## [1]  175 1223

Value range check

protein_values <- proteomics_clean %>% select(-linked_id, -pathology)

range(protein_values, na.rm = TRUE)
## [1] 2.080078e+00 2.756291e+07

Range spans roughly 2 to 27.6 million — all positive, no corrupted values, but spanning nearly seven orders of magnitude. This is expected for proteomics abundance data and confirms a log-transformation step will be needed before any statistical modelling (PCA, Cox-LASSO).

Save checkpoint

saveRDS(proteomics_clean, "proteomics_clean.rds")
list.files(pattern = ".rds")
## [1] "clinical_clean.rds"   "proteomics_clean.rds"