plot module
survivalvolume/plot.py
Functions and classes for plotting tumour volume vs time and survival endpoints based on volume thresholds
Created by Matthew Wakefield. Copyright (c) 2016 Matthew Wakefield, The Walter and Eliza Hall Institute and The University of Melbourne. All rights reserved.
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.
#!/usr/bin/env python3 # encoding: utf-8 """ survivalvolume/plot.py Functions and classes for plotting tumour volume vs time and survival endpoints based on volume thresholds Created by Matthew Wakefield. Copyright (c) 2016 Matthew Wakefield, The Walter and Eliza Hall Institute and The University of Melbourne. All rights reserved. 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. """ import pandas import matplotlib import mpld3 import lifelines from lifelines.statistics import logrank_test import numpy as np import matplotlib.pyplot as plt import scipy.stats from mpld3 import plugins, utils __author__ = "Matthew Wakefield" __copyright__ = "Copyright 2016 Matthew Wakefield, The Walter and Eliza Hall Institute and The University of Melbourne" __credits__ = ["Matthew Wakefield",] __license__ = "GPLv3" __version__ = "1.2.0" __maintainer__ = "Matthew Wakefield" __email__ = "wakefield@wehi.edu.au" __status__ = "production" def version(): """Return the symatic versioning format version number""" return __version__ def get_survival_status(tv_series, endpoint=700): """Convert a pandas series of tumour volume measurements organised with one individual per column and indexed by time to a pandas data frame with columns for time and endpoint observed (the inverse of censored) and indexed by individual. Arguments: tv_series - a pandas series of tumour volume measurements indexed by time endpoint - the volume threshold at which and endpoint event is deemed to have occurred Returns: time - the last time index observed observed - a boolean for whether the endpoint has been reached (inverse of censored status) """ tv_series = tv_series.dropna() observed = True try: if tv_series.iloc[-1] < endpoint: observed = False return tv_series.index[-1], observed except: print(tv_series) raise def volume_to_survival(tv_data, endpoint=700): """Convert a pandas data frame of tumour volume measurements organised with one individual per column and indexed by time to a pandas data frame with columns for time and endpoint observed (the inverse of censored) and indexed by individual. Arguments: tv_data - a pandas data frame of volume measurements arranged with individuals in columns and indexed by time endpoint - the volume threshold at which and endpoint event is deemed to have occurred Returns: A pandas data frame with columns 'Time' and 'Observed' indexed by individuals (column names in tv_data) Time - the last time index observed Observed - a boolean for whether the endpoint has been reached (inverse of censored status) """ survival = pandas.DataFrame( {x:get_survival_status(tv_data[x],endpoint=endpoint) \ for x in tv_data if tv_data[x].any()} ).T survival.columns=['Time','Observed'] return survival def make_km(tv_data, label='Untitled',endpoint=700): """Construct a Kaplan-Meier function for a dataframe of tumour volume measurements Arguments: tv_data - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint label - a title for this grouping endpoint - the volume at which the endpoint is reached Default: 700 Returns: a lifelines KaplanMeierFitter object """ survival = volume_to_survival(tv_data, endpoint=endpoint) kmf = lifelines.KaplanMeierFitter() kmf.fit(survival['Time'],event_observed=survival['Observed'],label=label) return kmf class HighlightLines(plugins.PluginBase): #pragma non cover """A plugin to highlight lines on hover Adapted from mpld3 example code""" JAVASCRIPT = """ mpld3.register_plugin("linehighlight", LineHighlightPlugin); LineHighlightPlugin.prototype = Object.create(mpld3.Plugin.prototype); LineHighlightPlugin.prototype.constructor = LineHighlightPlugin; LineHighlightPlugin.prototype.requiredProps = ["line_ids"]; LineHighlightPlugin.prototype.defaultProps = {alpha_bg:0.3, alpha_fg:1.0} function LineHighlightPlugin(fig, props){ mpld3.Plugin.call(this, fig, props); }; LineHighlightPlugin.prototype.draw = function(){ for(var i=0; i<this.props.line_ids.length; i++){ var obj = mpld3.get_element(this.props.line_ids[i], this.fig), alpha_fg = this.props.alpha_fg; alpha_bg = this.props.alpha_bg; obj.elements() .on("mouseover.highlight", function(d, i){ d3.select(this).transition().duration(50) .style("stroke-opacity", alpha_fg); }) .on("mouseout.highlight", function(d, i){ d3.select(this).transition().duration(200) .style("stroke-opacity", alpha_bg); }); } }; """ def __init__(self, lines): self.lines = lines self.dict_ = {"type": "linehighlight", "line_ids": [utils.get_id(line) for line in lines], "alpha_bg": lines[0].get_alpha(), "alpha_fg": 1.0} class TumourVolumePlot(): """Construct a plot of volume vs time This plot is designed to consist of measurements for individuals, a mean volume for the group and a standard error of the mean. When used to produce html the resulting graph is interactive with mouse over highlighting of individuals and labeling with the column name. Static pdfs can also be generated. """ def __init__(self, **kw): """Initiate a new tumour TumourVolumePlot object Arguments: **kw - keyword arguments are passed to matplotlib.pyplot.subplots """ self.fig, self.ax = plt.subplots(**kw) self.lines = {} self.means = {} self.intervals = {} self.title = 'Untitled' self.ylabel = 'Tumour Volume mm$^{3}$' self.xlabel = 'Days' self.xlim = None self.ylim = None self.fontsize = None pass def remove_legend(self): """Remove any existing matplotlib axis legend""" if self.ax.legend_: self.ax.legend_.remove() pass def set_spines_and_ticks(self, axes = None, remove_spines=['right','top'], y_set_ticks = ['left'], x_set_ticks = ['bottom'], ): """Set spines (the lines along the axis) and tick locations for the plot Arguments: axes - the matplotlib.axis object to act on Default: None (interpreted as self.ax) remove_spines - a list of spines to remove. Default: ['right','top'] y_set_ticks - a list of locations of y axis ticks Default: ['left'] x_set_ticks - a list of locations of x axis ticks Default: ['bottom'] """ if not axes: axes = [self.ax,] for ax in axes: for spine in remove_spines: ax.spines[spine].set_visible(False) for pos in y_set_ticks: ax.yaxis.set_ticks_position(pos) for pos in x_set_ticks: ax.xaxis.set_ticks_position(pos) if self.fontsize: for item in (ax.get_xticklabels() + ax.get_yticklabels()): item.set_fontsize(self.fontsize) pass def set_limits(self): """Apply the axis limits set by self.xlim and self.ylim to the graph axis""" if self.xlim or self.ylim: self.ax.set_xlim(self.xlim) ylim = self.ylim if self.ylim else [0,1000] self.ax.set_ylim(ylim) self.ax.set_autoscaley_on(False) else: self.ax.set_autoscaley_on(True) pass def display(self, legend=True, update=True, use_mpld3=True, hide_volume_labels = False, **kw): """Generate the figure and return an object for display This function is for viewing figures in a Jupyter notebook Arguments: legend - boolean for displaying a legend Default: True update - boolean for reapplying spine, tick, title and axis limits to the graph Default: True use_mpld3 - boolean for using mpld3 to produce interactive html Default: True hide_volume_labels - boolean for hiding x axis volume labels Default: False **kw - additional keyword arguments are passed to mpld3.display """ if legend == True: self.add_legend() if legend == 'custom': pass else: self.remove_legend() if update: self.set_spines_and_ticks() self.set_title_and_labels(hide_volume_labels = hide_volume_labels) self.set_limits() if use_mpld3: return mpld3.display(self.fig, **kw) else: return self.fig def save_html(self, fileobj=None, legend=True, update=True, template_type = "general", **kw): """Generate the figure as a html file Arguments: fileobj - a python file object Default: None (saved to a new file with the same name as the parent object with .html) legend - boolean for displaying a legend Default: True update - boolean for reapplying spine, tick, title and axis limits to the graph Default: True template_type - string specifying the type of HTML template to use. Options from mpld3 are: "simple" suitable for simple html page with one figure. Will fail if require.js is available on the page. "notebook" assumes require.js and jquery are available. "general" works both in and out of the notebook, whether or not require.js and jquery are available Default: general **kw - additional keyword arguments are passed to mpld3.save_html """ if legend: self.add_legend() else: self.remove_legend() if not fileobj: fileobj='{0}.html'.format(self.name) if update: self.set_spines_and_ticks() self.set_title_and_labels() self.set_limits() mpld3.save_html(fig=self.fig, fileobj=fileobj, template_type=template_type, **kw) pass def save_pdf(self, fileobj=None, legend=True, update=True, **kw): """Generate the figure as a pdf file Arguments: fileobj - a python file object Default: None (saved to a new file with the same name as the parent object with .html) legend - boolean for displaying a legend Default: True update - boolean for reapplying spine, tick, title and axis limits to the graph Default: True **kw - additional keyword arguments are passed to matplotlib.PdfPages.savefig """ if legend: self.add_legend() else: self.remove_legend() if update: self.set_spines_and_ticks() self.set_title_and_labels() self.set_limits() if not fileobj: fileobj='{0}.html'.format(self.name) from matplotlib.backends.backend_pdf import PdfPages with PdfPages(fileobj) as pdf: pdf.savefig(self.fig, **kw) pass def add_individuals(self, name, tv_table, color = 'black', alpha=0.1, lw=3, dashes = [2,2], **kw): """Add line plots of individual volume vs time for all individuals in a tumour volume table. These should be a logical group (eg treatment) When used to produce html plots mouse over events will make the line opaque (alpha=1.0) and label the line with the column name Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.plot and can be any matplotlib.Line2D attributes """ self.lines[name] = self.ax.plot(tv_table.index, tv_table, color=color, alpha=alpha, lw=lw, dashes=dashes, **kw) plugins.connect(self.fig, HighlightLines(self.lines[name])) for i, l in enumerate(self.lines[name]): plugins.connect(self.fig, plugins.LineLabelTooltip(l, str(tv_table.columns[i]))) pass def add_mean(self, name, tv_table, threshold=2, color = 'black', alpha=0.8, lw=4, dashes = [], **kw): """Calculate and add to the plot a line for the mean volume vs time for all individuals in a tumour volume table. These individuals should be a logical group (eg treatment) Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint threshold - the minimum group size to plot at a given time point. Default: 2 color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.plot and can be any matplotlib.Line2D attributes """ self.means[name] = self.ax.plot(tv_table[tv_table.count(axis=1) > threshold].index, tv_table[tv_table.count(axis=1) > threshold].mean(axis=1), color=color, alpha=alpha, lw=lw, dashes=dashes, **kw) pass def _calc_t_ci(self, tv_table, ci=0.95): uppers = [] lowers = [] means = [] for entry in tv_table.T: data = tv_table.T[entry].dropna() mean = np.mean(data) (lower,upper) = scipy.stats.t.interval(0.95, df=len(data)-1, loc=mean, scale=np.std(data,ddof=1) / np.sqrt(len(data)), ) uppers.append(upper) lowers.append(lower) means.append(mean) cis = pandas.DataFrame({'mean':means, 'lower bound':[max(0,x) for x in lowers], #limit to +ve 'upper bound':uppers, }).dropna() cis.index = tv_table.index[:len(cis.index)] return cis def _calc_norm_ci(self, tv_table, ci=0.95): interval = scipy.stats.norm.interval(ci, loc=tv_table.mean(axis=1), scale=tv_table.sem(axis=1)) cis = pandas.DataFrame({'mean':tv_table.mean(axis=1), 'lower bound':[max(0,x) for x in interval[0]], #limit to +ve 'upper bound':interval[1], }).dropna() return cis def add_interval(self, name, tv_table, threshold=2, ci=0.95, color = 'black', alpha=0.2, lw=0, dashes = [], **kw): """Calculate the confidence interval of the mean and add to the plot as a shaded band around the mean. These individuals should be a logical group (eg treatment). Note that the value is the confidence interval for the standard error of the mean and indicates the range the mean is expected to lie within ci % of the time. This should not be confused with the standard deviation of the data (the range within individual data is expected to lie) or the standard error of the mean. As most data is expected to have a small number of observations, the 95% confidence interval is calculated using a t distribution. Results match R's t-test and Graphpad Prism's 95% CI Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint threshold - the minimum group size to plot a confidence interval for Default: 2 ci - the confidence interval range to plot Default: 0.95 color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.fill_between """ cis = self._calc_t_ci(tv_table[tv_table.count(axis=1) > threshold], ci=ci) self.intervals[name] = self.ax.fill_between([int(x) for x in cis.index], cis['lower bound'], cis['upper bound'], color=color, alpha=alpha, lw=lw, dashes=dashes, **kw) pass def set_title_and_labels(self, title=None, ylabel=None, xlabel=None, hide_volume_labels=False, **kw): """Set plot titles and axis labels Arguments: title - main plot title Default: None (uses self.title) ylabel - y axis label Default: None (uses self.ylabel) xlabel - x axis label Default: None (uses self.xlabel) hide_volume_labels - for compatibility with dual plot Default: False """ if title == None: title = self.title if ylabel == None: ylabel = self.ylabel if xlabel == None: xlabel = self.xlabel self.ax.set_title(title, **kw) self.ax.set_ylabel(ylabel, **kw) self.ax.set_xlabel(xlabel, **kw) pass def add_legend(self, loc='best', **kw): """Create a legend from axis data after removing any existing legends Arguments: loc - location of legend from: 'best','upper right', 'upper left','lower left','lower right','right', 'center left','center right','lower center', 'upper center','center' Default: 'best' **kw - keyword arguments passed to matplotlib.axes.legend """ self.remove_legend() patches = [] for key in self.means: color=self.means[key][0].get_color() patches.append(matplotlib.lines.Line2D([],[],color=color, label=key, lw=3)) self.ax.legend(handles=patches, loc=loc, **kw) pass def show_treatment_days(self, days, facecolor="lightgrey",lw=0, **kw): """Add rectangular patches on the x axis to indicate days on which treatment occurred. Arguments: days - an integer list of days eg [1,3,5] facecolor - the fill color for the rectangular patches Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) lw - the width of the line bordering the patch in points Default: 0 (no line) **kw - keyword arguments passed to matplotlib.patches.Rectangle """ for day in days: self.ax.add_patch(matplotlib.patches.Rectangle((day, 0), 1, 20, facecolor=facecolor, lw=lw, **kw)) pass def shade_interval(self, start, end, facecolor="lightgrey", alpha=0.2, lw=0, **kw): """Add rectangular fill the full height of the axis to indicate a date range (eg for treatment). Arguments: start - the start point of the rectangle end - the end point of the rectangle facecolor - the fill color for the rectangular patches Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) Default: 0.2 lw - the width of the line bordering the patch in points Default: 0 (no line) **kw - keyword arguments passed to matplotlib.patches.Rectangle """ miny,maxy = self.ax.get_yaxis().get_data_interval() self.ax.add_patch(matplotlib.patches.Rectangle((start, min(miny,self.ylim[0])), end, max(maxy,self.ylim[1]), facecolor="lightgrey",alpha=alpha, lw=0, **kw)) pass class VolumeSurvivalPlot(TumourVolumePlot): """Initiate a new dual panel Tumour Volume and Survival plot object Arguments: km_size - The relative size of the survival plot Default: 0.5 (ie half or 1/3 total plot area) vertical - Stack the Tumour Volume plot over the survival plot with a shared x axis (days) so events co occur. Default: True **kw - keyword arguments are passed to matplotlib.pyplot.subplots """ def __init__(self, km_size=0.5, vertical=True, **kw): if vertical: self.fig, (self.ax, self.km_ax) = plt.subplots(nrows=2, ncols=1, sharex=True, gridspec_kw={'height_ratios':[1,km_size]},**kw) else: self.fig, (self.ax, self.km_ax) = plt.subplots(nrows=1, ncols=2, gridspec_kw={'width_ratios':[1,km_size]},**kw) self.lines = {} self.means = {} self.kmfs = {} self.intervals = {} self.title = 'Untitled' self.ylabel = 'Tumour Volume mm$^{3}$' self.xlabel = 'Days' self.km_title = '' self.km_ylabel = 'Survival' self.km_xlabel = 'Days' self.km_yticks = [0,0.25,0.5,0.75,1.0] self.km_ci_show = True self.km_show_censors = True self.vertical = vertical self.volume_data = {} self.endpoint = None self.xlim = None self.ylim = None self.fontsize = None pass def remove_legend(self): """Remove any existing matplotlib axis legend""" if self.ax.legend_: self.ax.legend_.remove() if self.km_ax.legend_: self.km_ax.legend_.remove() pass def add_mean(self, name, tv_table, endpoint = 700, threshold=2, color = 'black', alpha=0.8, lw=4, dashes = [], **kw): """Calculate and add to the plot a line for the mean volume vs time for all individuals in a tumour volume table. These individuals should be a logical group (eg treatment) Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint endpoint - the volume at which the endpoint is reached Default: 700 threshold - the minimum group size to plot at a given time point. Default: 2 color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points Default: 4 dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.plot and lifelines.kmf.plot and can be any matplotlib.Line2D attributes """ super(VolumeSurvivalPlot, self).add_mean(name=name, tv_table=tv_table, threshold=threshold, color=color, alpha=alpha, lw=lw, dashes = dashes, **kw) self.volume_data[name] = tv_table self.add_kmf(name, tv_table, endpoint = endpoint, color = color, alpha=alpha, lw=lw, dashes = dashes, **kw) pass def add_kmf(self, name, tv_table, endpoint = 700, color = 'black', alpha=0.8, lw=4, dashes = [], **kw): """Calculate and add a Kaplan Meier curve to the second panel of the plot for all individuals in a tumour volume table. These individuals should be a logical group (eg treatment) Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint endpoint - the volume at which the endpoint is reached Default: 700 color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points Default: 4 dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to lifelines.kmf.plot and can be any matplotlib.Line2D attributes """ self.kmfs[name] = make_km(tv_table, label=name, endpoint=endpoint) self.endpoint = endpoint self.kmfs[name].plot(color = color, alpha=alpha, lw=lw, dashes = dashes, show_censors = self.km_show_censors, ci_show = self.km_ci_show, ax=self.km_ax, **kw) pass def set_title_and_labels(self, title=None, ylabel=None, xlabel=None, km_title=None, km_ylabel=None, km_xlabel=None, hide_volume_labels = True,**kw): """Set plot titles and axis labels Arguments: title - main plot title Default: None (uses self.title) ylabel - y axis label Default: None (uses self.ylabel) xlabel - x axis label Default: None (uses self.xlabel) kmtitle - Kaplan Meier plot label Default: None (uses self.km_title) ylabel - y axis label Default: None (uses self.km_ylabel) xlabel - x axis label Default: None (uses self.km_xlabel) hide_volume_labels - do not display x axis label between volume plot and Kaplan-Meier Default: True """ if title == None: title = self.title if ylabel == None: ylabel = self.ylabel if xlabel == None: xlabel = self.xlabel if km_title == None: km_title = self.km_title if km_ylabel == None: km_ylabel = self.km_ylabel if km_xlabel == None: km_xlabel = self.km_xlabel if self.fontsize and not 'fontsize' in kw: kw['fontsize'] = self.fontsize self.ax.set_title(title, **kw) self.ax.set_ylabel(ylabel, **kw) if hide_volume_labels: self.ax.set_xlabel('', **kw) else: self.ax.set_xlabel(xlabel, **kw) self.km_ax.set_title(km_title, **kw) self.km_ax.set_ylabel(km_ylabel, **kw) self.km_ax.set_xlabel(km_xlabel, **kw) pass def set_spines_and_ticks(self, axes = None, remove_spines=['right','top'], y_set_ticks = ['left'], x_set_ticks = ['bottom'], ): """Set spines (the lines along the axis) and tick locations for the plot Arguments: axes - the matplotlib.axis object to act on Default: None (interpreted as self.ax and self.km_ax) remove_spines - a list of spines to remove. Default: ['right','top'] y_set_ticks - a list of locations of y axis ticks Default: ['left'] x_set_ticks - a list of locations of x axis ticks Default: ['bottom'] """ if axes == None: axes = [self.km_ax, self.ax] super(VolumeSurvivalPlot, self).set_spines_and_ticks(axes = axes, remove_spines = remove_spines, y_set_ticks = y_set_ticks, x_set_ticks = x_set_ticks,) self.km_ax.yaxis.set_ticks(self.km_yticks) pass def set_limits(self): """Apply the axis limits set by self.xlim and self.ylim to the graph axes. The Kaplan-Meier y axis will remain [0,1.02]""" if self.xlim or self.ylim: self.ax.set_xlim(self.xlim) ylim = self.ylim if self.ylim else [0,1000] self.ax.set_ylim(ylim) self.ax.set_autoscaley_on(False) self.km_ax.set_xlim(self.xlim) self.km_ax.set_ylim([0,1.02]) #set >1 so lines retain full width self.km_ax.set_autoscaley_on(False) else: self.ax.set_autoscaley_on(True) self.km_ax.set_ylim([0,1.02]) self.km_ax.set_autoscaley_on(True) pass def logrank_test(self, treatment_a, treatment_b, t1error=0.05): """Calculate a log rank test (Mantel-Cox) statistic between two treatments Calls lifelines.statistics.logrank_test for calculation. Arguments: treatment_a - The legend label for this group as used with add_mean. treatment_b - The legend label for this group as used with add_mean. t1error - probability of a type 1 error (alpha) Default: 0.05 """ if not self.endpoint or not self.volume_data: print('you need to add data with .add_mean() before using logrank_test') raise ValueError survival_a = volume_to_survival(self.volume_data[treatment_a], endpoint=self.endpoint) survival_b = volume_to_survival(self.volume_data[treatment_b], endpoint=self.endpoint) result = logrank_test(list(survival_a['Time']), list(survival_b['Time']), list(survival_a['Observed']), list(survival_b['Observed']), alpha=1-t1error) result.print_summary() return result def display(self, legend=True, use_mpld3=True, update=True, hide_volume_labels=True, **kw): """Display the dual tumour volume and Kaplan Meier plot KNOWN BUG - mpld3 does not correctly display the step plot in version < 0.3 """ result = super(VolumeSurvivalPlot, self).display(legend=legend, use_mpld3=use_mpld3, update=update, hide_volume_labels = hide_volume_labels, **kw) if use_mpld3: return result
Functions
def get_survival_status(
tv_series, endpoint=700)
Convert a pandas series of tumour volume measurements organised with one individual per column and indexed by time to a pandas data frame with columns for time and endpoint observed (the inverse of censored) and indexed by individual.
Arguments:
tv_series - a pandas series of tumour volume measurements
indexed by time
endpoint - the volume threshold at which and endpoint
event is deemed to have occurred
Returns:
time - the last time index observed
observed - a boolean for whether the endpoint has
been reached (inverse of censored status)
def get_survival_status(tv_series, endpoint=700): """Convert a pandas series of tumour volume measurements organised with one individual per column and indexed by time to a pandas data frame with columns for time and endpoint observed (the inverse of censored) and indexed by individual. Arguments: tv_series - a pandas series of tumour volume measurements indexed by time endpoint - the volume threshold at which and endpoint event is deemed to have occurred Returns: time - the last time index observed observed - a boolean for whether the endpoint has been reached (inverse of censored status) """ tv_series = tv_series.dropna() observed = True try: if tv_series.iloc[-1] < endpoint: observed = False return tv_series.index[-1], observed except: print(tv_series) raise
def make_km(
tv_data, label='Untitled', endpoint=700)
Construct a Kaplan-Meier function for a dataframe of tumour volume measurements
Arguments:
tv_data - a pandas data frame of volume measurements
with individuals in columns and timepoints
as rows. Individuals are removed from study
at the first NaN timepoint
label - a title for this grouping
endpoint - the volume at which the endpoint is reached
Default: 700
Returns:
a lifelines KaplanMeierFitter object
def make_km(tv_data, label='Untitled',endpoint=700): """Construct a Kaplan-Meier function for a dataframe of tumour volume measurements Arguments: tv_data - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint label - a title for this grouping endpoint - the volume at which the endpoint is reached Default: 700 Returns: a lifelines KaplanMeierFitter object """ survival = volume_to_survival(tv_data, endpoint=endpoint) kmf = lifelines.KaplanMeierFitter() kmf.fit(survival['Time'],event_observed=survival['Observed'],label=label) return kmf
def version(
)
Return the symatic versioning format version number
def version(): """Return the symatic versioning format version number""" return __version__
def volume_to_survival(
tv_data, endpoint=700)
Convert a pandas data frame of tumour volume measurements organised with one individual per column and indexed by time to a pandas data frame with columns for time and endpoint observed (the inverse of censored) and indexed by individual.
Arguments:
tv_data - a pandas data frame of volume measurements
arranged with individuals in columns and
indexed by time
endpoint - the volume threshold at which and endpoint
event is deemed to have occurred
Returns:
A pandas data frame with columns 'Time' and 'Observed'
indexed by individuals (column names in tv_data)
Time - the last time index observed
Observed - a boolean for whether the endpoint has
been reached (inverse of censored status)
def volume_to_survival(tv_data, endpoint=700): """Convert a pandas data frame of tumour volume measurements organised with one individual per column and indexed by time to a pandas data frame with columns for time and endpoint observed (the inverse of censored) and indexed by individual. Arguments: tv_data - a pandas data frame of volume measurements arranged with individuals in columns and indexed by time endpoint - the volume threshold at which and endpoint event is deemed to have occurred Returns: A pandas data frame with columns 'Time' and 'Observed' indexed by individuals (column names in tv_data) Time - the last time index observed Observed - a boolean for whether the endpoint has been reached (inverse of censored status) """ survival = pandas.DataFrame( {x:get_survival_status(tv_data[x],endpoint=endpoint) \ for x in tv_data if tv_data[x].any()} ).T survival.columns=['Time','Observed'] return survival
Classes
class HighlightLines
A plugin to highlight lines on hover Adapted from mpld3 example code
class HighlightLines(plugins.PluginBase): #pragma non cover """A plugin to highlight lines on hover Adapted from mpld3 example code""" JAVASCRIPT = """ mpld3.register_plugin("linehighlight", LineHighlightPlugin); LineHighlightPlugin.prototype = Object.create(mpld3.Plugin.prototype); LineHighlightPlugin.prototype.constructor = LineHighlightPlugin; LineHighlightPlugin.prototype.requiredProps = ["line_ids"]; LineHighlightPlugin.prototype.defaultProps = {alpha_bg:0.3, alpha_fg:1.0} function LineHighlightPlugin(fig, props){ mpld3.Plugin.call(this, fig, props); }; LineHighlightPlugin.prototype.draw = function(){ for(var i=0; i<this.props.line_ids.length; i++){ var obj = mpld3.get_element(this.props.line_ids[i], this.fig), alpha_fg = this.props.alpha_fg; alpha_bg = this.props.alpha_bg; obj.elements() .on("mouseover.highlight", function(d, i){ d3.select(this).transition().duration(50) .style("stroke-opacity", alpha_fg); }) .on("mouseout.highlight", function(d, i){ d3.select(this).transition().duration(200) .style("stroke-opacity", alpha_bg); }); } }; """ def __init__(self, lines): self.lines = lines self.dict_ = {"type": "linehighlight", "line_ids": [utils.get_id(line) for line in lines], "alpha_bg": lines[0].get_alpha(), "alpha_fg": 1.0}
Ancestors (in MRO)
- HighlightLines
- mpld3.plugins.PluginBase
- builtins.object
Class variables
var JAVASCRIPT
Static methods
def __init__(
self, lines)
Initialize self. See help(type(self)) for accurate signature.
def __init__(self, lines): self.lines = lines self.dict_ = {"type": "linehighlight", "line_ids": [utils.get_id(line) for line in lines], "alpha_bg": lines[0].get_alpha(), "alpha_fg": 1.0}
def css(
self)
def css(self): if hasattr(self, "css_"): return self.css_ else: return ""
def get_dict(
self)
def get_dict(self): return self.dict_
def javascript(
self)
def javascript(self): if hasattr(self, "JAVASCRIPT"): if hasattr(self, "js_args_"): return self.JAVASCRIPT.render(self.js_args_) else: return self.JAVASCRIPT else: return ""
Instance variables
var dict_
var lines
class TumourVolumePlot
Construct a plot of volume vs time This plot is designed to consist of measurements for individuals, a mean volume for the group and a standard error of the mean. When used to produce html the resulting graph is interactive with mouse over highlighting of individuals and labeling with the column name. Static pdfs can also be generated.
class TumourVolumePlot(): """Construct a plot of volume vs time This plot is designed to consist of measurements for individuals, a mean volume for the group and a standard error of the mean. When used to produce html the resulting graph is interactive with mouse over highlighting of individuals and labeling with the column name. Static pdfs can also be generated. """ def __init__(self, **kw): """Initiate a new tumour TumourVolumePlot object Arguments: **kw - keyword arguments are passed to matplotlib.pyplot.subplots """ self.fig, self.ax = plt.subplots(**kw) self.lines = {} self.means = {} self.intervals = {} self.title = 'Untitled' self.ylabel = 'Tumour Volume mm$^{3}$' self.xlabel = 'Days' self.xlim = None self.ylim = None self.fontsize = None pass def remove_legend(self): """Remove any existing matplotlib axis legend""" if self.ax.legend_: self.ax.legend_.remove() pass def set_spines_and_ticks(self, axes = None, remove_spines=['right','top'], y_set_ticks = ['left'], x_set_ticks = ['bottom'], ): """Set spines (the lines along the axis) and tick locations for the plot Arguments: axes - the matplotlib.axis object to act on Default: None (interpreted as self.ax) remove_spines - a list of spines to remove. Default: ['right','top'] y_set_ticks - a list of locations of y axis ticks Default: ['left'] x_set_ticks - a list of locations of x axis ticks Default: ['bottom'] """ if not axes: axes = [self.ax,] for ax in axes: for spine in remove_spines: ax.spines[spine].set_visible(False) for pos in y_set_ticks: ax.yaxis.set_ticks_position(pos) for pos in x_set_ticks: ax.xaxis.set_ticks_position(pos) if self.fontsize: for item in (ax.get_xticklabels() + ax.get_yticklabels()): item.set_fontsize(self.fontsize) pass def set_limits(self): """Apply the axis limits set by self.xlim and self.ylim to the graph axis""" if self.xlim or self.ylim: self.ax.set_xlim(self.xlim) ylim = self.ylim if self.ylim else [0,1000] self.ax.set_ylim(ylim) self.ax.set_autoscaley_on(False) else: self.ax.set_autoscaley_on(True) pass def display(self, legend=True, update=True, use_mpld3=True, hide_volume_labels = False, **kw): """Generate the figure and return an object for display This function is for viewing figures in a Jupyter notebook Arguments: legend - boolean for displaying a legend Default: True update - boolean for reapplying spine, tick, title and axis limits to the graph Default: True use_mpld3 - boolean for using mpld3 to produce interactive html Default: True hide_volume_labels - boolean for hiding x axis volume labels Default: False **kw - additional keyword arguments are passed to mpld3.display """ if legend == True: self.add_legend() if legend == 'custom': pass else: self.remove_legend() if update: self.set_spines_and_ticks() self.set_title_and_labels(hide_volume_labels = hide_volume_labels) self.set_limits() if use_mpld3: return mpld3.display(self.fig, **kw) else: return self.fig def save_html(self, fileobj=None, legend=True, update=True, template_type = "general", **kw): """Generate the figure as a html file Arguments: fileobj - a python file object Default: None (saved to a new file with the same name as the parent object with .html) legend - boolean for displaying a legend Default: True update - boolean for reapplying spine, tick, title and axis limits to the graph Default: True template_type - string specifying the type of HTML template to use. Options from mpld3 are: "simple" suitable for simple html page with one figure. Will fail if require.js is available on the page. "notebook" assumes require.js and jquery are available. "general" works both in and out of the notebook, whether or not require.js and jquery are available Default: general **kw - additional keyword arguments are passed to mpld3.save_html """ if legend: self.add_legend() else: self.remove_legend() if not fileobj: fileobj='{0}.html'.format(self.name) if update: self.set_spines_and_ticks() self.set_title_and_labels() self.set_limits() mpld3.save_html(fig=self.fig, fileobj=fileobj, template_type=template_type, **kw) pass def save_pdf(self, fileobj=None, legend=True, update=True, **kw): """Generate the figure as a pdf file Arguments: fileobj - a python file object Default: None (saved to a new file with the same name as the parent object with .html) legend - boolean for displaying a legend Default: True update - boolean for reapplying spine, tick, title and axis limits to the graph Default: True **kw - additional keyword arguments are passed to matplotlib.PdfPages.savefig """ if legend: self.add_legend() else: self.remove_legend() if update: self.set_spines_and_ticks() self.set_title_and_labels() self.set_limits() if not fileobj: fileobj='{0}.html'.format(self.name) from matplotlib.backends.backend_pdf import PdfPages with PdfPages(fileobj) as pdf: pdf.savefig(self.fig, **kw) pass def add_individuals(self, name, tv_table, color = 'black', alpha=0.1, lw=3, dashes = [2,2], **kw): """Add line plots of individual volume vs time for all individuals in a tumour volume table. These should be a logical group (eg treatment) When used to produce html plots mouse over events will make the line opaque (alpha=1.0) and label the line with the column name Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.plot and can be any matplotlib.Line2D attributes """ self.lines[name] = self.ax.plot(tv_table.index, tv_table, color=color, alpha=alpha, lw=lw, dashes=dashes, **kw) plugins.connect(self.fig, HighlightLines(self.lines[name])) for i, l in enumerate(self.lines[name]): plugins.connect(self.fig, plugins.LineLabelTooltip(l, str(tv_table.columns[i]))) pass def add_mean(self, name, tv_table, threshold=2, color = 'black', alpha=0.8, lw=4, dashes = [], **kw): """Calculate and add to the plot a line for the mean volume vs time for all individuals in a tumour volume table. These individuals should be a logical group (eg treatment) Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint threshold - the minimum group size to plot at a given time point. Default: 2 color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.plot and can be any matplotlib.Line2D attributes """ self.means[name] = self.ax.plot(tv_table[tv_table.count(axis=1) > threshold].index, tv_table[tv_table.count(axis=1) > threshold].mean(axis=1), color=color, alpha=alpha, lw=lw, dashes=dashes, **kw) pass def _calc_t_ci(self, tv_table, ci=0.95): uppers = [] lowers = [] means = [] for entry in tv_table.T: data = tv_table.T[entry].dropna() mean = np.mean(data) (lower,upper) = scipy.stats.t.interval(0.95, df=len(data)-1, loc=mean, scale=np.std(data,ddof=1) / np.sqrt(len(data)), ) uppers.append(upper) lowers.append(lower) means.append(mean) cis = pandas.DataFrame({'mean':means, 'lower bound':[max(0,x) for x in lowers], #limit to +ve 'upper bound':uppers, }).dropna() cis.index = tv_table.index[:len(cis.index)] return cis def _calc_norm_ci(self, tv_table, ci=0.95): interval = scipy.stats.norm.interval(ci, loc=tv_table.mean(axis=1), scale=tv_table.sem(axis=1)) cis = pandas.DataFrame({'mean':tv_table.mean(axis=1), 'lower bound':[max(0,x) for x in interval[0]], #limit to +ve 'upper bound':interval[1], }).dropna() return cis def add_interval(self, name, tv_table, threshold=2, ci=0.95, color = 'black', alpha=0.2, lw=0, dashes = [], **kw): """Calculate the confidence interval of the mean and add to the plot as a shaded band around the mean. These individuals should be a logical group (eg treatment). Note that the value is the confidence interval for the standard error of the mean and indicates the range the mean is expected to lie within ci % of the time. This should not be confused with the standard deviation of the data (the range within individual data is expected to lie) or the standard error of the mean. As most data is expected to have a small number of observations, the 95% confidence interval is calculated using a t distribution. Results match R's t-test and Graphpad Prism's 95% CI Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint threshold - the minimum group size to plot a confidence interval for Default: 2 ci - the confidence interval range to plot Default: 0.95 color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.fill_between """ cis = self._calc_t_ci(tv_table[tv_table.count(axis=1) > threshold], ci=ci) self.intervals[name] = self.ax.fill_between([int(x) for x in cis.index], cis['lower bound'], cis['upper bound'], color=color, alpha=alpha, lw=lw, dashes=dashes, **kw) pass def set_title_and_labels(self, title=None, ylabel=None, xlabel=None, hide_volume_labels=False, **kw): """Set plot titles and axis labels Arguments: title - main plot title Default: None (uses self.title) ylabel - y axis label Default: None (uses self.ylabel) xlabel - x axis label Default: None (uses self.xlabel) hide_volume_labels - for compatibility with dual plot Default: False """ if title == None: title = self.title if ylabel == None: ylabel = self.ylabel if xlabel == None: xlabel = self.xlabel self.ax.set_title(title, **kw) self.ax.set_ylabel(ylabel, **kw) self.ax.set_xlabel(xlabel, **kw) pass def add_legend(self, loc='best', **kw): """Create a legend from axis data after removing any existing legends Arguments: loc - location of legend from: 'best','upper right', 'upper left','lower left','lower right','right', 'center left','center right','lower center', 'upper center','center' Default: 'best' **kw - keyword arguments passed to matplotlib.axes.legend """ self.remove_legend() patches = [] for key in self.means: color=self.means[key][0].get_color() patches.append(matplotlib.lines.Line2D([],[],color=color, label=key, lw=3)) self.ax.legend(handles=patches, loc=loc, **kw) pass def show_treatment_days(self, days, facecolor="lightgrey",lw=0, **kw): """Add rectangular patches on the x axis to indicate days on which treatment occurred. Arguments: days - an integer list of days eg [1,3,5] facecolor - the fill color for the rectangular patches Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) lw - the width of the line bordering the patch in points Default: 0 (no line) **kw - keyword arguments passed to matplotlib.patches.Rectangle """ for day in days: self.ax.add_patch(matplotlib.patches.Rectangle((day, 0), 1, 20, facecolor=facecolor, lw=lw, **kw)) pass def shade_interval(self, start, end, facecolor="lightgrey", alpha=0.2, lw=0, **kw): """Add rectangular fill the full height of the axis to indicate a date range (eg for treatment). Arguments: start - the start point of the rectangle end - the end point of the rectangle facecolor - the fill color for the rectangular patches Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) Default: 0.2 lw - the width of the line bordering the patch in points Default: 0 (no line) **kw - keyword arguments passed to matplotlib.patches.Rectangle """ miny,maxy = self.ax.get_yaxis().get_data_interval() self.ax.add_patch(matplotlib.patches.Rectangle((start, min(miny,self.ylim[0])), end, max(maxy,self.ylim[1]), facecolor="lightgrey",alpha=alpha, lw=0, **kw)) pass
Ancestors (in MRO)
- TumourVolumePlot
- builtins.object
Static methods
def __init__(
self, **kw)
Initiate a new tumour TumourVolumePlot object
Arguments:
**kw - keyword arguments are passed to
matplotlib.pyplot.subplots
def __init__(self, **kw): """Initiate a new tumour TumourVolumePlot object Arguments: **kw - keyword arguments are passed to matplotlib.pyplot.subplots """ self.fig, self.ax = plt.subplots(**kw) self.lines = {} self.means = {} self.intervals = {} self.title = 'Untitled' self.ylabel = 'Tumour Volume mm$^{3}$' self.xlabel = 'Days' self.xlim = None self.ylim = None self.fontsize = None pass
def add_individuals(
self, name, tv_table, color='black', alpha=0.1, lw=3, dashes=[2, 2], **kw)
Add line plots of individual volume vs time for all individuals in a tumour volume table. These should be a logical group (eg treatment) When used to produce html plots mouse over events will make the line opaque (alpha=1.0) and label the line with the column name
Arguments:
name - The legend label for this group
tv_table - a pandas data frame of volume measurements
with individuals in columns and timepoints
as rows. Individuals are removed from study
at the first NaN timepoint
color - the color to plot this data group
Valid colors include matplotlib named colors
html colors (eg '#029386') or RGB tuples
(eg (0.0078, 0.58, 0.53))
alpha - Percent transparency as a value between
0.0 (transparent) and 1.0 (opaque)
lw - line width in points
dashes - A line dash pattern as an even length list
of on off lengths in points
**kw - additional key word arguments are passed to
matplotlib.axes.plot and can be any
matplotlib.Line2D attributes
def add_individuals(self, name, tv_table, color = 'black', alpha=0.1, lw=3, dashes = [2,2], **kw): """Add line plots of individual volume vs time for all individuals in a tumour volume table. These should be a logical group (eg treatment) When used to produce html plots mouse over events will make the line opaque (alpha=1.0) and label the line with the column name Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.plot and can be any matplotlib.Line2D attributes """ self.lines[name] = self.ax.plot(tv_table.index, tv_table, color=color, alpha=alpha, lw=lw, dashes=dashes, **kw) plugins.connect(self.fig, HighlightLines(self.lines[name])) for i, l in enumerate(self.lines[name]): plugins.connect(self.fig, plugins.LineLabelTooltip(l, str(tv_table.columns[i]))) pass
def add_interval(
self, name, tv_table, threshold=2, ci=0.95, color='black', alpha=0.2, lw=0, dashes=[], **kw)
Calculate the confidence interval of the mean and add to the plot as a shaded band around the mean. These individuals should be a logical group (eg treatment). Note that the value is the confidence interval for the standard error of the mean and indicates the range the mean is expected to lie within ci % of the time. This should not be confused with the standard deviation of the data (the range within individual data is expected to lie) or the standard error of the mean. As most data is expected to have a small number of observations, the 95% confidence interval is calculated using a t distribution. Results match R's t-test and Graphpad Prism's 95% CI
Arguments:
name - The legend label for this group
tv_table - a pandas data frame of volume measurements
with individuals in columns and timepoints
as rows. Individuals are removed from study
at the first NaN timepoint
threshold - the minimum group size to plot a confidence
interval for
Default: 2
ci - the confidence interval range to plot
Default: 0.95
color - the color to plot this data group
Valid colors include matplotlib named colors
html colors (eg '#029386') or RGB tuples
(eg (0.0078, 0.58, 0.53))
alpha - Percent transparency as a value between
0.0 (transparent) and 1.0 (opaque)
lw - line width in points
dashes - A line dash pattern as an even length list
of on off lengths in points
**kw - additional key word arguments are passed to
matplotlib.axes.fill_between
def add_interval(self, name, tv_table, threshold=2, ci=0.95, color = 'black', alpha=0.2, lw=0, dashes = [], **kw): """Calculate the confidence interval of the mean and add to the plot as a shaded band around the mean. These individuals should be a logical group (eg treatment). Note that the value is the confidence interval for the standard error of the mean and indicates the range the mean is expected to lie within ci % of the time. This should not be confused with the standard deviation of the data (the range within individual data is expected to lie) or the standard error of the mean. As most data is expected to have a small number of observations, the 95% confidence interval is calculated using a t distribution. Results match R's t-test and Graphpad Prism's 95% CI Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint threshold - the minimum group size to plot a confidence interval for Default: 2 ci - the confidence interval range to plot Default: 0.95 color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.fill_between """ cis = self._calc_t_ci(tv_table[tv_table.count(axis=1) > threshold], ci=ci) self.intervals[name] = self.ax.fill_between([int(x) for x in cis.index], cis['lower bound'], cis['upper bound'], color=color, alpha=alpha, lw=lw, dashes=dashes, **kw) pass
def add_legend(
self, loc='best', **kw)
Create a legend from axis data after removing any existing legends
Arguments:
loc - location of legend from: 'best','upper right',
'upper left','lower left','lower right','right',
'center left','center right','lower center',
'upper center','center'
Default: 'best'
**kw - keyword arguments passed to matplotlib.axes.legend
def add_legend(self, loc='best', **kw): """Create a legend from axis data after removing any existing legends Arguments: loc - location of legend from: 'best','upper right', 'upper left','lower left','lower right','right', 'center left','center right','lower center', 'upper center','center' Default: 'best' **kw - keyword arguments passed to matplotlib.axes.legend """ self.remove_legend() patches = [] for key in self.means: color=self.means[key][0].get_color() patches.append(matplotlib.lines.Line2D([],[],color=color, label=key, lw=3)) self.ax.legend(handles=patches, loc=loc, **kw) pass
def add_mean(
self, name, tv_table, threshold=2, color='black', alpha=0.8, lw=4, dashes=[], **kw)
Calculate and add to the plot a line for the mean volume vs time for all individuals in a tumour volume table. These individuals should be a logical group (eg treatment)
Arguments:
name - The legend label for this group
tv_table - a pandas data frame of volume measurements
with individuals in columns and timepoints
as rows. Individuals are removed from study
at the first NaN timepoint
threshold - the minimum group size to plot at a given
time point.
Default: 2
color - the color to plot this data group
Valid colors include matplotlib named colors
html colors (eg '#029386') or RGB tuples
(eg (0.0078, 0.58, 0.53))
alpha - Percent transparency as a value between
0.0 (transparent) and 1.0 (opaque)
lw - line width in points
dashes - A line dash pattern as an even length list
of on off lengths in points
**kw - additional key word arguments are passed to
matplotlib.axes.plot and can be any
matplotlib.Line2D attributes
def add_mean(self, name, tv_table, threshold=2, color = 'black', alpha=0.8, lw=4, dashes = [], **kw): """Calculate and add to the plot a line for the mean volume vs time for all individuals in a tumour volume table. These individuals should be a logical group (eg treatment) Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint threshold - the minimum group size to plot at a given time point. Default: 2 color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.plot and can be any matplotlib.Line2D attributes """ self.means[name] = self.ax.plot(tv_table[tv_table.count(axis=1) > threshold].index, tv_table[tv_table.count(axis=1) > threshold].mean(axis=1), color=color, alpha=alpha, lw=lw, dashes=dashes, **kw) pass
def display(
self, legend=True, update=True, use_mpld3=True, hide_volume_labels=False, **kw)
Generate the figure and return an object for display This function is for viewing figures in a Jupyter notebook
Arguments:
legend - boolean for displaying a legend
Default: True
update - boolean for reapplying spine, tick, title and
axis limits to the graph
Default: True
use_mpld3 - boolean for using mpld3 to produce interactive html
Default: True
hide_volume_labels - boolean for hiding x axis volume labels
Default: False
**kw - additional keyword arguments are passed to mpld3.display
def display(self, legend=True, update=True, use_mpld3=True, hide_volume_labels = False, **kw): """Generate the figure and return an object for display This function is for viewing figures in a Jupyter notebook Arguments: legend - boolean for displaying a legend Default: True update - boolean for reapplying spine, tick, title and axis limits to the graph Default: True use_mpld3 - boolean for using mpld3 to produce interactive html Default: True hide_volume_labels - boolean for hiding x axis volume labels Default: False **kw - additional keyword arguments are passed to mpld3.display """ if legend == True: self.add_legend() if legend == 'custom': pass else: self.remove_legend() if update: self.set_spines_and_ticks() self.set_title_and_labels(hide_volume_labels = hide_volume_labels) self.set_limits() if use_mpld3: return mpld3.display(self.fig, **kw) else: return self.fig
def remove_legend(
self)
Remove any existing matplotlib axis legend
def remove_legend(self): """Remove any existing matplotlib axis legend""" if self.ax.legend_: self.ax.legend_.remove() pass
def save_html(
self, fileobj=None, legend=True, update=True, template_type='general', **kw)
Generate the figure as a html file
Arguments:
fileobj - a python file object
Default: None (saved to a new file with the same
name as the parent object with .html)
legend - boolean for displaying a legend
Default: True
update - boolean for reapplying spine, tick, title and
axis limits to the graph
Default: True
template_type - string specifying the type of HTML template to use.
Options from mpld3 are:
"simple" suitable for simple html page with one figure.
Will fail if require.js is available on the page.
"notebook" assumes require.js and jquery are available.
"general" works both in and out of the notebook,
whether or not require.js and jquery are available
Default: general
**kw - additional keyword arguments are passed to mpld3.save_html
def save_html(self, fileobj=None, legend=True, update=True, template_type = "general", **kw): """Generate the figure as a html file Arguments: fileobj - a python file object Default: None (saved to a new file with the same name as the parent object with .html) legend - boolean for displaying a legend Default: True update - boolean for reapplying spine, tick, title and axis limits to the graph Default: True template_type - string specifying the type of HTML template to use. Options from mpld3 are: "simple" suitable for simple html page with one figure. Will fail if require.js is available on the page. "notebook" assumes require.js and jquery are available. "general" works both in and out of the notebook, whether or not require.js and jquery are available Default: general **kw - additional keyword arguments are passed to mpld3.save_html """ if legend: self.add_legend() else: self.remove_legend() if not fileobj: fileobj='{0}.html'.format(self.name) if update: self.set_spines_and_ticks() self.set_title_and_labels() self.set_limits() mpld3.save_html(fig=self.fig, fileobj=fileobj, template_type=template_type, **kw) pass
def save_pdf(
self, fileobj=None, legend=True, update=True, **kw)
Generate the figure as a pdf file
Arguments:
fileobj - a python file object
Default: None (saved to a new file with the same
name as the parent object with .html)
legend - boolean for displaying a legend
Default: True
update - boolean for reapplying spine, tick, title and
axis limits to the graph
Default: True
**kw - additional keyword arguments are passed to
matplotlib.PdfPages.savefig
def save_pdf(self, fileobj=None, legend=True, update=True, **kw): """Generate the figure as a pdf file Arguments: fileobj - a python file object Default: None (saved to a new file with the same name as the parent object with .html) legend - boolean for displaying a legend Default: True update - boolean for reapplying spine, tick, title and axis limits to the graph Default: True **kw - additional keyword arguments are passed to matplotlib.PdfPages.savefig """ if legend: self.add_legend() else: self.remove_legend() if update: self.set_spines_and_ticks() self.set_title_and_labels() self.set_limits() if not fileobj: fileobj='{0}.html'.format(self.name) from matplotlib.backends.backend_pdf import PdfPages with PdfPages(fileobj) as pdf: pdf.savefig(self.fig, **kw) pass
def set_limits(
self)
Apply the axis limits set by self.xlim and self.ylim to the graph axis
def set_limits(self): """Apply the axis limits set by self.xlim and self.ylim to the graph axis""" if self.xlim or self.ylim: self.ax.set_xlim(self.xlim) ylim = self.ylim if self.ylim else [0,1000] self.ax.set_ylim(ylim) self.ax.set_autoscaley_on(False) else: self.ax.set_autoscaley_on(True) pass
def set_spines_and_ticks(
self, axes=None, remove_spines=['right', 'top'], y_set_ticks=['left'], x_set_ticks=['bottom'])
Set spines (the lines along the axis) and tick locations for the plot
Arguments:
axes - the matplotlib.axis object to act on
Default: None (interpreted as self.ax)
remove_spines - a list of spines to remove.
Default: ['right','top']
y_set_ticks - a list of locations of y axis ticks
Default: ['left']
x_set_ticks - a list of locations of x axis ticks
Default: ['bottom']
def set_spines_and_ticks(self, axes = None, remove_spines=['right','top'], y_set_ticks = ['left'], x_set_ticks = ['bottom'], ): """Set spines (the lines along the axis) and tick locations for the plot Arguments: axes - the matplotlib.axis object to act on Default: None (interpreted as self.ax) remove_spines - a list of spines to remove. Default: ['right','top'] y_set_ticks - a list of locations of y axis ticks Default: ['left'] x_set_ticks - a list of locations of x axis ticks Default: ['bottom'] """ if not axes: axes = [self.ax,] for ax in axes: for spine in remove_spines: ax.spines[spine].set_visible(False) for pos in y_set_ticks: ax.yaxis.set_ticks_position(pos) for pos in x_set_ticks: ax.xaxis.set_ticks_position(pos) if self.fontsize: for item in (ax.get_xticklabels() + ax.get_yticklabels()): item.set_fontsize(self.fontsize) pass
def set_title_and_labels(
self, title=None, ylabel=None, xlabel=None, hide_volume_labels=False, **kw)
Set plot titles and axis labels
Arguments:
title - main plot title
Default: None (uses self.title)
ylabel - y axis label
Default: None (uses self.ylabel)
xlabel - x axis label
Default: None (uses self.xlabel)
hide_volume_labels - for compatibility with dual plot
Default: False
def set_title_and_labels(self, title=None, ylabel=None, xlabel=None, hide_volume_labels=False, **kw): """Set plot titles and axis labels Arguments: title - main plot title Default: None (uses self.title) ylabel - y axis label Default: None (uses self.ylabel) xlabel - x axis label Default: None (uses self.xlabel) hide_volume_labels - for compatibility with dual plot Default: False """ if title == None: title = self.title if ylabel == None: ylabel = self.ylabel if xlabel == None: xlabel = self.xlabel self.ax.set_title(title, **kw) self.ax.set_ylabel(ylabel, **kw) self.ax.set_xlabel(xlabel, **kw) pass
def shade_interval(
self, start, end, facecolor='lightgrey', alpha=0.2, lw=0, **kw)
Add rectangular fill the full height of the axis to indicate a date range (eg for treatment).
Arguments:
start - the start point of the rectangle
end - the end point of the rectangle
facecolor - the fill color for the rectangular patches
Valid colors include matplotlib named colors
html colors (eg '#029386') or RGB tuples
(eg (0.0078, 0.58, 0.53))
alpha - Percent transparency as a value between
0.0 (transparent) and 1.0 (opaque)
Default: 0.2
lw - the width of the line bordering the patch in points
Default: 0 (no line)
**kw - keyword arguments passed to matplotlib.patches.Rectangle
def shade_interval(self, start, end, facecolor="lightgrey", alpha=0.2, lw=0, **kw): """Add rectangular fill the full height of the axis to indicate a date range (eg for treatment). Arguments: start - the start point of the rectangle end - the end point of the rectangle facecolor - the fill color for the rectangular patches Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) Default: 0.2 lw - the width of the line bordering the patch in points Default: 0 (no line) **kw - keyword arguments passed to matplotlib.patches.Rectangle """ miny,maxy = self.ax.get_yaxis().get_data_interval() self.ax.add_patch(matplotlib.patches.Rectangle((start, min(miny,self.ylim[0])), end, max(maxy,self.ylim[1]), facecolor="lightgrey",alpha=alpha, lw=0, **kw)) pass
def show_treatment_days(
self, days, facecolor='lightgrey', lw=0, **kw)
Add rectangular patches on the x axis to indicate days on which treatment occurred.
Arguments:
days - an integer list of days eg [1,3,5]
facecolor - the fill color for the rectangular patches
Valid colors include matplotlib named colors
html colors (eg '#029386') or RGB tuples
(eg (0.0078, 0.58, 0.53))
lw - the width of the line bordering the patch in points
Default: 0 (no line)
**kw - keyword arguments passed to matplotlib.patches.Rectangle
def show_treatment_days(self, days, facecolor="lightgrey",lw=0, **kw): """Add rectangular patches on the x axis to indicate days on which treatment occurred. Arguments: days - an integer list of days eg [1,3,5] facecolor - the fill color for the rectangular patches Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) lw - the width of the line bordering the patch in points Default: 0 (no line) **kw - keyword arguments passed to matplotlib.patches.Rectangle """ for day in days: self.ax.add_patch(matplotlib.patches.Rectangle((day, 0), 1, 20, facecolor=facecolor, lw=lw, **kw)) pass
Instance variables
var fontsize
var intervals
var lines
var means
var title
var xlabel
var xlim
var ylabel
var ylim
class VolumeSurvivalPlot
Initiate a new dual panel Tumour Volume and Survival plot object
Arguments:
km_size - The relative size of the survival plot
Default: 0.5 (ie half or 1/3 total plot area)
vertical - Stack the Tumour Volume plot over the survival plot
with a shared x axis (days) so events co occur.
Default: True
**kw - keyword arguments are passed to
matplotlib.pyplot.subplots
class VolumeSurvivalPlot(TumourVolumePlot): """Initiate a new dual panel Tumour Volume and Survival plot object Arguments: km_size - The relative size of the survival plot Default: 0.5 (ie half or 1/3 total plot area) vertical - Stack the Tumour Volume plot over the survival plot with a shared x axis (days) so events co occur. Default: True **kw - keyword arguments are passed to matplotlib.pyplot.subplots """ def __init__(self, km_size=0.5, vertical=True, **kw): if vertical: self.fig, (self.ax, self.km_ax) = plt.subplots(nrows=2, ncols=1, sharex=True, gridspec_kw={'height_ratios':[1,km_size]},**kw) else: self.fig, (self.ax, self.km_ax) = plt.subplots(nrows=1, ncols=2, gridspec_kw={'width_ratios':[1,km_size]},**kw) self.lines = {} self.means = {} self.kmfs = {} self.intervals = {} self.title = 'Untitled' self.ylabel = 'Tumour Volume mm$^{3}$' self.xlabel = 'Days' self.km_title = '' self.km_ylabel = 'Survival' self.km_xlabel = 'Days' self.km_yticks = [0,0.25,0.5,0.75,1.0] self.km_ci_show = True self.km_show_censors = True self.vertical = vertical self.volume_data = {} self.endpoint = None self.xlim = None self.ylim = None self.fontsize = None pass def remove_legend(self): """Remove any existing matplotlib axis legend""" if self.ax.legend_: self.ax.legend_.remove() if self.km_ax.legend_: self.km_ax.legend_.remove() pass def add_mean(self, name, tv_table, endpoint = 700, threshold=2, color = 'black', alpha=0.8, lw=4, dashes = [], **kw): """Calculate and add to the plot a line for the mean volume vs time for all individuals in a tumour volume table. These individuals should be a logical group (eg treatment) Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint endpoint - the volume at which the endpoint is reached Default: 700 threshold - the minimum group size to plot at a given time point. Default: 2 color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points Default: 4 dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.plot and lifelines.kmf.plot and can be any matplotlib.Line2D attributes """ super(VolumeSurvivalPlot, self).add_mean(name=name, tv_table=tv_table, threshold=threshold, color=color, alpha=alpha, lw=lw, dashes = dashes, **kw) self.volume_data[name] = tv_table self.add_kmf(name, tv_table, endpoint = endpoint, color = color, alpha=alpha, lw=lw, dashes = dashes, **kw) pass def add_kmf(self, name, tv_table, endpoint = 700, color = 'black', alpha=0.8, lw=4, dashes = [], **kw): """Calculate and add a Kaplan Meier curve to the second panel of the plot for all individuals in a tumour volume table. These individuals should be a logical group (eg treatment) Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint endpoint - the volume at which the endpoint is reached Default: 700 color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points Default: 4 dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to lifelines.kmf.plot and can be any matplotlib.Line2D attributes """ self.kmfs[name] = make_km(tv_table, label=name, endpoint=endpoint) self.endpoint = endpoint self.kmfs[name].plot(color = color, alpha=alpha, lw=lw, dashes = dashes, show_censors = self.km_show_censors, ci_show = self.km_ci_show, ax=self.km_ax, **kw) pass def set_title_and_labels(self, title=None, ylabel=None, xlabel=None, km_title=None, km_ylabel=None, km_xlabel=None, hide_volume_labels = True,**kw): """Set plot titles and axis labels Arguments: title - main plot title Default: None (uses self.title) ylabel - y axis label Default: None (uses self.ylabel) xlabel - x axis label Default: None (uses self.xlabel) kmtitle - Kaplan Meier plot label Default: None (uses self.km_title) ylabel - y axis label Default: None (uses self.km_ylabel) xlabel - x axis label Default: None (uses self.km_xlabel) hide_volume_labels - do not display x axis label between volume plot and Kaplan-Meier Default: True """ if title == None: title = self.title if ylabel == None: ylabel = self.ylabel if xlabel == None: xlabel = self.xlabel if km_title == None: km_title = self.km_title if km_ylabel == None: km_ylabel = self.km_ylabel if km_xlabel == None: km_xlabel = self.km_xlabel if self.fontsize and not 'fontsize' in kw: kw['fontsize'] = self.fontsize self.ax.set_title(title, **kw) self.ax.set_ylabel(ylabel, **kw) if hide_volume_labels: self.ax.set_xlabel('', **kw) else: self.ax.set_xlabel(xlabel, **kw) self.km_ax.set_title(km_title, **kw) self.km_ax.set_ylabel(km_ylabel, **kw) self.km_ax.set_xlabel(km_xlabel, **kw) pass def set_spines_and_ticks(self, axes = None, remove_spines=['right','top'], y_set_ticks = ['left'], x_set_ticks = ['bottom'], ): """Set spines (the lines along the axis) and tick locations for the plot Arguments: axes - the matplotlib.axis object to act on Default: None (interpreted as self.ax and self.km_ax) remove_spines - a list of spines to remove. Default: ['right','top'] y_set_ticks - a list of locations of y axis ticks Default: ['left'] x_set_ticks - a list of locations of x axis ticks Default: ['bottom'] """ if axes == None: axes = [self.km_ax, self.ax] super(VolumeSurvivalPlot, self).set_spines_and_ticks(axes = axes, remove_spines = remove_spines, y_set_ticks = y_set_ticks, x_set_ticks = x_set_ticks,) self.km_ax.yaxis.set_ticks(self.km_yticks) pass def set_limits(self): """Apply the axis limits set by self.xlim and self.ylim to the graph axes. The Kaplan-Meier y axis will remain [0,1.02]""" if self.xlim or self.ylim: self.ax.set_xlim(self.xlim) ylim = self.ylim if self.ylim else [0,1000] self.ax.set_ylim(ylim) self.ax.set_autoscaley_on(False) self.km_ax.set_xlim(self.xlim) self.km_ax.set_ylim([0,1.02]) #set >1 so lines retain full width self.km_ax.set_autoscaley_on(False) else: self.ax.set_autoscaley_on(True) self.km_ax.set_ylim([0,1.02]) self.km_ax.set_autoscaley_on(True) pass def logrank_test(self, treatment_a, treatment_b, t1error=0.05): """Calculate a log rank test (Mantel-Cox) statistic between two treatments Calls lifelines.statistics.logrank_test for calculation. Arguments: treatment_a - The legend label for this group as used with add_mean. treatment_b - The legend label for this group as used with add_mean. t1error - probability of a type 1 error (alpha) Default: 0.05 """ if not self.endpoint or not self.volume_data: print('you need to add data with .add_mean() before using logrank_test') raise ValueError survival_a = volume_to_survival(self.volume_data[treatment_a], endpoint=self.endpoint) survival_b = volume_to_survival(self.volume_data[treatment_b], endpoint=self.endpoint) result = logrank_test(list(survival_a['Time']), list(survival_b['Time']), list(survival_a['Observed']), list(survival_b['Observed']), alpha=1-t1error) result.print_summary() return result def display(self, legend=True, use_mpld3=True, update=True, hide_volume_labels=True, **kw): """Display the dual tumour volume and Kaplan Meier plot KNOWN BUG - mpld3 does not correctly display the step plot in version < 0.3 """ result = super(VolumeSurvivalPlot, self).display(legend=legend, use_mpld3=use_mpld3, update=update, hide_volume_labels = hide_volume_labels, **kw) if use_mpld3: return result
Ancestors (in MRO)
- VolumeSurvivalPlot
- TumourVolumePlot
- builtins.object
Static methods
def __init__(
self, km_size=0.5, vertical=True, **kw)
Initiate a new tumour TumourVolumePlot object
Arguments:
**kw - keyword arguments are passed to
matplotlib.pyplot.subplots
def __init__(self, km_size=0.5, vertical=True, **kw): if vertical: self.fig, (self.ax, self.km_ax) = plt.subplots(nrows=2, ncols=1, sharex=True, gridspec_kw={'height_ratios':[1,km_size]},**kw) else: self.fig, (self.ax, self.km_ax) = plt.subplots(nrows=1, ncols=2, gridspec_kw={'width_ratios':[1,km_size]},**kw) self.lines = {} self.means = {} self.kmfs = {} self.intervals = {} self.title = 'Untitled' self.ylabel = 'Tumour Volume mm$^{3}$' self.xlabel = 'Days' self.km_title = '' self.km_ylabel = 'Survival' self.km_xlabel = 'Days' self.km_yticks = [0,0.25,0.5,0.75,1.0] self.km_ci_show = True self.km_show_censors = True self.vertical = vertical self.volume_data = {} self.endpoint = None self.xlim = None self.ylim = None self.fontsize = None pass
def add_individuals(
self, name, tv_table, color='black', alpha=0.1, lw=3, dashes=[2, 2], **kw)
Add line plots of individual volume vs time for all individuals in a tumour volume table. These should be a logical group (eg treatment) When used to produce html plots mouse over events will make the line opaque (alpha=1.0) and label the line with the column name
Arguments:
name - The legend label for this group
tv_table - a pandas data frame of volume measurements
with individuals in columns and timepoints
as rows. Individuals are removed from study
at the first NaN timepoint
color - the color to plot this data group
Valid colors include matplotlib named colors
html colors (eg '#029386') or RGB tuples
(eg (0.0078, 0.58, 0.53))
alpha - Percent transparency as a value between
0.0 (transparent) and 1.0 (opaque)
lw - line width in points
dashes - A line dash pattern as an even length list
of on off lengths in points
**kw - additional key word arguments are passed to
matplotlib.axes.plot and can be any
matplotlib.Line2D attributes
def add_individuals(self, name, tv_table, color = 'black', alpha=0.1, lw=3, dashes = [2,2], **kw): """Add line plots of individual volume vs time for all individuals in a tumour volume table. These should be a logical group (eg treatment) When used to produce html plots mouse over events will make the line opaque (alpha=1.0) and label the line with the column name Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.plot and can be any matplotlib.Line2D attributes """ self.lines[name] = self.ax.plot(tv_table.index, tv_table, color=color, alpha=alpha, lw=lw, dashes=dashes, **kw) plugins.connect(self.fig, HighlightLines(self.lines[name])) for i, l in enumerate(self.lines[name]): plugins.connect(self.fig, plugins.LineLabelTooltip(l, str(tv_table.columns[i]))) pass
def add_interval(
self, name, tv_table, threshold=2, ci=0.95, color='black', alpha=0.2, lw=0, dashes=[], **kw)
Calculate the confidence interval of the mean and add to the plot as a shaded band around the mean. These individuals should be a logical group (eg treatment). Note that the value is the confidence interval for the standard error of the mean and indicates the range the mean is expected to lie within ci % of the time. This should not be confused with the standard deviation of the data (the range within individual data is expected to lie) or the standard error of the mean. As most data is expected to have a small number of observations, the 95% confidence interval is calculated using a t distribution. Results match R's t-test and Graphpad Prism's 95% CI
Arguments:
name - The legend label for this group
tv_table - a pandas data frame of volume measurements
with individuals in columns and timepoints
as rows. Individuals are removed from study
at the first NaN timepoint
threshold - the minimum group size to plot a confidence
interval for
Default: 2
ci - the confidence interval range to plot
Default: 0.95
color - the color to plot this data group
Valid colors include matplotlib named colors
html colors (eg '#029386') or RGB tuples
(eg (0.0078, 0.58, 0.53))
alpha - Percent transparency as a value between
0.0 (transparent) and 1.0 (opaque)
lw - line width in points
dashes - A line dash pattern as an even length list
of on off lengths in points
**kw - additional key word arguments are passed to
matplotlib.axes.fill_between
def add_interval(self, name, tv_table, threshold=2, ci=0.95, color = 'black', alpha=0.2, lw=0, dashes = [], **kw): """Calculate the confidence interval of the mean and add to the plot as a shaded band around the mean. These individuals should be a logical group (eg treatment). Note that the value is the confidence interval for the standard error of the mean and indicates the range the mean is expected to lie within ci % of the time. This should not be confused with the standard deviation of the data (the range within individual data is expected to lie) or the standard error of the mean. As most data is expected to have a small number of observations, the 95% confidence interval is calculated using a t distribution. Results match R's t-test and Graphpad Prism's 95% CI Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint threshold - the minimum group size to plot a confidence interval for Default: 2 ci - the confidence interval range to plot Default: 0.95 color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.fill_between """ cis = self._calc_t_ci(tv_table[tv_table.count(axis=1) > threshold], ci=ci) self.intervals[name] = self.ax.fill_between([int(x) for x in cis.index], cis['lower bound'], cis['upper bound'], color=color, alpha=alpha, lw=lw, dashes=dashes, **kw) pass
def add_kmf(
self, name, tv_table, endpoint=700, color='black', alpha=0.8, lw=4, dashes=[], **kw)
Calculate and add a Kaplan Meier curve to the second panel of the plot for all individuals in a tumour volume table. These individuals should be a logical group (eg treatment)
Arguments:
name - The legend label for this group
tv_table - a pandas data frame of volume measurements
with individuals in columns and timepoints
as rows. Individuals are removed from study
at the first NaN timepoint
endpoint - the volume at which the endpoint is reached
Default: 700
color - the color to plot this data group
Valid colors include matplotlib named colors
html colors (eg '#029386') or RGB tuples
(eg (0.0078, 0.58, 0.53))
alpha - Percent transparency as a value between
0.0 (transparent) and 1.0 (opaque)
lw - line width in points
Default: 4
dashes - A line dash pattern as an even length list
of on off lengths in points
**kw - additional key word arguments are passed to
lifelines.kmf.plot and can be any
matplotlib.Line2D attributes
def add_kmf(self, name, tv_table, endpoint = 700, color = 'black', alpha=0.8, lw=4, dashes = [], **kw): """Calculate and add a Kaplan Meier curve to the second panel of the plot for all individuals in a tumour volume table. These individuals should be a logical group (eg treatment) Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint endpoint - the volume at which the endpoint is reached Default: 700 color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points Default: 4 dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to lifelines.kmf.plot and can be any matplotlib.Line2D attributes """ self.kmfs[name] = make_km(tv_table, label=name, endpoint=endpoint) self.endpoint = endpoint self.kmfs[name].plot(color = color, alpha=alpha, lw=lw, dashes = dashes, show_censors = self.km_show_censors, ci_show = self.km_ci_show, ax=self.km_ax, **kw) pass
def add_legend(
self, loc='best', **kw)
Create a legend from axis data after removing any existing legends
Arguments:
loc - location of legend from: 'best','upper right',
'upper left','lower left','lower right','right',
'center left','center right','lower center',
'upper center','center'
Default: 'best'
**kw - keyword arguments passed to matplotlib.axes.legend
def add_legend(self, loc='best', **kw): """Create a legend from axis data after removing any existing legends Arguments: loc - location of legend from: 'best','upper right', 'upper left','lower left','lower right','right', 'center left','center right','lower center', 'upper center','center' Default: 'best' **kw - keyword arguments passed to matplotlib.axes.legend """ self.remove_legend() patches = [] for key in self.means: color=self.means[key][0].get_color() patches.append(matplotlib.lines.Line2D([],[],color=color, label=key, lw=3)) self.ax.legend(handles=patches, loc=loc, **kw) pass
def add_mean(
self, name, tv_table, endpoint=700, threshold=2, color='black', alpha=0.8, lw=4, dashes=[], **kw)
Calculate and add to the plot a line for the mean volume vs time for all individuals in a tumour volume table. These individuals should be a logical group (eg treatment)
Arguments:
name - The legend label for this group
tv_table - a pandas data frame of volume measurements
with individuals in columns and timepoints
as rows. Individuals are removed from study
at the first NaN timepoint
endpoint - the volume at which the endpoint is reached
Default: 700
threshold - the minimum group size to plot at a given
time point.
Default: 2
color - the color to plot this data group
Valid colors include matplotlib named colors
html colors (eg '#029386') or RGB tuples
(eg (0.0078, 0.58, 0.53))
alpha - Percent transparency as a value between
0.0 (transparent) and 1.0 (opaque)
lw - line width in points
Default: 4
dashes - A line dash pattern as an even length list
of on off lengths in points
**kw - additional key word arguments are passed to
matplotlib.axes.plot and
lifelines.kmf.plot and can be any
matplotlib.Line2D attributes
def add_mean(self, name, tv_table, endpoint = 700, threshold=2, color = 'black', alpha=0.8, lw=4, dashes = [], **kw): """Calculate and add to the plot a line for the mean volume vs time for all individuals in a tumour volume table. These individuals should be a logical group (eg treatment) Arguments: name - The legend label for this group tv_table - a pandas data frame of volume measurements with individuals in columns and timepoints as rows. Individuals are removed from study at the first NaN timepoint endpoint - the volume at which the endpoint is reached Default: 700 threshold - the minimum group size to plot at a given time point. Default: 2 color - the color to plot this data group Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) lw - line width in points Default: 4 dashes - A line dash pattern as an even length list of on off lengths in points **kw - additional key word arguments are passed to matplotlib.axes.plot and lifelines.kmf.plot and can be any matplotlib.Line2D attributes """ super(VolumeSurvivalPlot, self).add_mean(name=name, tv_table=tv_table, threshold=threshold, color=color, alpha=alpha, lw=lw, dashes = dashes, **kw) self.volume_data[name] = tv_table self.add_kmf(name, tv_table, endpoint = endpoint, color = color, alpha=alpha, lw=lw, dashes = dashes, **kw) pass
def display(
self, legend=True, use_mpld3=True, update=True, hide_volume_labels=True, **kw)
Display the dual tumour volume and Kaplan Meier plot KNOWN BUG - mpld3 does not correctly display the step plot in version < 0.3
def display(self, legend=True, use_mpld3=True, update=True, hide_volume_labels=True, **kw): """Display the dual tumour volume and Kaplan Meier plot KNOWN BUG - mpld3 does not correctly display the step plot in version < 0.3 """ result = super(VolumeSurvivalPlot, self).display(legend=legend, use_mpld3=use_mpld3, update=update, hide_volume_labels = hide_volume_labels, **kw) if use_mpld3: return result
def logrank_test(
self, treatment_a, treatment_b, t1error=0.05)
Calculate a log rank test (Mantel-Cox) statistic between two treatments Calls lifelines.statistics.logrank_test for calculation.
Arguments:
treatment_a - The legend label for this group as used
with add_mean.
treatment_b - The legend label for this group as used
with add_mean.
t1error - probability of a type 1 error (alpha)
Default: 0.05
def logrank_test(self, treatment_a, treatment_b, t1error=0.05): """Calculate a log rank test (Mantel-Cox) statistic between two treatments Calls lifelines.statistics.logrank_test for calculation. Arguments: treatment_a - The legend label for this group as used with add_mean. treatment_b - The legend label for this group as used with add_mean. t1error - probability of a type 1 error (alpha) Default: 0.05 """ if not self.endpoint or not self.volume_data: print('you need to add data with .add_mean() before using logrank_test') raise ValueError survival_a = volume_to_survival(self.volume_data[treatment_a], endpoint=self.endpoint) survival_b = volume_to_survival(self.volume_data[treatment_b], endpoint=self.endpoint) result = logrank_test(list(survival_a['Time']), list(survival_b['Time']), list(survival_a['Observed']), list(survival_b['Observed']), alpha=1-t1error) result.print_summary() return result
def remove_legend(
self)
Remove any existing matplotlib axis legend
def remove_legend(self): """Remove any existing matplotlib axis legend""" if self.ax.legend_: self.ax.legend_.remove() if self.km_ax.legend_: self.km_ax.legend_.remove() pass
def save_html(
self, fileobj=None, legend=True, update=True, template_type='general', **kw)
Generate the figure as a html file
Arguments:
fileobj - a python file object
Default: None (saved to a new file with the same
name as the parent object with .html)
legend - boolean for displaying a legend
Default: True
update - boolean for reapplying spine, tick, title and
axis limits to the graph
Default: True
template_type - string specifying the type of HTML template to use.
Options from mpld3 are:
"simple" suitable for simple html page with one figure.
Will fail if require.js is available on the page.
"notebook" assumes require.js and jquery are available.
"general" works both in and out of the notebook,
whether or not require.js and jquery are available
Default: general
**kw - additional keyword arguments are passed to mpld3.save_html
def save_html(self, fileobj=None, legend=True, update=True, template_type = "general", **kw): """Generate the figure as a html file Arguments: fileobj - a python file object Default: None (saved to a new file with the same name as the parent object with .html) legend - boolean for displaying a legend Default: True update - boolean for reapplying spine, tick, title and axis limits to the graph Default: True template_type - string specifying the type of HTML template to use. Options from mpld3 are: "simple" suitable for simple html page with one figure. Will fail if require.js is available on the page. "notebook" assumes require.js and jquery are available. "general" works both in and out of the notebook, whether or not require.js and jquery are available Default: general **kw - additional keyword arguments are passed to mpld3.save_html """ if legend: self.add_legend() else: self.remove_legend() if not fileobj: fileobj='{0}.html'.format(self.name) if update: self.set_spines_and_ticks() self.set_title_and_labels() self.set_limits() mpld3.save_html(fig=self.fig, fileobj=fileobj, template_type=template_type, **kw) pass
def save_pdf(
self, fileobj=None, legend=True, update=True, **kw)
Generate the figure as a pdf file
Arguments:
fileobj - a python file object
Default: None (saved to a new file with the same
name as the parent object with .html)
legend - boolean for displaying a legend
Default: True
update - boolean for reapplying spine, tick, title and
axis limits to the graph
Default: True
**kw - additional keyword arguments are passed to
matplotlib.PdfPages.savefig
def save_pdf(self, fileobj=None, legend=True, update=True, **kw): """Generate the figure as a pdf file Arguments: fileobj - a python file object Default: None (saved to a new file with the same name as the parent object with .html) legend - boolean for displaying a legend Default: True update - boolean for reapplying spine, tick, title and axis limits to the graph Default: True **kw - additional keyword arguments are passed to matplotlib.PdfPages.savefig """ if legend: self.add_legend() else: self.remove_legend() if update: self.set_spines_and_ticks() self.set_title_and_labels() self.set_limits() if not fileobj: fileobj='{0}.html'.format(self.name) from matplotlib.backends.backend_pdf import PdfPages with PdfPages(fileobj) as pdf: pdf.savefig(self.fig, **kw) pass
def set_limits(
self)
Apply the axis limits set by self.xlim and self.ylim to the graph axes. The Kaplan-Meier y axis will remain [0,1.02]
def set_limits(self): """Apply the axis limits set by self.xlim and self.ylim to the graph axes. The Kaplan-Meier y axis will remain [0,1.02]""" if self.xlim or self.ylim: self.ax.set_xlim(self.xlim) ylim = self.ylim if self.ylim else [0,1000] self.ax.set_ylim(ylim) self.ax.set_autoscaley_on(False) self.km_ax.set_xlim(self.xlim) self.km_ax.set_ylim([0,1.02]) #set >1 so lines retain full width self.km_ax.set_autoscaley_on(False) else: self.ax.set_autoscaley_on(True) self.km_ax.set_ylim([0,1.02]) self.km_ax.set_autoscaley_on(True) pass
def set_spines_and_ticks(
self, axes=None, remove_spines=['right', 'top'], y_set_ticks=['left'], x_set_ticks=['bottom'])
Set spines (the lines along the axis) and tick locations for the plot
Arguments:
axes - the matplotlib.axis object to act on
Default: None
(interpreted as self.ax and self.km_ax)
remove_spines - a list of spines to remove.
Default: ['right','top']
y_set_ticks - a list of locations of y axis ticks
Default: ['left']
x_set_ticks - a list of locations of x axis ticks
Default: ['bottom']
def set_spines_and_ticks(self, axes = None, remove_spines=['right','top'], y_set_ticks = ['left'], x_set_ticks = ['bottom'], ): """Set spines (the lines along the axis) and tick locations for the plot Arguments: axes - the matplotlib.axis object to act on Default: None (interpreted as self.ax and self.km_ax) remove_spines - a list of spines to remove. Default: ['right','top'] y_set_ticks - a list of locations of y axis ticks Default: ['left'] x_set_ticks - a list of locations of x axis ticks Default: ['bottom'] """ if axes == None: axes = [self.km_ax, self.ax] super(VolumeSurvivalPlot, self).set_spines_and_ticks(axes = axes, remove_spines = remove_spines, y_set_ticks = y_set_ticks, x_set_ticks = x_set_ticks,) self.km_ax.yaxis.set_ticks(self.km_yticks) pass
def set_title_and_labels(
self, title=None, ylabel=None, xlabel=None, km_title=None, km_ylabel=None, km_xlabel=None, hide_volume_labels=True, **kw)
Set plot titles and axis labels
Arguments:
title - main plot title
Default: None (uses self.title)
ylabel - y axis label
Default: None (uses self.ylabel)
xlabel - x axis label
Default: None (uses self.xlabel)
kmtitle - Kaplan Meier plot label
Default: None (uses self.km_title)
ylabel - y axis label
Default: None (uses self.km_ylabel)
xlabel - x axis label
Default: None (uses self.km_xlabel)
hide_volume_labels - do not display x axis label
between volume plot and Kaplan-Meier
Default: True
def set_title_and_labels(self, title=None, ylabel=None, xlabel=None, km_title=None, km_ylabel=None, km_xlabel=None, hide_volume_labels = True,**kw): """Set plot titles and axis labels Arguments: title - main plot title Default: None (uses self.title) ylabel - y axis label Default: None (uses self.ylabel) xlabel - x axis label Default: None (uses self.xlabel) kmtitle - Kaplan Meier plot label Default: None (uses self.km_title) ylabel - y axis label Default: None (uses self.km_ylabel) xlabel - x axis label Default: None (uses self.km_xlabel) hide_volume_labels - do not display x axis label between volume plot and Kaplan-Meier Default: True """ if title == None: title = self.title if ylabel == None: ylabel = self.ylabel if xlabel == None: xlabel = self.xlabel if km_title == None: km_title = self.km_title if km_ylabel == None: km_ylabel = self.km_ylabel if km_xlabel == None: km_xlabel = self.km_xlabel if self.fontsize and not 'fontsize' in kw: kw['fontsize'] = self.fontsize self.ax.set_title(title, **kw) self.ax.set_ylabel(ylabel, **kw) if hide_volume_labels: self.ax.set_xlabel('', **kw) else: self.ax.set_xlabel(xlabel, **kw) self.km_ax.set_title(km_title, **kw) self.km_ax.set_ylabel(km_ylabel, **kw) self.km_ax.set_xlabel(km_xlabel, **kw) pass
def shade_interval(
self, start, end, facecolor='lightgrey', alpha=0.2, lw=0, **kw)
Add rectangular fill the full height of the axis to indicate a date range (eg for treatment).
Arguments:
start - the start point of the rectangle
end - the end point of the rectangle
facecolor - the fill color for the rectangular patches
Valid colors include matplotlib named colors
html colors (eg '#029386') or RGB tuples
(eg (0.0078, 0.58, 0.53))
alpha - Percent transparency as a value between
0.0 (transparent) and 1.0 (opaque)
Default: 0.2
lw - the width of the line bordering the patch in points
Default: 0 (no line)
**kw - keyword arguments passed to matplotlib.patches.Rectangle
def shade_interval(self, start, end, facecolor="lightgrey", alpha=0.2, lw=0, **kw): """Add rectangular fill the full height of the axis to indicate a date range (eg for treatment). Arguments: start - the start point of the rectangle end - the end point of the rectangle facecolor - the fill color for the rectangular patches Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) alpha - Percent transparency as a value between 0.0 (transparent) and 1.0 (opaque) Default: 0.2 lw - the width of the line bordering the patch in points Default: 0 (no line) **kw - keyword arguments passed to matplotlib.patches.Rectangle """ miny,maxy = self.ax.get_yaxis().get_data_interval() self.ax.add_patch(matplotlib.patches.Rectangle((start, min(miny,self.ylim[0])), end, max(maxy,self.ylim[1]), facecolor="lightgrey",alpha=alpha, lw=0, **kw)) pass
def show_treatment_days(
self, days, facecolor='lightgrey', lw=0, **kw)
Add rectangular patches on the x axis to indicate days on which treatment occurred.
Arguments:
days - an integer list of days eg [1,3,5]
facecolor - the fill color for the rectangular patches
Valid colors include matplotlib named colors
html colors (eg '#029386') or RGB tuples
(eg (0.0078, 0.58, 0.53))
lw - the width of the line bordering the patch in points
Default: 0 (no line)
**kw - keyword arguments passed to matplotlib.patches.Rectangle
def show_treatment_days(self, days, facecolor="lightgrey",lw=0, **kw): """Add rectangular patches on the x axis to indicate days on which treatment occurred. Arguments: days - an integer list of days eg [1,3,5] facecolor - the fill color for the rectangular patches Valid colors include matplotlib named colors html colors (eg '#029386') or RGB tuples (eg (0.0078, 0.58, 0.53)) lw - the width of the line bordering the patch in points Default: 0 (no line) **kw - keyword arguments passed to matplotlib.patches.Rectangle """ for day in days: self.ax.add_patch(matplotlib.patches.Rectangle((day, 0), 1, 20, facecolor=facecolor, lw=lw, **kw)) pass
Instance variables
var endpoint
var fontsize
var intervals
var km_ci_show
var km_show_censors
var km_title
var km_xlabel
var km_ylabel
var km_yticks
var kmfs
var means
var title
var vertical
var volume_data
var xlabel
var xlim
var ylabel
var ylim