{"action":"created","comment":{"url":"https://api.github.com/repos/lcdb/lcdblib/pulls/comments/90924065","pull_request_review_id":11437529,"id":90924065,"diff_hunk":"@@ -0,0 +1,1265 @@\n+import itertools\n+import copy\n+from textwrap import dedent\n+import numpy as np\n+import pandas\n+import matplotlib\n+from matplotlib import pyplot as plt\n+import matplotlib.patches as patches\n+from mpl_toolkits.axes_grid1 import make_axes_locatable\n+from matplotlib.transforms import blended_transform_factory\n+from matplotlib.ticker import MaxNLocator\n+from matplotlib.collections import EventCollection\n+from mpl_toolkits.axes_grid1 import make_axes_locatable\n+import gffutils\n+import pybedtools\n+from pybedtools import featurefuncs\n+\n+from .. utils import utils\n+\n+_base_doc = \"\"\"%s\n+The underlying pandas.DataFrame is always available with the `data`\n+attribute.\n+\n+Any attributes not explicitly in this class will be looked for in the\n+underlying pandas.DataFrame.\n+\n+Parameters\n+----------\n+data : string or pandas.DataFrame\n+    If string, assumes it's a filename and calls\n+    pandas.read_table(data, **import_kwargs).\n+\n+db : string or gffutils.FeatureDB\n+    Optional database that can be used to generate features\n+\n+import_kwargs : dict\n+    These arguments will be passed to pandas.read_table() if `data` is\n+    a filename.\n+\"\"\"\n+\n+\n+class ResultsTable(object):\n+    __doc__ = _base_doc % dedent(\n+        \"\"\"\n+        Wrapper around a pandas.DataFrame that adds additional functionality.\n+        \"\"\")\n+\n+    def __init__(self, data, db=None, import_kwargs=None):\n+        if isinstance(data, str):\n+            import_kwargs = import_kwargs or {}\n+            data = pandas.read_table(data, **import_kwargs)\n+        if not isinstance(data, pandas.DataFrame):\n+            raise ValueError(\"`data` is not a pandas.DataFrame\")\n+        self.data = data\n+\n+        self._kwargs = dict(db=db, import_kwargs=import_kwargs)\n+        self.attach_db(db)\n+        self._cached_features = None\n+\n+    def __getattr__(self, attr):\n+        if attr in self.__dict__:\n+            return getattr(self, attr)\n+        return getattr(self.data, attr)\n+\n+    def __getitem__(self, attr):\n+        if isinstance(attr, str):\n+            return self.data.__getitem__(attr)\n+        else:\n+            return self.__class__(self.data.__getitem__(attr), **self._kwargs)\n+\n+    def update(self, dataframe):\n+        \"\"\"\n+        Updates the current data with a new dataframe.\n+\n+        This extra step is required to get around the fancy pandas.DataFrame\n+        indexing (like .ix, .iloc, etc).\n+        \"\"\"\n+        return self.__class__(dataframe, **self._kwargs)\n+\n+    def copy(self):\n+        data = self.data.copy(deep=True)\n+        return self.__class__(data, db=self.db, import_kwargs=self._kwargs)\n+\n+    def __repr__(self):\n+        s = []\n+        s.append(\"<%s instance, wrapping the following:\"\n+                 % self.__class__.__name__)\n+        s.append('')\n+        s.extend('\\t' + i for i in repr(self.data).splitlines(False))\n+        s.append('>')\n+        return '\\n'.join(s)\n+\n+    def attach_db(self, db):\n+        \"\"\"\n+        Attach a gffutils.FeatureDB for access to features.\n+\n+        Useful if you want to attach a db after this instance has already been\n+        created.\n+\n+        Parameters\n+        ----------\n+        db : gffutils.FeatureDB\n+        \"\"\"\n+        if db is not None:\n+            if isinstance(db, str):\n+                db = gffutils.FeatureDB(db)\n+            if not isinstance(db, gffutils.FeatureDB):\n+                raise ValueError(\n+                    \"`db` must be a filename or a gffutils.FeatureDB\")\n+        self._kwargs['db'] = db\n+        self.db = db\n+\n+    def features(self, ignore_unknown=False):\n+        \"\"\"\n+        Generator of features.\n+\n+        If a gffutils.FeatureDB is attached, returns a pybedtools.Interval for\n+        every feature in the dataframe's index.\n+\n+        Parameters\n+        ----------\n+        ignore_unknown : bool\n+            If True, silently ignores features that are not found in the db.\n+        \"\"\"\n+        if not self.db:\n+            raise ValueError(\"Please attach a gffutils.FeatureDB\")\n+        for i in self.data.index:\n+            try:\n+                yield gffutils.helpers.asinterval(self.db[i])\n+            except gffutils.FeatureNotFoundError:\n+                if ignore_unknown:\n+                    continue\n+                else:\n+                    raise gffutils.FeatureNotFoundError('%s not found' % i)\n+\n+    def reindex_to(self, x, attribute=None):\n+        \"\"\"\n+        Returns a copy that only has rows corresponding to feature names in x.\n+\n+\n+        Parameters\n+        ----------\n+        x : str or pybedtools.BedTool\n+            If str, then assume it's a filename. BED, GFF, GTF, or VCF where\n+            the \"Name\" field (that is, the value returned by feature['Name'])\n+            or any arbitrary attribute\n+\n+        attribute : str or int or None\n+            If `x` is GFF or GTF format, and `attribute` is str, then attribute\n+            containing the name of the feature to use. If `x` format is BED and\n+            `attribute` is str, then use getattr on the interval (e.g., 'name'\n+            or 'score'). If `attribute` is int, then use that column. If None,\n+            then use the \"name\" attribute of the Interval, which falls back to\n+            one of \"gene_id\", \"Name\", \"transcript_id\" for GFF/GTF.\n+        \"\"\"\n+        if (\n+            attribute is not None and (\n+                x.file_type == 'gff' or\n+                isinstance(attribute, int)\n+            )\n+        ):\n+            names = [i[attribute] for i in x]\n+\n+        else:\n+            if attribute is None:\n+                attribute = 'name'\n+            names = [getattr(i, attribute) for i in x]\n+\n+        new = self.copy()\n+        new.data = new.data.reindex(names)\n+        return new\n+\n+    def align_with(self, other):\n+        \"\"\"\n+        Align the dataframe's index with another.\n+        \"\"\"\n+        return self.__class__(self.data.reindex_like(other), **self._kwargs)\n+\n+    def __len__(self):\n+        return len(self.data)\n+\n+    def scatter(self, x, y, xfunc=None, yfunc=None, xscale=None, yscale=None,\n+                xlab=None, ylab=None, genes_to_highlight=None,\n+                marginal_histograms=False,\n+                general_kwargs=dict(color=\"k\", alpha=0.2, picker=True),\n+                general_hist_kwargs=None, offset_kwargs={}, label_kwargs=None,\n+                ax=None, one_to_one=None, callback=None, hist_size=0.3,\n+                hist_pad=0.0, nan_offset=0.015, pos_offset=0.99,\n+                linelength=0.01, neg_offset=0.005):\n+        \"\"\"\n+        Do-it-all method for making annotated scatterplots.\n+\n+        Includes rugplots for NaN/Inf/-Inf, a default callback that prints the\n+        entries of the underlying dataframe when a point is clicked, point\n+        labeling options, marginal histograms for multiple subsets, arbitrary\n+        styling of points for arbitrary subsets, and more.\n+\n+        Parameters\n+        ----------\n+\n+        x, y : array-like\n+            Variables to plot.  Must be names in self.data's DataFrame.\n+\n+        xfunc, yfunc : callable\n+            Functions to apply to `xvar` and `yvar` respectively. If xlab or\n+            ylab is not set separately, the function name will be used along\n+            with the column name to label the corresponding axis. This lets you\n+            play around with transformation functions (e.g., np.log, np.log1p)\n+            without having to add the corresponding column to the underlying\n+            dataframe.\n+\n+        xlab, ylab : string\n+            Labels for x and y axes; default is to use function names for\n+            `xfunc` and `yfunc` and variable names `xvar` and `yvar`, e.g.,\n+            \"log2(baseMeanA)\"\n+\n+        ax : None or Axes object\n+            If not None then plot on the provided Axes, otherwise create a new\n+            figure and axes.\n+\n+        general_kwargs : dict\n+            Kwargs for matplotlib.scatter; specifies how all points look. Note\n+            that if you override this, you should include at least\n+            `picker=True` so that the callback function will work.\n+\n+        genes_to_highlight : list of 2-tuples or 3-tuples\n+            Provides lots of control to colors.  It is a list of (`ind`,\n+            `kwargs`) tuples, where each `ind` specifies genes to plot with\n+            `kwargs`. `ind` is anything that can be used with `DataFrame.ix`.\n+\n+            For example::\n+\n+                [\n+                    (\n+                        x.log2FoldChange < 0,\n+                        dict(color='b', label='downregulated')\n+                    ),\n+                    (\n+                        x.log2FoldChange > 0,\n+                        dict(color='r', label='upregulated')\n+                    ),\n+                ]\n+\n+            Each dictionary updates a copy of `general_kwargs`. If\n+            `genes_to_highlight` has a \"name\" kwarg, this must be a list that't\n+            the same length as `ind`.  It will be used to label the genes in\n+            `ind` using `label_kwargs`.\n+\n+            For example::\n+\n+                [\n+                    (\n+                        ['ENSG001', 'ENSG002'],\n+                        dict(color='r', name=['geneA', 'geneB'])\n+                    ),\n+                ]\n+\n+            The tuples can also be 3-tuples of (ind, scatter_kwargs,\n+            hist_kwargs). The first two items act as above, and the third can\n+            be used to control histogram kwargs if `marginal_hists` is True.\n+\n+            Note that, unless overridden, the color and alpha of the histograms\n+            will be inherited from the scatter kwargs, which in turn are\n+            inherited from general_kwargs. So the following will modifiy\n+            marginal histograms to have 100 bins::\n+\n+                [\n+                    (\n+                        x.log2FoldChange < 0,\n+                        dict(color='b', label='down'),\n+                        dict(bins=100)\n+                    )\n+                ]\n+\n+        callback : callable\n+            Function to call upon clicking a point. Must accept a single\n+            argument which is the gene ID. The function can do whatever it\n+            wants with it, but probably will want to access the underlying\n+            dataframe. Default is to print the corresponding row from the\n+            underlying dataframe.\n+\n+        one_to_one : None or dict\n+            If not None, a dictionary of matplotlib.plot kwargs that will be\n+            used to plot a 1:1 line, e.g., dict(color='r', linestyle=':').\n+\n+        label_kwargs : dict\n+            Kwargs for labeled genes, e.g., dict=(style='italic').  Will only\n+            be used if an entry in `genes_to_highlight` has a `name` key.\n+\n+        offset_kwargs : dict\n+            Kwargs to be passed to matplotlib.transforms.offset_copy, used for\n+            adjusting the positioning of gene labels in relation to the actual\n+            point.\n+\n+        xlab_prefix, ylab_prefix : str\n+            Optional label prefix that will be added to the beginning of `xlab`\n+            and/or `ylab`.\n+\n+        marginal_histograms : bool\n+            If True, for each subset in `genes_to_highlight`, add marginal\n+            histograms along x and y axes subject to the various histogram\n+            controls below.\n+\n+        hist_size : float\n+            Size of marginal histograms\n+\n+        hist_pad : float\n+            Spacing between marginal histograms\n+\n+        nan_offset, pos_offset, neg_offset : float\n+            Offset, in units of \"fraction of axes\" for the NaN, +inf, and -inf\n+            \"rug plots\"\n+\n+        linelength : float\n+            Line length for the rug plots\n+\n+        \"\"\"\n+        _x = self.data[x]\n+        _y = self.data[y]\n+\n+        # Construct defaults---------------------------------------------------\n+        def identity(x):\n+            return x.copy()\n+\n+        # Axis label setup\n+        if xlab is None:\n+            xlab = x\n+\n+            if xfunc is not None:\n+                xlab = \"%s(%s)\" % (xfunc.__name__, str(x))\n+            else:\n+                xlab = \"%s\" % (str(x))\n+\n+        if ylab is None:\n+            ylab = y\n+            if yfunc is not None:\n+                ylab = \"%s(%s)\" % (yfunc.__name__, str(y))\n+            else:\n+                ylab = \"%s\" % (str(y))\n+\n+        if xfunc is None:\n+            xfunc = identity\n+\n+        if yfunc is None:\n+            yfunc = identity\n+\n+        if general_kwargs is None:\n+            general_kwargs = {}\n+\n+        if general_hist_kwargs is None:\n+            general_hist_kwargs = {}\n+\n+        if genes_to_highlight is None:\n+            genes_to_highlight = []\n+\n+        if ax is None:\n+            fig = plt.figure()\n+            ax = fig.add_subplot(111)\n+\n+        if label_kwargs is None:\n+            label_kwargs = dict(\n+                horizontalalignment='right',\n+                verticalalignment='center',\n+                style='italic',\n+                bbox=dict(facecolor='w', edgecolor='None', alpha=0.5)\n+            )\n+\n+        # Clean data ---------------------------------------------------------\n+        xi = xfunc(_x)\n+        yi = yfunc(_y)\n+\n+        # handle inf, -inf, and NaN so we can build the rug plots appropriately\n+        x_is_pos_inf = np.isinf(xi) & (xi > 0)\n+        x_is_neg_inf = np.isinf(xi) & (xi < 0)\n+        x_is_nan = np.isnan(xi)\n+        y_is_pos_inf = np.isinf(yi) & (yi > 0)\n+        y_is_neg_inf = np.isinf(yi) & (yi < 0)\n+        y_is_nan = np.isnan(yi)\n+\n+        # Indexes for valid values\n+        x_valid = ~(x_is_pos_inf | x_is_neg_inf | x_is_nan)\n+        y_valid = ~(y_is_pos_inf | y_is_neg_inf | y_is_nan)\n+\n+        # global min/max\n+        gmin = max(xi[x_valid].min(), yi[y_valid].min())\n+        gmax = min(xi[x_valid].max(), yi[y_valid].max())\n+\n+        # Now we remove any genes from in allind (which will be plotted using\n+        # `general_kwargs`) that will be plotted by something in\n+        # genes_to_highlight.  This avoids double-plotting.\n+        allind = np.zeros_like(xi) == 0\n+        for block in genes_to_highlight:\n+            ind = block[0]\n+            allind[ind] = False\n+\n+        # Copy over the color and alpha from general_kwargs (the scatter\n+        # points) to general_hist_kwargs (marginal histograms) if they're not\n+        # otherwise specified\n+        general_hist_kwargs = utils.updatecopy(\n+            orig=general_hist_kwargs, update_with=general_kwargs,\n+            keys=['color', 'alpha'])\n+\n+        # Put the non-highlighted genes at the beginning of _genes_to_highlight\n+        # list so we can just iterate over one list. If genes_to_highlight\n+        # styles don't otherwise override z-order, this means the \"background\"\n+        # gets plotted first and therefore under the highlighted points.\n+        _genes_to_highlight = genes_to_highlight[:]\n+        _genes_to_highlight.insert(\n+            0,\n+            (allind, general_kwargs, general_hist_kwargs)\n+        )\n+\n+        # Set up the object that will handle the marginal histograms and\n+        # plotting.\n+        self.marginal = MarginalHistScatter(\n+            ax, hist_size=hist_size, pad=hist_pad)\n+\n+        # Set up kwargs for x and y rug plots\n+        rug_x_kwargs = dict(\n+            linelength=linelength,\n+            transform=blended_transform_factory(ax.transData, ax.transAxes),\n+        )\n+        rug_y_kwargs = dict(\n+            linelength=linelength,\n+            transform=blended_transform_factory(ax.transAxes, ax.transData),\n+            orientation='vertical',\n+        )\n+\n+        # EventCollection objects need a color as a 3-tuple, so set up\n+        # a converter here.\n+        color_converter = matplotlib.colors.ColorConverter().to_rgb\n+\n+        # Plot the one-to-one line, if kwargs were specified. If zorder is not\n+        # overridden, points will overlay it.\n+        if one_to_one:\n+            ax.plot([gmin, gmax],\n+                    [gmin, gmax],\n+                    **one_to_one)\n+\n+        # Plot 'em all, and label if specified\n+\n+        # In order to avoid calling the callback function multiple times when\n+        # we have overlapping genes to highlight (e.g., a gene that is both\n+        # upregulated AND has a peak), here we keep track of everything that's\n+        # been added so far.\n+        self._seen = np.ones_like(xi) == 0\n+        for block in _genes_to_highlight:\n+            ind = block[0]\n+            kwargs = block[1]\n+\n+            if len(block) == 3:\n+                hist_kwargs = block[2]\n+            else:\n+                hist_kwargs = {}\n+\n+            names = kwargs.pop('names', None)\n+\n+            _marginal_histograms = (\n+                kwargs.pop('marginal_histograms', False) or\n+                marginal_histograms)\n+\n+            updated_kwargs = utils.updatecopy(\n+                orig=kwargs, update_with=general_kwargs)\n+\n+            updated_hist_kwargs = utils.updatecopy(\n+                orig=hist_kwargs, update_with=general_hist_kwargs)\n+\n+            updated_hist_kwargs = utils.updatecopy(\n+                orig=updated_hist_kwargs, update_with=kwargs,\n+                keys=['color', 'alpha'], override=True)\n+\n+            xhist_kwargs = updated_kwargs.pop('xhist_kwargs', None)\n+            yhist_kwargs = updated_kwargs.pop('yhist_kwargs', None)\n+\n+            # MarginalHistScatter does most of the actual plotting work.\n+            scatter_ind = ind & x_valid & y_valid\n+            self.marginal.append(\n+                xi[scatter_ind],\n+                yi[scatter_ind],\n+                scatter_kwargs=dict(**updated_kwargs),\n+                hist_kwargs=updated_hist_kwargs,\n+                xhist_kwargs=xhist_kwargs,\n+                yhist_kwargs=yhist_kwargs,\n+                marginal_histograms=_marginal_histograms,\n+            )\n+\n+            # Callback functions have access to the Collection object that was\n+            # picked as well as the index into the collection. Since ultimately\n+            # we want to access a row of the dataframe, to the collection we\n+            # attach the dataframe itself and the index into that dataframe\n+            # represented by the scatter points in the collection.\n+            coll = self.marginal.scatter_ax.collections[-1]\n+            coll.df = self.data\n+            coll.ind = scatter_ind\n+\n+            color = color_converter(updated_kwargs['color'])\n+            rug_x_kwargs['color'] = color\n+            rug_y_kwargs['color'] = color\n+\n+            # Note: if both x and y are not valid, then they will not be on the\n+            # plot at all (since there's no obvious place to put them).\n+            items = [\n+                # top rug, y is +inf and x is valid\n+                (xi, ind & x_valid & y_is_pos_inf, pos_offset, rug_x_kwargs),\n+\n+                # one of the bottom rugs, where y is NaN\n+                (xi, ind & x_valid & y_is_nan, nan_offset, rug_x_kwargs),\n+\n+                # bottom rug, y is -inf\n+                (xi, ind & x_valid & y_is_neg_inf, neg_offset, rug_x_kwargs),\n+\n+                # right rug, x is +inf\n+                (yi, ind & y_valid & x_is_pos_inf, pos_offset, rug_y_kwargs),\n+\n+                # one of the left rugs; x is NaN\n+                (yi, ind & y_valid & x_is_nan, nan_offset, rug_y_kwargs),\n+\n+                # left rug, x is -inf\n+                (yi, ind & y_valid & x_is_neg_inf, neg_offset, rug_y_kwargs),\n+            ]\n+            for values, index, offset, kwargs in items:\n+                coll = EventCollection(\n+                    values[index], lineoffset=offset, **kwargs)\n+                coll.df = self.data\n+                coll.ind = index\n+                ax.add_collection(coll)\n+\n+            # Plot the text names if configured\n+            if names:\n+                transOffset = matplotlib.transforms.offset_copy(\n+                    ax.transData, fig=ax.figure, **offset_kwargs)\n+\n+                for xii, yii, name in zip(xi[ind], yi[ind], names):\n+                    ax.text(xii,\n+                            yii,\n+                            name,\n+                            transform=transOffset,\n+                            **label_kwargs)\n+\n+        # register callback\n+        if callback is None:\n+            callback = self._default_callback\n+\n+        # This trick is so that we can say, in the docstring, that a callback\n+        # function must accept an index name (i.e. gene ID). Much easier than\n+        # saying \"the callback function accepts an event, and the event's\n+        # artist has an `ind` and `df` attribute\".\n+        def wrapped_callback(event):\n+            for _id in self._id_callback(event):\n+                callback(_id)\n+\n+        # Connect the callback.\n+        ax.figure.canvas.mpl_connect('pick_event', wrapped_callback)\n+\n+        ax.set_xlabel(xlab)\n+        ax.set_ylabel(ylab)\n+        ax.axis('tight')\n+\n+        return ax\n+\n+    def radviz(self, column_names, transforms=dict(), **kwargs):\n+        \"\"\"\n+        Radviz plot.\n+\n+        Useful for exploratory visualization, a radviz plot can show\n+        multivariate data in 2D.  Conceptually, the variables (here, specified\n+        in `column_names`) are distributed evenly around the unit circle.  Then\n+        each point (here, each row in the dataframe) is attached to each\n+        variable by a spring, where the stiffness of the spring is proportional\n+        to the value of corresponding variable.  The final position of a point\n+        represents the equilibrium position with all springs pulling on it.\n+\n+        In practice, each variable is normalized to 0-1 (by subtracting the\n+        mean and dividing by the range).\n+\n+        This is a very exploratory plot.  The order of `column_names` will\n+        affect the results, so it's best to try a couple different orderings.\n+        For other caveats, see [1].\n+\n+        Additional kwargs are passed to self.scatter, so subsetting, callbacks,\n+        and other configuration can be performed using options for that method\n+        (e.g., `genes_to_highlight` is particularly useful).\n+\n+        Parameters\n+        ----------\n+        column_names : list\n+            Which columns of the dataframe to consider.  The columns provided\n+            should only include numeric data, and they should not contain any\n+            NaN, inf, or -inf values.\n+\n+        transforms : dict\n+            Dictionary mapping column names to transformations that will be\n+            applied just for the radviz plot.  For example, np.log1p is\n+            a useful function. If a column name is not in this dictionary, it\n+            will be used as-is.\n+\n+        ax : matplotlib.Axes\n+            If not None, then plot the radviz on this axes.  If None, then\n+            a new figure will be created.\n+\n+        kwargs : dict\n+            Additional arguments are passed to self.scatter.  Note that not all\n+            possible kwargs for self.scatter are necessarily useful for\n+            a radviz plot (for example, margninal histograms would not be\n+            meaningful).\n+\n+        Notes\n+        -----\n+        This method adds two new variables to self.data: \"radviz_x\" and\n+        \"radviz_y\".  It then calls the self.scatter method, using these new\n+        variables.\n+\n+        The data transformation was adapted from the\n+        pandas.tools.plotting.radviz function.\n+\n+        References\n+        ----------\n+        [1]  Hoffman,P.E. et al. (1997) DNA visual and analytic data mining. In\n+             the Proceedings of the IEEE Visualization. Phoenix, AZ, pp.\n+             437-441.\n+        [2] http://www.agocg.ac.uk/reports/visual/casestud/brunsdon/radviz.htm\n+        [3] http://pandas.pydata.org/pandas-docs/stable/visualization.html\\\n+                #radviz\n+        \"\"\"\n+        # make a copy of data\n+        x = self.data[column_names].copy()\n+\n+        for k, v in transforms.items():\n+            x[k] = v(x[k])\n+\n+        def normalize(series):\n+            mn = min(series)\n+            mx = max(series)\n+            return (series - mn) / (mx - mn)\n+\n+        df = x.apply(normalize)\n+\n+        to_plot = []\n+\n+        n = len(column_names)\n+\n+        s = np.array([(np.cos(t), np.sin(t))\n+                      for t in [2.0 * np.pi * (i / float(n))\n+                                for i in range(n)]])\n+\n+        for i in range(len(x)):\n+            row = df.irow(i).values\n+            row_ = np.repeat(np.expand_dims(row, axis=1), 2, axis=1)\n+            to_plot.append((s * row_).sum(axis=0) / row.sum())\n+\n+        x_, y_ = zip(*to_plot)\n+        self.data['radviz_x'] = x_\n+        self.data['radviz_y'] = y_\n+\n+        ax = self.scatter('radviz_x', 'radviz_y', **kwargs)\n+\n+        ax.add_patch(patches.Circle((0.0, 0.0), radius=1.0, facecolor='none'))\n+        for xy, name in zip(s, column_names):\n+            ax.add_patch(patches.Circle(xy, radius=0.025, facecolor='gray'))\n+            if xy[0] < 0.0 and xy[1] < 0.0:\n+                ax.text(xy[0] - 0.025, xy[1] - 0.025, name,\n+                        ha='right', va='top', size='small')\n+            elif xy[0] < 0.0 and xy[1] >= 0.0:\n+                ax.text(xy[0] - 0.025, xy[1] + 0.025, name,\n+                        ha='right', va='bottom', size='small')\n+            elif xy[0] >= 0.0 and xy[1] < 0.0:\n+                ax.text(xy[0] + 0.025, xy[1] - 0.025, name,\n+                        ha='left', va='top', size='small')\n+            elif xy[0] >= 0.0 and xy[1] >= 0.0:\n+                ax.text(xy[0] + 0.025, xy[1] + 0.025, name,\n+                        ha='left', va='bottom', size='small')\n+\n+        ax.axis('equal')\n+        return ax\n+\n+    def _id_callback(self, event):\n+        # event.ind is the index into event's x and y data.\n+        #\n+        # event.artist.ind is the index of the entire artist into the original\n+        # dataframe.\n+        subset_df = event.artist.df.ix[event.artist.ind]\n+        for i in event.ind:\n+            _id = subset_df.index[i]\n+            yield _id\n+\n+    def _default_callback(self, i):\n+        print(self.data.ix[i])\n+\n+\n+class DifferentialExpressionResults(ResultsTable):\n+\n+    __doc__ = _base_doc % dedent(\"\"\"\n+    A ResultsTable subclass for working with differential expression results.\n+\n+    Adds methods for up/down regulation, ma_plot, and sets class variables for\n+    which columns should be considered for pval, log fold change, and mean\n+    values. This class acts as a parent for subclasses like DESeqResults,\n+    EdgeRResults, and others/\n+    \"\"\")\n+    pval_column = 'padj'\n+    lfc_column = 'log2FoldChange'\n+    mean_column = 'baseMean'\n+\n+    def __init__(self, data, db=None, header_check=True, **kwargs):\n+        import_kwargs = kwargs.pop('import_kwargs', {})\n+        if header_check and isinstance(data, str):\n+            comment_char = import_kwargs.get('comment', '#')\n+            for i, line in enumerate(open(data)):\n+                if line[0] != comment_char:\n+                    break\n+            import_kwargs['skiprows'] = i\n+        import_kwargs['na_values'] = ['nan']","path":"lcdblib/plotting/results_table.py","position":712,"original_position":712,"commit_id":"02b6bd3ce58ff180a2232c1e5dd392e91d11102c","original_commit_id":"02b6bd3ce58ff180a2232c1e5dd392e91d11102c","user":{"login":"daler","id":115406,"avatar_url":"https://avatars.githubusercontent.com/u/115406?v=3","gravatar_id":"","url":"https://api.github.com/users/daler","html_url":"https://github.com/daler","followers_url":"https://api.github.com/users/daler/followers","following_url":"https://api.github.com/users/daler/following{/other_user}","gists_url":"https://api.github.com/users/daler/gists{/gist_id}","starred_url":"https://api.github.com/users/daler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/daler/subscriptions","organizations_url":"https://api.github.com/users/daler/orgs","repos_url":"https://api.github.com/users/daler/repos","events_url":"https://api.github.com/users/daler/events{/privacy}","received_events_url":"https://api.github.com/users/daler/received_events","type":"User","site_admin":false},"body":"Yep. This was copied from metaseq. My guess is that the comment char wasn't added to pandas back then? I'll fix it.","created_at":"2016-12-05T18:20:38Z","updated_at":"2016-12-05T18:20:38Z","html_url":"https://github.com/lcdb/lcdblib/pull/11#discussion_r90924065","pull_request_url":"https://api.github.com/repos/lcdb/lcdblib/pulls/11","_links":{"self":{"href":"https://api.github.com/repos/lcdb/lcdblib/pulls/comments/90924065"},"html":{"href":"https://github.com/lcdb/lcdblib/pull/11#discussion_r90924065"},"pull_request":{"href":"https://api.github.com/repos/lcdb/lcdblib/pulls/11"}}},"pull_request":{"url":"https://api.github.com/repos/lcdb/lcdblib/pulls/11","id":95821006,"html_url":"https://github.com/lcdb/lcdblib/pull/11","diff_url":"https://github.com/lcdb/lcdblib/pull/11.diff","patch_url":"https://github.com/lcdb/lcdblib/pull/11.patch","issue_url":"https://api.github.com/repos/lcdb/lcdblib/issues/11","number":11,"state":"open","locked":false,"title":"results_table","user":{"login":"daler","id":115406,"avatar_url":"https://avatars.githubusercontent.com/u/115406?v=3","gravatar_id":"","url":"https://api.github.com/users/daler","html_url":"https://github.com/daler","followers_url":"https://api.github.com/users/daler/followers","following_url":"https://api.github.com/users/daler/following{/other_user}","gists_url":"https://api.github.com/users/daler/gists{/gist_id}","starred_url":"https://api.github.com/users/daler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/daler/subscriptions","organizations_url":"https://api.github.com/users/daler/orgs","repos_url":"https://api.github.com/users/daler/repos","events_url":"https://api.github.com/users/daler/events{/privacy}","received_events_url":"https://api.github.com/users/daler/received_events","type":"User","site_admin":false},"body":"This is a cleaned-up version of the `metaseq.results_table` module for flexible plotting with marginal histograms and callback functions (by default, print the pandas.DataFrame row of the point that was clicked).\r\n\r\nStill needs a lot of testing before merging. In the past I've tested this module sort of by hand and by using it in everyday work. Not sure the best way to test it now, so that'll take some playing around.","created_at":"2016-11-30T03:21:01Z","updated_at":"2016-12-05T18:20:38Z","closed_at":null,"merged_at":null,"merge_commit_sha":"178ab2a4d08ebfa15d71c10e1e08ca45d192b2e0","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/lcdb/lcdblib/pulls/11/commits","review_comments_url":"https://api.github.com/repos/lcdb/lcdblib/pulls/11/comments","review_comment_url":"https://api.github.com/repos/lcdb/lcdblib/pulls/comments{/number}","comments_url":"https://api.github.com/repos/lcdb/lcdblib/issues/11/comments","statuses_url":"https://api.github.com/repos/lcdb/lcdblib/statuses/02b6bd3ce58ff180a2232c1e5dd392e91d11102c","head":{"label":"lcdb:results_table","ref":"results_table","sha":"02b6bd3ce58ff180a2232c1e5dd392e91d11102c","user":{"login":"lcdb","id":7584817,"avatar_url":"https://avatars.githubusercontent.com/u/7584817?v=3","gravatar_id":"","url":"https://api.github.com/users/lcdb","html_url":"https://github.com/lcdb","followers_url":"https://api.github.com/users/lcdb/followers","following_url":"https://api.github.com/users/lcdb/following{/other_user}","gists_url":"https://api.github.com/users/lcdb/gists{/gist_id}","starred_url":"https://api.github.com/users/lcdb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lcdb/subscriptions","organizations_url":"https://api.github.com/users/lcdb/orgs","repos_url":"https://api.github.com/users/lcdb/repos","events_url":"https://api.github.com/users/lcdb/events{/privacy}","received_events_url":"https://api.github.com/users/lcdb/received_events","type":"Organization","site_admin":false},"repo":{"id":69355416,"name":"lcdblib","full_name":"lcdb/lcdblib","owner":{"login":"lcdb","id":7584817,"avatar_url":"https://avatars.githubusercontent.com/u/7584817?v=3","gravatar_id":"","url":"https://api.github.com/users/lcdb","html_url":"https://github.com/lcdb","followers_url":"https://api.github.com/users/lcdb/followers","following_url":"https://api.github.com/users/lcdb/following{/other_user}","gists_url":"https://api.github.com/users/lcdb/gists{/gist_id}","starred_url":"https://api.github.com/users/lcdb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lcdb/subscriptions","organizations_url":"https://api.github.com/users/lcdb/orgs","repos_url":"https://api.github.com/users/lcdb/repos","events_url":"https://api.github.com/users/lcdb/events{/privacy}","received_events_url":"https://api.github.com/users/lcdb/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/lcdb/lcdblib","description":"A set of useful helper functions for doing NGS analysis with snakemake.","fork":false,"url":"https://api.github.com/repos/lcdb/lcdblib","forks_url":"https://api.github.com/repos/lcdb/lcdblib/forks","keys_url":"https://api.github.com/repos/lcdb/lcdblib/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lcdb/lcdblib/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lcdb/lcdblib/teams","hooks_url":"https://api.github.com/repos/lcdb/lcdblib/hooks","issue_events_url":"https://api.github.com/repos/lcdb/lcdblib/issues/events{/number}","events_url":"https://api.github.com/repos/lcdb/lcdblib/events","assignees_url":"https://api.github.com/repos/lcdb/lcdblib/assignees{/user}","branches_url":"https://api.github.com/repos/lcdb/lcdblib/branches{/branch}","tags_url":"https://api.github.com/repos/lcdb/lcdblib/tags","blobs_url":"https://api.github.com/repos/lcdb/lcdblib/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lcdb/lcdblib/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lcdb/lcdblib/git/refs{/sha}","trees_url":"https://api.github.com/repos/lcdb/lcdblib/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lcdb/lcdblib/statuses/{sha}","languages_url":"https://api.github.com/repos/lcdb/lcdblib/languages","stargazers_url":"https://api.github.com/repos/lcdb/lcdblib/stargazers","contributors_url":"https://api.github.com/repos/lcdb/lcdblib/contributors","subscribers_url":"https://api.github.com/repos/lcdb/lcdblib/subscribers","subscription_url":"https://api.github.com/repos/lcdb/lcdblib/subscription","commits_url":"https://api.github.com/repos/lcdb/lcdblib/commits{/sha}","git_commits_url":"https://api.github.com/repos/lcdb/lcdblib/git/commits{/sha}","comments_url":"https://api.github.com/repos/lcdb/lcdblib/comments{/number}","issue_comment_url":"https://api.github.com/repos/lcdb/lcdblib/issues/comments{/number}","contents_url":"https://api.github.com/repos/lcdb/lcdblib/contents/{+path}","compare_url":"https://api.github.com/repos/lcdb/lcdblib/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lcdb/lcdblib/merges","archive_url":"https://api.github.com/repos/lcdb/lcdblib/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lcdb/lcdblib/downloads","issues_url":"https://api.github.com/repos/lcdb/lcdblib/issues{/number}","pulls_url":"https://api.github.com/repos/lcdb/lcdblib/pulls{/number}","milestones_url":"https://api.github.com/repos/lcdb/lcdblib/milestones{/number}","notifications_url":"https://api.github.com/repos/lcdb/lcdblib/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lcdb/lcdblib/labels{/name}","releases_url":"https://api.github.com/repos/lcdb/lcdblib/releases{/id}","deployments_url":"https://api.github.com/repos/lcdb/lcdblib/deployments","created_at":"2016-09-27T12:43:33Z","updated_at":"2016-09-27T12:43:52Z","pushed_at":"2016-12-03T02:38:41Z","git_url":"git://github.com/lcdb/lcdblib.git","ssh_url":"git@github.com:lcdb/lcdblib.git","clone_url":"https://github.com/lcdb/lcdblib.git","svn_url":"https://github.com/lcdb/lcdblib","homepage":null,"size":139,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"lcdb:master","ref":"master","sha":"b829de4ca98c73e6d9f0c2f1815a63606fed8f7d","user":{"login":"lcdb","id":7584817,"avatar_url":"https://avatars.githubusercontent.com/u/7584817?v=3","gravatar_id":"","url":"https://api.github.com/users/lcdb","html_url":"https://github.com/lcdb","followers_url":"https://api.github.com/users/lcdb/followers","following_url":"https://api.github.com/users/lcdb/following{/other_user}","gists_url":"https://api.github.com/users/lcdb/gists{/gist_id}","starred_url":"https://api.github.com/users/lcdb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lcdb/subscriptions","organizations_url":"https://api.github.com/users/lcdb/orgs","repos_url":"https://api.github.com/users/lcdb/repos","events_url":"https://api.github.com/users/lcdb/events{/privacy}","received_events_url":"https://api.github.com/users/lcdb/received_events","type":"Organization","site_admin":false},"repo":{"id":69355416,"name":"lcdblib","full_name":"lcdb/lcdblib","owner":{"login":"lcdb","id":7584817,"avatar_url":"https://avatars.githubusercontent.com/u/7584817?v=3","gravatar_id":"","url":"https://api.github.com/users/lcdb","html_url":"https://github.com/lcdb","followers_url":"https://api.github.com/users/lcdb/followers","following_url":"https://api.github.com/users/lcdb/following{/other_user}","gists_url":"https://api.github.com/users/lcdb/gists{/gist_id}","starred_url":"https://api.github.com/users/lcdb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lcdb/subscriptions","organizations_url":"https://api.github.com/users/lcdb/orgs","repos_url":"https://api.github.com/users/lcdb/repos","events_url":"https://api.github.com/users/lcdb/events{/privacy}","received_events_url":"https://api.github.com/users/lcdb/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/lcdb/lcdblib","description":"A set of useful helper functions for doing NGS analysis with snakemake.","fork":false,"url":"https://api.github.com/repos/lcdb/lcdblib","forks_url":"https://api.github.com/repos/lcdb/lcdblib/forks","keys_url":"https://api.github.com/repos/lcdb/lcdblib/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lcdb/lcdblib/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lcdb/lcdblib/teams","hooks_url":"https://api.github.com/repos/lcdb/lcdblib/hooks","issue_events_url":"https://api.github.com/repos/lcdb/lcdblib/issues/events{/number}","events_url":"https://api.github.com/repos/lcdb/lcdblib/events","assignees_url":"https://api.github.com/repos/lcdb/lcdblib/assignees{/user}","branches_url":"https://api.github.com/repos/lcdb/lcdblib/branches{/branch}","tags_url":"https://api.github.com/repos/lcdb/lcdblib/tags","blobs_url":"https://api.github.com/repos/lcdb/lcdblib/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lcdb/lcdblib/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lcdb/lcdblib/git/refs{/sha}","trees_url":"https://api.github.com/repos/lcdb/lcdblib/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lcdb/lcdblib/statuses/{sha}","languages_url":"https://api.github.com/repos/lcdb/lcdblib/languages","stargazers_url":"https://api.github.com/repos/lcdb/lcdblib/stargazers","contributors_url":"https://api.github.com/repos/lcdb/lcdblib/contributors","subscribers_url":"https://api.github.com/repos/lcdb/lcdblib/subscribers","subscription_url":"https://api.github.com/repos/lcdb/lcdblib/subscription","commits_url":"https://api.github.com/repos/lcdb/lcdblib/commits{/sha}","git_commits_url":"https://api.github.com/repos/lcdb/lcdblib/git/commits{/sha}","comments_url":"https://api.github.com/repos/lcdb/lcdblib/comments{/number}","issue_comment_url":"https://api.github.com/repos/lcdb/lcdblib/issues/comments{/number}","contents_url":"https://api.github.com/repos/lcdb/lcdblib/contents/{+path}","compare_url":"https://api.github.com/repos/lcdb/lcdblib/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lcdb/lcdblib/merges","archive_url":"https://api.github.com/repos/lcdb/lcdblib/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lcdb/lcdblib/downloads","issues_url":"https://api.github.com/repos/lcdb/lcdblib/issues{/number}","pulls_url":"https://api.github.com/repos/lcdb/lcdblib/pulls{/number}","milestones_url":"https://api.github.com/repos/lcdb/lcdblib/milestones{/number}","notifications_url":"https://api.github.com/repos/lcdb/lcdblib/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lcdb/lcdblib/labels{/name}","releases_url":"https://api.github.com/repos/lcdb/lcdblib/releases{/id}","deployments_url":"https://api.github.com/repos/lcdb/lcdblib/deployments","created_at":"2016-09-27T12:43:33Z","updated_at":"2016-09-27T12:43:52Z","pushed_at":"2016-12-03T02:38:41Z","git_url":"git://github.com/lcdb/lcdblib.git","ssh_url":"git@github.com:lcdb/lcdblib.git","clone_url":"https://github.com/lcdb/lcdblib.git","svn_url":"https://github.com/lcdb/lcdblib","homepage":null,"size":139,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/lcdb/lcdblib/pulls/11"},"html":{"href":"https://github.com/lcdb/lcdblib/pull/11"},"issue":{"href":"https://api.github.com/repos/lcdb/lcdblib/issues/11"},"comments":{"href":"https://api.github.com/repos/lcdb/lcdblib/issues/11/comments"},"review_comments":{"href":"https://api.github.com/repos/lcdb/lcdblib/pulls/11/comments"},"review_comment":{"href":"https://api.github.com/repos/lcdb/lcdblib/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/lcdb/lcdblib/pulls/11/commits"},"statuses":{"href":"https://api.github.com/repos/lcdb/lcdblib/statuses/02b6bd3ce58ff180a2232c1e5dd392e91d11102c"}}}}
{"actor":{"display_login":"daler"}}