def plot(self, channel_names, kind='histogram', gates=None, gate_colors=None, gate_lw=1, **kwargs): """Plot the flow cytometry data associated with the sample on the current axis. To produce the plot, follow up with a call to matplotlib's show() function. Parameters ---------- {graph_plotFCM_pars} {FCMeasurement_plot_pars} {common_plot_ax} gates : [None, Gate, list of Gate] Gate must be of type {_gate_available_classes}. gate_lw: float | iterable line width to use when drawing gates if float, uses the same line width for all gates if iterable, then cycles between the values kwargs : dict Additional keyword arguments to be passed to graph.plotFCM Returns ------- None : if no data is present plot_output : output of plot command used to draw (e.g., output of hist) Examples -------- >>> sample.plot('Y2-A', bins=100, alpha=0.7, color='green', normed=1) # 1d histogram >>> sample.plot(['B1-A', 'Y2-A'], cmap=cm.Oranges, colorbar=False) # 2d histogram """ ax = kwargs.get('ax') channel_names = to_list(channel_names) gates = to_list(gates) plot_output = graph.plotFCM(self.data, channel_names, kind=kind, **kwargs) if gates is not None: if gate_colors is None: gate_colors = cycle(('b', 'g', 'r', 'm', 'c', 'y')) if not isinstance(gate_lw, collections.Iterable): gate_lw = [gate_lw] gate_lw = cycle(gate_lw) for (g, c, lw) in zip(gates, gate_colors, gate_lw): g.plot(ax=ax, ax_channels=channel_names, color=c, lw=lw) return plot_output
def get_meta_fields(self, fields, kwargs={}): ''' Return a dictionary of metadata fields ''' fields = to_list(fields) meta = self.get_meta() return {field: meta.get(field) for field in fields}
def apply(self, func, ids=None, applyto='measurement', noneval=nan, setdata=False, output_format='dict', ID=None, **kwargs): ''' Apply func to each of the specified measurements. Parameters ---------- func : callable Accepts a Measurement object or a DataFrame. ids : hashable| iterable of hashables | None Keys of measurements to which func will be applied. If None is given apply to all measurements. applyto : 'measurement' | 'data' * 'measurement' : apply to measurements objects themselves. * 'data' : apply to measurement associated data noneval : obj Value returned if applyto is 'data' but no data is available. setdata : bool Whether to set the data in the Measurement object. Used only if data is not already set. output_format : ['dict' | 'collection'] * collection : keeps result as collection WARNING: For collection, func should return a copy of the measurement instance rather than the original measurement instance. Returns ------- Dictionary keyed by measurement keys containing the corresponding output of func or returns a collection (if output_format='collection'). ''' if ids is None: ids = self.keys() else: ids = to_list(ids) result = dict((i, self[i].apply(func, applyto, noneval, setdata)) for i in ids) if output_format == 'collection': can_keep_as_collection = all( [isinstance(r, self._measurement_class) for r in result.values()]) if not can_keep_as_collection: raise TypeError( 'Cannot turn output into a collection. The provided func must return results of type {}'.format( self._measurement_class)) new_collection = self.copy() # Locate IDs to remove ids_to_remove = [x for x in self.keys() if x not in ids] # Remove data for these IDs for ids in ids_to_remove: new_collection.pop(ids) # Update keys with new values for k, v in new_collection.items(): new_collection[k] = result[k] if ID is not None: new_collection.ID = ID return new_collection else: # Return a dictionary return result
def add_callback(self, func): """ Registers a call back function """ if func is None: return func_list = to_list(func) if not hasattr(self, 'callback_list'): self.callback_list = func_list else: self.callback_list.extend(func_list)
def filter_by_key(self, keys, ID=None): """ Keep only Measurements with given keys. """ keys = to_list(keys) fil = lambda x: x in keys if ID is None: ID = self.ID return self.filter(fil, applyto='keys', ID=ID)
def filter_by_key(self, keys, ID=None): """ Keep only Measurements with given keys. """ keys = to_list(keys) fil = lambda x: x in keys if ID is None: ID = self.ID return self.filter(fil, applyto='keys', ID=ID)
def add_callback(self, func): """ Registers a call back function """ if func is None: return func_list = to_list(func) if not hasattr(self, 'callback_list'): self.callback_list = func_list else: self.callback_list.extend(func_list)
def filter_by_rows(self, rows, ID=None): """ Keep only Measurements in corresponding rows. """ rows = to_list(rows) fil = lambda x: x in rows applyto = {k: self._positions[k][0] for k in self.keys()} if ID is None: ID = self.ID return self.filter(fil, applyto=applyto, ID=ID)
def filter_by_cols(self, cols, ID=None): """ Keep only Measurements in corresponding columns. """ rows = to_list(cols) fil = lambda x: x in rows applyto = {k: self._positions[k][1] for k in self.keys()} if ID is None: ID = self.ID + '.filtered_by_cols' return self.filter(fil, applyto=applyto, ID=ID)
def filter_by_rows(self, rows, ID=None): """ Keep only Measurements in corresponding rows. """ rows = to_list(rows) fil = lambda x: x in rows applyto = {k: self._positions[k][0] for k in self.keys()} if ID is None: ID = self.ID return self.filter(fil, applyto=applyto, ID=ID)
def filter_by_cols(self, cols, ID=None): """ Keep only Measurements in corresponding columns. """ rows = to_list(cols) fil = lambda x: x in rows applyto = {k: self._positions[k][1] for k in self.keys()} if ID is None: ID = self.ID + '.filtered_by_cols' return self.filter(fil, applyto=applyto, ID=ID)
def __init__(self, vert, channels, region, name=None): self.vert = vert channels = to_list(channels) self.channels = channels if name == None: self.name = "Unnamed Gate {0}".format(Gate.unnamed_gate_num) Gate.unnamed_gate_num += 1 else: self.name = name self.region = region self.validate_input()
def __init__(self, vert, channels, region, name=None): self.vert = vert channels = to_list(channels) self.channels = channels if name == None: self.name = "Unnamed Gate {0}".format(Gate.unnamed_gate_num) Gate.unnamed_gate_num += 1 else: self.name = name self.region = region self.validate_input()
def get_measurement_metadata(self, fields, ids=None, noneval=nan, output_format='DataFrame'): """ Get the metadata fields of specified measurements (all if None given). Parameters ---------- fields : str | iterable of str Names of metadata fields to be returned. ids : hashable| iterable of hashables | None Keys of measurements for which metadata will be returned. If None is given return metadata of all measurements. noneval : obj Value returned if applyto is 'data' but no data is available. output_format : 'DataFrame' | 'dict' 'DataFrame' : return DataFrame, 'dict' : return dictionary. Returns ------- Measurement metadata in specified output_format. """ fields = to_list(fields) func = lambda x: x.get_meta_fields(fields) meta_d = self.apply(func, ids=ids, applyto='measurement', noneval=noneval, output_format='dict') if output_format == 'dict': return meta_d elif output_format == 'DataFrame': from pandas import DataFrame as DF meta_df = DF(meta_d, index=fields) return meta_df else: msg = ("The output_format must be either 'dict' or 'DataFrame'. " + "Encountered unsupported value %s." % repr(output_format)) raise Exception(msg)
def set_positions(self, positions=None, position_mapper='name', ids=None): """ checks for position validity & collisions, but not that all measurements are assigned. Parameters ----------- positions : is dict-like of measurement_key:(row,col) parser : callable - gets key and returns position mapping - key:pos 'name' - parses things like 'A1', 'G12' 'number' - converts number to positions, going over rows first. ids : parser will be applied to specified ids only. If None is given, parser will be applied to all measurements. TODO: output a more informative message for position collisions """ if positions is None: if ids is None: ids = self.keys() else: ids = to_list(ids) mapper = self._get_ID2position_mapper(position_mapper) positions = dict((ID, mapper(ID)) for ID in ids) else: pass # check that resulting assignment is unique (one measurement per position) temp = self._positions.copy() temp.update(positions) if not len(temp.values()) == len(set(temp.values())): msg = 'A position can only be occupied by a single measurement' raise Exception(msg) for k, pos in positions.items(): if not self._is_valid_position(pos): msg = 'Position {} is not supported for this collection'.format( pos) raise ValueError(msg) self._positions[k] = pos self[k]._set_position(self.ID, pos)
def set_positions(self, positions=None, position_mapper='name', ids=None): ''' checks for position validity & collisions, but not that all measurements are assigned. Parameters ----------- positions : is dict-like of measurement_key:(row,col) parser : callable - gets key and returns position mapping - key:pos 'name' - parses things like 'A1', 'G12' 'number' - converts number to positions, going over rows first. ids : parser will be applied to specified ids only. If None is given, parser will be applied to all measurements. TODO: output a more informative message for position collisions ''' if positions is None: if ids is None: ids = self.keys() else: ids = to_list(ids) mapper = self._get_ID2position_mapper(position_mapper) positions = dict((ID, mapper(ID)) for ID in ids) else: pass # check that resulting assignment is unique (one measurement per position) temp = self._positions.copy() temp.update(positions) if not len(temp.values()) == len(set(temp.values())): msg = 'A position can only be occupied by a single measurement' raise Exception(msg) for k, pos in positions.items(): if not self._is_valid_position(pos): msg = 'Position {} is not supported for this collection'.format(pos) raise ValueError(msg) self._positions[k] = pos self[k]._set_position(self.ID, pos)
def _find_orientation(self, ax_channels): ax_channels = to_list(ax_channels) c = self.channels[0] if ax_channels is not None: try: i = ax_channels.index(c) if i == 0: flip = False else: flip = True except ValueError: raise Exception("""Trying to plot gate that is defined on channel {0}, but figure axis correspond to channels {1}""".format(c, ax_channels)) if len(self.channels) == 2: c = self.channels[1] if c not in ax_channels: raise Exception("""Trying to plot gate that is defined on channel {0}, but figure axis correspond to channels {1}""".format(c, ax_channels)) return flip
def transform_frame(frame, transform, columns=None, direction='forward', return_all=True, args=(), **kwargs): """ Apply transform to specified columns. direction: 'forward' | 'inverse' return_all: bool True - return all columns, with specified ones transformed. False - return only specified columns. .. warning:: deprecated """ tfun, tname = parse_transform(transform, direction) columns = to_list(columns) if columns is None: columns = frame.columns if return_all: transformed = frame.copy() for c in columns: transformed[c] = tfun(frame[c], *args, **kwargs) else: transformed = frame.filter(columns).apply(tfun, *args, **kwargs) return transformed
def _find_orientation(self, ax_channels): ax_channels = to_list(ax_channels) c = self.channels[0] if ax_channels is not None: try: i = ax_channels.index(c) if i == 0: flip = False else: flip = True except ValueError: raise Exception( """Trying to plot gate that is defined on channel {0}, but figure axis correspond to channels {1}""". format(c, ax_channels)) if len(self.channels) == 2: c = self.channels[1] if c not in ax_channels: raise Exception( """Trying to plot gate that is defined on channel {0}, but figure axis correspond to channels {1}""". format(c, ax_channels)) return flip
def get_measurement_metadata(self, fields, ids=None, noneval=nan, output_format='DataFrame'): """ Get the metadata fields of specified measurements (all if None given). Parameters ---------- fields : str | iterable of str Names of metadata fields to be returned. ids : hashable| iterable of hashables | None Keys of measurements for which metadata will be returned. If None is given return metadata of all measurements. noneval : obj Value returned if applyto is 'data' but no data is available. output_format : 'DataFrame' | 'dict' 'DataFrame' : return DataFrame, 'dict' : return dictionary. Returns ------- Measurement metadata in specified output_format. """ fields = to_list(fields) func = lambda x: x.get_meta_fields(fields) meta_d = self.apply(func, ids=ids, applyto='measurement', noneval=noneval, output_format='dict') if output_format is 'dict': return meta_d elif output_format is 'DataFrame': from pandas import DataFrame as DF meta_df = DF(meta_d, index=fields) return meta_df else: msg = ("The output_format must be either 'dict' or 'DataFrame'. " + "Encountered unsupported value %s." % repr(output_format)) raise Exception(msg)
def plotFCM(data, channel_names, kind='histogram', ax=None, autolabel=True, xlabel_kwargs={}, ylabel_kwargs={}, colorbar=False, grid=False, **kwargs): """ Plots the sample on the current axis. Follow with a call to matplotlibs show() in order to see the plot. Parameters ---------- data : DataFrame {graph_plotFCM_pars} {common_plot_ax} Returns ------- The output of the plot command used """ if ax == None: ax = pl.gca() xlabel_kwargs.setdefault('size', 16) ylabel_kwargs.setdefault('size', 16) channel_names = to_list(channel_names) if len(channel_names) == 1: # 1D so histogram plot kwargs.setdefault('color', 'gray') kwargs.setdefault('histtype', 'stepfilled') kwargs.setdefault('bins', 200) # Do not move above x = data[channel_names[0]].values if len(x) >= 1: if (len(x) == 1) and isinstance(kwargs['bins'], int): # Only needed for hist (not hist2d) due to hist function doing # excessive input checking warnings.warn( "One of the data sets only has a single event. " "This event won't be plotted unless the bin locations" " are explicitly provided to the plotting function. ") return None plot_output = ax.hist(x, **kwargs) else: return None elif len(channel_names) == 2: x = data[channel_names[0]].values # value of first channel y = data[channel_names[1]].values # value of second channel if len(x) == 0: # Don't draw a plot if there's no data return None if kind == 'scatter': kwargs.setdefault('edgecolor', 'none') plot_output = ax.scatter(x, y, **kwargs) elif kind == 'histogram': kwargs.setdefault('bins', 200) # Do not move above kwargs.setdefault('cmin', 1) kwargs.setdefault('cmap', pl.cm.copper) kwargs.setdefault('norm', matplotlib.colors.LogNorm()) plot_output = ax.hist2d(x, y, **kwargs) mappable = plot_output[-1] if colorbar: pl.colorbar(mappable, ax=ax) else: raise ValueError( "Not a valid plot type. Must be 'scatter', 'histogram'") else: raise ValueError( 'Received an unexpected number of channels: "{}"'.format( channel_names)) pl.grid(grid) if autolabel: y_label_text = 'Counts' if len( channel_names) == 1 else channel_names[1] ax.set_xlabel(channel_names[0], **xlabel_kwargs) ax.set_ylabel(y_label_text, **ylabel_kwargs) return plot_output
def grid_plot(self, func, applyto='measurement', ids=None, row_labels=None, col_labels=None, xlim='auto', ylim='auto', xlabel=None, ylabel=None, colorbar=True, row_label_xoffset=None, col_label_yoffset=None, hide_tick_labels=True, hide_tick_lines=True, hspace=0, wspace=0, row_labels_kwargs={}, col_labels_kwargs={}): """ Creates subplots for each well in the plate. Uses func to plot on each axis. Follow with a call to matplotlibs show() in order to see the plot. Parameters ---------- func : callable func is a callable that accepts a measurement object (with an optional axis reference) and plots on the current axis. Return values from func are ignored. .. note: if using applyto='measurement', the function when querying for data should make sure that the data actually exists applyto : 'measurement' | 'data' {_graph_grid_layout} {bases_OrderedCollection_grid_plot_pars} Returns ------- {_graph_grid_layout_returns} Examples --------- >>> def y(well, ax): >>> data = well.get_data() >>> if data is None: >>> return None >>> graph.plotFCM(data, 'Y2-A') >>> def z(data, ax): >>> plot(data[0:100, 1], data[0:100, 2]) >>> plate.plot(y, applyto='measurement'); >>> plate.plot(z, applyto='data'); """ # Acquire call arguments to be passed to create plate layout callArgs = locals().copy() # This statement must remain first. The copy is just defensive. [callArgs.pop(varname) for varname in ['self', 'func', 'applyto', 'ids', 'colorbar', 'xlim', 'ylim']] # pop args callArgs['rowNum'] = self.shape[0] callArgs['colNum'] = self.shape[1] subplots_adjust_args = {} subplots_adjust_args.setdefault('right', 0.85) subplots_adjust_args.setdefault('top', 0.85) pl.subplots_adjust(**subplots_adjust_args) # Uses plate default row/col labels if user does not override them by specifying row/col # labels if row_labels == None: callArgs['row_labels'] = self.row_labels if col_labels == None: callArgs['col_labels'] = self.col_labels ax_main, ax_subplots = graph.create_grid_layout(**callArgs) subplots_ax = DF(ax_subplots, index=self.row_labels, columns=self.col_labels) if ids is None: ids = self.keys() ids = to_list(ids) for ID in ids: measurement = self[ID] if not hasattr(measurement, 'data'): continue row, col = self._positions[ID] ax = subplots_ax[col][row] pl.sca(ax) # sets the current axis if applyto == 'measurement': func(measurement, ax) # reminder: pandas row/col order is reversed elif applyto == 'data': data = measurement.get_data() if data is not None: if func.func_code.co_argcount == 1: func(data) else: func(data, ax) else: raise ValueError('Encountered unsupported value {} for applyto parameter.'.format( applyto)) # Autoscaling axes graph.scale_subplots(ax_subplots, xlim=xlim, ylim=ylim) ##### # Placing ticks on the top left subplot ax_label = ax_subplots[0, -1] pl.sca(ax_label) if xlabel: xlim = ax_label.get_xlim() pl.xticks([xlim[0], xlim[1]], rotation=90) if ylabel: ylim = ax_label.get_ylim() pl.yticks([ylim[0], ylim[1]], rotation=0) pl.sca(ax_main) # sets to the main axis -- more intuitive return ax_main, ax_subplots
def create_artist(self): self.poly = pl.Polygon(self.coordinates, color='k', fill=False) self.artist_list = to_list(self.poly) self.ax.add_artist(self.poly)
def set_visible(self, visible=True): for artist in to_list(self.artist): artist.set_visible(visible)
def create_artist(self): self.poly = pl.Polygon(self.coordinates, color='k', fill=False) self.artist_list = to_list(self.poly) self.ax.add_artist(self.poly)
def set_visible(self, visible=True): for artist in to_list(self.artist): artist.set_visible(visible)
def transform(self, transform, direction='forward', channels=None, return_all=True, auto_range=True, use_spln=True, get_transformer=False, ID=None, apply_now=True, args=(), **kwargs): """ Applies a transformation to the specified channels. The transformation parameters are shared between all transformed channels. If different parameters need to be applied to different channels, use several calls to `transform`. Parameters ---------- {FCMeasurement_transform_pars} ID : hashable | None ID for the resulting collection. If None is passed, the original ID is used. Returns ------- new : FCMeasurement New measurement containing the transformed data. transformer : Transformation The Transformation applied to the input measurement. Only returned if get_transformer=True. Examples -------- {FCMeasurement_transform_examples} """ # Create new measurement new = self.copy() data = new.data channels = to_list(channels) if channels is None: channels = data.columns ## create transformer if isinstance(transform, Transformation): transformer = transform else: if auto_range: # determine transformation range if 'd' in kwargs: warnings.warn( 'Encountered both auto_range=True and user-specified range value in ' 'parameter d.\n Range value specified in parameter d is used.') else: channel_meta = self.channels # the -1 below because the channel numbers begin from 1 instead of 0 # (this is fragile code) ranges = [float(r['$PnR']) for i, r in channel_meta.iterrows() if self.channel_names[i - 1] in channels] if not np.allclose(ranges, ranges[0]): raise Exception("""Not all specified channels have the same data range, therefore they cannot be transformed together.\n HINT: Try transforming one channel at a time. You'll need to provide the name of the channel in the transform.""") if transform in {'hlog', 'tlog', 'hlog_inv', 'tlog_inv'}: # Hacky fix to make sure that 'd' is provided only # for hlog / tlog transformations kwargs['d'] = np.log10(ranges[0]) transformer = Transformation(transform, direction, args, **kwargs) ## create new data transformed = transformer(data[channels], use_spln) if return_all: new_data = data else: new_data = data.filter(channels) new_data[channels] = transformed ## update new Measurement new.data = new_data if ID is not None: new.ID = ID if get_transformer: return new, transformer else: return new
def transform(self, transform, direction='forward', share_transform=True, channels=None, return_all=True, auto_range=True, use_spln=True, get_transformer=False, ID=None, apply_now=True, args=(), **kwargs): ''' Apply transform to each Measurement in the Collection. Return a new Collection with transformed data. {_containers_held_in_memory_warning} Parameters ---------- {FCMeasurement_transform_pars} ID : hashable | None ID for the resulting collection. If None is passed, the original ID is used. Returns ------- new : FCCollection New collection containing the transformed measurements. transformer : Transformation The Transformation applied to the measurements. Only returned if get_transformer=True & share_transform=True. Examples -------- {FCMeasurement_transform_examples} ''' new = self.copy() if share_transform: channel_meta = list(self.values())[0].channels channel_names = list(self.values())[0].channel_names if channels is None: channels = list(channel_names) else: channels = to_list(channels) ## create transformer if isinstance(transform, Transformation): transformer = transform else: if auto_range: # determine transformation range if 'd' in kwargs: warnings.warn('Encountered both auto_range=True and user-specified range ' 'value in parameter d.\n ' 'Range value specified in parameter d is used.') else: # the -1 below because the channel numbers begin from 1 instead of 0 (this is fragile code) ranges = [float(r['$PnR']) for i, r in channel_meta.iterrows() if channel_names[i - 1] in channels] if not np.allclose(ranges, ranges[0]): raise Exception('Not all specified channels have the same ' 'data range, therefore they cannot be ' 'transformed together.') if transform in {'hlog', 'tlog', 'hlog_inv', 'tlog_inv'}: # Hacky fix to make sure that 'd' is provided only # for hlog / tlog transformations kwargs['d'] = np.log10(ranges[0]) transformer = Transformation(transform, direction, args, **kwargs) if use_spln: xmax = self.apply(lambda x: x[channels].max().max(), applyto='data').max().max() xmin = self.apply(lambda x: x[channels].min().min(), applyto='data').min().min() transformer.set_spline(xmin, xmax) ## transform all measurements for k, v in new.items(): new[k] = v.transform(transformer, channels=channels, return_all=return_all, use_spln=use_spln, apply_now=apply_now) else: for k, v in new.items(): new[k] = v.transform(transform, direction=direction, channels=channels, return_all=return_all, auto_range=auto_range, get_transformer=False, use_spln=use_spln, apply_now=apply_now, args=args, **kwargs) if ID is not None: new.ID = ID if share_transform and get_transformer: return new, transformer else: return new
def grid_plot(self, func, applyto='measurement', ids=None, row_labels=None, col_labels=None, xlim='auto', ylim='auto', xlabel=None, ylabel=None, colorbar=True, row_label_xoffset=None, col_label_yoffset=None, hide_tick_labels=True, hide_tick_lines=True, hspace=0, wspace=0, row_labels_kwargs={}, col_labels_kwargs={}): """ Creates subplots for each well in the plate. Uses func to plot on each axis. Follow with a call to matplotlibs show() in order to see the plot. Parameters ---------- func : callable func is a callable that accepts a measurement object (with an optional axis reference) and plots on the current axis. Return values from func are ignored. .. note: if using applyto='measurement', the function when querying for data should make sure that the data actually exists applyto : 'measurement' | 'data' {_graph_grid_layout} {bases_OrderedCollection_grid_plot_pars} Returns ------- {_graph_grid_layout_returns} Examples --------- >>> def y(well, ax): >>> data = well.get_data() >>> if data is None: >>> return None >>> graph.plotFCM(data, 'Y2-A') >>> def z(data, ax): >>> plot(data[0:100, 1], data[0:100, 2]) >>> plate.plot(y, applyto='measurement'); >>> plate.plot(z, applyto='data'); """ # Acquire call arguments to be passed to create plate layout callArgs = locals().copy( ) # This statement must remain first. The copy is just defensive. [ callArgs.pop(varname) for varname in ['self', 'func', 'applyto', 'ids', 'colorbar', 'xlim', 'ylim'] ] # pop args callArgs['rowNum'] = self.shape[0] callArgs['colNum'] = self.shape[1] subplots_adjust_args = {} subplots_adjust_args.setdefault('right', 0.85) subplots_adjust_args.setdefault('top', 0.85) pl.subplots_adjust(**subplots_adjust_args) # Uses plate default row/col labels if user does not override them by specifying row/col # labels if row_labels == None: callArgs['row_labels'] = self.row_labels if col_labels == None: callArgs['col_labels'] = self.col_labels ax_main, ax_subplots = graph.create_grid_layout(**callArgs) subplots_ax = DF(ax_subplots, index=self.row_labels, columns=self.col_labels) if ids is None: ids = self.keys() ids = to_list(ids) for ID in ids: measurement = self[ID] if not hasattr(measurement, 'data'): continue row, col = self._positions[ID] ax = subplots_ax[col][row] pl.sca(ax) # sets the current axis if applyto == 'measurement': func(measurement, ax) # reminder: pandas row/col order is reversed elif applyto == 'data': data = measurement.get_data() if data is not None: if func.func_code.co_argcount == 1: func(data) else: func(data, ax) else: raise ValueError( 'Encountered unsupported value {} for applyto parameter.'. format(applyto)) # Autoscaling axes graph.scale_subplots(ax_subplots, xlim=xlim, ylim=ylim) ##### # Placing ticks on the top left subplot ax_label = ax_subplots[0, -1] pl.sca(ax_label) if xlabel: xlim = ax_label.get_xlim() pl.xticks([xlim[0], xlim[1]], rotation=90) if ylabel: ylim = ax_label.get_ylim() pl.yticks([ylim[0], ylim[1]], rotation=0) pl.sca(ax_main) # sets to the main axis -- more intuitive return ax_main, ax_subplots
def plotFCM(data, channel_names, kind='histogram', ax=None, autolabel=True, xlabel_kwargs={}, ylabel_kwargs={}, colorbar=False, grid=False, **kwargs): """ Plots the sample on the current axis. Follow with a call to matplotlibs show() in order to see the plot. Parameters ---------- data : DataFrame {graph_plotFCM_pars} {common_plot_ax} Returns ------- The output of the plot command used """ if ax == None: ax = pl.gca() xlabel_kwargs.setdefault('size', 16) ylabel_kwargs.setdefault('size', 16) channel_names = to_list(channel_names) if len(channel_names) == 1: # 1D so histogram plot kwargs.setdefault('color', 'gray') kwargs.setdefault('histtype', 'stepfilled') kwargs.setdefault('bins', 200) # Do not move above x = data[channel_names[0]].values if len(x) >= 1: if (len(x) == 1) and isinstance(kwargs['bins'], int): # Only needed for hist (not hist2d) due to hist function doing # excessive input checking warnings.warn("One of the data sets only has a single event. " "This event won't be plotted unless the bin locations" " are explicitly provided to the plotting function. ") return None plot_output = ax.hist(x, **kwargs) else: return None elif len(channel_names) == 2: x = data[channel_names[0]].values # value of first channel y = data[channel_names[1]].values # value of second channel if len(x) == 0: # Don't draw a plot if there's no data return None if kind == 'scatter': kwargs.setdefault('edgecolor', 'none') plot_output = ax.scatter(x, y, **kwargs) elif kind == 'histogram': kwargs.setdefault('bins', 200) # Do not move above kwargs.setdefault('cmin', 1) kwargs.setdefault('cmap', pl.cm.copper) kwargs.setdefault('norm', matplotlib.colors.LogNorm()) plot_output = ax.hist2d(x, y, **kwargs) mappable = plot_output[-1] if colorbar: pl.colorbar(mappable, ax=ax) else: raise ValueError("Not a valid plot type. Must be 'scatter', 'histogram'") else: raise ValueError('Received an unexpected number of channels: "{}"'.format(channel_names)) pl.grid(grid) if autolabel: y_label_text = 'Counts' if len(channel_names) == 1 else channel_names[1] ax.set_xlabel(channel_names[0], **xlabel_kwargs) ax.set_ylabel(y_label_text, **ylabel_kwargs) return plot_output
def plot(self, channel_names, kind='histogram', gates=None, gate_colors=None, ids=None, row_labels=None, col_labels=None, xlim='auto', ylim='auto', autolabel=True, **kwargs): """ Produces a grid plot with each subplot corresponding to the data at the given position. Parameters --------------- {FCMeasurement_plot_pars} {graph_plotFCM_pars} {_graph_grid_layout} Returns ------- {_graph_grid_layout_returns} Examples -------- Below, plate is an instance of FCOrderedCollection >>> plate.plot(['SSC-A', 'FSC-A'], kind='histogram', autolabel=True) >>> plate.plot(['SSC-A', 'FSC-A'], xlim=(0, 10000)) >>> plate.plot(['B1-A', 'Y2-A'], kind='scatter', color='red', s=1, alpha=0.3) >>> plate.plot(['B1-A', 'Y2-A'], bins=100, alpha=0.3) >>> plate.plot(['B1-A', 'Y2-A'], bins=[linspace(-1000, 10000, 100), linspace(-1000, 10000, 100)], alpha=0.3) .. note:: For more details see documentation for FCMeasurement.plot **kwargs passes arguments to both grid_plot and to FCMeasurement.plot. """ ## # Note # ------- # The function assumes that grid_plot and FCMeasurement.plot use unique key words. # Any key word arguments that appear in both functions are passed only to grid_plot in the end. ## # Automatically figure out which of the kwargs should # be sent to grid_plot instead of two sample.plot # (May not be a robust solution, we'll see as the code evolves grid_arg_list = inspect.getargspec(OrderedCollection.grid_plot).args grid_plot_kwargs = {'ids': ids, 'row_labels': row_labels, 'col_labels': col_labels} for key, value in list(kwargs.items()): if key in grid_arg_list: kwargs.pop(key) grid_plot_kwargs[key] = value ## # Make sure channel names is a list to make the code simpler below channel_names = to_list(channel_names) ## # Determine data limits for binning # if kind == 'histogram': nbins = kwargs.get('bins', 200) if isinstance(nbins, int): min_list = [] max_list = [] for sample in self: min_list.append(self[sample].data[channel_names].min().values) max_list.append(self[sample].data[channel_names].max().values) min_list = list(zip(*min_list)) max_list = list(zip(*max_list)) bins = [] for i, c in enumerate(channel_names): min_v = min(min_list[i]) max_v = max(max_list[i]) bins.append(np.linspace(min_v, max_v, nbins)) # Check if 1d if len(channel_names) == 1: bins = bins[0] # bins should be an ndarray, not a list of ndarrays kwargs['bins'] = bins ########## # Defining the plotting function that will be used. # At the moment grid_plot handles the labeling # (rather than sample.plot or the base function # in GoreUtilities.graph def plot_sample(sample, ax): return sample.plot(channel_names, ax=ax, gates=gates, gate_colors=gate_colors, colorbar=False, kind=kind, autolabel=False, **kwargs) xlabel, ylabel = None, None if autolabel: cnames = to_list(channel_names) xlabel = cnames[0] if len(cnames) == 2: ylabel = cnames[1] return self.grid_plot(plot_sample, xlim=xlim, ylim=ylim, xlabel=xlabel, ylabel=ylabel, **grid_plot_kwargs)