diff --git a/backend/main/views_helper.py b/backend/main/views_helper.py index 281799b9..be488195 100644 --- a/backend/main/views_helper.py +++ b/backend/main/views_helper.py @@ -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', @@ -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, diff --git a/backend/protzilla/data_analysis/crosslinking_validation.py b/backend/protzilla/data_analysis/crosslinking_validation.py index 8d8b765d..4804272f 100644 --- a/backend/protzilla/data_analysis/crosslinking_validation.py +++ b/backend/protzilla/data_analysis/crosslinking_validation.py @@ -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, @@ -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 @@ -194,44 +219,23 @@ 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) @@ -239,10 +243,16 @@ def get_crosslink_positions_in_protein( 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( @@ -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:] @@ -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( @@ -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"] ) @@ -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 @@ -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, @@ -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, @@ -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( diff --git a/backend/protzilla/importing/crosslinking_import.py b/backend/protzilla/importing/crosslinking_import.py index 8b1ae718..e8d939e4 100644 --- a/backend/protzilla/importing/crosslinking_import.py +++ b/backend/protzilla/importing/crosslinking_import.py @@ -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 ) @@ -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", } ) diff --git a/backend/protzilla/importing/import_utils.py b/backend/protzilla/importing/import_utils.py index b73cc135..3330d383 100644 --- a/backend/protzilla/importing/import_utils.py +++ b/backend/protzilla/importing/import_utils.py @@ -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", } @@ -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", ] diff --git a/backend/tests/protzilla/data_analysis/test_crosslinking_validation.py b/backend/tests/protzilla/data_analysis/test_crosslinking_validation.py index 0e815a17..e66b9e06 100644 --- a/backend/tests/protzilla/data_analysis/test_crosslinking_validation.py +++ b/backend/tests/protzilla/data_analysis/test_crosslinking_validation.py @@ -16,6 +16,8 @@ diagrams_of_crosslinking_validation_data, expand_crosslinks_to_chain_combinations, get_chains, + get_crosslink_positions_in_protein, + get_protein_sequence_from_df, ) from backend.protzilla.constants.colors import PLOT_PRIMARY_COLOR from backend.protzilla.data_analysis.plots import ( @@ -28,6 +30,37 @@ ) +def test_get_crosslink_positions_in_protein_maps_to_same_amino_acid(): + amino_acid_sequences_df = pd.DataFrame( + { + "Protein ID": ["P12345-1"], + "Protein Sequence": ["ABCDEFGH"], + } + ) + + peptide = "CDE" + protein_id = "P12345" + cl_position_within_peptide = 2 # points to "D" in peptide + + positions = get_crosslink_positions_in_protein( + peptide=peptide, + protein_id=protein_id, + amino_acid_sequences_df=amino_acid_sequences_df, + cl_position_within_peptide=cl_position_within_peptide, + ) + + protein_sequence = get_protein_sequence_from_df( + amino_acid_sequences_df=amino_acid_sequences_df, + protein_id=protein_id, + ) + + amino_acid_absolute = protein_sequence[positions[0] - 1] + amino_acid_peptide = peptide[cl_position_within_peptide - 1] + + assert amino_acid_absolute == amino_acid_peptide + assert amino_acid_absolute == "D" + + @pytest.mark.parametrize( "distance, expected", [ @@ -65,8 +98,8 @@ def test_monomer_validation_baseline_manual_bounds(distance, expected): "Protein_id2": ["P12345"], "Peptide1": ["A"], "Peptide2": ["B"], - "CL_position_within_peptide1": [0], - "CL_position_within_peptide2": [0], + "1_based_CL_position_within_peptide1": [1], + "1_based_CL_position_within_peptide2": [1], "Crosslinker": ["DSS"], } ) @@ -139,8 +172,8 @@ def test_cl_validation_pae_noerrror(distance, expected): "Protein_id2": ["P12345"], "Peptide1": ["A"], "Peptide2": ["B"], - "CL_position_within_peptide1": [0], - "CL_position_within_peptide2": [0], + "1_based_CL_position_within_peptide1": [1], + "1_based_CL_position_within_peptide2": [1], "Crosslinker": ["DSS"], } ) @@ -210,8 +243,8 @@ def test_cl_validation_pae_haserror(distance, expected_min, expected_max): "Protein_id2": ["P12345"], "Peptide1": ["A"], "Peptide2": ["B"], - "CL_position_within_peptide1": [0], - "CL_position_within_peptide2": [0], + "1_based_CL_position_within_peptide1": [1], + "1_based_CL_position_within_peptide2": [1], "Crosslinker": ["DSS"], } ) @@ -301,8 +334,8 @@ def test_cl_validation_plddt_noerrror(distance, expected): "Protein_id2": ["P12345"], "Peptide1": ["A"], "Peptide2": ["B"], - "CL_position_within_peptide1": [0], - "CL_position_within_peptide2": [0], + "1_based_CL_position_within_peptide1": [1], + "1_based_CL_position_within_peptide2": [1], "Crosslinker": ["DSS"], } ) @@ -384,8 +417,8 @@ def test_cl_validation_plddt_witherror(distance, expected): "Protein_id2": ["P12345"], "Peptide1": ["A"], "Peptide2": ["B"], - "CL_position_within_peptide1": [0], - "CL_position_within_peptide2": [0], + "1_based_CL_position_within_peptide1": [1], + "1_based_CL_position_within_peptide2": [1], "Crosslinker": ["DSS"], } ) @@ -465,8 +498,8 @@ def test_add_crosslinker_positions_with_exactly_one_possible_position(): "Protein_id2": ["P1"], "Peptide1": ["ABC"], "Peptide2": ["DEF"], - "CL_position_within_peptide1": [1], - "CL_position_within_peptide2": [2], + "1_based_CL_position_within_peptide1": [1], + "1_based_CL_position_within_peptide2": [2], } ) @@ -478,11 +511,11 @@ def test_add_crosslinker_positions_with_exactly_one_possible_position(): assert messages == [] - assert df.loc[0, "crosslinker_position1"] == 2 + 1 + 1 # 1-based - assert df.loc[0, "crosslinker_position2"] == 8 + 2 + 1 # 1-based + assert df.loc[0, "1_based_crosslinker_position1"] == 3 # 1-based + assert df.loc[0, "1_based_crosslinker_position2"] == 10 # 1-based - assert str(df["crosslinker_position1"].dtype) == "Int64" - assert str(df["crosslinker_position2"].dtype) == "Int64" + assert str(df["1_based_crosslinker_position1"].dtype) == "Int64" + assert str(df["1_based_crosslinker_position2"].dtype) == "Int64" def test_add_crosslinker_positions_with_more_than_one_possible_position(): @@ -492,8 +525,8 @@ def test_add_crosslinker_positions_with_more_than_one_possible_position(): "Protein_id2": ["P1"], "Peptide1": ["AA"], "Peptide2": ["BB"], - "CL_position_within_peptide1": [0], - "CL_position_within_peptide2": [0], + "1_based_CL_position_within_peptide1": [1], + "1_based_CL_position_within_peptide2": [1], } ) @@ -512,8 +545,8 @@ def test_add_crosslinker_positions_with_more_than_one_possible_position(): assert "duplicated" in messages[0]["msg"] # All rows should have valid positions - assert df["crosslinker_position1"].notna().all() - assert df["crosslinker_position2"].notna().all() + assert df["1_based_crosslinker_position1"].notna().all() + assert df["1_based_crosslinker_position2"].notna().all() def test_add_crosslinker_positions_but_one_peptide_not_found_deletes_row(): @@ -523,8 +556,8 @@ def test_add_crosslinker_positions_but_one_peptide_not_found_deletes_row(): "Protein_id2": ["P1"], "Peptide1": ["ABC"], "Peptide2": ["DEF"], - "CL_position_within_peptide1": [0], - "CL_position_within_peptide2": [0], + "1_based_CL_position_within_peptide1": [1], + "1_based_CL_position_within_peptide2": [1], } ) @@ -549,8 +582,8 @@ def test_add_crosslinker_positions_with_valid_and_invalid_rows_mixed(): "Protein_id2": ["P1", "P1", "P1"], "Peptide1": ["ABC", "XXX", "ABC"], "Peptide2": ["DEF", "DEF", "YYY"], - "CL_position_within_peptide1": [0, 0, 0], - "CL_position_within_peptide2": [0, 0, 0], + "1_based_CL_position_within_peptide1": [1, 1, 1], + "1_based_CL_position_within_peptide2": [1, 1, 1], } ) @@ -564,8 +597,8 @@ def test_add_crosslinker_positions_with_valid_and_invalid_rows_mixed(): assert messages[0]["level"] == logging.WARNING # First row valid - assert df.loc[0, "crosslinker_position1"] == 1 - assert df.loc[0, "crosslinker_position2"] == 4 + assert df.loc[0, "1_based_crosslinker_position1"] == 1 + assert df.loc[0, "1_based_crosslinker_position2"] == 4 # Second and third row invalid -> df should only have one row assert len(df) == 1 @@ -578,8 +611,8 @@ def test_add_crosslinker_positions_with_overlapping_peptide_matches(): "Protein_id2": ["P1"], "Peptide1": ["AAA"], "Peptide2": ["B"], - "CL_position_within_peptide1": [0], - "CL_position_within_peptide2": [0], + "1_based_CL_position_within_peptide1": [1], + "1_based_CL_position_within_peptide2": [1], } ) @@ -601,8 +634,8 @@ def test_add_crosslinker_positions_with_overlapping_peptide_matches(): observed_positions = set( zip( - df["crosslinker_position1"].astype(int), - df["crosslinker_position2"].astype(int), + df["1_based_crosslinker_position1"].astype(int), + df["1_based_crosslinker_position2"].astype(int), ) ) @@ -622,19 +655,19 @@ def test_validate_multimer_filters_only_pairs_within_structures_to_validate(): crosslinking_df = pd.DataFrame( [ # within set: P1-P2 (should be kept when validating ["P1","P2"]) - ("P1", "P2", "BC", "WX", 0, 0, "XL"), + ("P1", "P2", "BC", "WX", 1, 1, "XL"), # within set: P2-P2 - ("P2", "P2", "WX", "WX", 0, 0, "XL"), + ("P2", "P2", "WX", "WX", 1, 1, "XL"), # outside set: P1-P3 (should be filtered out) - ("P1", "P3", "BC", "LM", 0, 0, "XL"), + ("P1", "P3", "BC", "LM", 1, 1, "XL"), ], columns=[ "Protein_id1", "Protein_id2", "Peptide1", "Peptide2", - "CL_position_within_peptide1", - "CL_position_within_peptide2", + "1_based_CL_position_within_peptide1", + "1_based_CL_position_within_peptide2", "Crosslinker", ], ) @@ -685,8 +718,8 @@ def test_validate_multimer_filters_only_pairs_within_structures_to_validate(): assert "alphafold_distance" in result_df.columns assert "valid_crosslink" in result_df.columns - assert "crosslinker_position1" in result_df.columns - assert "crosslinker_position2" in result_df.columns + assert "1_based_crosslinker_position1" in result_df.columns + assert "1_based_crosslinker_position2" in result_df.columns assert "link_type" in result_df.columns assert "Chain_id1" in result_df.columns assert "Chain_id2" in result_df.columns @@ -704,16 +737,16 @@ def test_validate_multimer_no_links_between_structures_returns_empty_and_warning crosslinking_df = pd.DataFrame( [ - ("P1", "P3", "BC", "LM", 0, 0, "XL"), - ("P3", "P2", "LM", "WX", 0, 0, "XL"), + ("P1", "P3", "BC", "LM", 1, 1, "XL"), + ("P3", "P2", "LM", "WX", 1, 1, "XL"), ], columns=[ "Protein_id1", "Protein_id2", "Peptide1", "Peptide2", - "CL_position_within_peptide1", - "CL_position_within_peptide2", + "1_based_CL_position_within_peptide1", + "1_based_CL_position_within_peptide2", "Crosslinker", ], ) @@ -775,15 +808,15 @@ def test_validate_multimer_duplicates_rows_for_multiple_peptide_matches_and_vali crosslinking_df = pd.DataFrame( [ - ("P1", "P2", "AB", "AB", 0, 0, "XL"), + ("P1", "P2", "AB", "AB", 1, 1, "XL"), ], columns=[ "Protein_id1", "Protein_id2", "Peptide1", "Peptide2", - "CL_position_within_peptide1", - "CL_position_within_peptide2", + "1_based_CL_position_within_peptide1", + "1_based_CL_position_within_peptide2", "Crosslinker", ], ) @@ -830,8 +863,8 @@ def test_validate_multimer_duplicates_rows_for_multiple_peptide_matches_and_vali # Crosslinker positions should cover the product of {1,3} x {1,3}. combos = set( zip( - result_df["crosslinker_position1"].astype(int).tolist(), - result_df["crosslinker_position2"].astype(int).tolist(), + result_df["1_based_crosslinker_position1"].astype(int).tolist(), + result_df["1_based_crosslinker_position2"].astype(int).tolist(), ) ) assert combos == {(1, 1), (1, 3), (3, 1), (3, 3)} @@ -1103,15 +1136,15 @@ def test_validate_multimer_with_invalid_crosslinks(): crosslinking_df = pd.DataFrame( [ - ("P1", "P2", "AB", "AB", 0, 0, "XL"), + ("P1", "P2", "AB", "AB", 1, 1, "XL"), ], columns=[ "Protein_id1", "Protein_id2", "Peptide1", "Peptide2", - "CL_position_within_peptide1", - "CL_position_within_peptide2", + "1_based_CL_position_within_peptide1", + "1_based_CL_position_within_peptide2", "Crosslinker", ], ) @@ -1204,15 +1237,15 @@ def test_expand_crosslinks_to_chain_combinations_homodimer(): """Test expanding crosslinks for homodimer (same protein twice).""" crosslinking_df = pd.DataFrame( [ - ("P1", "P1", "AB", "CD", 0, 0, "XL"), + ("P1", "P1", "AB", "CD", 1, 1, "XL"), ], columns=[ "Protein_id1", "Protein_id2", "Peptide1", "Peptide2", - "CL_position_within_peptide1", - "CL_position_within_peptide2", + "1_based_CL_position_within_peptide1", + "1_based_CL_position_within_peptide2", "Crosslinker", ], ) @@ -1239,15 +1272,15 @@ def test_expand_crosslinks_to_chain_combinations_heterodimer(): """Test expanding crosslinks for heterodimer (different proteins).""" crosslinking_df = pd.DataFrame( [ - ("P1", "P2", "AB", "CD", 0, 0, "XL"), + ("P1", "P2", "AB", "CD", 1, 1, "XL"), ], columns=[ "Protein_id1", "Protein_id2", "Peptide1", "Peptide2", - "CL_position_within_peptide1", - "CL_position_within_peptide2", + "1_based_CL_position_within_peptide1", + "1_based_CL_position_within_peptide2", "Crosslinker", ], ) @@ -1279,15 +1312,15 @@ def test_validate_multimer_same_protein_different_chains_intra_vs_inter(): # Single protein P1 with two copies in multimer (P1 appears twice as different chains) crosslinking_df = pd.DataFrame( [ - ("P1", "P1", "AB", "AB", 0, 0, "XL"), + ("P1", "P1", "AB", "AB", 1, 1, "XL"), ], columns=[ "Protein_id1", "Protein_id2", "Peptide1", "Peptide2", - "CL_position_within_peptide1", - "CL_position_within_peptide2", + "1_based_CL_position_within_peptide1", + "1_based_CL_position_within_peptide2", "Crosslinker", ], ) diff --git a/backend/tests/protzilla/importing/test_crosslinking_import.py b/backend/tests/protzilla/importing/test_crosslinking_import.py index d6e26ade..7b544970 100644 --- a/backend/tests/protzilla/importing/test_crosslinking_import.py +++ b/backend/tests/protzilla/importing/test_crosslinking_import.py @@ -138,8 +138,8 @@ def _minimal_valid_crosslinking_df(): "Crosslinker": ["DSS"], "Peptide1": ["AAA"], "Peptide2": ["BBB"], - "CL_position_within_peptide1": [1], - "CL_position_within_peptide2": [2], + "1_based_CL_position_within_peptide1": [1], + "1_based_CL_position_within_peptide2": [2], "Q_value": [0.01], } ) @@ -244,7 +244,7 @@ def test_crosslinking_import_csv(tmp_path): csv_file = tmp_path / "test.csv" csv_file.write_text( "Protein1,Protein2,Peptide1,Peptide2," - "CL_position_within_peptide1,CL_position_within_peptide2," + "1_based_CL_position_within_peptide1,1_based_CL_position_within_peptide2," "Crosslinker,Q_value\n" "RAD50,MRE11,AAA,BBB,1,2,DSS,0.01\n" ) @@ -271,8 +271,8 @@ def test_crosslinking_import_xlsx(monkeypatch, tmp_path): "Peptide1": ["[AAA]"], "Peptide2": ["[BBB]"], "Is_intra_crosslink": ["Intra"], - "CL_position_within_peptide1": [1], - "CL_position_within_peptide2": [2], + "1_based_CL_position_within_peptide1": [1], + "1_based_CL_position_within_peptide2": [2], "Crosslinker": ["DSS"], "Q_value": [0.01], }