Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions backend/main/views_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,8 @@ def extract_relevant_crosslink_information(
As well as a boolean for its validity and wether it is an intra or inter crosslink.

:param crosslinking_df: DataFrame with columns
'crosslinker_position1',
'crosslinker_position2',
'1_based_crosslinker_position1',
'1_based_crosslinker_position2',
'Chain_id1',
'Chain_id2',
'valid_crosslink',
Expand All @@ -306,8 +306,8 @@ def extract_relevant_crosslink_information(
"""
crosslinks = []
for _, row in crosslinking_df.iterrows():
position1 = row.get("crosslinker_position1")
position2 = row.get("crosslinker_position2")
position1 = row.get("1_based_crosslinker_position1")
position2 = row.get("1_based_crosslinker_position2")
# When the validation is extended to treat multimeres with more than one chain correctly,
# it should ideally store chain_id1 and chain_id2 into the crosslinking_df.
# Since we already need those chain ids to calculate correct distances in the validation,
Expand Down
120 changes: 69 additions & 51 deletions backend/protzilla/data_analysis/crosslinking_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,31 @@ def get_protein_sequence_from_df(
return matches.iloc[0]


def get_crosslink_positions_in_protein(
peptide: str,
protein_id: str,
amino_acid_sequences_df: pd.DataFrame,
cl_position_within_peptide: int,
) -> list:
"""
Returns the 1-based positions of the crosslinked residue within the full
protein sequence for all occurrences of a given peptide.

:param peptide: peptide sequence to search for in the protein
:param protein_id: UniProt protein identifier
:param cl_position_within_peptide: 1-based position of the crosslinked residue within the peptide
:return: list of 1-based residue positions in the protein sequence
"""
protein_sequence = get_protein_sequence_from_df(
amino_acid_sequences_df=amino_acid_sequences_df, protein_id=protein_id
)
positions = [
m.start() + cl_position_within_peptide
for m in re.finditer(f"(?={peptide})", protein_sequence)
]
return positions


def add_protein_crosslink_positions_to_df(
input_crosslinking_df: pd.DataFrame,
amino_acid_sequences_df: pd.DataFrame,
Expand All @@ -181,8 +206,8 @@ def add_protein_crosslink_positions_to_df(
sequence(s) that correspond to the crosslinked residue within each peptide. The
protein-level positions are written to two new columns:

- 'crosslinker_position1': 1-based residue position in Protein_id1 for Peptide1.
- 'crosslinker_position2': 1-based residue position in Protein_id2 for Peptide2.
- '1_based_crosslinker_position1': 1-based residue position in Protein_id1 for Peptide1.
- '1_based_crosslinker_position2': 1-based residue position in Protein_id2 for Peptide2.

If a peptide occurs multiple times in the corresponding protein sequence, all
combinations of (position1, position2) are generated. The first combination is
Expand All @@ -194,55 +219,40 @@ def add_protein_crosslink_positions_to_df(
:param input_crosslinking_df: DataFrame containing crosslinking data with at least the following columns:
- 'Peptide1': first peptide sequence
- 'Peptide2': second peptide sequence
- 'CL_position_within_peptide1': 0-based crosslinker position within Peptide1
- 'CL_position_within_peptide2': 0-based crosslinker position within Peptide2
- '1_based_CL_position_within_peptide1': 1-based crosslinker position within Peptide1
- '1_based_CL_position_within_peptide2': 1-based crosslinker position within Peptide2
:param amino_acid_sequences_df: Dataframe that contains all amino acid sequences
:return: tuple (updated_crosslinking_df, messages)
- updated_crosslinking_df: input DataFrame with two new columns:
- 'crosslinker_position1': 1-based crosslinker position in Peptide1
- 'crosslinker_position2': 1-based crosslinker position in Peptide2
- '1_based_crosslinker_position1': 1-based crosslinker position in Peptide1
- '1_based_crosslinker_position2': 1-based crosslinker position in Peptide2
Rows are duplicated for multiple peptide matches.
- messages: list of warning dictionaries if the peptide was not found or a row was duplicated
"""
crosslinking_df = input_crosslinking_df.copy()
crosslinking_df["crosslinker_position1"] = pd.Series(dtype="Int64")
crosslinking_df["crosslinker_position2"] = pd.Series(dtype="Int64")
crosslinking_df["1_based_crosslinker_position1"] = pd.Series(dtype="Int64")
crosslinking_df["1_based_crosslinker_position2"] = pd.Series(dtype="Int64")
rows_to_duplicate = {}
rows_to_delete = []
messages = []

def get_crosslink_positions_in_protein(
peptide: str, protein_id: str, cl_position_within_peptide: int
) -> list:
"""
Returns the 1-based positions of the crosslinked residue within the full
protein sequence for all occurrences of a given peptide.

:param peptide: peptide sequence to search for in the protein
:param protein_id: UniProt protein identifier
:param cl_position_within_peptide: 1-based position of the crosslinked residue within the peptide
:return: list of 1-based residue positions in the protein sequence
"""
protein_sequence = get_protein_sequence_from_df(
amino_acid_sequences_df=amino_acid_sequences_df, protein_id=protein_id
)
positions = [
m.start() + cl_position_within_peptide + 1
for m in re.finditer(f"(?={peptide})", protein_sequence)
]
return positions

for idx, crosslinker_row in crosslinking_df.iterrows():
peptide_sequence1 = re.escape(crosslinker_row.Peptide1)
peptide_sequence2 = re.escape(crosslinker_row.Peptide2)
protein_id1 = crosslinker_row.Protein_id1
protein_id2 = crosslinker_row.Protein_id2

peptide1_positions = get_crosslink_positions_in_protein(
peptide_sequence1, protein_id1, crosslinker_row.CL_position_within_peptide1
peptide_sequence1,
protein_id1,
amino_acid_sequences_df,
crosslinker_row["1_based_CL_position_within_peptide1"],
)
peptide2_positions = get_crosslink_positions_in_protein(
peptide_sequence2, protein_id2, crosslinker_row.CL_position_within_peptide2
peptide_sequence2,
protein_id2,
amino_acid_sequences_df,
crosslinker_row["1_based_CL_position_within_peptide2"],
)

all_position_combinations = list(
Expand All @@ -258,8 +268,8 @@ def get_crosslink_positions_in_protein(
continue
crosslinker_position1, crosslinker_position2 = all_position_combinations[0]

crosslinking_df.at[idx, "crosslinker_position1"] = crosslinker_position1
crosslinking_df.at[idx, "crosslinker_position2"] = crosslinker_position2
crosslinking_df.at[idx, "1_based_crosslinker_position1"] = crosslinker_position1
crosslinking_df.at[idx, "1_based_crosslinker_position2"] = crosslinker_position2
if len(all_position_combinations) > 1:
rows_to_duplicate[idx] = all_position_combinations[1:]

Expand All @@ -271,8 +281,8 @@ def get_crosslink_positions_in_protein(
for row_to_duplicate_idx, potential_positions in rows_to_duplicate.items():
for potential_cl_position1, potential_cl_position2 in potential_positions:
new_row = crosslinking_df.loc[row_to_duplicate_idx].copy()
new_row["crosslinker_position1"] = potential_cl_position1
new_row["crosslinker_position2"] = potential_cl_position2
new_row["1_based_crosslinker_position1"] = potential_cl_position1
new_row["1_based_crosslinker_position2"] = potential_cl_position2
new_rows.append(new_row)
messages.append(
dict(
Expand Down Expand Up @@ -644,13 +654,13 @@ def get_site_plddts(crosslink: pd.Series):

plddt_at_position1 = float(
plddt_df.query(
"residueNumber == @crosslink.crosslinker_position1 and "
"residueNumber == @crosslink['1_based_crosslinker_position1'] and "
+ "chainID == @crosslink.Chain_id1"
).iloc[0]["confidenceScore"]
)
plddt_at_position2 = float(
plddt_df.query(
"residueNumber == @crosslink.crosslinker_position2 and "
"residueNumber == @crosslink['1_based_crosslinker_position2'] and "
+ "chainID == @crosslink.Chain_id2"
).iloc[0]["confidenceScore"]
)
Expand All @@ -662,10 +672,10 @@ def get_paes():
return np.nan, np.nan

pae_index_pos1 = get_global_residue_index(
crosslink.crosslinker_position1, crosslink.Chain_id1, cif_df
crosslink["1_based_crosslinker_position1"], crosslink.Chain_id1, cif_df
)
pae_index_pos2 = get_global_residue_index(
crosslink.crosslinker_position2, crosslink.Chain_id2, cif_df
crosslink["1_based_crosslinker_position2"], crosslink.Chain_id2, cif_df
)
pae_x_position1 = pae_matrix[
pae_index_pos1, pae_index_pos2
Expand All @@ -680,10 +690,14 @@ def get_paes():
pae_x_position1, pae_x_position2 = get_paes()

predicted_distance = get_distance_between_two_amino_acids_in_angstrom(
amino_acid_position1=crosslink.crosslinker_position1,
amino_acid_position2=crosslink.crosslinker_position2,
amino_acid_type1=protein_sequence1[crosslink.crosslinker_position1 - 1],
amino_acid_type2=protein_sequence2[crosslink.crosslinker_position2 - 1],
amino_acid_position1=crosslink["1_based_crosslinker_position1"],
amino_acid_position2=crosslink["1_based_crosslinker_position2"],
amino_acid_type1=protein_sequence1[
crosslink["1_based_crosslinker_position1"] - 1
],
amino_acid_type2=protein_sequence2[
crosslink["1_based_crosslinker_position2"] - 1
],
cif_df=cif_df,
chain_id1=crosslink.Chain_id1,
chain_id2=crosslink.Chain_id2,
Expand Down Expand Up @@ -772,8 +786,12 @@ def get_paes():
{
"alphafold_distance": predicted_distance,
"valid_crosslink": valid,
"crosslinker_position1": crosslink.crosslinker_position1,
"crosslinker_position2": crosslink.crosslinker_position2,
"1_based_crosslinker_position1": crosslink[
"1_based_crosslinker_position1"
],
"1_based_crosslinker_position2": crosslink[
"1_based_crosslinker_position2"
],
"plddt_at_position1": plddt_at_position1,
"plddt_at_position2": plddt_at_position2,
"pae_x_position1": pae_x_position1,
Expand All @@ -785,19 +803,19 @@ def get_paes():
new_columns = [
"alphafold_distance",
"valid_crosslink",
"crosslinker_position1",
"crosslinker_position2",
"1_based_crosslinker_position1",
"1_based_crosslinker_position2",
"plddt_at_position1",
"plddt_at_position2",
"pae_x_position1",
"pae_x_position2",
]

relevant_crosslinks_df["crosslinker_position1"] = relevant_crosslinks_df[
"crosslinker_position1"
relevant_crosslinks_df["1_based_crosslinker_position1"] = relevant_crosslinks_df[
"1_based_crosslinker_position1"
].astype("Int64")
relevant_crosslinks_df["crosslinker_position2"] = relevant_crosslinks_df[
"crosslinker_position2"
relevant_crosslinks_df["1_based_crosslinker_position2"] = relevant_crosslinks_df[
"1_based_crosslinker_position2"
].astype("Int64")

relevant_crosslinks_df[new_columns] = relevant_crosslinks_df.apply(
Expand Down
8 changes: 4 additions & 4 deletions backend/protzilla/importing/crosslinking_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,10 +577,10 @@ def read_ProteomeDiscoverer_XlinkX_file(
columns=rename_columns_proteomediscoverer_xlinkx_format
)

df["CL_position_within_peptide1"] = df["Peptide1"].apply(
df["1_based_CL_position_within_peptide1"] = df["Peptide1"].apply(
get_amino_acid_where_crosslink_is_connected_proteomediscoverer_xlinkx_format
)
df["CL_position_within_peptide2"] = df["Peptide2"].apply(
df["1_based_CL_position_within_peptide2"] = df["Peptide2"].apply(
get_amino_acid_where_crosslink_is_connected_proteomediscoverer_xlinkx_format
)

Expand Down Expand Up @@ -653,8 +653,8 @@ def normalize_crosslinking_df(df: pd.DataFrame) -> pd.DataFrame:
"Crosslinker": "string",
"Peptide1": "string",
"Peptide2": "string",
"CL_position_within_peptide1": "int",
"CL_position_within_peptide2": "int",
"1_based_CL_position_within_peptide1": "int",
"1_based_CL_position_within_peptide2": "int",
"Q_value": "Float64",
}
)
Expand Down
8 changes: 4 additions & 4 deletions backend/protzilla/importing/import_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class AggregationMethods(Enum):
"Crosslink Type": "Is_intra_crosslink",
"PepSeq1": "Peptide1",
"PepSeq2": "Peptide2",
"LinkPos1": "CL_position_within_peptide1",
"LinkPos2": "CL_position_within_peptide2",
"LinkPos1": "1_based_CL_position_within_peptide1",
"LinkPos2": "1_based_CL_position_within_peptide2",
"PEP": "Q_value",
}

Expand All @@ -43,7 +43,7 @@ class AggregationMethods(Enum):
"Crosslinker",
"Peptide1",
"Peptide2",
"CL_position_within_peptide1",
"CL_position_within_peptide2",
"1_based_CL_position_within_peptide1",
"1_based_CL_position_within_peptide2",
"Q_value",
]
Loading
Loading