Source code for genometools.expression.visualize.corr_heatmap

# Copyright (c) 2016 Florian Wagner
#
# This file is part of GenomeTools.
#
# GenomeTools is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License, Version 3,
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""Module containing the `SampleCorrelationHeatmap` class.

"""

from __future__ import (absolute_import, division,
                        print_function, unicode_literals)
from builtins import *

import os
import logging

from plotly import graph_objs as go
import numpy as np

import genometools
from .. import ExpMatrix
from . import read_colorscale

# from . import HeatmapSampleAnnotation, HeatmapBlockAnnotation

from collections import Iterable

logger = logging.getLogger(__name__)


[docs]class SampleCorrelationHeatmap(object): """A sample correlation heatmap.""" _default_cmap_file = genometools._root.rstrip(os.sep) + os.sep + \ os.sep.join(['data', 'RdBu_r_colormap.tsv']) def __init__(self, corr_matrix, sample_annotations=None, block_annotations=None, colorscale=None, colorbar_label=None): if sample_annotations is None: sample_annotations = [] if block_annotations is None: block_annotations = [] if colorscale is None: # use default colorscale colorscale = read_colorscale(self._default_cmap_file) assert isinstance(corr_matrix, ExpMatrix) assert isinstance(sample_annotations, Iterable) assert isinstance(block_annotations, Iterable) assert isinstance(colorscale, Iterable) if colorbar_label is not None: assert isinstance(colorbar_label, str) # make sure correlation matrix is square assert corr_matrix.shape[0] == corr_matrix.shape[1] self.corr_matrix = corr_matrix self.sample_annotations = sample_annotations self.block_annotations = block_annotations self.colorscale = colorscale self.colorbar_label = colorbar_label
[docs] def get_figure(self, **kwargs): """Get a plotly figure of the heatmap.""" emin = kwargs.pop('emin', -1.0) emax = kwargs.pop('emax', 1.0) width = kwargs.pop('width', 800) height = kwargs.pop('height', 600) margin_left = kwargs.pop('margin_left', 100) margin_bottom = kwargs.pop('margin_bottom', 60) margin_top = kwargs.pop('margin_top', 30) margin_right = kwargs.pop('margin_right', 0) colorbar_size = kwargs.pop('colorbar_size', 0.4) xaxis_label = kwargs.pop('xaxis_label', None) yaxis_label = kwargs.pop('yaxis_label', None) xticks_angle = kwargs.pop('xaxis_angle', 30) font = kwargs.pop('font', '"Droid Serif", "Open Serif", serif') font_size = kwargs.pop('font_size', 12) title = kwargs.pop('title', None) title_font_size = kwargs.pop('title_font_size', None) annotation_font_size = kwargs.pop('annotation_font_size', None) show_sample_labels = kwargs.pop('show_sample_labels', 'x') if show_sample_labels not in ['none', 'x', 'y', 'both']: raise ValueError('"show_sample_labels" must be "none", "x", "y", ' 'or "both".') padding_top = kwargs.pop('padding_top', 0.1) padding_right = kwargs.pop('padding_top', 0.1) xaxis_nticks = kwargs.pop('xaxis_nticks', None) #yaxis_nticks = kwargs.pop('yaxis_nticks', None) if title_font_size is None: title_font_size = font_size if annotation_font_size is None: annotation_font_size = font_size colorbar_label = self.colorbar_label or 'Pearson Correlation' ### set up heatmap colorbar = go.ColorBar( lenmode='fraction', len=colorbar_size, title=colorbar_label, titlefont = dict( size=title_font_size, ), titleside='right', xpad=0, ypad=0, outlinewidth=0, # no border thickness=20, # in pixels # outlinecolor = '#000000', ) def fix_plotly_label_bug(labels): """ This fixes a bug whereby plotly treats labels that look like numbers (integers or floats) as numeric instead of categorical, even when they are passed as strings. The fix consists of appending an underscore to any label that looks like a number. """ assert isinstance(labels, Iterable) fixed_labels = [] for l in labels: try: float(l) except ValueError: fixed_labels.append(str(l)) else: fixed_labels.append(str(l) + '_') return fixed_labels x = fix_plotly_label_bug(self.corr_matrix.samples) y = x data = [ go.Heatmap( z=self.corr_matrix.X, x=x, y=y, zmin=emin, zmax=emax, colorscale=self.colorscale, colorbar=colorbar, hoverinfo='x+y+z', **kwargs ), ] xshowticklabels = False yshowticklabels = False ### set up layout if show_sample_labels == 'x': xshowticklabels = True elif show_sample_labels == 'y': yshowticklabels = True elif show_sample_labels == 'both': xshowticklabels = True yshowticklabels = True xticks = 'outside' yticks = 'outside' if xaxis_label is None: if self.corr_matrix.samples.name is not None: xaxis_label = self.corr_matrix.samples.name else: xaxis_label = 'Samples' xaxis_label = xaxis_label + ' (n = %d)' % self.corr_matrix.n if yaxis_label is None: yaxis_label = xaxis_label layout = go.Layout( width=width, height=height, title=title, titlefont=go.Font( size=title_font_size ), font=go.Font( size=font_size, family=font ), xaxis=go.XAxis( title=xaxis_label, titlefont=dict(size=title_font_size), showticklabels=xshowticklabels, ticks=xticks, nticks=xaxis_nticks, tickangle=xticks_angle, #range=[-0.5, self.corr_matrix.n-0.5], showline=True, zeroline=False, showgrid=False, ), yaxis=go.YAxis( title=yaxis_label, titlefont=dict(size=title_font_size), showticklabels=yshowticklabels, ticks=xticks, nticks=xaxis_nticks, autorange='reversed', showline=True, zeroline=False, showgrid=False, ), margin=go.Margin( l=margin_left, t=margin_top, b=margin_bottom, r=margin_right, pad=0 ), ) ### add annotations # we need separate, but overlaying, axes to place the annotations layout['xaxis2'] = go.XAxis( overlaying='x', showline=False, tickfont=dict(size=0), autorange=False, #range=[-0.5, self.corr_matrix.n-0.5], range=[-0.5, self.corr_matrix.n-0.5], ticks='', showticklabels=False, zeroline=False, showgrid=False, ) layout['yaxis2'] = go.YAxis( overlaying='y', showline=False, tickfont=dict(size=0), autorange=False, range=[self.corr_matrix.n-0.5, -0.5], ticks='', showticklabels=False, zeroline=False, showgrid=False, ) # generate coordinates and labels for the block annotations k = len(self.block_annotations) block_coords = np.zeros((k, 2), dtype=np.float64) block_labels = [] for i, ann in enumerate(self.block_annotations): block_coords[i, :] = [ann.start_index-0.5, ann.end_index+0.5] block_labels.append(ann.label) # this produces the squares for the block annotations for i in range(k): mn = block_coords[i, 0] mx = block_coords[i, 1] data.append( go.Scatter( x=[mn, mx, mx, mn, mn], y=[mn, mn, mx, mx, mn], mode='lines', hoverinfo='none', showlegend=False, line=dict(color='black'), xaxis='x2', yaxis='y2', ) ) # - this produces the square labels for the block annotations # - we use plotly annotations, so that the labels are not limited # by the plotting area for i in range(k): mn = block_coords[i, 0] mx = block_coords[i, 1] layout.annotations.append( dict( x=mx, y=(mn+mx)/2.0, text=block_labels[i], xref='x2', yref='y2', showarrow=False, #ax=20, #ay=-20, xanchor='left', yanchor='middle', font=dict( color='black', size=annotation_font_size, family='serif bold', ), bgcolor='white', opacity=0.7, #textanchor='top right' ) ) fig = go.Figure( data=data, layout=layout ) return fig