cytoscape / RCy3

New version of RCy3, redesigned and collaboratively maintained by Cytoscape developer community
MIT License
50 stars 20 forks source link

map value generators #131

Closed AlexanderPico closed 3 years ago

AlexanderPico commented 3 years ago

From Barry's email 5/1/21:

Mapping Value Generators

I’m starting work on a 0.0.9 feature needed for the Gang Su workflow, and which exists in the Cytoscape styles GUI for discrete mapping … called the Mapping Value Generators. You can see this by using VizMap to choose a style (e.g., Label Color), setting the column and choosing Discrete Mapping, and then right-clicking to see the Mapping Value Generators choices. Doing this provides a quick way of mapping columns to colors, heights/widths, etc.

In a word, Mapping Value Generators allow discrete mappings to common color or value collections painlessly (for the user). The implementation I’m working on looks like it’ll be painless for API writers and API users, too. The following discussion gives high level details.

The Scheme

The scheme is fairly simple … create generator functions that can be passed to existing style API functions (e.g., setNodeColorMapping), where the generator creates the key-value list ordinarily passed in for a discrete mapping.

The generators do all of the work of loading the values for the column, determining what unique values there are, and then determining appropriate mappings from column values to style values. There are different generators for different kinds of values (i.e., colors, numbers, etc). And a color generator can be further customized for color schemes (e.g., ColorBrewer Accents, Rainbow, Random) or number schemes (e.g., Number Series and Random Numbers).

To be clear, no changes are needed to existing style mapping functions … the generators do all of the work, and are fairly easy to write. They leverage a programming trick available in both Python and R. For Python, it’s possible for a function to create the parameters passed to a style mapping function … in combination with the parameter list operator. For R, the do_call operator approximates what Python’s does.

Example – Current Call

For example, consider set_node_color_mapping, which has a function signature of:

def set_node_color_mapping(table_column, table_column_values=None, colors=None, mapping_type='c', default_color=None, style_name=None, network=None, base_url=DEFAULT_BASE_URL):

A typical call would look like this:

set_node_color_mapping('Degree', ['1', '2'], ['#FFFF00', '#00FF00'], 'd')

What Changes

The problem that Mapping Value Generators addresses is the enumeration of the degrees (i.e., [‘1’, ‘2’]) and the choice of corresponding colors (i.e., ['#FFFF00', '#00FF00']).

A Mapping Value Generator for colors would have a signature like this:

def gen_color_map(table_column, color_scheme, color_scheme_params=None, style_name=None, network=None, base_url=DEFAULT_BASE_URL):

It would return a dictionary containing all of the parameters needed by a style mapping function like set_node_color_mapping(). For example:

