<jupyter_start><jupyter_text>Cysteine Oxidative PTMs in 3D Protein Structures.Under oxidative stress Cysteines can undergo oxidative post-translational modifications (PTMs). This notebook retrieves oxidized forms of L-cysteine in the PDB using the [PSI-MOD Ontology](https://www.ebi.ac.uk/ols/ontologies/mod)* [MOD:00210](https://www.ebi.ac.uk/ols/ontologies/mod/terms?iri=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FMOD_00210) - oxydation to L-cysteine sulfenic acid (RSOH)* [MOD:00267](https://www.ebi.ac.uk/ols/ontologies/mod/terms?iri=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FMOD_00267) - oxydation to L-cysteine sulfinic acid (RSO2H)* [MOD:00460](https://www.ebi.ac.uk/ols/ontologies/mod/terms?iri=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FMOD_00460) - oxydation to L-cysteine sulfonic acid (RSO3H)PTMs in the PDB are identified with BioJava-ModFinder:BioJava-ModFinder: identification of protein modifications in 3D structures from the Protein Data Bank. <NAME>, <NAME>,  <NAME>  <NAME>, <NAME>, <NAME>  Bourne, PE, Rose PW, Bioinformatics 2017, 33: 2047–2049. [doi: doi.org/10.1093/bioinformatics/btx101](https://doi.org/10.1093/bioinformatics/btx101)<jupyter_code>import pandas as pd
import numpy as np
from io import BytesIO
import xlrd
from ipywidgets import interact, IntSlider, widgets
import py3Dmol
from pyspark.sql import SparkSession
from pyspark.sql.functions import asc, collect_set, collect_list, col, concat_ws, sort_array
from mmtfPyspark.datasets import pdbToUniProt, pdbPtmDataset<jupyter_output><empty_output><jupyter_text>Retrieve oxidized forms of L-cysteine in the PDB using a query by the [PSI-MOD Ontology](https://www.ebi.ac.uk/ols/ontologies/mod)* [MOD:00210](https://www.ebi.ac.uk/ols/ontologies/mod/terms?iri=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FMOD_00210) - oxydation to L-cysteine sulfenic acid (RSOH)* [MOD:00267](https://www.ebi.ac.uk/ols/ontologies/mod/terms?iri=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FMOD_00267) - oxydation to L-cysteine sulfinic acid (RSO2H)* [MOD:00460](https://www.ebi.ac.uk/ols/ontologies/mod/terms?iri=http%3A%2F%2Fpurl.obolibrary.org%2Fobo%2FMOD_00460) - oxydation to L-cysteine sulfonic acid (RSO3H)  Summary of oxidized Cysteines in the PDB<jupyter_code># get PTM data
spark = SparkSession.builder.appName("CysOxydationInPdb").getOrCreate()
pt = pdbPtmDataset.get_ptm_dataset()
pt = pt.filter("psimodId = 'MOD:00210' OR psimodId = 'MOD:00267' OR psimodId = 'MOD:00460'")
print("Total number of oxidized cysteines in PDB: ", pt.count())
print("   L-cysteine sulfenic acid (RSOH) :", pt.filter("psimodId = 'MOD:00210'").count())
print("   L-cysteine sulfinic acid (RSO2H):", pt.filter("psimodId = 'MOD:00267'").count())
print("   L-cysteine sulfonic acid (RSO3H):", pt.filter("psimodId = 'MOD:00460'").count())<jupyter_output>Total number of oxidized cysteines in PDB:  4046
   L-cysteine sulfenic acid (RSOH) : 1817
   L-cysteine sulfinic acid (RSO2H): 1710
   L-cysteine sulfonic acid (RSO3H): 519<jupyter_text>Table of oxidized Cysteines in the PDB<jupyter_code>pd.options.display.max_rows = None # show all rows
pt.limit(5).toPandas()<jupyter_output><empty_output><jupyter_text>Download PDB to UniProt mappings and filter out residues that were not observed in the 3D structure.<jupyter_code>up = pdbToUniProt.get_cached_residue_mappings().filter("pdbResNum IS NOT NULL")<jupyter_output><empty_output><jupyter_text>Joint PTM data with UniProt data if the UniProt Id and UniProt residue numbers match<jupyter_code># join datasets
pt = pt.withColumnRenamed("pdbResNum", "resNum") # avoid two columns with identical names
pt = pt.withColumnRenamed("psimodId", "ptms")
st = up.join(pt, (up.pdbResNum == pt.resNum) & (up.structureChainId == pt.pdbChainId))
st = st.sort(st.uniprotId, st.uniprotNum)<jupyter_output><empty_output><jupyter_text>Aggregate PTM data on a per residue and per chain basis<jupyter_code># Aggregate data
st = st.groupBy("structureChainId","pdbResNum","uniprotId","uniprotNum").agg(collect_set("ptms").alias("ptms"))
st = st.withColumn("ptms", concat_ws((","), col("ptms")))
st = st.groupBy("structureChainId","uniprotId").agg(collect_list("ptms").alias("ptms"), collect_list("pdbResNum").alias("pdbResNum"),  collect_list("uniprotNum").alias("uniprotNum"))<jupyter_output><empty_output><jupyter_text>Keep only a single structural representative<jupyter_code>st = st.drop_duplicates(["uniprotId","uniprotNum"])<jupyter_output><empty_output><jupyter_text>Show Table with PDB mappings PDB residue numbers do not always match UniProt residue numbers. The table below shows the mapping for each protein chain.<jupyter_code># convert Spark dataframe back to a Pandas dataframe
sp = st.toPandas()
sp.head()
def view_modifications(df, cutoff_distance, *args):

    def view3d(show_labels=True,show_bio_assembly=False, show_surface=False, i=0):
        pdb_id, chain_id = df.iloc[i]['structureChainId'].split('.')
        res_num = df.iloc[i]['pdbResNum']
        labels = df.iloc[i]['ptms']
        
        # print header
        print ("PDB Id: " + pdb_id + " chain Id: " + chain_id)
        
        # print any specified additional columns from the dataframe
        for a in args:
            print(a + ": " + df.iloc[i][a])
        
        mod_res = {'chain': chain_id, 'resi': res_num}  
        
        # select neigboring residues by distance
        surroundings = {'chain': chain_id, 'resi': res_num, 'byres': True, 'expand': cutoff_distance}
        
        viewer = py3Dmol.view(query='pdb:' + pdb_id, options={'doAssembly': show_bio_assembly})
    
        # polymer style
        viewer.setStyle({'cartoon': {'color': 'spectrum', 'width': 0.6, 'opacity':0.8}})
        # non-polymer style
        viewer.setStyle({'hetflag': True}, {'stick':{'radius': 0.3, 'singleBond': False}})
        
        # style for modifications
        viewer.addStyle(surroundings,{'stick':{'colorscheme':'orangeCarbon', 'radius': 0.15}})
        viewer.addStyle(mod_res, {'stick':{'colorscheme':'redCarbon', 'radius': 0.4}})
        viewer.addStyle(mod_res, {'sphere':{'colorscheme':'gray', 'opacity': 0.7}})
        
        # set residue labels    
        if show_labels:
            for residue, label in zip(res_num, labels):
                viewer.addLabel(residue + ": " + label, \
                                {'fontColor':'black', 'fontSize': 9, 'backgroundColor': 'lightgray'}, \
                                {'chain': chain_id, 'resi': residue})

        viewer.zoomTo(surroundings)
        
        if show_surface:
            viewer.addSurface(py3Dmol.SES,{'opacity':0.8,'color':'lightblue'})

        return viewer.show()
       
    s_widget = IntSlider(min=0, max=len(df)-1, description='Structure', continuous_update=False)
    
    return interact(view3d, show_labels=True, show_bio_assembly=False, show_surface=False, i=s_widget)<jupyter_output><empty_output><jupyter_text>Visualize ResultsResidues with reported modifications are shown in an all atom prepresentation as red sticks with transparent spheres. Each modified residue position is labeled by the PDB residue number and the type of the modification. Residues surrounding modified residue (within 6 A) are highlighted as yellow sticks. Small molecules within the structure are rendered as gray sticks.* Move slider to browse through the results* To rotate the structure, hold down the left mouse button and move the mouse.<jupyter_code>view_modifications(sp, 6, 'uniprotId');<jupyter_output>PDB Id: 1MJB chain Id: A
uniprotId: Q08649<jupyter_text>List set of UniProt Ids for proteins with Cysteine oxidations in PDB<jupyter_code>rs = sp[['uniprotId']].drop_duplicates()
rs.head()
spark.stop()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1304008328995315, 'fraction_numerical': 0.02745965642894326, 'mean_word_length': 4.870893812070283, 'pattern_counts': {'":': 2, '<': 45, '<?xml version=': 0, '>': 45, 'https://': 9, 'lorem ipsum': 0, 'www.': 8, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '340483', 'license': 'Apache-2.0', 'n_tokens_mistral': 2903, 'n_tokens_neox': 2639, 'n_words': 596, 'path': 'notebooks/CysOxidationInPDB.ipynb', 'repo_name': 'sbl-sdsc/mmtf-proteomics', 'size': '7691'}