#!/usr/bin/env python3.8
# coding: latin-1
# (c) Massachusetts Institute of Technology 2015-2018
# (c) Brian Teague 2018-2022
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
cytoflow.views.bar_chart
------------------------
Plot a bar chart from a statistic.
`BarChartView` -- the `IView` class that makes the plot.
"""
from traits.api import provides, Constant
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import cytoflow.utility as util
from .i_view import IView
from .base_views import Base1DStatisticsView
[docs]@provides(IView)
class BarChartView(Base1DStatisticsView):
"""
Plots a bar chart of some summary statistic
Attributes
----------
Examples
--------
Make a little data set.
.. plot::
:context: close-figs
>>> import cytoflow as flow
>>> import_op = flow.ImportOp()
>>> import_op.tubes = [flow.Tube(file = "Plate01/RFP_Well_A3.fcs",
... conditions = {'Dox' : 10.0}),
... flow.Tube(file = "Plate01/CFP_Well_A4.fcs",
... conditions = {'Dox' : 1.0})]
>>> import_op.conditions = {'Dox' : 'float'}
>>> ex = import_op.apply()
Add a threshold gate
.. plot::
:context: close-figs
>>> ex2 = flow.ThresholdOp(name = 'Threshold',
... channel = 'Y2-A',
... threshold = 2000).apply(ex)
Add a statistic
.. plot::
:context: close-figs
>>> ex3 = flow.ChannelStatisticOp(name = "ByDox",
... channel = "Y2-A",
... by = ['Dox', 'Threshold'],
... function = len).apply(ex2)
Plot the bar chart
.. plot::
:context: close-figs
>>> flow.BarChartView(statistic = ("ByDox", "len"),
... variable = "Dox",
... huefacet = "Threshold").plot(ex3)
"""
# traits
id = Constant("edu.mit.synbio.cytoflow.view.barchart")
friendly_id = Constant("Bar Chart")
[docs] def enum_plots(self, experiment):
"""
Returns an iterator over the possible plots that this View can
produce. The values returned can be passed to "plot".
"""
return super().enum_plots(experiment)
[docs] def plot(self, experiment, plot_name = None, **kwargs):
"""
Plot a bar chart
Parameters
----------
color : a matplotlib color
Sets the colors of all the bars, even if there is a hue facet
errwidth : scalar
The width of the error bars, in points
errcolor : a matplotlib color
The color of the error bars
capsize : scalar
The size of the error bar caps, in points
Notes
-----
Other ``kwargs`` are passed to `matplotlib.axes.Axes.bar <https://matplotlib.org/devdocs/api/_as_gen/matplotlib.axes.Axes.bar.html>`_
"""
super().plot(experiment, plot_name, **kwargs)
def _grid_plot(self, experiment, grid, **kwargs):
# because the bottom of a bar chart is "0", masking out bad
# values on a log scale doesn't work. we must clip instead.
orientation = kwargs.pop('orientation', 'vertical')
# statistic scale
scale = kwargs.pop('scale')
if scale.name == "log":
scale.mode = "clip"
# limits
lim = kwargs.pop('lim', None)
stat = experiment.statistics[self.statistic]
if orientation == 'vertical':
map_args = [self.variable, stat.name]
else:
map_args = [stat.name, self.variable]
if self.huefacet:
map_args.append(self.huefacet)
if self.error_statistic[0]:
error_stat = experiment.statistics[self.error_statistic]
map_args.append(error_stat.name)
else:
error_stat = None
grid.map(_barplot,
*map_args,
view = self,
stat_name = stat.name,
error_name = error_stat.name if error_stat is not None else None,
orientation = orientation,
grid = grid,
**kwargs)
if orientation == 'horizontal':
return dict(xscale = scale,
xlim = lim)
else:
return dict(yscale = scale,
ylim = lim)
def _barplot(*args, view, stat_name, error_name, orientation, grid, **kwargs):
"""
A custom barchart function. This is assembled from pieces cobbled
together from seaborn v0.7.1.
"""
data = pd.DataFrame({s.name: s for s in args}).sort_values(view.variable)
categories = data[view.variable].unique()
# plot the bars
width = kwargs.pop('width', 0.8)
ax = kwargs.pop('ax', None)
if ax is None:
ax = plt.gca()
err_kws = {}
errwidth = kwargs.pop('errwidth', None)
if errwidth:
err_kws['lw'] = errwidth
else:
err_kws['lw'] = mpl.rcParams["lines.linewidth"]
errcolor = kwargs.pop('errcolor', '0.2')
capsize = kwargs.pop('capsize', None)
# Get the right matplotlib function depending on the orientation
barfunc = ax.bar if orientation == "vertical" else ax.barh
barpos = np.arange(len(categories))
if view.huefacet:
hue_names = grid.hue_names
hue_level = data[view.huefacet].iloc[0]
hue_idx = hue_names.index(hue_level)
hue_offsets = np.linspace(0, width - (width / len(hue_names)), len(hue_names))
hue_offsets -= hue_offsets.mean()
nested_width = width / len(hue_names) * 0.98
offpos = barpos + hue_offsets[hue_idx]
barfunc(offpos,
data[stat_name],
nested_width,
align="center",
**kwargs)
if error_name:
confint = data[error_name]
errcolors = [errcolor] * len(offpos)
_draw_confints(ax,
offpos,
data[data[view.huefacet] == hue_level][stat_name],
confint,
errcolors,
orientation,
errwidth = errwidth,
capsize = capsize)
else:
barfunc(barpos, data[stat_name], width, align="center", **kwargs)
if error_name:
confint = data[error_name]
errcolors = [errcolor] * len(barpos)
_draw_confints(ax,
barpos,
data[stat_name],
confint,
errcolors,
orientation,
errwidth = errwidth,
capsize = capsize)
if orientation == "vertical":
ax.set_xticks(np.arange(len(categories)))
ax.set_xticklabels(categories)
else:
ax.set_yticks(np.arange(len(categories)))
ax.set_yticklabels(categories)
if orientation == "vertical":
ax.xaxis.grid(False)
ax.set_xlim(-.5, len(categories) - .5)
else:
ax.yaxis.grid(False)
ax.set_ylim(-.5, len(categories) - .5)
return ax
def _draw_confints(ax, at_group, stat, confints, colors,
orient, errwidth=None, capsize=None, **kws):
if errwidth is not None:
kws.setdefault("lw", errwidth)
else:
kws.setdefault("lw", mpl.rcParams["lines.linewidth"] * 1.8)
if isinstance(confints.iloc[0], tuple):
ci_lo = [x[0] for x in confints]
ci_hi = [x[1] for x in confints]
else:
ci_lo = [stat.iloc[i] - x for i, x in confints.reset_index(drop = True).items()]
ci_hi = [stat.iloc[i] + x for i, x in confints.reset_index(drop = True).items()]
for at, lo, hi, color in zip(at_group,
ci_lo,
ci_hi,
colors):
if orient == "vertical":
if capsize is not None:
kws['marker'] = '_'
kws['markersize'] = capsize * 2
kws['markeredgewidth'] = kws['lw']
ax.plot([at, at], [lo, hi], color=color, **kws)
else:
if capsize is not None:
kws['marker'] = '|'
kws['markersize'] = capsize * 2
kws['markeredgewidth'] = kws['lw']
ax.plot([lo, hi], [at, at], color=color, **kws)
util.expand_class_attributes(BarChartView)
util.expand_method_parameters(BarChartView, BarChartView.plot)