{‘table_column’: ‘Disorder class’, ‘table_column_values’: [‘Bone’, ‘Cancer’, ‘Cardiovascular’], ‘colors’: ['#FFFF00', '#00FF00', ‘#CC00CC’], ‘mapping_type’: ‘d', ‘style_name’: style_name, ‘network’: network, ‘base_url’: base_url)

Critically, the scheme and scheme_parameters (e.g., color_scheme and color_scheme_parameters) amount to a value generator, which returns a list of style map values (e.g., a color list). A value generator has a signature like this:

            def scheme_color_brewer_accents(value_count, scheme_parameters):

Its job is to return a list of values that will be returned in the ‘colors’ value in the dictionary above. For example:

['#FFFF00', '#00FF00']

Examples of schemes that have this signature are scheme_color_brewer_accents(), scheme_color_rainbow(), scheme_color_random(), etc.

Example – How to Use

In turn, the call to (the existing) set_node_color_mapping() would look something like this:

   # Set discrete mapping of column ‘Disorder class” to ColorBrewer Accents

set_node_color_mapping(**gen_color_map(‘Disorder class’, scheme_color_brewer_accents)) or

Set discrete mapping of column ‘Disorder class” to Random colors

set_node_color_mapping(**gen_color_map(‘Disorder class’, scheme_color_random))

In R, this would be something like this:

do_call(gen_color_map, list(table_column=’Disorder class’, color_scheme=scheme_color_brewer_accents))

Implementation

To realize this, there are two collections of functions: map value generators (e.g., gen_color_map, gen_number_map, gen_shape_map, etc) and scheme generators (e.g., scheme_color_brewer_accents, scheme_color_rainbow, scheme_color_random, scheme_number_range, scheme_number_random)). Because they apply to style_mappings functions, they are in the style_mappings.py module. Discussion

Considering that no existing code need change, there aren’t many risks in this approach. The actual code needed for the two types of generators is small, and the generator collections are easily expandable. The clear benefits are the addition of easy-to-call critical visualization functionality. The reason for implementing it at the API level is that there’s little chance of getting similar functionality exposed at the CyREST level … and the value is high.

Of course, feel free to comment … am I missing anything? … or is there a bigger opportunity than I think here?

AlexanderPico commented 3 years ago

We might take a peek at Scooter’s latest updates to palettes in Cytoscape. We should include, for example, scheme_color_viridis, and there are multiple brewer palettes relevant here. I’m not sure “accents” covers all the common uses cases.

bdemchak commented 3 years ago

I think I may be done with this ... I'll verify the implementation via the GangSu workflow.

Meanwhile, I have added new function signatures to automation_api_definition, and bumped the current version to 1.2.0 ... see https://docs.google.com/spreadsheets/d/1XLWsKxGLqcBWLzoW2y6HyAUU2jMXaEaWw7QLn3NE5nY/edit#gid=329629714

Here is the documentation I added to the py4cytoscape manual ... it's slightly different than the original proposal, mainly reflecting a few issues I didn't originally think of:

Value Generators

You can set visual graph attributes (e.g., color, size, opacity and shapes) according to attributes assigned to nodes or edges by using Style Mapping functions such as set_node_color_mapping() or set_node_size_mapping(). As described in the Cytoscape Manual <http://manual.cytoscape.org/en/stable/Styles.html#how-mappings-work>_, there are three different ways to mapping node or edge attributes to visual attributes.

Briefly:

A value generator <http://manual.cytoscape.org/en/stable/Styles.html#automatic-value-generators>_ makes discrete mapping more convenient by creating automatic mappings between attribute values and visual styles. It automatically determines the unique values of a particular node or edge attribute, then allows you to choose a mapping to colors, sizes, opacities or shapes. For example, you can use a value generator to map a node with a Degree attribute having values 1, 10 and 20 to node fill colors of Red, Blue or Green ... or to a node size of 100, 150 or 200 ... or to circle, square or diamond shapes.

Essentially, a value generator spares you from having to know both the specific values of a node or edge attribute and the specific visual attributes to display ... it lets you focus on whether to render the attribute as a color, size, opacity or shape.

For example, to set a node's fill color based on its Degree attribute using a style mapping function, you could use the longhand (without value generator) where you know the unique Degree values in advance and choose specific colors to represent them:

.. code:: python

set_node_color_mapping('Degree', ['1', '10', '20'], ['#FF0000', '#00FF00', '#0000FF], 'd', style_name='galFiltered Style')

or you could use a color value generator that determines the unique Degree values and assigns each to a different color in a Brewer palette:

.. code:: python

set_node_color_mapping(**gen_node_color_map('Degree', scheme_color_brewer_accent, style_name='galFiltered Style'))

The general methodology is to use the value generator (e.g., gen_node_color_map()) as the sole parameter to a style mapping function, binding it by using the Python ** operator. The color value generators accept all of the same parameters as the color-oriented style mapping functions, and provides the same defaults for them. So,

.. code:: python

set_node_color_mapping(**gen_node_color_map('Degree', scheme_color_brewer_accent, style_name='galFiltered Style'))

is the equivalent of:

.. code:: python

set_node_color_mapping(table_column='Degree',
                       table_column_values=['8', '7', '6', '5', '4', '3', '2', '1'],
                       colors=['#7FC97F', '#BEAED4', '#FDC086', '#FFFF99', '#386CB0', '#F0027F', '#BF5B17', '#666666'],
                       mapping_type='d',
                       default_color=None,
                       style_name='galFiltered Style',
                       network=None,
                       base_url:'http://127.0.0.1:1234/v1')

The scheme_color_brewer_accent parameter is used to generate the specific colors values according to the predefined Brewer Accent palette. You can choose between any of the 8 Brewer Qualitative Palettes <https://colorbrewer2.org>_, which are widely regarded as aesthetic and visually effective.

+-----------------+-----------------------------------+ | Color Palette | scheme_color Parameter | +=================+===================================+ | Brewer Pastel2 | scheme_color_brewer_pastel2 | +-----------------+-----------------------------------+ | Brewer Pastel1 | scheme_color_brewer_pastel1 | +-----------------+-----------------------------------+ | Brewer Dark2 | scheme_color_brewer_dark2 | +-----------------+-----------------------------------+ | Brewer Accent | scheme_color_brewer_accent | +-----------------+-----------------------------------+ | Brewer Paired | scheme_color_brewer_paired | +-----------------+-----------------------------------+ | Brewer Set1 | scheme_color_brewer_set1 | +-----------------+-----------------------------------+ | Brewer Set2 | scheme_color_brewer_set2 | +-----------------+-----------------------------------+ | Brewer Set3 | scheme_color_brewer_set3 | +-----------------+-----------------------------------+ | Random | scheme_color_random | +-----------------+-----------------------------------+

.. note:: You can generate random colors by using the scheme_color_random scheme.

Similarly, there are value generators for opacities, sizes, heights, widths and shapes, with variants for node and edge values.

You can use a node value generator with a node mapping function, and you can use an edge value generator with an edge mapping function.

+-------------------------------+-------------------------------------------+ | Generator | Style Function | +===============================+===========================================+ | gen_node_color_map() | set_node_border_color_mapping() | +-------------------------------+-------------------------------------------+ | | set_node_color_mapping() | +-------------------------------+-------------------------------------------+ | | set_node_label_color_mapping() | +-------------------------------+-------------------------------------------+ | gen_edge_color_map() | set_edge_color_mapping() | +-------------------------------+-------------------------------------------+ | | set_edge_label_color_mapping() | +-------------------------------+-------------------------------------------+ | | set_edge_source_arrow_color_mapping() | +-------------------------------+-------------------------------------------+ | | set_edge_target_arrow_color_mapping() | +-------------------------------+-------------------------------------------+ | gen_node_opacity_map() | set_node_border_opacity_mapping() | +-------------------------------+-------------------------------------------+ | | set_node_fill_opacity_mapping() | +-------------------------------+-------------------------------------------+ | | set_node_label_opacity_mapping() | +-------------------------------+-------------------------------------------+ | | set_node_combo_opacity_mapping() | +-------------------------------+-------------------------------------------+ | gen_edge_opacity_map() | set_edge_label_opacity_mapping() | +-------------------------------+-------------------------------------------+ | | set_edge_opacity_mapping() | +-------------------------------+-------------------------------------------+ | gen_node_width_map() | set_node_border_width_mapping() | +-------------------------------+-------------------------------------------+ | | set_node_width_mapping() | +-------------------------------+-------------------------------------------+ | gen_edge_width_map() | set_edge_line_width_mapping() | +-------------------------------+-------------------------------------------+ | gen_node_height_map() | set_node_height_mapping() | +-------------------------------+-------------------------------------------+ | gen_node_size_map() | set_node_font_size_mapping() | +-------------------------------+-------------------------------------------+ | | set_node_size_mapping() | +-------------------------------+-------------------------------------------+ | gen_edge_size_map() | set_edge_font_size_mapping() | +-------------------------------+-------------------------------------------+ | gen_node_shape_map() | set_node_shape_mapping() | +-------------------------------+-------------------------------------------+ | gen_edge_line_style_map() | set_edge_line_style_mapping() | +-------------------------------+-------------------------------------------+ | gen_edge_arrow_map() | set_edge_source_arrow_shape_mapping() | +-------------------------------+-------------------------------------------+ | | set_edge_target_arrow_shape_mapping() | +-------------------------------+-------------------------------------------+

Most value generators accept a scheme parameter that indicates how mapped values should be generated. While the color mapping functions accept a scheme_* (as described above), numeric generators accept the scheme_number_series and scheme_number_random to mappings to serial or random values.

.. note:: When using a numerical value generator, you must provide both a scheme_number_* parameter and a scheme_number_params dictionary, which contains range information.

For example:

.. code:: python

set_node_fill_opacity_mapping(**gen_node_opacity_map('Degree', scheme_number_series, scheme_number_params={'start_value': 100, 'step': 20}, style_name='galFiltered Style'))

set_node_fill_opacity_mapping(**gen_node_opacity_map('Degree', scheme_number_random, scheme_number_params={'min_value': 10, 'max_value': 120}, style_name='galFiltered Style'))

Shape generators don't require a scheme parameter. For example:

.. code:: python

set_node_shape_mapping(**gen_node_shape_map('Degree', style_name='galFiltered Style'))

set_edge_source_arrow_shape_mapping(**gen_edge_arrow_map('interaction', style_name='galFiltered Style'))

set_edge_target_arrow_shape_mapping(**gen_edge_arrow_map('interaction', style_name='galFiltered Style'))
AlexanderPico commented 3 years ago

@yihangx Could you start on translating these from py4cy? It would be great to have these in for this release.

m-grimes commented 3 years ago

I look forward to testing these in R. Thank for keeping up with this!

Mark

University of Montana

On 9 May 2021, at 19:07, Barry Demchak wrote:

I think I may be done with this ... I'll verify the implementation via the GangSu workflow.

Meanwhile, I have added new function signatures to automation_api_definition, and bumped the current version to 1.2.0 ... see https://docs.google.com/spreadsheets/d/1XLWsKxGLqcBWLzoW2y6HyAUU2jMXaEaWw7QLn3NE5nY/edit#gid=329629714

Here is the documentation I added to the py4cytoscape manual ... it's slightly different than the original proposal, mainly reflecting a few issues I didn't originally think of:

Value Generators

You can set visual graph attributes (e.g., color, size, opacity and shapes) according to attributes assigned to nodes or edges by using Style Mapping functions such as set_node_color_mapping() or set_node_size_mapping(). As described in the Cytoscape Manual <http://manual.cytoscape.org/en/stable/Styles.html#how-mappings-work> _, there are three different ways to mapping node or edge attributes to visual attributes.

Briefly:

  • continuous mappings map a range of values to a range of sizes or a color gradient
  • discrete mappings allow specific values to map to specific sizes or colors
  • passthrough mappings allow node or edge labels to be taken from node or edge attributes

A value generator <http://manual.cytoscape.org/en/stable/Styles.html#automatic-value-generators> _ makes discrete mapping more convenient by creating automatic mappings between attribute values and visual styles. It automatically determines the unique values of a particular node or edge attribute, then allows you to choose a mapping to colors, sizes, opacities or shapes. For example, you can use a value generator to map a node with a Degree attribute having values 1, 10 and 20 to node fill colors of Red, Blue or Green ... or to a node size of 100, 150 or 200 ... or to circle, square or diamond shapes.

Essentially, a value generator spares you from having to know both the specific values of a node or edge attribute and the specific visual attributes to display ... it lets you focus on whether to render the attribute as a color, size, opacity or shape.

For example, to set a node's fill color based on its Degree attribute using a style mapping function, you could use the longhand (without value generator) where you know the unique Degree values in advance and choose specific colors to represent them:

.. code:: python

set_node_color_mapping('Degree', ['1', '10', '20'], ['#FF0000', 

'#00FF00', '#0000FF], 'd', style_name='galFiltered Style')

or you could use a color value generator that determines the unique Degree values and assigns each to a different color in a Brewer palette:

.. code:: python

set_node_color_mapping(**gen_node_color_map('Degree', 

scheme_color_brewer_accent, style_name='galFiltered Style'))

The general methodology is to use the value generator (e.g., gen_node_color_map()) as the sole parameter to a style mapping function, binding it by using the Python ** operator. The color value generators accept all of the same parameters as the color-oriented style mapping functions, and provides the same defaults for them. So,

.. code:: python

set_node_color_mapping(**gen_node_color_map('Degree', 

scheme_color_brewer_accent, style_name='galFiltered Style'))

is the equivalent of:

.. code:: python

set_node_color_mapping(table_column='Degree',
                       table_column_values=['8', '7', '6', '5', 

'4', '3', '2', '1'], colors=['#7FC97F', '#BEAED4', '#FDC086', '#FFFF99', '#386CB0', '#F0027F', '#BF5B17', '#666666'], mapping_type='d', default_color=None, style_name='galFiltered Style', network=None, base_url:'http://127.0.0.1:1234/v1')

The scheme_color_brewer_accent parameter is used to generate the specific colors values according to the predefined Brewer Accent palette. You can choose between any of the 8 Brewer Qualitative Palettes <https://colorbrewer2.org>_, which are widely regarded as aesthetic and visually effective.

+-----------------+-----------------------------------+ | Color Palette | scheme_color Parameter | +=================+===================================+ | Brewer Pastel2 | scheme_color_brewer_pastel2 | +-----------------+-----------------------------------+ | Brewer Pastel1 | scheme_color_brewer_pastel1 | +-----------------+-----------------------------------+ | Brewer Dark2 | scheme_color_brewer_dark2 | +-----------------+-----------------------------------+ | Brewer Accent | scheme_color_brewer_accent | +-----------------+-----------------------------------+ | Brewer Paired | scheme_color_brewer_paired | +-----------------+-----------------------------------+ | Brewer Set1 | scheme_color_brewer_set1 | +-----------------+-----------------------------------+ | Brewer Set2 | scheme_color_brewer_set2 | +-----------------+-----------------------------------+ | Brewer Set3 | scheme_color_brewer_set3 | +-----------------+-----------------------------------+ | Random | scheme_color_random | +-----------------+-----------------------------------+

.. note:: You can generate random colors by using the scheme_color_random scheme.

Similarly, there are value generators for opacities, sizes, heights, widths and shapes, with variants for node and edge values.

You can use a node value generator with a node mapping function, and you can use an edge value generator with an edge mapping function.

+-------------------------------+-------------------------------------------+ Generator Style Function
+===============================+===========================================+ gen_node_color_map() set_node_border_color_mapping()
+-------------------------------+-------------------------------------------+ set_node_color_mapping()
+-------------------------------+-------------------------------------------+ set_node_label_color_mapping()
+-------------------------------+-------------------------------------------+ gen_edge_color_map() set_edge_color_mapping()
+-------------------------------+-------------------------------------------+ set_edge_label_color_mapping()
+-------------------------------+-------------------------------------------+ set_edge_source_arrow_color_mapping() +-------------------------------+-------------------------------------------+
set_edge_target_arrow_color_mapping()
+-------------------------------+-------------------------------------------+ gen_node_opacity_map() set_node_border_opacity_mapping() +-------------------------------+-------------------------------------------+ set_node_fill_opacity_mapping()
+-------------------------------+-------------------------------------------+ set_node_label_opacity_mapping()
+-------------------------------+-------------------------------------------+ set_node_combo_opacity_mapping()
+-------------------------------+-------------------------------------------+ gen_edge_opacity_map() set_edge_label_opacity_mapping()
+-------------------------------+-------------------------------------------+ set_edge_opacity_mapping()
+-------------------------------+-------------------------------------------+ gen_node_width_map() set_node_border_width_mapping()
+-------------------------------+-------------------------------------------+ set_node_width_mapping()
+-------------------------------+-------------------------------------------+ gen_edge_width_map() set_edge_line_width_mapping()
+-------------------------------+-------------------------------------------+ gen_node_height_map() set_node_height_mapping()
+-------------------------------+-------------------------------------------+ gen_node_size_map() set_node_font_size_mapping()
+-------------------------------+-------------------------------------------+ set_node_size_mapping()
+-------------------------------+-------------------------------------------+ gen_edge_size_map() set_edge_font_size_mapping()
+-------------------------------+-------------------------------------------+ gen_node_shape_map() set_node_shape_mapping()
+-------------------------------+-------------------------------------------+ gen_edge_line_style_map() set_edge_line_style_mapping()
+-------------------------------+-------------------------------------------+ gen_edge_arrow_map() set_edge_source_arrow_shape_mapping() +-------------------------------+-------------------------------------------+
set_edge_target_arrow_shape_mapping()

+-------------------------------+-------------------------------------------+

Most value generators accept a scheme parameter that indicates how mapped values should be generated. While the color mapping functions accept a scheme_* (as described above), numeric generators accept the scheme_number_series and scheme_number_random to mappings to serial or random values.

.. note:: When using a numerical value generator, you must provide both a scheme_number_* parameter and a scheme_number_params dictionary, which contains range information.

For example:

.. code:: python

set_node_fill_opacity_mapping(**gen_node_opacity_map('Degree', 

scheme_number_series, scheme_number_params={'start_value': 100, 'step': 20}, style_name='galFiltered Style'))

set_node_fill_opacity_mapping(**gen_node_opacity_map('Degree', 

scheme_number_random, scheme_number_params={'min_value': 10, 'max_value': 120}, style_name='galFiltered Style'))

Shape generators don't require a scheme parameter. For example:

.. code:: python

set_node_shape_mapping(**gen_node_shape_map('Degree', 

style_name='galFiltered Style'))

set_edge_source_arrow_shape_mapping(**gen_edge_arrow_map('interaction', 

style_name='galFiltered Style'))

set_edge_target_arrow_shape_mapping(**gen_edge_arrow_map('interaction', 

style_name='galFiltered Style'))

-- You are receiving this because you are subscribed to this thread. Reply to this email directly or view it on GitHub: https://github.com/cytoscape/RCy3/issues/131#issuecomment-836028008

AlexanderPico commented 3 years ago

Porting py4cy code from here: https://github.com/cytoscape/py4cytoscape/blob/0.0.9/py4cytoscape/style_mappings.py#L1906

Also porting examples added to previous mapping functions, i.e., using the new scheme* generators.

AlexanderPico commented 3 years ago

Hmm... This doesn't port well to R. We can't pass in a map as a substitute for a set of args. The do.call function is used to accomplish this, but that defeats the purpose of the gen_* functions. I'm not seeing the advantage of the gen_* functions in RCy3, they just lead to longer (not short) function calls to perform the same thing. Will look into passing schemes directly into existing functions.

Also, the current set of schemes on py4cy 0.0.9 only addresses discrete mapping. Continous is also quite common (if not more common). Will look into using schemes for both of these use cases.

AlexanderPico commented 3 years ago
AlexanderPico commented 3 years ago

Ok. Slightly different strategy.

I. Created functions for color Brewer palettes

paletteColorBrewerAccent
paletteColorBrewerBlues
paletteColorBrewerBrBG
paletteColorBrewerBuGn
paletteColorBrewerBuPu
paletteColorBrewerDark2
paletteColorBrewerGnBu
paletteColorBrewerGreens
paletteColorBrewerGreys
paletteColorBrewerOrRd
paletteColorBrewerOranges
paletteColorBrewerPRGn
paletteColorBrewerPaired
paletteColorBrewerPastel1
paletteColorBrewerPastel2
paletteColorBrewerPiYG
paletteColorBrewerPuBu
paletteColorBrewerPuBuGn
paletteColorBrewerPuOr
paletteColorBrewerPuRd
paletteColorBrewerPurples
paletteColorBrewerRdBu
paletteColorBrewerRdPu
paletteColorBrewerRdYlBu
paletteColorBrewerReds
paletteColorBrewerSet1
paletteColorBrewerSet2
paletteColorBrewerSet3
paletteColorBrewerYlGn
paletteColorBrewerYlGnBu
paletteColorBrewerYlOrBr
paletteColorBrewerYlOrRd
paletteColorRandom

II. Created internal generator functions by type that work for node and edge cases.

.genColorMap
.genDimMap
.genLineStyleMap
.genArrowMap
.genShapeMap

III. Added check to existing mapping functions to:

Advantages:

  1. Works in R. I could not use the same function-to-gen-args-for-function approach as py4cy
  2. Works for common continuous and discrete mapping use cases
  3. Fewer functions to maintain