193 lines
7.0 KiB
Python
Executable File
193 lines
7.0 KiB
Python
Executable File
import pandas as pd
|
|
import numpy as np
|
|
import re
|
|
from sklearn.preprocessing import PowerTransformer
|
|
|
|
# 1. Load Data
|
|
antigen1 = pd.read_excel('../xmap_biomarkers/data/02a.AP0211_GLA02_SBA01_Antigen_list.xlsx')
|
|
antigen2 = pd.read_excel('../xmap_biomarkers/data/02b. AP0211 GLA02_SBA02_Antigen_list.xlsx')
|
|
data1 = pd.read_excel('../xmap_biomarkers/data/12a. AP0211 GLA02 SBA01_Data Intensity.xlsx')
|
|
data2 = pd.read_excel('../xmap_biomarkers/data/12b. AP0211 GLA02 SBA02_Data_Intensity.xlsx')
|
|
layout = pd.read_excel('../xmap_biomarkers/data/layout.xlsx')
|
|
|
|
datasets = [data1, data2]
|
|
antigens = [antigen1, antigen2]
|
|
controls = ["Anti-human IgG", "EBNA1", "Bare-bead", "His6ABP"]
|
|
|
|
# Helper functions
|
|
def handle_background(df):
|
|
# Empty sample is "EMPTY-0001"
|
|
empty_df = df[df['Internal LIMS ID'] == 'EMPTY-0001']
|
|
if empty_df.empty:
|
|
return df
|
|
|
|
# Calculate background noise
|
|
# We only care about Analyte columns
|
|
analyte_cols = [c for c in df.columns if c.startswith('Analyte')]
|
|
empty_means = empty_df[analyte_cols].mean()
|
|
median_mean = empty_means.median()
|
|
sd_mean = empty_means.std()
|
|
|
|
# subset of empty_means < median + sd
|
|
inset = empty_means[empty_means < (median_mean + sd_mean)]
|
|
if len(inset) < 0.95 * len(empty_means):
|
|
calset = empty_means
|
|
else:
|
|
calset = inset
|
|
|
|
cutoff = calset.max() + calset.std()
|
|
|
|
# Keep columns where at least one sample is > cutoff
|
|
bgmap = df[analyte_cols] > cutoff
|
|
keep_cols = bgmap.sum() > 0
|
|
keep_analyte_cols = [c for c in analyte_cols if keep_cols[c]]
|
|
|
|
return df[['Internal LIMS ID', 'Original ID'] + keep_analyte_cols]
|
|
|
|
def emptyadjust(df):
|
|
empty_df = df[df['Internal LIMS ID'] == 'EMPTY-0001']
|
|
if empty_df.empty:
|
|
return df
|
|
|
|
analyte_cols = [c for c in df.columns if c.startswith('Analyte')]
|
|
emptyvector = empty_df[analyte_cols].mean()
|
|
|
|
df = df[(df['Internal LIMS ID'] != 'EMPTY-0001') & (df['Internal LIMS ID'] != 'MIX_2-0029')].copy()
|
|
|
|
# Subtract empty vector
|
|
df[analyte_cols] = df[analyte_cols].sub(emptyvector, axis=1)
|
|
|
|
# set <0 to 0, then add 1
|
|
df[analyte_cols] = df[analyte_cols].clip(lower=0) + 1
|
|
return df
|
|
|
|
def compress_duplicates(df, layout_df):
|
|
# Layout merges based on Tube label having a hyphen
|
|
# Find base names before hyphen
|
|
tube_labels = layout_df['Tube label'].dropna()
|
|
hyphen_labels = tube_labels[tube_labels.str.contains('-')]
|
|
|
|
base_names = []
|
|
for lbl in hyphen_labels:
|
|
base = lbl.split('-')[0]
|
|
if base not in base_names:
|
|
base_names.append(base)
|
|
|
|
analyte_cols = [c for c in df.columns if c.startswith('Analyte')]
|
|
|
|
for base in base_names:
|
|
# Find sample ids in layout matching base$ or base-
|
|
pattern = f"^{base}$|^{base}-"
|
|
matching_layout = layout_df[layout_df['Tube label'].str.contains(pattern, regex=True, na=False)]
|
|
mergerows = matching_layout['Sample id_LIMS'].dropna().tolist()
|
|
|
|
if not mergerows:
|
|
continue
|
|
|
|
# Find these sample ids in df
|
|
regex_pattern = '|'.join(mergerows)
|
|
matching_idx = df['Internal LIMS ID'].str.contains(regex_pattern, regex=True, na=False)
|
|
|
|
if matching_idx.sum() > 0:
|
|
# calculate mean
|
|
mean_vals = df.loc[matching_idx, analyte_cols].mean()
|
|
# replace first occurrence
|
|
first_idx = df[matching_idx].index[0]
|
|
df.loc[first_idx, analyte_cols] = mean_vals
|
|
# drop others
|
|
drop_idx = df[matching_idx].index[1:]
|
|
df = df.drop(drop_idx)
|
|
|
|
return df
|
|
|
|
def set_colname_adapter(df, antigen_df):
|
|
mapping = {}
|
|
for col in df.columns:
|
|
if col.startswith('Analyte'):
|
|
num = int(col.split(' ')[1])
|
|
match = antigen_df[antigen_df['BeadID (Analyte)'] == num]
|
|
if not match.empty:
|
|
antigen_name = match.iloc[0]['Antigen name']
|
|
mapping[col] = antigen_name
|
|
df = df.rename(columns=mapping)
|
|
# Remove controls
|
|
drop_cols = [c for c in df.columns if c in controls]
|
|
df = df.drop(columns=drop_cols)
|
|
return df
|
|
|
|
# Apply stage 1
|
|
processed_datasets = []
|
|
for i in range(2):
|
|
df = datasets[i]
|
|
df = handle_background(df)
|
|
df = emptyadjust(df)
|
|
df = compress_duplicates(df, layout)
|
|
df = set_colname_adapter(df, antigens[i])
|
|
# Extract Group based on Original ID (GC -> 1, HD -> 0, else NaN)
|
|
df['group'] = df['Original ID'].apply(lambda x: 1 if 'GC' in str(x) else (0 if 'HD' in str(x) else np.nan))
|
|
processed_datasets.append(df)
|
|
|
|
# Stage 2: Merge down
|
|
df1, df2 = processed_datasets
|
|
# Align columns: Internal LIMS ID, Original ID, group
|
|
common_keys = ['Internal LIMS ID', 'Original ID', 'group']
|
|
all_cols = set(df1.columns).union(set(df2.columns))
|
|
analyte_cols_all = list(all_cols - set(common_keys))
|
|
|
|
# Since df1 and df2 have same rows, we can merge on Internal LIMS ID
|
|
merged = pd.merge(df1, df2, on=['Internal LIMS ID', 'Original ID', 'group'], how='outer', suffixes=('_1', '_2'))
|
|
|
|
# Average common columns
|
|
final_cols = {}
|
|
for col in analyte_cols_all:
|
|
if col + '_1' in merged.columns and col + '_2' in merged.columns:
|
|
merged[col] = merged[[col + '_1', col + '_2']].mean(axis=1)
|
|
merged = merged.drop(columns=[col + '_1', col + '_2'])
|
|
elif col + '_1' in merged.columns:
|
|
merged = merged.rename(columns={col + '_1': col})
|
|
elif col + '_2' in merged.columns:
|
|
merged = merged.rename(columns={col + '_2': col})
|
|
|
|
# Drop rows with NaN group
|
|
merged = merged.dropna(subset=['group'])
|
|
merged = merged.reset_index(drop=True)
|
|
|
|
# Separate features and target
|
|
X = merged.drop(columns=common_keys)
|
|
y = merged['group']
|
|
meta = merged[['Internal LIMS ID', 'Original ID']]
|
|
|
|
# Box-Cox transformation + Standardization via PowerTransformer
|
|
pt = PowerTransformer(method='box-cox', standardize=True)
|
|
# Ensure strictly positive values for Box-Cox
|
|
min_val = X.min().min()
|
|
if min_val <= 0:
|
|
X = X - min_val + 1e-5
|
|
|
|
X_transformed = pt.fit_transform(X)
|
|
X_transformed_df = pd.DataFrame(X_transformed, columns=X.columns)
|
|
|
|
# Final dataset
|
|
final_df = pd.concat([meta, y.astype(int), X_transformed_df], axis=1)
|
|
|
|
# Now, create the slices!
|
|
# 1) Significant 7
|
|
sig_7 = ['HPRA000767', 'HPRA034083', 'HPRA006876', 'HPRA019035', 'HPRA003490', 'HPRA022019', 'HPRA017192']
|
|
# Filter out any that might have been dropped during background removal
|
|
sig_7_present = [c for c in sig_7 if c in final_df.columns]
|
|
slice_1 = final_df[common_keys + sig_7_present]
|
|
slice_1.to_csv('slice_1_significant_7.csv', index=False)
|
|
|
|
# 2) Significant 7 + 33 random proteins = 40 total
|
|
np.random.seed(42)
|
|
other_proteins = [c for c in X.columns if c not in sig_7_present]
|
|
random_33 = np.random.choice(other_proteins, size=min(33, len(other_proteins)), replace=False).tolist()
|
|
slice_2_cols = sig_7_present + random_33
|
|
slice_2 = final_df[common_keys + slice_2_cols]
|
|
slice_2.to_csv('slice_2_sig7_plus_33_random.csv', index=False)
|
|
|
|
# 3) All proteins
|
|
final_df.to_csv('slice_3_all_proteins.csv', index=False)
|
|
|
|
print("Preprocessing complete. Slices saved.")
|