Color-mapping¶
Color mapping is a convenient method and syntax that allows for transforming/projecting raw data values into a range of valid color values drawn either from a discrete or continuous colormap. This is especially useful for visualizing a range of data values on the edges or nodes of a tree. (See Range-Mapping for a similar implementation to project continuous data to a new continuous range of values.)
import toytree
import toyplot
import numpy as np
Quick Example¶
In this example we will project Node "height" data from a tree into a range of colors. Color mapping can be applied to any continuous or discrete data feature. This is done using the function toytree.style.get_color_mapped_feature
, given a feature name and the name of a colormap. Below this function is used to return an array of mapped color values. This array could be passed as a node_color argument to the draw function, or, as demonstrated below, you can use the shortcut syntax by simply entering (feature-name, colormap-name)
as a tuple into the specific field.
# example tree
tree = toytree.rtree.bdtree(10, seed=123)
# example: project a data feature to an array of colors
toytree.style.get_color_mapped_feature(tree, "height", "Spectral")
# example: map node height to colors using '(feature, cmap)' syntax
tree.draw(
node_sizes=10,
node_mask=False,
node_colors=("height", "Spectral"),
);
Example Data¶
To demonstrate color-mapping we will use a 10 tip birth-death tree with three data features assigned to Nodes of the tree. The first feature "W" contains random float values between 0-1. The second "X" contains float values sampled along a continuous range from (0-100). The third feature contains discrete str values randomly sampled from ("A", "B", "C"). And the final feature contains the same data as feature "X", but with missing values for alternating Nodes.
# assign data to an example tree
tree = toytree.rtree.bdtree(10, seed=123)
rng = np.random.default_rng(seed=123)
tree.set_node_data("W", rng.random(tree.nnodes), inplace=True);
tree.set_node_data("X", np.linspace(0, 100, tree.nnodes), inplace=True);
tree.set_node_data("Y", rng.choice(["A", "B", "C"], tree.nnodes), inplace=True);
tree.set_node_data("Z", {i: i.X for i in tree[::2]}, inplace=True);
# show data in a table
tree.get_node_data(["W", "X", "Y", "Z"])
W | X | Y | Z | |
---|---|---|---|---|
0 | 0.682352 | 0.000000 | A | 0.000000 |
1 | 0.053821 | 5.555556 | B | NaN |
2 | 0.220360 | 11.111111 | C | 11.111111 |
3 | 0.184372 | 16.666667 | A | NaN |
4 | 0.175906 | 22.222222 | A | 22.222222 |
5 | 0.812095 | 27.777778 | A | NaN |
6 | 0.923345 | 33.333333 | A | 33.333333 |
7 | 0.276574 | 38.888889 | B | NaN |
8 | 0.819755 | 44.444444 | A | 44.444444 |
9 | 0.889893 | 50.000000 | B | NaN |
10 | 0.512970 | 55.555556 | B | 55.555556 |
11 | 0.244965 | 61.111111 | A | NaN |
12 | 0.824242 | 66.666667 | B | 66.666667 |
13 | 0.213763 | 72.222222 | A | NaN |
14 | 0.741467 | 77.777778 | A | 77.777778 |
15 | 0.629940 | 83.333333 | B | NaN |
16 | 0.927407 | 88.888889 | A | 88.888889 |
17 | 0.231908 | 94.444444 | C | NaN |
18 | 0.799125 | 100.000000 | A | 100.000000 |
Colors¶
toytree
relies on toyplot.color
for parsing color data and defining color maps. Please see the incredibly good color module documentation of toyplot for more details. Here I provide a simple introduction. You can select colors in three main ways: (1) CSS color name; (2) rgb tuple; or (3) rgba tuple.
# define colors by name, rgb, or rgba
color1 = "teal"
color2 = (0.1, 0.5, 0.5)
color3 = (0.1, 0.5, 0.5, 0.3)
Here I pass the three objects above to toyplot.color.Palette
, which is an object that can parse multiple different types of color inputs and store the results as an array. A palette represents the simplest form of ColorMap, containing a discrete collection of colors. This object has a nice property for displaying a color palette in the notebook, like below.
# create a Palette object to easily visualize
colors = toyplot.color.Palette([color1, color2, color3])
colors
Color array dtype¶
Colors in a toyplot.color
array are stored in a complex data format that allows for performing mathematical operations on colors, which allows for blending colors from a palette together to create gradients in colormaps. In most cases, this is an advanced feature you do not need to worry about. It is simplest to select colors by name, or by selecting them from a pre-defined palette or colormap, as described in the next section.
# the dtype of numpy array based colors in toyplot
colors[0].dtype
dtype([('r', '<f8'), ('g', '<f8'), ('b', '<f8'), ('a', '<f8')])
Color Palettes¶
There are a number of pre-defined color palettes that can be selected by name from the toyplot.color
subpackage named brewer
, which contains the popular "brewer2" color sets. The default color palette in toyplot
is called Set2, and can be selected like below. A number of "diverging" color palettes (see more below) can also be selected and discretized into a palette by using the count
argument to maximize divergence among the colors for the number of discrete states in a set of data.
# select a pre-defined discrete color palette
toyplot.color.brewer.palette("Set2")
# select a pre-defined diverging color palette
toyplot.color.brewer.palette("BlueRed", count=8)
# define a color palette manually
toyplot.color.Palette(['darkcyan', 'darkmagenta', 'goldenrod'])
ColorMaps¶
A colormap (toyplot.color.Map
) is a more advanced container for describing a distribution of colors.
There are two main types of colormaps, discrete and continous. A discrete map contains a small number of colors that are typically grouped together by a shared design palette with the goal of being maximally divergent from each other. By contrast, a continuous colormap represents colors sampled along a continuous range of RGBA values such that a gradient can be easily observed spanning from minimum to maximum values in the map. The default colormaps used by toytree
are "Spectral" for continous data and "Set2" for categorical.
# select a pre-defined diverging or linear colormap
toyplot.color.brewer.map("Spectral")
get_color_mapped_feature()
¶
Here I will demonstrate color mapping using a function from toytree
named get_color_mapped_feature
. In practice users can use this function to transform data from a set of values into a distribution of colors in a colormap, however, it is not required, since the simpler "tuple syntax" can also be used when entering drawing arguments. Nevertheless, this is the function that is used under the hood to map data to colors, so it helps to understand its options and how it works. This function accepts a number of arguments to specify a tree, a feature to select data from the tree, and a colormap name. It also accepts options that can limit the upper or lower limits of the colormapped distribution, or skew the distribution, and options for dealing with missing data, all of which are demonstrated below.
# project data 'X' to colors from a colormap
toytree.style.get_color_mapped_feature(tree, feature="X", cmap="Spectral")
Continuous map examples¶
Here the data are extracted from the "X" feature on the tree, which contians values equally spaced between 0-100 assigned in order to the 18 Nodes in the tree. Thus, the colors assigned to each Node match the gradient of the colormap. In the next example, the feature "W" is mapped to the same colormap, which yields a very different distribution of colors, since Nodes 0-17 contain random values for this feature.
# project data "X" to a named colormap
toytree.style.get_color_mapped_feature(tree, "X", "BlueRed")
# project data "W" to a named colormap
toytree.style.get_color_mapped_feature(tree, "W", "BlueRed")
The domain min or max can be set to limit the range of colors such that multiple values at the upper or lower end of the data map to the same color. For example, here we set the max to 50 even though the max of the data we are mapping ("X") is 100. Consequently, the color map range is concentrated between 0-50 and all values above 50 are assigned the max color. If min and max values are not set on a colormap then it will by default use the min and max values of the data being projected. Thus, it is only relevant to set these values if you wish to condense colors at one end or the other.
# create a ColorMap with a restricted range
toytree.style.get_color_mapped_feature(tree, "X", "BlueRed", domain_min=0, domain_max=50)
The order in which values are mapped to colors in a colomap can be reversed using the reverse
argument.
# create a reversed ColorMap by name
toytree.style.get_color_mapped_feature(tree, "X", "BlueRed", reverse=True)
The center of the colormap can be skewed towards one end or the other. Here it is set at 25, closer to the min value. This compresses the range of colors to the left of 25, and expands the range to right of 25. This can be useful if the variation among the larger values is of greater interest than among the lower values.
# create a ColorMap with a skewed center
cmap = toyplot.color.brewer.map("BlueRed", domain_min=0, domain_max=100, center=25)
toytree.style.get_color_mapped_feature(tree, "X", cmap)
By default colormapping in toytree
using get_color_mapped_feature
or the tuple syntax will assign "transparent" (i.e., rgba(0, 0, 0, 0)
) for missing values. You can change this behavior by entering a value to use for missing data instead using the nan_value
argument.
# default behavior sets 'transparent' to missing/NaN values
toytree.style.get_color_mapped_feature(tree, "Z", "BlueRed")
# setting 'nan_value' imputes a data value to be colormapped for missing
toytree.style.get_color_mapped_feature(tree, "Z", "BlueRed", nan_value=0)
Discrete map example¶
You can similarly map discrete/categorical data to colormaps. For this you can enter either a Categorical or Linear ColorMap, which in the latter case will reduce the linear map into equally spaced discrete colors. Like before you can either enter the name of a colormap, or create a ColorMap
object. As an even simpler option, you can simply enter a list of color names that is of the same length as the number of discrete states in the data, as shown below. Here the data are extracted from the "Y" feature on the tree which represents random discrete states of "A", "B" or "C".
# map a discrete feature to a list/Palette of color names
toytree.style.get_color_mapped_feature(tree, "Y", ["red", "blue", "green"])
# map a discrete feature to a discrete colormap
toytree.style.get_color_mapped_feature(tree, "Y", "Set2")
# map a discrete feature to a continuous colormap
toytree.style.get_color_mapped_feature(tree, "Y", "BlueRed")
By creating the ColorMap manually you can set more additional arguments, such as the number of states, which can be used to discretize colors at a more fine or coarse scale.
# enter a colormap object
cmap = toyplot.color.brewer.map("BlueRed", count=8)
toytree.style.get_color_mapped_feature(tree, "Y", cmap)
Best ColorMaps¶
These are are a few of my favorites:
toytree.style.get_color_mapped_feature(tree, "X", "BlueRed")
toytree.style.get_color_mapped_feature(tree, "X", "Spectral")
toytree.style.get_color_mapped_feature(tree, "X", "Blackbody")
toytree.style.get_color_mapped_feature(tree, "X", "Greys")
toytree.style.get_color_mapped_feature(tree, "Y", "Set1")
toytree.style.get_color_mapped_feature(tree, "Y", "Set2")
Using Color Mapping¶
For convenience you perform colormapping in toytree
drawing functions by using the tuple syntax, as a simpler alternative to calling the function get_color_mapped_feature
. This simply involves entering the feature name to be colormapped inside of a tuple as (feature-name,)
. You can provide additional arguments inside of the tuple, in order, to specify additional arguments available to the get_color_mapped_feature
function. For example, to use a specific colormap rather than the default one you can specify the colormap name as well as (feature-name, colormap-name)
. You can also add min, max, and nan_value arguments, as demonstrated below.
Tuple Syntax¶
Several examples of entering colormap arguments using the tuple syntax.
- (feature,)
- (feature, cmap)
- (feature, cmap, min_value, max_value)
- (feature, cmap, min_value, max_value, nan_value)
- (feature, None, None, None, 10)
# project "W" values to default 'Spectral' colormap
tree.draw(node_colors=("W",), node_sizes=10, node_mask=False);
# project "Y" values to discrete colors
tree.draw(node_colors=("Y", "BlueRed"), node_sizes=10, node_mask=False);
# project "X" in minmax range 0-50 using the default colormap
tree.draw(node_colors=("X", None, 0, 50), node_sizes=10, node_mask=False);
# project "Z" to "Greys" colormap with nan_values set to 0 (black)
tree.draw(node_colors=("Z", "Greys", None, None, 0), node_sizes=10, node_mask=False);
Colormapping by the tuple syntax is also supported when using the annotation
subpackage to add drawing annotations after initially creating a tree drawing, like in the example below.
# draw a tree and add node marker annotations w/ colormapped "X"
canvas, axes, mark = tree.draw();
tree.annotate.add_node_markers(axes, color=("X", "BlueRed"), mask=False);
Using toyplot.color.Map objects¶
The function get_color_mapped_feature
provides a convenient way to map data to colors that would otherwise require additional experience and knowledge about the toyplot.color
library. Nevertheless, advanced users may want to become familiar with the core objects and functions available in this library to expand the options that are available. In addition to selecting a colormap by name when using colormapping in toytree
you can alternative pass it a toyplot.color.Map
object and it will use this instead. This example demonstrates creating a custom colormap that blends two colors.
# create a custom Diverging colormap
col0 = toyplot.color.css("goldenrod")
col1 = toyplot.color.css("darkcyan")
custom_map = toyplot.color.DivergingMap(low=col0, high=col1, domain_min=0, domain_max=1)
custom_map
tree.draw(node_sizes=10, node_colors=("W", custom_map), node_mask=False);