#!/usr/bin/env python # coding: utf-8 # # Introduction to _py2cytoscape_: Pythonista-friendly wrapper for cyREST # # ![](http://cl.ly/XohP/logo300.png) # #

For

# # ![](https://www.python.org/static/community_logos/python-logo-master-v3-TM.png) # # by [Keiichiro Ono](http://keiono.github.io/) - University of California, San Diego [Trey Ideker Lab](http://healthsciences.ucsd.edu/som/medicine/research/labs/ideker/Pages/default.aspx) # # ## Requirments # * Java 8 # * Cytoscape 3.2.1+ # * cyREST 1.1.0+ # * py2cytoscape 0.4.2+ # # ---- # # ## Q. What is _py2cytoscape_? # ## A. A Python package to drive Cytoscape in pythonic way # # In a Nutshell... # In[1]: from py2cytoscape.data.cynetwork import CyNetwork from py2cytoscape.data.cyrest_client import CyRestClient from py2cytoscape.data.style import StyleUtil import py2cytoscape.util.cytoscapejs as cyjs import py2cytoscape.cytoscapejs as renderer import networkx as nx import pandas as pd import json # In[2]: # !!!!!!!!!!!!!!!!! Step 0: Start Cytoscape 3 with cyREST App !!!!!!!!!!!!!!!!!!!!!!!!!! # Step 1: Create py2cytoscape client cy = CyRestClient() # Reset cy.session.delete() # Step 2: Load network from somewhere yeast_net = cy.network.create_from('../tests/data/galFiltered.sif') # Step 3: Load table as pandas' DataFrame table_data = pd.read_csv('sample_data_table.csv', index_col=0) table_data.head() # In[3]: all_suid = cy.network.get_all() net1 = cy.network.create(all_suid[0]) print(net1.get_first_view()) # In[4]: # Step 4: Merge them in Cytoscape yeast_net.update_node_table(df=table_data, network_key_col='name') # In[13]: # Step 5: Apply layout cy.layout.apply(name='degree-circle', network=yeast_net) # Step 6: Create Visual Style as code (or by hand if you prefer) my_yeast_style = cy.style.create('GAL Style') basic_settings = { # You can set default values as key-value pairs. 'NODE_FILL_COLOR': '#6AACB8', 'NODE_SIZE': 55, 'NODE_BORDER_WIDTH': 0, 'NODE_LABEL_COLOR': '#555555', 'EDGE_WIDTH': 2, 'EDGE_TRANSPARENCY': 100, 'EDGE_STROKE_UNSELECTED_PAINT': '#333333', 'NETWORK_BACKGROUND_PAINT': '#FFFFEA' } my_yeast_style.update_defaults(basic_settings) # Create some mappings my_yeast_style.create_passthrough_mapping(column='label', vp='NODE_LABEL', col_type='String') degrees = yeast_net.get_node_column('degree.layout') # In[14]: color_gradient = StyleUtil.create_2_color_gradient(min=degrees.min(), max=degrees.max(), colors=('white', '#6AACB8')) degree_to_size = StyleUtil.create_slope(min=degrees.min(), max=degrees.max(), values=(10, 100)) my_yeast_style.create_continuous_mapping(column='degree.layout', vp='NODE_FILL_COLOR', col_type='Integer', points=color_gradient) my_yeast_style.create_continuous_mapping(column='degree.layout', vp='NODE_SIZE', col_type='Integer', points=degree_to_size) my_yeast_style.create_continuous_mapping(column='degree.layout', vp='NODE_WIDTH', col_type='Integer', points=degree_to_size) my_yeast_style.create_continuous_mapping(column='degree.layout', vp='NODE_HEIGHT', col_type='Integer', points=degree_to_size) my_yeast_style.create_continuous_mapping(column='degree.layout', vp='NODE_LABEL_FONT_SIZE', col_type='Integer', points=degree_to_size) cy.style.apply(my_yeast_style, yeast_net) # Step 7: (Optional) Embed as interactive Cytoscape.js widget yeast_net_view = yeast_net.get_first_view() style_for_widget = cy.style.get(my_yeast_style.get_name(), data_format='cytoscapejs') renderer.render(yeast_net_view, style=style_for_widget['style'], background='radial-gradient(#FFFFFF 15%, #DDDDDD 105%)') # # Long Description # From version 0.4.0, ___py2cytoscape___ has wrapper modules for [cyREST](http://apps.cytoscape.org/apps/cyrest) RESTful API. This means you can access Cytoscape features in more _Pythonic_ way instead of calling raw REST API via HTTP. # # # ## Features # # ### Pandas for basic data exchange # Since [pandas](http://pandas.pydata.org/) is a standard library for data mangling/analysis in Python, this new version uses its [DataFrame](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.html#pandas.DataFrame) as its basic data object. # # ### Embedded Cytoscaep.js Widget # You can use Cytoscape.js widget to embed your final result as a part of your notebook. # # ### Simpler Code to access Cytoscape # cyREST provides language-agnostic RESTful API, but you need to use a lot of template code to access raw API. Here is an example. Both of the following do the same task, which is creating an empty network in Cytoscape. You will notice it is significantly simpler if you use _py2cytoscape_ wrapper API. # # #### Raw cyREST # In[ ]: # HTTP Client for Python import requests # Standard JSON library import json # Basic Setup PORT_NUMBER = 1234 BASE = 'http://localhost:' + str(PORT_NUMBER) + '/v1/' # Header for posting data to the server as JSON HEADERS = {'Content-Type': 'application/json'} # Define dictionary of empty network empty_network = { 'data': { 'name': 'I\'m empty!' }, 'elements': { 'nodes':[], 'edges':[] } } res = requests.post(BASE + 'networks?collection=My%20Collection', data=json.dumps(empty_network), headers=HEADERS) new_network_id = res.json()['networkSUID'] print('New network created with raw REST API. Its SUID is ' + str(new_network_id)) # #### With py2cytoscape # In[ ]: network = cy.network.create(name='My Network', collection='My network collection') print('New network created with py2cytoscape. Its SUID is ' + str(network.get_id())) # ## Status # # As of 6/4/2015, this is still in alpha status and feature requests are always welcome. If youi have questions or feature requests, please send them to our Google Groups: # # * https://groups.google.com/forum/#!forum/cytoscape-discuss # # Quick Tour of py2cytoscape Features # # ---- # # ## Create a client object to connect to Cytoscape # In[ ]: # Create an instance of cyREST client. Default IP is 'localhost', and port number is 1234. # cy = CyRestClient() - This default constructor creates connection to http://localhost:1234/v1 cy = CyRestClient(ip='127.0.0.1', port=1234) # Cleanup: Delete all existing networks and tables in current Cytoscape session cy.session.delete() # ## Creating empty networks # In[ ]: # Empty network empty1 = cy.network.create() # With name empty2 = cy.network.create(name='Created in Jupyter Notebook') # With name and collection name empty3 = cy.network.create(name='Also created in Jupyter', collection='New network collection') # ## Load networks from files, URLs or web services # In[ ]: # Load a single local file net_from_local2 = cy.network.create_from('../tests/data/galFiltered.json') net_from_local1 = cy.network.create_from('sample_yeast_network.xgmml', collection='My Collection') net_from_local2 = cy.network.create_from('../tests/data/galFiltered.gml', collection='My Collection') # Load from multiple locations network_locations = [ 'sample_yeast_network.xgmml', # Local file 'http://chianti.ucsd.edu/cytoscape-data/galFiltered.sif', # Static file on a web server 'http://www.ebi.ac.uk/Tools/webservices/psicquic/intact/webservices/current/search/query/brca1?format=xml25' # or a web service ] # This requrns Series networks = cy.network.create_from(network_locations) pd.DataFrame(networks, columns=['CyNetwork']) # ## Create networks from various types of data # Currently, py2cytoscape accepts the following data as input: # # * Cytoscape.js # * NetworkX # * Pandas DataFrame # # # * igraph (TBD) # * Numpy adjacency matrix (binary or weighted) (TBD) # * GraphX (TBD) # In[ ]: # Cytoscape.js JSON n1 = cy.network.create(data=cyjs.get_empty_network(), name='Created from Cytoscape.js JSON') # In[ ]: # Pandas DataFrame # Example 1: From a simple text table df_from_sif = pd.read_csv('../tests/data/galFiltered.sif', names=['source', 'interaction', 'target'], sep=' ') df_from_sif.head() # In[ ]: # By default, it uses 'source' for source node column, 'target' for target node column, and 'interaction' for interaction yeast1 = cy.network.create_from_dataframe(df_from_sif, name='Yeast network created from pandas DataFrame') # Example 2: from more complicated table df_from_mitab = pd.read_csv('intact_pubid_22094256.txt', sep='\t') df_from_mitab.head() # In[ ]: source = df_from_mitab.columns[0] target = df_from_mitab.columns[1] interaction = 'Interaction identifier(s)' title='A Systematic Screen for CDK4/6 Substrates Links FOXM1 Phosphorylation to Senescence Suppression in Cancer Cells.' human1 = cy.network.create_from_dataframe(df_from_mitab, source_col=source, target_col=target, interaction_col=interaction, name=title) # Import edge attributes and node attributes at the same time (TBD) # In[ ]: # NetworkX nx_graph = nx.scale_free_graph(100) nx.set_node_attributes(nx_graph, 'Degree', nx.degree(nx_graph)) nx.set_node_attributes(nx_graph, 'Betweenness_Centrality', nx.betweenness_centrality(nx_graph)) scale_free100 = cy.network.create_from_networkx(nx_graph, collection='Generated by NetworkX') # TODO: igraph # TODO: Numpy adj. martix # TODO: GraphX # ## Get Network from Cytoscape # # You can get network data in the following forms: # # * Cytoscape.js # * NetworkX # * DataFrame # In[ ]: # As Cytoscape.js (dict) yeast1_json = yeast1.to_json() # print(json.dumps(yeast1_json, indent=4)) # In[ ]: # As NetworkX graph object sf100 = scale_free100.to_networkx() num_nodes = sf100.number_of_nodes() num_edges = sf100.number_of_edges() print('Number of Nodes: ' + str(num_nodes)) print('Number of Edges: ' + str(num_edges)) # In[ ]: # As a simple, SIF-like DataFrame yeast1_df = yeast1.to_dataframe() yeast1_df.head() # ## Working with CyNetwork API # ___CyNetwork___ class is a simple wrapper for network-related cyREST raw REST API. __It does not hold the actual network data. It's a reference to a network in current Cytoscape session__. With CyNetwork API, you can access Cytoscape data objects in more Pythonista-friendly way. # In[ ]: network_suid = yeast1.get_id() print('This object references to Cytoscape network with SUID ' + str(network_suid) + '\n') print('And its name is: ' + str(yeast1.get_network_value(column='name')) + '\n') nodes = yeast1.get_nodes() edges = yeast1.get_edges() print('* This network has ' + str(len(nodes)) + ' nodes and ' + str(len(edges)) + ' edges\n') # Get a row in the node table as pandas Series object node0 = nodes[0] row = yeast1.get_node_value(id=node0) print(row) # Or, pick one cell in the table cell = yeast1.get_node_value(id=node0, column='name') print('\nThis node has name: ' + str(cell)) # ### Get references from existing networks # And of course, you can grab references to existing Cytoscape networks: # In[ ]: # Create a new CyNetwork object from existing network network_ref1 = cy.network.create(suid=yeast1.get_id()) # And they are considered as same objects. print(network_ref1 == yeast1) print(network_ref1.get_network_value(column='name')) # ## Tables as DataFrame # Cytoscape has two main data types: ___Network___ and ___Table___. Network is the graph topology, and Tables are properties for those graphs. For simplicity, this library has access to three basic table objects: # # * Node Table # * Edge Table # * Network Table # # For 99% of your use cases, you can use these three to store properties. Since [pandas](http://pandas.pydata.org/) is extremely useful to handle table data, default data type for tables is __DataFrame__. However, you can also use other data types including: # # * Cytoscape.js style JSON # * CSV # * TSV # * CX (TBD) # In[ ]: # Get table from Cytoscape node_table = scale_free100.get_node_table() edge_table = scale_free100.get_edge_table() network_table = scale_free100.get_network_table() node_table.head() # In[ ]: network_table.transpose().head() # In[ ]: names = scale_free100.get_node_column('Degree') print(names.head()) # Node Column information. "name" is the unique Index scale_free100.get_node_columns() # ## Edit Network Topology # # ### Adding and deleteing nodes/edges # In[ ]: # Add new nodes: Simply send the list of node names. NAMES SHOULD BE UNIQUE! new_node_names = ['a', 'b', 'c'] # Return value contains dictionary from name to SUID. new_nodes = scale_free100.add_nodes(new_node_names) # Add new edges # Send a list of tuples: (source node SUID, target node SUID, interaction type new_edges = [] new_edges.append((new_nodes['a'], new_nodes['b'], 'type1')) new_edges.append((new_nodes['a'], new_nodes['c'], 'type2')) new_edges.append((new_nodes['b'], new_nodes['c'], 'type3')) new_edge_ids = scale_free100.add_edges(new_edges) new_edge_ids # In[ ]: # Delete node scale_free100.delete_node(new_nodes['a']) # Delete edge scale_free100.delete_edge(new_edge_ids.index[0]) # ## Update Table # Let's do something a bit more realistic. You can update any Tables by using DataFrame objects. # # ### 1. ID conversion with external service # Let's use [ID Conversion web service by Uniprot](http://www.uniprot.org/help/programmatic_access) to add more information to existing yeast network in current session. # In[ ]: # Small utility function to convert ID sets import requests def uniprot_id_mapping_service(query=None, from_id=None, to_id=None): # Uniprot ID Mapping service url = 'http://www.uniprot.org/mapping/' payload = { 'from': from_id, 'to': to_id, 'format':'tab', 'query': query } res = requests.get(url, params=payload) df = pd.read_csv(res.url, sep='\t') res.close() return df # In[ ]: # Get node table from Cytoscape yeast_node_table = yeast1.get_node_table() # From KEGG ID to UniprotKB ID query1 = ' '.join(yeast_node_table['name'].map(lambda gene_id: 'sce:' + gene_id).values) id_map_kegg2uniprot = uniprot_id_mapping_service(query1, from_id='KEGG_ID', to_id='ID') id_map_kegg2uniprot.columns = ['kegg', 'uniprot'] # From UniprotKB to SGD query2 = ' '.join(id_map_kegg2uniprot['uniprot'].values) id_map_uniprot2sgd = uniprot_id_mapping_service(query2, from_id='ID', to_id='SGD_ID') id_map_uniprot2sgd.columns = ['uniprot', 'sgd'] # From UniprotKB to Entrez Gene ID query3 = ' '.join(id_map_kegg2uniprot['uniprot'].values) id_map_uniprot2ncbi = uniprot_id_mapping_service(query3, from_id='ID', to_id='P_ENTREZGENEID') id_map_uniprot2ncbi.columns = ['uniprot', 'entrez'] # Merge them merged = pd.merge(id_map_kegg2uniprot, id_map_uniprot2sgd, on='uniprot') merged = pd.merge(merged, id_map_uniprot2ncbi, on='uniprot') # Add key column by removing prefix merged['name'] = merged['kegg'].map(lambda kegg_id : kegg_id[4:]) merged.head() # In[ ]: update_url = BASE + 'networks/' + str(yeast1.get_id()) + '/tables/defaultnode' print(update_url) ut = { 'key': 'name', 'dataKey': 'name', 'data': [ { 'name': 'YBR112C', 'foo': 'aaaaaaaa' } ] } requests.put(update_url, json=ut, headers=HEADERS) # In[ ]: # Now update existing node table with the data frame above. yeast1.update_node_table(merged, network_key_col='name', data_key_col='name') # Check the table is actually updated yeast1.get_node_table().head() # ## Create / Delete Table Data # Currently, ___you cannot delete the table or rows___ due to the Cytoscape data model design. However, it is easy to create / delete columns: # In[ ]: # Delete columns yeast1.delete_node_table_column('kegg') # Create columns yeast1.create_node_column(name='New Empty Double Column', data_type='Double', is_immutable=False, is_list=False) # Default is String, mutable column. yeast1.create_node_column(name='Empty String Col') yeast1.get_node_table().head() # ## Visual Styles # You can also use wrapper API to access Visual Styles. # # Current limitations are: # # * You need to use unique name for the Styles # * Need to know how to write serialized form of objects # In[ ]: # Get all existing Visual Styles import json styles = cy.style.get_all() print(json.dumps(styles, indent=4)) # Create a new style style1 = cy.style.create('sample_style1') # Get a reference to the existing style default_style = cy.style.create('default') print(style1.get_name()) print(default_style.get_name()) # Get all available Visual Properties print(len(cy.style.vps.get_all())) # Get Visual Properties for each data type node_vps = cy.style.vps.get_node_visual_props() edge_vps = cy.style.vps.get_edge_visual_props() network_vps = cy.style.vps.get_network_visual_props() print(pd.Series(edge_vps).head()) # ### Set default values # To set default values for Visual Properties, simply pass key-value pairs as dictionary. # In[ ]: # Prepare key-value pair for Style defaults new_defaults = { # Node defaults 'NODE_FILL_COLOR': '#eeeeff', 'NODE_SIZE': 20, 'NODE_BORDER_WIDTH': 0, 'NODE_TRANSPARENCY': 120, 'NODE_LABEL_COLOR': 'white', # Edge defaults 'EDGE_WIDTH': 3, 'EDGE_STROKE_UNSELECTED_PAINT': '#aaaaaa', 'EDGE_LINE_TYPE': 'LONG_DASH', 'EDGE_TRANSPARENCY': 120, # Network defaults 'NETWORK_BACKGROUND_PAINT': 'black' } # Update style1.update_defaults(new_defaults) # Apply the new style cy.style.apply(style1, yeast1) # ### Visual Mappings # In[ ]: # Passthrough mapping style1.create_passthrough_mapping(column='name', col_type='String', vp='NODE_LABEL') # Discrete mapping: Simply prepare key-value pairs and send it kv_pair = { 'pp': 'pink', 'pd': 'green' } style1.create_discrete_mapping(column='interaction', col_type='String', vp='EDGE_STROKE_UNSELECTED_PAINT', mappings=kv_pair) # Continuous mapping points = [ { 'value': '1.0', 'lesser':'white', 'equal':'white', 'greater': 'white' }, { 'value': '20.0', 'lesser':'green', 'equal':'green', 'greater': 'green' } ] minimal_style = cy.style.create('Minimal') minimal_style.create_continuous_mapping(column='Degree', col_type='Double', vp='NODE_FILL_COLOR', points=points) # Or, use utility for simple mapping simple_slope = StyleUtil.create_slope(min=1, max=20, values=(10, 60)) minimal_style.create_continuous_mapping(column='Degree', col_type='Double', vp='NODE_SIZE', points=simple_slope) # Apply the new style cy.style.apply(minimal_style, scale_free100) # ## Layouts # Currently, this supports automatic layouts with default parameters. # In[ ]: # Get list of available layout algorithms layouts = cy.layout.get_all() print(json.dumps(layouts, indent=4)) # In[ ]: # Apply layout cy.layout.apply(name='circular', network=yeast1) yeast1.get_views() yeast_view1 = yeast1.get_first_view() node_views = yeast_view1['elements']['nodes'] df3 = pd.DataFrame(node_views) df3.head() # ## Embed Interactive Widget # In[ ]: from py2cytoscape.cytoscapejs import viewer as cyjs cy.layout.apply(network=scale_free100) view1 = scale_free100.get_first_view() view2 = yeast1.get_first_view() # print(view1) cyjs.render(view2, 'default2', background='#efefef') # In[ ]: # Use Cytoscape.js style JSON cyjs_style = cy.style.get(minimal_style.get_name(), data_format='cytoscapejs') cyjs.render(view1, style=cyjs_style['style'], background='white') # In[ ]: