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.")