# BSD 3-Clause License
#
# Copyright (c) 2016-17, University of Liverpool
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
A module to produce a precision evaluation plot
"""
from __future__ import division
from __future__ import print_function
__author__ = "Felix Simkovic"
__date__ = "07 Feb 2017"
__version__ = "0.1"
import matplotlib.pyplot as plt
import numpy as np
from conkit.plot._figure import Figure
from conkit.plot._plottools import ColorDefinitions
[docs]class PrecisionEvaluationFigure(Figure):
"""A Figure object specifically for a Precision evaluation.
This figure will illustrate the precision scores of a contact
map at different precision scores. These can be determined at
various start and end points with different stepwise increases
in between.
Attributes
----------
hierarchy : :obj:`conkit.core.ContactMap`
The contact map hierarchy
cutoff_step : float
The cutoff step
min_cutoff : float
The minimum cutoff factor
max_cutoff : float
The maximum cutoff factor
Examples
--------
>>> import conkit
>>> cmap = conkit.io.read('toxd/toxd.mat', 'ccmpred').top_map
>>> cmap.sequence = conkit.io.read('toxd/toxd.fasta', 'fasta').top_sequence
>>> pdb = conkit.io.read('toxd/toxd.pdb', 'pdb').top_map
>>> cmap.match(pdb, inplace=True)
>>> conkit.plot.PrecisionEvaluationFigure(cmap)
"""
def __init__(self, hierarchy, min_cutoff=0.0, max_cutoff=100.0, cutoff_step=0.2, **kwargs):
"""A precision evaluation figure
Parameters
----------
hierarchy : :obj:`conkit.core.ContactMap`
The contact map hierarchy
min_cutoff : float, optional
The minimum factor
max_cutoff : float, optional
The maximum facotr
cutoff_step : float, optional
The cutoff step
**kwargs
General :obj:`Figure <conkit.plot._Figure.Figure>` keyword arguments
"""
super(PrecisionEvaluationFigure, self).__init__(**kwargs)
self._hierarchy = None
self._cutoff_boundaries = [0.0, 100.0]
self._cutoff_step = 0.2
self.hierarchy = hierarchy
self.cutoff_step = cutoff_step
self.min_cutoff = min_cutoff
self.max_cutoff = max_cutoff
self._draw()
def __repr__(self):
return "{0}(file_name=\"{1}\")".format(
self.__class__.__name__, self.file_name
)
@property
def cutoff_step(self):
"""The cutoff step"""
return self._cutoff_step
@cutoff_step.setter
def cutoff_step(self, cutoff_step):
"""Define the cutoff step"""
self._cutoff_step = cutoff_step
@property
def hierarchy(self):
"""A ConKit :obj:`conkit.core.ContactMap`"""
return self._hierarchy
@hierarchy.setter
def hierarchy(self, hierarchy):
"""Define the ConKit :obj:`conkit.core.ContactMap`
Raises
------
RuntimeError
The hierarchy is not an alignment
"""
if hierarchy:
Figure._check_hierarchy(hierarchy, "ContactMap")
self._hierarchy = hierarchy
@property
def min_cutoff(self):
"""The minimum cutoff factor
Raises
------
ValueError
The minimum cutoff value is larger than or equal to the maximum
"""
if self._cutoff_boundaries[0] >= self._cutoff_boundaries[1]:
msg = "The minimum cutoff value is larger than or equal to the maximum"
raise ValueError(msg)
return self._cutoff_boundaries[0]
@min_cutoff.setter
def min_cutoff(self, min_cutoff):
"""Define the minimum cutoff factor"""
if min_cutoff < 0.0:
raise ValueError("Minimum factor cannot be negative")
self._cutoff_boundaries[0] = min_cutoff
@property
def max_cutoff(self):
"""The maximum cutoff factor
Raises
------
ValueError
The maximum cutoff value is smaller than the the minimum
"""
if self._cutoff_boundaries[1] < self._cutoff_boundaries[0]:
msg = "The maximum cutoff value is smaller than the the minimum"
raise ValueError(msg)
return self._cutoff_boundaries[1]
@max_cutoff.setter
def max_cutoff(self, max_cutoff):
"""Define the maximum cutoff factor"""
if max_cutoff > 100:
raise ValueError("Maximum factor cannot be greater than 100")
self._cutoff_boundaries[1] = max_cutoff
def _draw(self):
"""Draw the actual plot"""
factors = np.arange(self.min_cutoff, self.max_cutoff + 0.1, self.cutoff_step)
precisions = np.zeros(factors.shape[0])
for i, factor in enumerate(factors):
ncontacts = int(self._hierarchy.sequence.seq_len * factor)
m = self._hierarchy[:ncontacts]
precisions[i] = m.precision
fig, ax = plt.subplots()
# Add data points itself
ax.plot(factors, precisions, color=ColorDefinitions.GENERAL, marker='o', markersize=5, linestyle='-',
label='Precision score', zorder=1)
# Add indicator lines for clarity of data
ax.axhline(0.5, color=ColorDefinitions.PRECISION50, linestyle='-', label='50% Precision', zorder=0)
if self.min_cutoff <= 1.0:
ax.axvline(1.0, color=ColorDefinitions.FACTOR1, linestyle='-', label='Factor L', zorder=0)
if self.min_cutoff <= 0.5:
ax.axvline(0.5, color=ColorDefinitions.FACTOR1, linestyle='--', label='Factor L/2', zorder=0)
if self.min_cutoff <= 0.2:
ax.axvline(0.2, color=ColorDefinitions.FACTOR1, linestyle='-.', label='Factor L/5', zorder=0)
if self.min_cutoff <= 0.1:
ax.axvline(0.1, color=ColorDefinitions.FACTOR1, linestyle=':', label='Factor L/10', zorder=0)
# Prettify the plot
ax.set_xlim(self.min_cutoff, self.max_cutoff)
xticks = (ax.get_xticks() * self._hierarchy.sequence.seq_len).astype(np.int64)
ax.set_xticklabels(xticks)
ax.set_ylim(0.0, 1.0)
ax.set_xlabel('Number of Contacts')
ax.set_ylabel('Precision')
ax.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, ncol=3, mode="expand", borderaxespad=0.)
# Make axes length proportional and remove whitespace around the plot
aspectratio = Figure._correct_aspect(ax, 0.3)
ax.set(aspect=aspectratio)
fig.tight_layout()
fig.savefig(self.file_name, bbox_inches='tight', dpi=self.dpi)