├── LICENSE ├── README.md ├── examples ├── demuxlet_example.ipynb └── scrublet_basics.ipynb ├── old_versions └── v0.1 │ ├── LICENSE │ ├── README.md │ ├── examples │ ├── 10X_PBMC-8k_example.ipynb │ ├── 10X_PBMC-8k_scanpy_example.ipynb │ ├── demuxlet_PBMC_example.ipynb │ └── old │ │ └── 180306_basic_example.ipynb │ ├── requirements.txt │ ├── setup.py │ └── src │ └── scrublet │ ├── __init__.py │ ├── helper_functions.py │ └── scrublet.py ├── requirements.txt ├── setup.py └── src └── scrublet ├── __init__.py ├── helper_functions.py └── scrublet.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2018 Samuel Wolock 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scrublet 2 | **S**ingle-**C**ell **R**emover of Do**ublet**s 3 | 4 | Python code for identifying doublets in single-cell RNA-seq data. For details and validation of the method, see our paper in [Cell Systems](https://www.sciencedirect.com/science/article/pii/S2405471218304745) or the preprint on [bioRxiv](https://www.biorxiv.org/content/early/2018/07/09/357368). 5 | 6 | #### Quick start: 7 | For a typical workflow, including interpretation of predicted doublet scores, see the example [notebook](./examples/scrublet_basics.ipynb). 8 | 9 | Given a raw (unnormalized) UMI counts matrix `counts_matrix` with cells as rows and genes as columns, calculate a doublet score for each cell: 10 | ```python 11 | import scrublet as scr 12 | scrub = scr.Scrublet(counts_matrix) 13 | doublet_scores, predicted_doublets = scrub.scrub_doublets() 14 | ``` 15 | `scr.scrub_doublets()` simulates doublets from the observed data and uses a k-nearest-neighbor classifier to calculate a continuous `doublet_score` (between 0 and 1) for each transcriptome. The score is automatically thresholded to generate `predicted_doublets`, a boolean array that is `True` for predicted doublets and `False` otherwise. 16 | 17 | #### Best practices: 18 | - When working with data from multiple samples, run Scrublet on each sample separately. Because Scrublet is designed to detect technical doublets formed by the random co-encapsulation of two cells, it may perform poorly on merged datasets where the cell type proportions are not representative of any single sample. 19 | - Check that the doublet score threshold is reasonable (in an ideal case, separating the two peaks of a bimodal simulated doublet score histogram, as in [this example](./examples/scrublet_basics.ipynb)), and adjust manually if necessary. 20 | - Visualize the doublet predictions in a 2-D embedding (e.g., UMAP or t-SNE). Predicted doublets should mostly co-localize (possibly in multiple clusters). If they do not, you may need to adjust the doublet score threshold, or change the pre-processing parameters to better resolve the cell states present in your data. 21 | 22 | #### Installation: 23 | To install with PyPI: 24 | ```bash 25 | pip install scrublet 26 | ``` 27 | 28 | To install from source: 29 | ```bash 30 | git clone https://github.com/AllonKleinLab/scrublet.git 31 | cd scrublet 32 | pip install -r requirements.txt 33 | pip install --upgrade . 34 | ``` 35 | 36 | #### Old versions: 37 | Previous versions can be found [here](./old_versions/). 38 | 39 | #### Other doublet detection tools: 40 | [DoubletFinder](https://github.com/chris-mcginnis-ucsf/DoubletFinder) 41 | [DoubletDecon](https://github.com/EDePasquale/DoubletDecon) 42 | [DoubletDetection](https://github.com/JonathanShor/DoubletDetection) 43 | -------------------------------------------------------------------------------- /examples/scrublet_basics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "This example shows how to: \n", 8 | "1. Load a counts matrix (10X Chromium data from human peripheral blood cells)\n", 9 | "2. Run the default Scrublet pipeline \n", 10 | "3. Check that doublet predictions make sense" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 1, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "%matplotlib inline\n", 20 | "import scrublet as scr\n", 21 | "import scipy.io\n", 22 | "import matplotlib.pyplot as plt\n", 23 | "import numpy as np\n", 24 | "import os" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 2, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "plt.rcParams['font.family'] = 'sans-serif'\n", 34 | "plt.rcParams['font.sans-serif'] = 'Arial'\n", 35 | "plt.rc('font', size=14)\n", 36 | "plt.rcParams['pdf.fonttype'] = 42" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "#### Download 8k PBMC data set from 10X Genomics\n", 44 | "Download raw data from this link:\n", 45 | "http://cf.10xgenomics.com/samples/cell-exp/2.1.0/pbmc8k/pbmc8k_filtered_gene_bc_matrices.tar.gz\n", 46 | "\n", 47 | "\n", 48 | "Or use wget:" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 3, 54 | "metadata": {}, 55 | "outputs": [ 56 | { 57 | "name": "stdout", 58 | "output_type": "stream", 59 | "text": [ 60 | "--2018-10-04 11:21:04-- http://cf.10xgenomics.com/samples/cell-exp/2.1.0/pbmc8k/pbmc8k_filtered_gene_bc_matrices.tar.gz\n", 61 | "Resolving cf.10xgenomics.com (cf.10xgenomics.com)... 13.35.78.24, 13.35.78.116, 13.35.78.82, ...\n", 62 | "Connecting to cf.10xgenomics.com (cf.10xgenomics.com)|13.35.78.24|:80... connected.\n", 63 | "HTTP request sent, awaiting response... 200 OK\n", 64 | "Length: 37558165 (36M) [application/x-tar]\n", 65 | "Saving to: ‘pbmc8k_filtered_gene_bc_matrices.tar.gz’\n", 66 | "\n", 67 | "pbmc8k_filtered_gen 100%[===================>] 35.82M 16.5MB/s in 2.2s \n", 68 | "\n", 69 | "2018-10-04 11:21:07 (16.5 MB/s) - ‘pbmc8k_filtered_gene_bc_matrices.tar.gz’ saved [37558165/37558165]\n", 70 | "\n" 71 | ] 72 | } 73 | ], 74 | "source": [ 75 | "!wget http://cf.10xgenomics.com/samples/cell-exp/2.1.0/pbmc8k/pbmc8k_filtered_gene_bc_matrices.tar.gz" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "metadata": {}, 81 | "source": [ 82 | "Uncompress:" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 4, 88 | "metadata": {}, 89 | "outputs": [], 90 | "source": [ 91 | "!tar xfz pbmc8k_filtered_gene_bc_matrices.tar.gz" 92 | ] 93 | }, 94 | { 95 | "cell_type": "markdown", 96 | "metadata": {}, 97 | "source": [ 98 | "#### Load counts matrix and gene list\n", 99 | "Load the raw counts matrix as a scipy sparse matrix with cells as rows and genes as columns." 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": 5, 105 | "metadata": {}, 106 | "outputs": [ 107 | { 108 | "name": "stdout", 109 | "output_type": "stream", 110 | "text": [ 111 | "Counts matrix shape: 8381 rows, 33694 columns\n", 112 | "Number of genes in gene list: 33694\n" 113 | ] 114 | } 115 | ], 116 | "source": [ 117 | "input_dir = 'filtered_gene_bc_matrices/GRCh38/'\n", 118 | "counts_matrix = scipy.io.mmread(input_dir + '/matrix.mtx').T.tocsc()\n", 119 | "genes = np.array(scr.load_genes(input_dir + 'genes.tsv', delimiter='\\t', column=1))\n", 120 | "\n", 121 | "print('Counts matrix shape: {} rows, {} columns'.format(counts_matrix.shape[0], counts_matrix.shape[1]))\n", 122 | "print('Number of genes in gene list: {}'.format(len(genes)))" 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "metadata": {}, 128 | "source": [ 129 | "#### Initialize Scrublet object\n", 130 | "The relevant parameters are:\n", 131 | "- *expected_doublet_rate*: the expected fraction of transcriptomes that are doublets, typically 0.05-0.1. Results are not particularly sensitive to this parameter. For this example, the expected doublet rate comes from the Chromium User Guide: https://support.10xgenomics.com/permalink/3vzDu3zQjY0o2AqkkkI4CC\n", 132 | "- *sim_doublet_ratio*: the number of doublets to simulate, relative to the number of observed transcriptomes. This should be high enough that all doublet states are well-represented by simulated doublets. Setting it too high is computationally expensive. The default value is 2, though values as low as 0.5 give very similar results for the datasets that have been tested.\n", 133 | "- *n_neighbors*: Number of neighbors used to construct the KNN classifier of observed transcriptomes and simulated doublets. The default value of `round(0.5*sqrt(n_cells))` generally works well.\n" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": 6, 139 | "metadata": {}, 140 | "outputs": [], 141 | "source": [ 142 | "scrub = scr.Scrublet(counts_matrix, expected_doublet_rate=0.06)" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": {}, 148 | "source": [ 149 | "#### Run the default pipeline, which includes:\n", 150 | "1. Doublet simulation\n", 151 | "2. Normalization, gene filtering, rescaling, PCA\n", 152 | "3. Doublet score calculation \n", 153 | "4. Doublet score threshold detection and doublet calling\n" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 7, 159 | "metadata": {}, 160 | "outputs": [ 161 | { 162 | "name": "stdout", 163 | "output_type": "stream", 164 | "text": [ 165 | "Preprocessing...\n", 166 | "Simulating doublets...\n", 167 | "Embedding transcriptomes using PCA...\n", 168 | "Calculating doublet scores...\n", 169 | "Automatically set threshold at doublet score = 0.22\n", 170 | "Detected doublet rate = 4.3%\n", 171 | "Estimated detectable doublet fraction = 61.5%\n", 172 | "Overall doublet rate:\n", 173 | "\tExpected = 6.0%\n", 174 | "\tEstimated = 7.0%\n", 175 | "Elapsed time: 11.2 seconds\n" 176 | ] 177 | } 178 | ], 179 | "source": [ 180 | "doublet_scores, predicted_doublets = scrub.scrub_doublets(min_counts=2, \n", 181 | " min_cells=3, \n", 182 | " min_gene_variability_pctl=85, \n", 183 | " n_prin_comps=30)" 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "metadata": {}, 189 | "source": [ 190 | "#### Plot doublet score histograms for observed transcriptomes and simulated doublets\n", 191 | "The simulated doublet histogram is typically bimodal. The left mode corresponds to \"embedded\" doublets generated by two cells with similar gene expression. The right mode corresponds to \"neotypic\" doublets, which are generated by cells with distinct gene expression (e.g., different cell types) and are expected to introduce more artifacts in downstream analyses. Scrublet can only detect neotypic doublets. \n", 192 | " \n", 193 | "To call doublets vs. singlets, we must set a threshold doublet score, ideally at the minimum between the two modes of the simulated doublet histogram. `scrub_doublets()` attempts to identify this point automatically and has done a good job in this example. However, if automatic threshold detection doesn't work well, you can adjust the threshold with the `call_doublets()` function. For example:\n", 194 | "```python\n", 195 | "scrub.call_doublets(threshold=0.25)\n", 196 | "```" 197 | ] 198 | }, 199 | { 200 | "cell_type": "code", 201 | "execution_count": 8, 202 | "metadata": {}, 203 | "outputs": [ 204 | { 205 | "data": { 206 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjAAAADICAYAAAD2r9syAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvhp/UCwAAIABJREFUeJzt3Xm0HFW5xuHfG2KCTAEkCDJLFAkqIgcuICAqDqhRVBCUq+BAEAEXk8hVkICgCIKIChqnhEHAAYQgKkMIEhAwUVFkEiWGmYRAEkhCAnz3j707qVTO0J3Tfbr79Pus1et0jf1VVdd3du/au0oRgZmZmVk7GdLsAMzMzMxq5QKMmZmZtR0XYMzMzKztuABjZmZmbccFGDMzM2s7LsDYgJGkZsfQTJ2+/WbV8rmy8jpp37kAs5Ik7STpF5IelfS8pBmSLpTU1c28MyRd1Iw4B4KkKZKm9jHPV4DjByikhlqZ4ylpNDBV0tAGhWUdTNJoSRfnfLRY0uOSfi1p59J8EyQ9PEAx7SEpJO1Z43IfAOqSLyUdlGMYVeNyKxV7levu8xi0wr5rBy7ArARJxwC3AusD/we8FzgZ2AK4TdIXmhheqzoVWL3ZQdTJPsApNS7zUWCXBsRiHU7SNsBtwMbAF4G9gGOAkcDNkt5fmP004EMDHmRtjgY2a3YQbaqj9p1/DdZI0nuBbwHfiIgvl6ZNAL4PnCPpnxFxQxNCtAaLiGnNjsGs4GhgHvDOiFhcGSnpl8BfgG8AVwNExL+aEqFZA7gGpnYnAfcDXylPiHRb46OAh/N8RUMlnS1pjqRn8uWmV1YmSlpV0nckzcyXpB6QdHLxkoOk4ZJOL8zzT0kHFj8kX865QNLPJT0r6U+S7pX0m3K8kiYXL/1IGiPpDkkLJT0p6XxJI0rLjJZ0jaT5kh6RdFhvO0vS5pIqt3v+iqQZefwESTfkbZ4r6T95H6wn6fuS/purwudIukLSqwvrnCDpekkHSLon74t7JR1Q+uzPS7o7b8/jebniPpekI/J+XJhjOFHSKnn6uHwcjpc0W9ITkjYuXkKqbJ+kj0u6StICSQ/lZSvrmcCy78MSSQfl8SMknSXp35IW5TgOLm3DDEmnSDpT0qx8TC+RtIako/J+mi/pWkmbl5bt9Xjm7T8pb+PzOe5zJQ2WmrJO8UoggOXaPuTCzBeBH1XGqXT5YmW/X+rmsrGkUflcOKinQCXtLukPkp7O5/eDSnmucq7MAN4KvCWva/M8fmOlS2RP5XPsJkk7ldY9TNI3lfLSc5IuA0bQh3weHJfP/4WSrgM26ma+10j6paTHCjHsXpheyQWfLS13qpblwOL4T+f9u1DSjZK27yPOXvdBd/tu0J/jEeFXlS9gPeAl4Ft9zPedPN8r8vAMYAlwB/Bh4BDgKeC2wjLjSQWfA4F3AOPyOr5SmOdq4DmWXbY6n5S4Pl+YZwqwGLgceBcwhlTYeh5YuzDfq4AXgUPy8Efz5/0qL3NojvFWYGieZwNgDvCPPP8BwL/yuqf2sC+GA7vmOCcCXXn8hBznzcC7gf1ICfh24EHgk3k/HA3MB64vrHMC8DTwAHBQ3hc35fhfm+f5eF7/cXk9n83bc11hPV/Py5xLqnb/Ul7m9Dx9XB6+O3/GQYXjeVF+v3netjnApcD7Sb94XwTOzvO8Bvhpnu+tpEuPL8/7cTZwRF7/j/I8Xy3EOAOYC0wC3pOPZQD/zMdmb2As6Xvx+8Jy1RzPL+d1H5r30ZHAQuBHzT7X/KopL43N34m/AJ8HXtfLvBOAh+vw/ZpC6ZwHRuVlK+fJHnl4zzy8LSkP/jJ/398H/DzP8795ni7gr8DfSXljOPAKYGaO9VOkS2A35O/q9oXPvySPOz6v+yJSbgpgVC/7pHK+np7j+g6wqBT7aFIt152k3PIRUs5ZArw9z7N5XuazpfWfSv59WzgGLwCPk/LSR/J65wGv7GHf9bkPeth3g/ocb3oA7fTKX5DlCgw9zHdEnm+7PDyD9I9qrcI8e+d53puH7wHGl9ZzNPCJ/H7PPP8nS/OMJ/0zf3kenpJPquJnbUr6Z/bp0roXAWuTCg4zgcmldb8lf+bH8vA38pd/w8I8W5D+yXdbgCnMF8CpheEJedzWhXGvAv5YSQiF8d8FXuhm2dGFcVvmccfk4fOBewEV5vkAqVAi0i+zxcB5pc86FfgTqXZyXF7nXqV5ZrBiAebG0jzn5uOwbh6urKtSeDg0D7+1tNzP8nEZWfisx4BhhXnuI/1DeUVh3PeB+fl9tcfzd8C1pXk+BXyh2eeaX7W9gK/m70Tk1yxSgfptpfkmsGIBpqbvVx6eUj7n6bsAcyBwPbBKYZkhwDPAj3tadz4nnwdeUxj3MlIh6w95+HX5s44qxXQ9vRRggLXyur9TGv/jUuyXkX6krF2K4X7gL3l4c6ovwASwS2HchqTcekYP+67PfdDDvhvU57gvIdWmUkW7pI/5KtOLVbq/jYh5heGrSKXwSivz64CDJV0t6VBJr46IsyPiwjz9HZXlJA2tvIArSIWQHQvr/m/xsyJiJunXwscL8xwATIqIZ4DXApsAV5TWfTspub0rL7MHcHtEPFZY94OkBoQrYzEpWVbW9WhE7B4Rk3N16duULlHtAqxSqWbOno6IuwvDD+W/larR64CtgGmSvpyrZydFxLhIZ/FOpATwq2JAEXFCROwcES8VRv+jim25sDT8S1Ibs54a7u4BPBIRN5XGTyT9cir2HvlzFNo2kI7J/RHxVGHcbGCNfNyqPZ7XAe/MVdFHSdomIn4WEedWsb3WQiLiFFIN6T7AecCTpFrNyZLO7GPxWr9fKxvjxIjYk3Quj5b0QdKl1aGk73xP3kE6Bx8sfJeDVGu0h6RhpJpNSPmw6Od9hLUzMKyK5fYArsn5srI9S/J826l0qb0KMyPi1sK6HiPVePXU66iafdCdQX2OuwBTmxn57xZ9zLdl/juzMO7x4gz5H+RsYJ086hjgWFJPgu8D/5b0V0m75enr5b9PkwpIldc1eXzxmu1yn5VNBN4maQNJWwFvBi4orbtSa1B8bVhY9ytIv+zKHu1mXDVmlQoKSPqYpAdJBZJfkWqqFlQmF2ZdwPIq6xkCEBGXky7XPU1KktOAhyR9Ls9X2eYnqoizu/1Z9khp+Mn8d53yjNm6Pay3Mm7twrj53cz3XGk4Cu+rPZ7fJlVhvww4E7hL0r8kfbiHmK2FRcT8iPh1RBwWEduQaiWmAsdKen0vi9b6/VopSm3cfkC6pHEXcBYply6h1H6nZD1ge1b8Ln+JVPhYj5SbYMX81Fduqna5vs7XWgsw3a3rSXrOF9Xsg+4M6nPcvZBqEBGzJN0KfETSCeV/vgC5luDDwC0RMbswad3SfENJX7on87qXkE7osyStT7oWeyJwpaQNSNWsC4Hd6d6DfYT/K1LBaB9S98pZwO/ztMqviuNJ11XLKgluFulXXllPJ09NJO1Kum59Lqmd0SN5/Bmka7o1iYgrSLUQqwFvJ102O1/SNJZt88hSDBuSrnffSm3K+6Cyn54sz5jNAbbuZvyr8t/id6fWfx5VHc9cE/UT4CeS1ibVzPwfcJmkTYs1bdaaJG1MavsyLiLOK06LiPskHUkqvI8mFRq6szKFkwBWKY1bq49lziXVCu0H3BARzwFI6ukcqXiGVBA7qofps1lWANkA+HdhWl+5qbjcP3tZbg7d577K+fpUYZlq9su63YzbgJ7zRTX7YAWD/Rx3DUztvkpqlHlGD9MrvypOLY1/t6RiNek+pALkjZJeLul+SV8EiIgnI2ICqR3HOqQTYAqp4eewiJhWeeVYTqOPe6xExLOkatK9gX2BS3OhCVJbkSeALUvrfjBvZ+VyxnXA/0haWgOl1KtnuZtl9WCFwl43diF9J08tFF6GsuySR9XfV0mXKfe8iogFEXE1qUEvpGvVt5N+wZR/iRxGurxX690sy+vZl9SWpVIQerE0/SZgo2Ivhux/SZcWV/ayHFR5PCXdKum7ABHxTET8gvRdGko3vTCsJT1G+mFzmKQ1upk+Ov/tqfCysuYBG0vL3fV1jz6W2RX4Y0RcVSi8dJF+RBTP7fK5MoV0OfiB0vd5P9I/9CUsa+uyf2nZvfuI6VZSbW5fy90EvDcXAsixD83L/TVvT+Wy/aaFecSyy1tFo5RublmZbzNS/ruxhzin0Pc+gNK+G+znuGtgahQRN0g6ilRTsh2p0eUjpEs/nwZ2A74YEb8vLfoKUm3Kd0iN3U4DJpMaYIWk6cBJkl4C/kb6J3sMqSHmbEm/I32Jr5D0ddKvhe1IjUNvze1c+nIB6ZLTKqRePpVtelHS/5FK6QBXkgpEx+dYK12lzwE+A1wn6SRSG5YTqvhcSL8gdpK0a0T0dNfeO/Lf70n6Kanwdhjwxjx+9fyZ1ZgM/CCfvFfnZY8j/VK5ISKelnQOcIyk50kJcDtSt9NvRMQC1XZH7r0lnZc/aw9SI92TIqJSe1WpFfmYpBtJDfkOAy6XNA74D6mR8aeA0yJiTi0fXlTD8bwJ+KKkOaTeYOuTLrfdR/oOWovLx/pQ4DfAXyR9n5QbhpG+h4cD55fai9XDVaTv63mSfk26JH04vdfm3A7sL+lwUqeFbUm1AcHyP8CeIXUFfiepgHE28AnSj72zSLUUe5POsRNzLcOD+fw7KbcHuT3Ht1dvGxERz+VcdqakhaRGr7uRel4VnUzu7SjpdNKPkyOAV5N6PJFzylRSYfLfpMLlZ0mXbcsWAL+RdALpx9LJpMvd3+4h1Gr2QXf7bnCf4/VsEdxJL1Kj2YuB/5K+zDNJBYQdupl3BukLeD6plP5Ufr9GYZ7VSbU3D5Jamz+a51mnMM9qpOuYM/M8M0g31SuuZwo9d2keQuqqfXcP0z9CKkQsyjH+FnhzaZ7NSF2055GqX08ltdDvqxfSEaRr33NIyXUChd4Qhfk+T6oCXpT37UTgg6Qk94E8zwrLkgrjQapKL67rH6Tr+c+Qkm6x55JIl5UqXcHvJ3UzVJ4+jkLPodLxLPdC+nLeXwvzeg4rLTMS+HP+nOPzuPVIXaefyNt7J3BwT5/V2zHuLta+jiepIHtCjndhPp4XAxs3+/zyq7YXqTBQzEfzSf/APsXyPfGWO3dW9vuVvzunkvLUQlLNwRtItYcH5Xn2YPmeNOvmGGfn+P6ez7fz8zlQWfdbSHlqIbBbHrcFqVfVU6R//v8ADi3FOCR/n2fmZf/Asi7mPXajzsseks+DRaReiPsWY8/zvIn0A3B+ft0A7F5az6vzefZcPp++AxzMir2QbiP9kHgkb89vizGW910N+2C5fTfYz/FKojazlaB0o60HSQWPHzc3GjOzzuE2MGZmZtZ2XIAxMzOztuNLSGZmZtZ2XANjZmZmbccFGDMzM2s7HXUfmPXWWy8233zzZofRENOnT2f77Xt9GrvZgJo+ffrsiBjZ95ytz7nDbGDUkjc6qg1MV1dXTJs2rdlhNIQkOulYWuuTND0iupodRz04d5gNjFryhi8hmZmZWdtxAcbMzMzaTkcUYCSNkTR+7ty5zQ7FzMzM6qAjCjARMSkixo4YMaLZoZiZmVkddFQvpJV18sknLzd80kknNSkSM7PBzznXqtERNTBmZmY2uAyKGhhJewMfiYhPNDsWMzNrLa7RGZzavgZG0pnA6YCaHYuZmZkNjLYvwAB3AIc2OwgzMzMbOG1fgImIXwK+jaSZmVkHGRRtYMzMrHO4TYuBCzDdKp8cZmZm1lpa6hKSpOGS7pK0Z2nceElPS3pc0nHNjNHMzMyar2VqYCStCvwc2KY06UxgF2BPYGPgQkkzI+LSygwRMQWYMjCRmpmZWbO1RA2MpNHAbcCWpfGrAwcDR0bE9Ii4EjgDOLyGdY+VNE3StFmzZtUzbDMzM2uSlijAALsB1wI7l8ZvCwwHphbGTQV2kFRV7VFEjI+IrojoGjlyZF2CNTMzs+ZqiUtIEfHDyntpufvRbQjMiYhFhXFPAMOAkcBj1axf0hhgzKhRo/ofrJmZmTVdSxRgerEa8HxpXGV4eLUriYhJwKSurq6D6xFUd72U3I3PzMxs4LTKJaSeLGLFgkpleEG1K5E0RtL4uXPn1i0wMzMza55WL8A8AqwjaVhh3AakWpg51a4kIiZFxNgRI0bUOz4zMzNrglYvwPwNWEzqRl2xKzA9Il6odiWugTEzMxtcWroAExELgInAeZJ2lPQB4Fjg3BrX4xoYMzOzQaSqAoykNzU6kF4cDfwZmAz8ADglIi5rYjxm1gRNzkNm1mKq7YX0J0kzgEuASyPi/kYFFBEqDS8ADsyvleJu1GaDQl3ykKTXAd8DdgKeAr4XEWfWLUrrlR/EaPVSbQFmfeBDwEeBr0j6BymJXBYRDzcquHqpdzdqM2uKfuchSS8DfgfcCHwOeB3wc0mPRsTFjQnb2p0LXa2pqktIETE/Ii6IiPcDr2TZr5d7Jd0k6RBJazUyUDPrbHXKQxsBdwCHRcQDEXE1cD3w1oYGb2Z1tzI3snsNsDXweuAl4CHSL6LTJR1afMhiq/AlJLNBZ6XyUETMAPYDULrt9y7A7sBhAxCzf8mb1VG1jXi7JJ0h6T/AzcBrgZOA9SPifyPiHcDpwPcbF+rKcy8ks/bXgDz0MOnZan8CftWImM2scaqtgbkNmAKcBvwqIrq7ocptwK/rFJeZWVm989AHgVcB5wPfBr5QnChpLDAWYNNNN125iM2sYaotwHyClDCWFEdKGg68JyKujIibgJvqHaCZWVbXPBQR0/LyqwETJR0bEYsL08cD4wG6urqiTttgZnXS6yUkSUMkrQJcBLwiDy99AW8EWq7NS5nvxGvWvuqZhyRtlG+IWXQ36Qn37ohg1kZ6LMBIOgR4gXQrf5GeS7Sk9KpU6bY0t4Exa08NyENbA5dLWr8wbntgVkTMrlPYZjYAeryEFBE/lHQPqZAzGdiH5R+gGMCzwD8aGqGZdawG5KGbSDUuEyQdA2xJavh7Wt2CLij3OjKz+um1DUxE/BFA0hbAzIjwdeAeuHukWWPUMw9FxBJJ7yfdQ+Z2YD5wDjU+X83Mmq/HAoykC0g3e5oPfC2P63beiPhkQ6Izs47WiDwUETOBcjsYM2szvdXAvNjD+7bjG9mZta1Bk4fMrL56awPzqe7etyM/C8msPQ2mPGRm9VXtnXhXk3SapK2U/FTSc/n5Ixs3OkgzM+chMyuqqgBDavD2IVI3xv2A/Ul3qHyKFn18gJkNOs5DZrZUtXfi/SCwZ0TcK+lrwDURcbGkacC0xoVnZraU85CZLVVtAWYoME/Sy4B3AUfn8S8Hnm9EYGZmJYMuD/n2C2Yrr9oCzC3AWcBc0i23fyPpTaQq3esbFJuZWZHzkJktVW0BZizpGvO2wH4R8ZSkI4HHgSMaFZyZWYHzkLUl17Q1RlUFmIh4mHT9uTjuxIZE1AC+D4xZ+2v3PGRm9VVtDQyS3g3sALyM1AtgqYj4ap3jqqtm3AfGJW6z+mvnPGRm9VVVAUbSt0lVtHcC80qT/XwkM2s456H24B9vNlCqrYHZDzg4In7WyGBqIWkIcD7wBlIPhM9ExH+aG5WZNVDL5SEza55qb2Q3lNQDoJV8CFglInYBTgTObHI8ZtZYrZiHzKxJqi3AfBcYJ2mNRgZTo7cAvweIiKmk6+JmNni1Yh4ysyap9hLSu4AdgY9Kmg0sLk6MiE3rHVgV1iLdD6JCPc1oZoNCK+YhM2uSagswP86vVjIPWLMw/GKzAjGzAdGKecjMmqTa+8BMhKUNZzcDHgKGRMTiXhdsrFuAvYDLJe0K/LWJsZhZg7VoHjKzJqmqDYykoZLOBBYA/wI2BS6S9HNJq9cjEEnDJd0lac/SuPGSnpb0uKTjCotcAbwk6Vbgm8AX6xGHmbWmgchDZtY+qm3E+zXg3aRr0IvyuHOB7UjPJukXSasClwDblCadCewC7AkcApwgaX+AiHgpIg6JiF0i4i0R8UB/4zCzltbQPGRm7aXaAszHgEMj4o/kG0blnj+fBj7cnwAkjQZuA7YsjV8dOBg4MiKmR8SVwBnA4f35PDNrWw3LQ2bWfqptxLse8GQ3458jPcq+P3YDrgXG5fVVbAsMB6YWxk0FTpQ0NCJeqGblksaSHgLHppu6k4JZG2tkHjJra514B+Rqa2CuB74kqdJVOSSNAL4BTO5PABHxw4g4LiIWlCZtCMyJiEWFcU8Aw4CRNax/fER0RUTXyJFVL2ZmracueUjSlpIm5bZ1D0s6K1/GNrM2Um0NzGGkRrNPkn7p/BbYBPgP8IHGhMZqpEcEFFWGh9eyolZ4GnUnlo7N6qzfeUjSMGAScDepfd36wE/z5GPqHK+ZNVC13agfAXaU9A7gdXm5+4BrI+KlBsW2iBULKpXhcm1Nr5rxNGozq6865aEdgVHAjhHxLHCPpBOBs3EBxqytVFsDA0BE3ADc0KBYyh4B1pE0rHCfhw1ItTBzallRK9TAmFl99DMP3Qe8Nxdelq6SGmt1zaz5eizASHqJKh9RHxGr1C2iZf5GulX4LsCUPG5XYHq1DXgrWrEGxpeUzPpW7zwUEbNIbWkq6x9C6tl488rGWE/OC2bV660G5p2F928GjgZOBaYBS/K4E0j3Yai7iFggaSJwnqSDSLUvx5J7FNXCNTBmbavReehs0n1kVngYrHswmrW2HgswuZoWAEnfAz4ZEdcVZvmLpH8DPyElgUY4Gjif1MNgHnBKRFxW60pasQbGzPrWqDyUezKdA3we2Cci/tnNZ48HxgN0dXVVVQtkZgOn2jYwG9H9/RcWAOvWK5iIUGl4AXBgfplZZ6tLHsqXjX4CHADsl2+SaWZtptr7wPwW+Kmk3SSNkLS2pHcCPwN+0bjw6kPSGEnj586d2+xQzGzl1SsPnQV8HPhwRFzeiEDNrPGqLcCMJbXen0zqAfQUcDVwK3BEY0Krn4iYFBFjR4wY0exQzGzl9TsPSdoJOBI4CZgmaYPKqzEhm1mjVHsfmPnAxyV9DnhtHn1fHm9m1nB1ykP75L/fyK+lJL2s1h6OZtY8td4HZh6p9X9bcS8ks8GjP3koIo4l9WY0szZX7SWktuZLSGZmZoNLTTUw1ji+gZWZtaNy7jIbKB1RA2NmZmaDS78KMJKGSGr5W1S6G7XZ4NUuecjM6qu/NTAjgQfrEUgjuQ2M2aDWFnnIzOqrvwWYOcDb6xGImdlKch4y60D9asQbEUuAm+oUi5lZzZyHzDpT1QUYSW8HPgdsDSwG7gHOioi/Nig2M7PlOA+5x6JZRVUFGEmfBs4Dfg78kHTpaUfgT5IOiohLGxdi/7XjjewGQ5IaDNtgraPd85CZ1Ve1NTCnAGMj4oLiSEk3k27H3dKJIyImAZO6uroObnYsZrbS2joPmVl9VduId03gz92MnwqsV79wzMx65DxkZktVWwNzLvBNSQdGxNMAklYDxpGqcs1q5ktMViPnITNbqscCjKSHgKgMAhsBj0p6EHgJ2AIYDnRM47lm8j9760Sdnod8m36znvVWA3PCgEVhNWuFxOZClQ0A5yEz61aPBZiImFgeJ2lN4DXAKsADlWpcs4HgAlPncR5qPa3w48kMqu9GPQw4CziElDQEvCDpEuDgiFjcuBD7rx27UQ+0apKSCwzWTO2eh8ysvqrthXQWsBcwBlgbWBfYG9gF+HpjQqsfPwvJbFBo6zxkZvVVbS+k/YF9IqJ4u+5rJC0g3Xvh2LpHZma2POehbvjSqnWqagswQ4DZ3Yx/ClijfuHYQPF1bGtDzkNmtlS1l5BuIN1/Yek1GElrk+5+ObkRgdVC0t6SLmx2HGbWUC2dh8xsYFVbA3MUcCPwiKQH8rhRwP2ka9BNI+lM0jXxac2Mw8warmXzkJkNvGoLMAuBbUgN6F4HLCI9Bfb6iIjeFhwAdwDXAJ9pchxm1lh1zUOShgPTgSMj4vp6BmpmjVdtAeZO4IMRcRVwVQPj6ZakzwGfK41+b0Q8GhG/lLTHQMdkZgOubnlI0qqkp1pvU4/A2onbv9lgUW0BZhHpdt1NERE/AH7QrM9vF8XE5J4INgjVJQ9JGk0qvKjfEZlZ01RbgPk9cK2k3wEzSIlkqYj4ap3jsjrzry4bBOqVh3YDriU9BPK5OsZnZgOo2gLM60mNZEfmV1Fdrj3ncd8F9gWeB86OiDNqXbdZT3y/jLZXlzwUEUufXC0N/koY/3ixwaqqAkxEvK1eH9jLteczSXfU3BPYGLhQ0syIuLSK+KYAU+oV42DgpGWDTT3zUDUkjQXGAmy66aYD+dEtxbnEWlWv94GRdICkyyVdImn//n5YvvZ8G7BlafzqwMGkGpnpEXElcAZweB0+c6ykaZKmzZo1q7+rM7MBVu88VK2IGB8RXRHRNXJkucLHzJqtxxoYSV8CTgWuB14GTJT0xoj4cj8+r6drz9uSGudNLYybCpwoaWhEvLCyHxgR44HxAF1dXc3u8m1mNWhQHhrUXGNinaK3GpixwGciYq+IGAN8DDhM/bhoHBE/jIjjImJBadKGwJyIKDbKewIYxorXumsmaYyk8XPnzu3vqsxsYNU9D5nZ4NBbG5hNSLfurrgKWJ1U2Hi0znGsRmq4W1QZ7ne3yYiYBEzq6uo6uL/rMrMBNZB5qN9c+2E2cHqrgRkKLKkM5Ms4C4FVGxBHd/d3qAyXa2tq5hoYs7Y1kHnIzNpItd2oG+0RYB1JwyJicR63AakWZk5/V+4aGDPrTkT4UlSJa5GsXfRVgPmYpPmF4VWAfSUt150nIn7azzj+BiwmdaOeksftCkzvTwNeMxsUBioPmVkb6a0AMxM4sjTuCVZ8JlEA/UocEbFA0kTgPEkHkWpfjiXfg6G/JI0BxowaNaoeqzOzgTNgecjM2kuPBZiI2HwA4wA4GjgfmAzMA06JiMvqsWJfQjJrT03IQ2bWJprWBqZ87Tl3rT4wv+rKNTBmZmaDS6s04m0o18CYmfXMDXetHfX6KAEzMzOzVuQCjJmZmbWdjijA+EZ2ZmZmg0tHFGAiYlJEjB0xYkSzQzEzM7M66IgCjJmZmQ0uHdELyd2orV7KvTVOOumkJkViZtbZOqIGxpeQzMzMBpeOKMCYmZnZ4OICjJmZmbUx7yuzAAAMc0lEQVQdF2DMzMys7bgAY2ZmZm2nIwowvpGdmZnZ4NIRBRj3QjIzMxtcOqIAY2ZmZoOLCzBmZmbWdjriTrxm7cJ3+m08ScOB7wL7As8DZ0fEGc2Nysxq5QKMmXWaM4FdgD2BjYELJc2MiEubG5aZ1cKXkMysY0haHTgYODIipkfElcAZwOHNjczMatURBRh3ozazbFtgODC1MG4qsIMk10ibtZGOKMC4G7WZZRsCcyJiUWHcE8AwYGRzQjKzldERBRgzs2w1UsPdosrw8AGOxcz6QRHR7BgGjKRZwH+rmHU9YHaDwxkI3o7W0mnbsVlEtFSthqR9gfMjYr3CuK2Bu4FXRsSThfFjgbF5cCvgvio+otOOcavzdrSOuueNjirAVEvStIjoanYc/eXtaC3ejuaTtAtwM/DyiFicx70N+B2wRkS80M/1t+2+KfJ2tJbBsB2N2AZfQjKzTvI3YDGpG3XFrsD0/hZezGxgudW9mXWMiFggaSJwnqSDgA2AY1l2qcjM2oQLMN0b3+wA6sTb0Vq8Ha3haOB8YDIwDzglIi6r07rbfd9UeDtay2DYjrpvg9vAmJmZWdtxGxgzMzNrOx1RgJE0XNJ4SU9LelzScb3Mu62kP0laIGm6pB1K0z8q6YE8/UpJ6zd+C5Z+di3bsZ+kuyQ9J+lOSWMK04ZIWigpSq+1W3A7ru0mzr0L01v+eEia0s02hKTJeXpTj0dpe+6StGcv82yWj8lzku6RtFdp+tsk/T0fjymSRjU+8sZx7nDuaITBlDuamjciYtC/gHOBu4DtgQ+Srnvv3818qwOPAt8GtgbOAZ4E1szTdwAWAgcBbwRuBH7fgtuxG6mnxcHAKOALwBJguzx9FPASsBmpEWPlpVbajjzvw8B+pTiHt9nxWLcU/zvy8RjTCscjx7AqcDkQwJ49zCNSL55LgNHA8cACYIs8fRNgPnBcnn4p8E9gyEBtRxOPsXNHC21Hnte5o/Hb0dS8MSAHq5mvnFgWFncucAIwtZt5P0260d2Qwo7/F/DZPHwBcFFh/k3ygRvVYtvxY+CS0rhrgdPz+w8AM9rgeKyV9+/mPayrLY5HaTkBdwA/Loxr2vHInz86J5g7+0hEb8/bvGZh3PXAqfn9KcXtJ931dl5P62v1l3PH0nHOHU3ajtJyLZU7WiFvdMIlpFoe3rYTcEtEvAQQaW/eAuxcmP7HyswR8RApae1M49WyHd8FvlYaF6TSMqQvXjV3FW2EWrZjNLAImNnDutrleBTtD7wO+EphXDOPB6Rf3dfS937bCfhrRMwvjJtKz+fHAuAvVay3VTl3JM4d9TVYckfT80YnFGBqeXjbhqRqYErzblzl9Eaqejsi4s6IuLsyLGkbUtVj5UsyGlhT0h8lPSbpGklbNTb8pWo5HqOBZ4BLc5x3SHpvaV0tfzxKvky6lf0ThXHNPB5ExA8j4ricOHrTyudHIzh3OHc0wqDIHa2QNzqhAFPLw9t6mnd4ldMbaaUeQpcbpl1Bun36b/LorYF1gHGk66+LgBslDcTjumvZjq2BNYCrgPcA1wCTJP1PH+tqyeMhaVfSL6jvliY183jUopXPj0Zw7nDuaIROyx0NOzc64UZ2i1hxR1SGyyXHnuZdUOX0RqplOwCQtDGpiu9FYJ9K9Tap6m+ViHguz/dx4CHSCXBBneMuq2U7jgdOi4hn8vCdkrYHDgFu72VdLXk8SA0KJ0fEw6XxzTwetVgElBNjNefHUw2Oq1GcO5w7GqHTckfD8kYn1MA8AqwjaVhh3AakEt6cbubdoDRuA+CxKqc3Ui3bgaRXk345BbBHRCz9MkTEosoXvjIMPAhs1KDYi6rejoh4sZCAKu5hWZxtczyyvUgt9pfT5ONRi1Y+PxrBucO5oxE6LXc07NzohAJMLQ9vuw3YRZIA8t9d8vjK9F0rM0vaBNi0ML2Rqt4OSesC1wFzgbcWr5lKGirpEUn7F8atAbwGuLeB8VfUsh2/knReafntWBZnWxyPHNt6wJbATaXxzT4etbgNeJOk1QvjdqXn82M10vEaiOPRCM4dzh2N0Gm5o3F5oxndrwb6BfwAuBvYkdTtbC6wX562AfDy/H4t0r0bvkdqHPVtUmOiyr0cdmbZPRLeQHqWyjUtuB3n52lvYvl7A4zI038K/BvYHXg9cGVe79AW245PkqoXP046KU8mdcfbsp2ORx7eI8e6wr0Nmn08SrEs1x2S1Khwjfx+FdL9GX4JbAN8CXiO3FUV2Dwfn6/k8+cS0r0u2vk+MM4dzh1N24483PK5o1l5Y8A2sJkvUiOhicCzpNbOx5R2/EGF4R1IXbgWkfrcb19a14Gk7nbPkhq4jWy17QBm5+Hy66I8fXVSY7DH8hfpSmDTVtuOPHw48EA+Hn8Gdm+345GH9wNm9bCeph6PUizlRDQDGFcYHkX6JbgoJ6V3lZbfi/TrbwHpn8KWzdiOJh1j544W2Y487NwxcNvTlLzhhzmamZlZ2+mENjBmZmY2yLgAY2ZmZm3HBRgzMzNrOy7AmJmZWdtxAcbMzMzajgswZmZm1nZcgOlgkmZIivx6SdKzkm6R9O46fsY4SVN7mT5F0qlVrmt9SfvVKzYzWznOHdYKXICxY0iPM98Y2Am4BfitpD2bGlX3vgmMaXYQZgY4d1iTdcLTqK138yLi8fz+UeA4SRuSboX+huaF1S01OwAzW8q5w5rKNTDWnfHA6yWNApC0jqTxkp6QNE/Sxfmhb0jaI1cjLy0MS5og6aLC+oZK+pGkBZL+I+l9PX2wpLF5nmclTZW0Qx4/jnTr7wMkzehh2c/nZRdJ+ruk9xembSHpaknzJT0s6cuFaRtL+oWkOZJmS/qepFXztIMk3ZYfDjdX0meVnJAfpDZX0u8lvab23Ww26Dh3OHcMGBdgrDt357+j898rSA93GwO8A9gKuLCG9f0P6RfQm4HzgCslbVWeSdIY4GvAUaSnkf4OmJx/1X0L+AXwa9IzZ8rLbgeck5fdCrgM+IWktSUNB64FlpAe4vYZ0q/FA5QeaT8ZWIP00LR9Sc/lOKsU/79ID16bRHrGyieBT+RpDwA3KD1F1ayTOXc4dwycgX7ok1+t8yI9cOuz3YwfSno41wHAG/P7rQvTt8rjtiGduEHh6afABJY9/G0c6UFjwwrTbwS+ld9PAU7N728GjirFciNwYnm93cT8IeB54E15eAjwLtJD095HetDZiML8BwB7k54EuwBYtzDtPcALwAjgoLx9axamPwR8qDAs4D/AJ5p9TP3yayBezh3OHa3wchsY685a+e88YGtgfkTcU5kYEfdJejpPm13F+u6MiMWF4b/kZcu2Br4u6WuFccOBh6v4jD8AU4G/SroLuAr4SUQskDQaeCAi5ha24WIASV/K0+YU1nUr6RHwlardpyJifp5/DVKjxYslvVRYZlXgtVXEaTaYOXc4dwwYF2CsO2/Mf+8qvC9bJb+6e5x5+Xv1Uml4CLCYFQ0l9Wy4tjT+2R4jzXKy2RPYFXg/sA9wuKTdevisikXdjFul9Lc4T2Xb9mdZdXnFM33FaTbIOXcs/9e5o4HcBsa682lgekQ8CNwLrClp6a+e/KtkLeA+lp3gaxWWf3VpfdtIKvYC2BG4hxXdB2wSEQ9UXqSktEee3l3Cq8S0M3BCRNwcEV8i/SJ7gnRN+l/AlpLWKsx/sqQJOY5RlYaF2c7Ai6Tr08uJiGeAJ4ENCzE+CHwd2Lan+Mw6hHOHc8eAcQHG1pK0gaQNJb1B0jmkXwjHQKryBa4GJkraIbfsnwhMjYi/Af8EFgLjcmv9o0mN6Io2Br4naWtJJ7KsQV7Z2cAXJB0oaUtJJ5ES4r15+rPAZpI26mbZhcBXJR0iaXPS9elNgOmkKuKHgB/lGPYCvkBq6Hc9cD9woaQ3StoDOBe4NCKe6mGfnQ18TdLeSr0tzgPeSfeJ1Wywcu5w7miuZjfC8at5L1JDvCi8niCdmLuW5lsXuJh0XftpUoO4dQrTDwD+S2rQdjFwOss3xJsEXERKFPcC7yosO4XcEC8PH0Fq1LYI+BuwV2HaDqT7TcwG1M32HEBKBIvyOj5fmPZa4Locw38pNPgDNicl2gWkX0jfBlbN0w4CHi59zirAKcAjeZlbgB2bfTz98mugXs4dS6c5dzTxpbxTzczMzNqGLyGZmZlZ23EBxszMzNqOCzBmZmbWdlyAMTMzs7bjAoyZmZm1HRdgzMzMrO24AGNmZmZtxwUYMzMzazsuwJiZmVnb+X9t6472rAmmYAAAAABJRU5ErkJggg==\n", 207 | "text/plain": [ 208 | "
" 209 | ] 210 | }, 211 | "metadata": {}, 212 | "output_type": "display_data" 213 | } 214 | ], 215 | "source": [ 216 | "scrub.plot_histogram();" 217 | ] 218 | }, 219 | { 220 | "cell_type": "markdown", 221 | "metadata": {}, 222 | "source": [ 223 | "#### Get 2-D embedding to visualize the results" 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": 9, 229 | "metadata": {}, 230 | "outputs": [ 231 | { 232 | "name": "stdout", 233 | "output_type": "stream", 234 | "text": [ 235 | "Running UMAP...\n", 236 | "Done.\n" 237 | ] 238 | } 239 | ], 240 | "source": [ 241 | "print('Running UMAP...')\n", 242 | "scrub.set_embedding('UMAP', scr.get_umap(scrub.manifold_obs_, 10, min_dist=0.3))\n", 243 | "\n", 244 | "# # Uncomment to run tSNE - slow\n", 245 | "# print('Running tSNE...')\n", 246 | "# scrub.set_embedding('tSNE', scr.get_tsne(scrub.manifold_obs_, angle=0.9))\n", 247 | "\n", 248 | "# # Uncomment to run force layout - slow\n", 249 | "# print('Running ForceAtlas2...')\n", 250 | "# scrub.set_embedding('FA', scr.get_force_layout(scrub.manifold_obs_, n_neighbors=5. n_iter=1000))\n", 251 | " \n", 252 | "print('Done.')" 253 | ] 254 | }, 255 | { 256 | "cell_type": "markdown", 257 | "metadata": {}, 258 | "source": [ 259 | "#### Plot doublet predictions on 2-D embedding\n", 260 | "Predicted doublets should co-localize in distinct states." 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": 10, 266 | "metadata": {}, 267 | "outputs": [ 268 | { 269 | "data": { 270 | "image/png": "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\n", 271 | "text/plain": [ 272 | "
" 273 | ] 274 | }, 275 | "metadata": {}, 276 | "output_type": "display_data" 277 | } 278 | ], 279 | "source": [ 280 | "scrub.plot_embedding('UMAP', order_points=True);\n", 281 | "\n", 282 | "# scrub.plot_embedding('tSNE', order_points=True);\n", 283 | "# scrub.plot_embedding('FA', order_points=True);" 284 | ] 285 | } 286 | ], 287 | "metadata": { 288 | "kernelspec": { 289 | "display_name": "Python 3", 290 | "language": "python", 291 | "name": "python3" 292 | }, 293 | "language_info": { 294 | "codemirror_mode": { 295 | "name": "ipython", 296 | "version": 3 297 | }, 298 | "file_extension": ".py", 299 | "mimetype": "text/x-python", 300 | "name": "python", 301 | "nbconvert_exporter": "python", 302 | "pygments_lexer": "ipython3", 303 | "version": "3.6.4" 304 | } 305 | }, 306 | "nbformat": 4, 307 | "nbformat_minor": 2 308 | } 309 | -------------------------------------------------------------------------------- /old_versions/v0.1/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2018 Samuel Wolock 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /old_versions/v0.1/README.md: -------------------------------------------------------------------------------- 1 | # Scrublet 2 | **S**ingle-**C**ell **R**emover of Do**ublet**s 3 | 4 | Python code for identifying doublets in single-cell RNA-seq data. For details and validation of the method, see our preprint on [bioRxiv](https://www.biorxiv.org/content/early/2018/07/09/357368). 5 | 6 | 7 | Given a raw counts matrix `E`, with cells as rows and genes as columns, you can calculate a doublet score for each cell with the following command: 8 | ```python 9 | import scrublet as scr 10 | scrublet_results = scr.compute_doublet_scores(E) 11 | doublet_scores = scrublet_results['doublet_scores_observed_cells'] 12 | ``` 13 | 14 | There are a number of optional parameters that can have major effects on the quality of results. For a typical workflow, including interpretation of predicted doublet scores, see the example [ipython notebook](./examples/10X_PBMC-8k_example.ipynb). 15 | 16 | We are also working on a [scanpy](https://github.com/theislab/scanpy) implementation. See https://github.com/swolock/scanpy for a fork implementing Scrublet and the example [here](./examples/10X_PBMC-8k_scanpy_example.ipynb). 17 | 18 | #### To install: 19 | ```bash 20 | git clone https://github.com/AllonKleinLab/scrublet.git 21 | cd scrublet 22 | pip install -r requirements.txt 23 | pip install --upgrade . 24 | ``` 25 | #### Other doublet detection tools: 26 | [DoubletFinder](https://github.com/chris-mcginnis-ucsf/DoubletFinder) 27 | [DoubletDecon](https://github.com/EDePasquale/DoubletDecon) 28 | [DoubletDetection](https://github.com/JonathanShor/DoubletDetection) 29 | -------------------------------------------------------------------------------- /old_versions/v0.1/examples/10X_PBMC-8k_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%matplotlib inline\n", 10 | "import scrublet as scr\n", 11 | "import scipy.io\n", 12 | "import matplotlib.pyplot as plt\n", 13 | "import numpy as np\n", 14 | "import os\n", 15 | "import time\n" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "#### Download 8k PBMC data set from 10X Genomics\n", 23 | "(Only need to do this once)\n", 24 | "\n", 25 | "Download raw data by navigating to the following URL in your web browser:\n", 26 | "http://cf.10xgenomics.com/samples/cell-exp/2.1.0/pbmc8k/pbmc8k_filtered_gene_bc_matrices.tar.gz\n", 27 | "\n", 28 | "Or use wget:" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 2, 34 | "metadata": {}, 35 | "outputs": [ 36 | { 37 | "name": "stdout", 38 | "output_type": "stream", 39 | "text": [ 40 | "--2018-07-16 16:49:19-- http://cf.10xgenomics.com/samples/cell-exp/2.1.0/pbmc8k/pbmc8k_filtered_gene_bc_matrices.tar.gz\n", 41 | "Resolving cf.10xgenomics.com (cf.10xgenomics.com)... 13.33.35.84, 13.33.35.97, 13.33.35.175, ...\n", 42 | "Connecting to cf.10xgenomics.com (cf.10xgenomics.com)|13.33.35.84|:80... connected.\n", 43 | "HTTP request sent, awaiting response... 200 OK\n", 44 | "Length: 37558165 (36M) [application/x-tar]\n", 45 | "Saving to: ‘pbmc8k_filtered_gene_bc_matrices.tar.gz’\n", 46 | "\n", 47 | "pbmc8k_filtered_gen 100%[===================>] 35.82M 7.21MB/s in 6.4s \n", 48 | "\n", 49 | "2018-07-16 16:49:25 (5.60 MB/s) - ‘pbmc8k_filtered_gene_bc_matrices.tar.gz’ saved [37558165/37558165]\n", 50 | "\n" 51 | ] 52 | } 53 | ], 54 | "source": [ 55 | "!wget http://cf.10xgenomics.com/samples/cell-exp/2.1.0/pbmc8k/pbmc8k_filtered_gene_bc_matrices.tar.gz" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "Uncompress:" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 3, 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "!tar xfz pbmc8k_filtered_gene_bc_matrices.tar.gz" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "#### Load counts matrix and gene list\n", 79 | "The first time this is run, the counts matrix is loaded from the mtx file. An npz file is saved for fast loading in the future." 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 4, 85 | "metadata": {}, 86 | "outputs": [ 87 | { 88 | "name": "stdout", 89 | "output_type": "stream", 90 | "text": [ 91 | "Expression matrix shape: 8381 rows, 33694 columns\n", 92 | "Number of genes in gene list: 33694\n" 93 | ] 94 | } 95 | ], 96 | "source": [ 97 | "input_dir = 'filtered_gene_bc_matrices/GRCh38/'\n", 98 | "\n", 99 | "# The raw counts matrix (E) should be a scipy sparse CSC matrix\n", 100 | "# with cells as rows and genes as columns\n", 101 | "\n", 102 | "if os.path.isfile(input_dir + '/matrix.npz'):\n", 103 | " E = scipy.sparse.load_npz(input_dir + '/matrix.npz')\n", 104 | "else:\n", 105 | " E = scipy.io.mmread(input_dir + '/matrix.mtx').T.tocsc()\n", 106 | " scipy.sparse.save_npz(input_dir + '/matrix.npz', E, compressed=True)\n", 107 | "\n", 108 | "genes = np.array(scr.load_genes(input_dir + 'genes.tsv', delimiter='\\t', column=1))\n", 109 | "\n", 110 | "print('Expression matrix shape: {} rows, {} columns'.format(E.shape[0], E.shape[1]))\n", 111 | "print('Number of genes in gene list: {}'.format(len(genes)))" 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "metadata": {}, 117 | "source": [ 118 | "#### Check that the distribution of total counts per cell looks reasonable (i.e., background has been filtered out)\n" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": 5, 124 | "metadata": {}, 125 | "outputs": [ 126 | { 127 | "data": { 128 | "text/plain": [ 129 | "Text(0,0.5,'Number of cells')" 130 | ] 131 | }, 132 | "execution_count": 5, 133 | "metadata": {}, 134 | "output_type": "execute_result" 135 | }, 136 | { 137 | "data": { 138 | "image/png": "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\n", 139 | "text/plain": [ 140 | "
" 141 | ] 142 | }, 143 | "metadata": {}, 144 | "output_type": "display_data" 145 | } 146 | ], 147 | "source": [ 148 | "total_counts = E.sum(1).A.squeeze()\n", 149 | "\n", 150 | "fig, ax = plt.subplots(figsize = (5, 3))\n", 151 | "ax.hist(total_counts, bins = np.logspace(3, 5, 40))\n", 152 | "ax.set_xscale('log')\n", 153 | "ax.set_xlabel('Total counts')\n", 154 | "ax.set_ylabel('Number of cells')\n" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": {}, 160 | "source": [ 161 | "#### Calculate doublet scores\n" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": 6, 167 | "metadata": {}, 168 | "outputs": [ 169 | { 170 | "name": "stdout", 171 | "output_type": "stream", 172 | "text": [ 173 | "Simulating doublets\n", 174 | "Total counts normalizing\n", 175 | "Finding highly variable genes\n", 176 | "Filtering genes from 33694 to 1697\n", 177 | "Applying z-score normalization\n", 178 | "Running PCA\n", 179 | "Building kNN graph and calculating doublet scores\n", 180 | "Elapsed time: 19.0 seconds\n" 181 | ] 182 | } 183 | ], 184 | "source": [ 185 | "# filtering/preprocessing parameters:\n", 186 | "min_counts = 2\n", 187 | "min_cells = 3\n", 188 | "vscore_percentile = 85\n", 189 | "n_pc = 30\n", 190 | "\n", 191 | "# doublet detector parameters:\n", 192 | "expected_doublet_rate = 0.06 \n", 193 | "sim_doublet_ratio = 3\n", 194 | "n_neighbors = 50\n", 195 | "\n", 196 | "t0 = time.time()\n", 197 | "\n", 198 | "scrublet_results = scr.compute_doublet_scores(\n", 199 | " E, \n", 200 | " min_counts = min_counts, \n", 201 | " min_cells = min_cells, \n", 202 | " vscore_percentile = vscore_percentile, \n", 203 | " n_prin_comps = n_pc,\n", 204 | " scaling_method = 'zscore',\n", 205 | " expected_doublet_rate = expected_doublet_rate,\n", 206 | " sim_doublet_ratio = sim_doublet_ratio,\n", 207 | " n_neighbors = n_neighbors, \n", 208 | " use_approx_neighbors = True, \n", 209 | " get_doublet_neighbor_parents = False\n", 210 | ")\n", 211 | "\n", 212 | "\n", 213 | "t1 = time.time()\n", 214 | "print('Elapsed time: {:.1f} seconds'.format(t1 - t0))" 215 | ] 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "metadata": {}, 220 | "source": [ 221 | "#### Get UMAP embedding to help visualize the results" 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": 7, 227 | "metadata": {}, 228 | "outputs": [], 229 | "source": [ 230 | "# UMAP: https://github.com/lmcinnes/umap\n", 231 | "import umap\n", 232 | "\n", 233 | "embedding = umap.UMAP(n_neighbors=10).fit_transform(scrublet_results['pca_observed_cells'])\n" 234 | ] 235 | }, 236 | { 237 | "cell_type": "markdown", 238 | "metadata": {}, 239 | "source": [ 240 | "#### Set doublet score threshold and visualize results\n", 241 | "To call doublets, manually set a threshold between the two peaks of the simulated doublet histogram." 242 | ] 243 | }, 244 | { 245 | "cell_type": "code", 246 | "execution_count": 8, 247 | "metadata": {}, 248 | "outputs": [ 249 | { 250 | "name": "stdout", 251 | "output_type": "stream", 252 | "text": [ 253 | "349/8381 = 4.2% of cells are predicted doublets.\n", 254 | "60.0% of doublets are predicted to be detectable.\n", 255 | "Predicted overall doublet rate = 6.9%\n" 256 | ] 257 | }, 258 | { 259 | "data": { 260 | "image/png": "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\n", 261 | "text/plain": [ 262 | "
" 263 | ] 264 | }, 265 | "metadata": {}, 266 | "output_type": "display_data" 267 | } 268 | ], 269 | "source": [ 270 | "score_threshold = 0.25\n", 271 | "\n", 272 | "fig, axs = scr.plot_scrublet_results(embedding, \n", 273 | " scrublet_results['doublet_scores_observed_cells'], \n", 274 | " scrublet_results['doublet_scores_simulated_doublets'], \n", 275 | " score_threshold, \n", 276 | " order_points = True, \n", 277 | " marker_size = 4)\n" 278 | ] 279 | } 280 | ], 281 | "metadata": { 282 | "kernelspec": { 283 | "display_name": "Python 3", 284 | "language": "python", 285 | "name": "python3" 286 | }, 287 | "language_info": { 288 | "codemirror_mode": { 289 | "name": "ipython", 290 | "version": 3 291 | }, 292 | "file_extension": ".py", 293 | "mimetype": "text/x-python", 294 | "name": "python", 295 | "nbconvert_exporter": "python", 296 | "pygments_lexer": "ipython3", 297 | "version": "3.6.4" 298 | } 299 | }, 300 | "nbformat": 4, 301 | "nbformat_minor": 2 302 | } 303 | -------------------------------------------------------------------------------- /old_versions/v0.1/requirements.txt: -------------------------------------------------------------------------------- 1 | annoy 2 | matplotlib 3 | numpy 4 | scikit-learn 5 | scipy 6 | -------------------------------------------------------------------------------- /old_versions/v0.1/setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name = "scrublet", 5 | packages = ['scrublet'], 6 | package_dir={'': 'src'}, 7 | version = '0.1', 8 | description = 'Doublet prediction in single-cell RNA-sequencing data', 9 | author = 'Samuel L. Wolock', 10 | author_email = 'swolock@g.harvard.edu', 11 | url = 'https://github.com/swolock/scrublet', 12 | download_url = 'https://github.com/swolock/scrublet/tarball/0.1', 13 | install_requires=['numpy', 'scipy', 'scikit-learn', 'matplotlib', 'annoy'], 14 | ) 15 | -------------------------------------------------------------------------------- /old_versions/v0.1/src/scrublet/__init__.py: -------------------------------------------------------------------------------- 1 | from .scrublet import compute_doublet_scores, plot_scrublet_results 2 | from .helper_functions import * 3 | -------------------------------------------------------------------------------- /old_versions/v0.1/src/scrublet/helper_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy 3 | import scipy.stats 4 | import scipy.sparse 5 | from sklearn.decomposition import PCA,TruncatedSVD 6 | from sklearn.neighbors import NearestNeighbors 7 | import time 8 | 9 | ########## LOADING DATA 10 | def load_genes(filename, delimiter='\t', column=0, skip_rows=0): 11 | gene_list = [] 12 | gene_dict = {} 13 | 14 | with open(filename) as f: 15 | for iL in range(skip_rows): 16 | f.readline() 17 | for l in f: 18 | gene = l.strip('\n').split(delimiter)[column] 19 | if gene in gene_dict: 20 | gene_dict[gene] += 1 21 | gene_list.append(gene + '__' + str(gene_dict[gene])) 22 | if gene_dict[gene] == 2: 23 | i = gene_list.index(gene) 24 | gene_list[i] = gene + '__1' 25 | else: 26 | gene_dict[gene] = 1 27 | gene_list.append(gene) 28 | return gene_list 29 | 30 | 31 | def make_genes_unique(orig_gene_list): 32 | gene_list = [] 33 | gene_dict = {} 34 | 35 | for gene in orig_gene_list: 36 | if gene in gene_dict: 37 | gene_dict[gene] += 1 38 | gene_list.append(gene + '__' + str(gene_dict[gene])) 39 | if gene_dict[gene] == 2: 40 | i = gene_list.index(gene) 41 | gene_list[i] = gene + '__1' 42 | else: 43 | gene_dict[gene] = 1 44 | gene_list.append(gene) 45 | return gene_list 46 | 47 | ########## USEFUL SPARSE FUNCTIONS 48 | 49 | def sparse_var(E, axis=0): 50 | ''' variance across the specified axis ''' 51 | 52 | mean_gene = E.mean(axis=axis).A.squeeze() 53 | tmp = E.copy() 54 | tmp.data **= 2 55 | return tmp.mean(axis=axis).A.squeeze() - mean_gene ** 2 56 | 57 | def sparse_multiply(E, a): 58 | ''' multiply each row of E by a scalar ''' 59 | 60 | nrow = E.shape[0] 61 | w = scipy.sparse.lil_matrix((nrow, nrow)) 62 | w.setdiag(a) 63 | return w * E 64 | 65 | def sparse_zscore(E, gene_mean=None, gene_stdev=None): 66 | ''' z-score normalize each column of E ''' 67 | 68 | if gene_mean is None: 69 | gene_mean = E.mean(0) 70 | if gene_stdev is None: 71 | gene_stdev = np.sqrt(sparse_var(E)) 72 | return sparse_multiply((E - gene_mean).T, 1/gene_stdev).T 73 | 74 | ########## GENE FILTERING 75 | 76 | def runningquantile(x, y, p, nBins): 77 | 78 | ind = np.argsort(x) 79 | x = x[ind] 80 | y = y[ind] 81 | 82 | dx = (x[-1] - x[0]) / nBins 83 | xOut = np.linspace(x[0]+dx/2, x[-1]-dx/2, nBins) 84 | 85 | yOut = np.zeros(xOut.shape) 86 | 87 | for i in range(len(xOut)): 88 | ind = np.nonzero((x >= xOut[i]-dx/2) & (x < xOut[i]+dx/2))[0] 89 | if len(ind) > 0: 90 | yOut[i] = np.percentile(y[ind], p) 91 | else: 92 | if i > 0: 93 | yOut[i] = yOut[i-1] 94 | else: 95 | yOut[i] = np.nan 96 | 97 | return xOut, yOut 98 | 99 | 100 | def get_vscores(E, min_mean=0, nBins=50, fit_percentile=0.1, error_wt=1): 101 | ''' 102 | Calculate v-score (above-Poisson noise statistic) for genes in the input counts matrix 103 | Return v-scores and other stats 104 | ''' 105 | 106 | ncell = E.shape[0] 107 | 108 | mu_gene = E.mean(axis=0).A.squeeze() 109 | gene_ix = np.nonzero(mu_gene > min_mean)[0] 110 | mu_gene = mu_gene[gene_ix] 111 | 112 | tmp = E[:,gene_ix] 113 | tmp.data **= 2 114 | var_gene = tmp.mean(axis=0).A.squeeze() - mu_gene ** 2 115 | del tmp 116 | FF_gene = var_gene / mu_gene 117 | 118 | data_x = np.log(mu_gene) 119 | data_y = np.log(FF_gene / mu_gene) 120 | 121 | x, y = runningquantile(data_x, data_y, fit_percentile, nBins) 122 | x = x[~np.isnan(y)] 123 | y = y[~np.isnan(y)] 124 | 125 | gLog = lambda input: np.log(input[1] * np.exp(-input[0]) + input[2]) 126 | h,b = np.histogram(np.log(FF_gene[mu_gene>0]), bins=200) 127 | b = b[:-1] + np.diff(b)/2 128 | max_ix = np.argmax(h) 129 | c = np.max((np.exp(b[max_ix]), 1)) 130 | errFun = lambda b2: np.sum(abs(gLog([x,c,b2])-y) ** error_wt) 131 | b0 = 0.1 132 | b = scipy.optimize.fmin(func = errFun, x0=[b0], disp=False) 133 | a = c / (1+b) - 1 134 | 135 | 136 | v_scores = FF_gene / ((1+a)*(1+b) + b * mu_gene); 137 | CV_eff = np.sqrt((1+a)*(1+b) - 1); 138 | CV_input = np.sqrt(b); 139 | 140 | return v_scores, CV_eff, CV_input, gene_ix, mu_gene, FF_gene, a, b 141 | 142 | def filter_genes(E, base_ix = [], min_vscore_pctl = 85, min_counts = 3, min_cells = 3, show_vscore_plot = False, sample_name = ''): 143 | ''' 144 | Filter genes by expression level and variability 145 | Return list of filtered gene indices 146 | ''' 147 | 148 | if len(base_ix) == 0: 149 | base_ix = np.arange(E.shape[0]) 150 | 151 | Vscores, CV_eff, CV_input, gene_ix, mu_gene, FF_gene, a, b = get_vscores(E[base_ix, :]) 152 | ix2 = Vscores>0 153 | Vscores = Vscores[ix2] 154 | gene_ix = gene_ix[ix2] 155 | mu_gene = mu_gene[ix2] 156 | FF_gene = FF_gene[ix2] 157 | min_vscore = np.percentile(Vscores, min_vscore_pctl) 158 | ix = (((E[:,gene_ix] >= min_counts).sum(0).A.squeeze() >= min_cells) & (Vscores >= min_vscore)) 159 | 160 | if show_vscore_plot: 161 | import matplotlib.pyplot as plt 162 | x_min = 0.5*np.min(mu_gene) 163 | x_max = 2*np.max(mu_gene) 164 | xTh = x_min * np.exp(np.log(x_max/x_min)*np.linspace(0,1,100)) 165 | yTh = (1 + a)*(1+b) + b * xTh 166 | plt.figure(figsize=(8, 6)); 167 | plt.scatter(np.log10(mu_gene), np.log10(FF_gene), c = [.8,.8,.8], alpha = 0.3, edgecolors=''); 168 | plt.scatter(np.log10(mu_gene)[ix], np.log10(FF_gene)[ix], c = [0,0,0], alpha = 0.3, edgecolors=''); 169 | plt.plot(np.log10(xTh),np.log10(yTh)); 170 | plt.title(sample_name) 171 | plt.xlabel('log10(mean)'); 172 | plt.ylabel('log10(Fano factor)'); 173 | plt.show() 174 | 175 | return gene_ix[ix] 176 | 177 | ########## CELL NORMALIZATION 178 | 179 | def tot_counts_norm(E, exclude_dominant_frac = 1, included = [], target_mean = 0): 180 | ''' 181 | Cell-level total counts normalization of input counts matrix, excluding overly abundant genes if desired. 182 | Return normalized counts, average total counts, and (if exclude_dominant_frac < 1) list of genes used to calculate total counts 183 | ''' 184 | 185 | E = E.tocsc() 186 | ncell = E.shape[0] 187 | if len(included) == 0: 188 | if exclude_dominant_frac == 1: 189 | tots_use = E.sum(axis=1) 190 | else: 191 | tots = E.sum(axis=1) 192 | wtmp = scipy.sparse.lil_matrix((ncell, ncell)) 193 | wtmp.setdiag(1. / tots) 194 | included = np.asarray(~(((wtmp * E) > exclude_dominant_frac).sum(axis=0) > 0))[0,:] 195 | tots_use = E[:,included].sum(axis = 1) 196 | print('Excluded %i genes from normalization' %(np.sum(~included))) 197 | else: 198 | tots_use = E[:,included].sum(axis = 1) 199 | 200 | if target_mean == 0: 201 | target_mean = np.mean(tots_use) 202 | 203 | w = scipy.sparse.lil_matrix((ncell, ncell)) 204 | w.setdiag(float(target_mean) / tots_use) 205 | Enorm = w * E 206 | 207 | return Enorm.tocsc(), target_mean, included 208 | 209 | ########## DIMENSIONALITY REDUCTION 210 | 211 | 212 | def get_pca(E, base_ix=[], numpc=50, keep_sparse=False, normalize=True): 213 | ''' 214 | Run PCA on the counts matrix E, gene-level normalizing if desired 215 | Return PCA coordinates 216 | ''' 217 | # If keep_sparse is True, gene-level normalization maintains sparsity 218 | # (no centering) and TruncatedSVD is used instead of normal PCA. 219 | 220 | if len(base_ix) == 0: 221 | base_ix = np.arange(E.shape[0]) 222 | 223 | if keep_sparse: 224 | if normalize: 225 | zstd = np.sqrt(sparse_var(E[base_ix,:])) 226 | Z = sparse_multiply(E.T, 1 / zstd).T 227 | else: 228 | Z = E 229 | pca = TruncatedSVD(n_components=numpc) 230 | 231 | else: 232 | if normalize: 233 | zmean = E[base_ix,:].mean(0) 234 | zstd = np.sqrt(sparse_var(E[base_ix,:])) 235 | Z = sparse_multiply((E - zmean).T, 1/zstd).T 236 | else: 237 | Z = E 238 | pca = PCA(n_components=numpc) 239 | 240 | pca.fit(Z[base_ix,:]) 241 | return pca.transform(Z) 242 | 243 | 244 | def preprocess_and_pca(E, total_counts_normalize=True, norm_exclude_abundant_gene_frac=1, min_counts=3, min_cells=5, min_vscore_pctl=85, gene_filter=None, num_pc=50, sparse_pca=False, zscore_normalize=True, show_vscore_plot=False): 245 | ''' 246 | Total counts normalize, filter genes, run PCA 247 | Return PCA coordinates and filtered gene indices 248 | ''' 249 | 250 | if total_counts_normalize: 251 | print('Total count normalizing') 252 | E = tot_counts_norm(E, exclude_dominant_frac = norm_exclude_abundant_gene_frac)[0] 253 | 254 | if gene_filter is None: 255 | print('Finding highly variable genes') 256 | gene_filter = filter_genes(E, min_vscore_pctl=min_vscore_pctl, min_counts=min_counts, min_cells=min_cells, show_vscore_plot=show_vscore_plot) 257 | 258 | print('Using %i genes for PCA' %len(gene_filter)) 259 | PCdat = get_pca(E[:,gene_filter], numpc=num_pc, keep_sparse=sparse_pca, normalize=zscore_normalize) 260 | 261 | return PCdat, gene_filter 262 | 263 | ########## GRAPH CONSTRUCTION 264 | 265 | def get_knn_graph(X, k=5, dist_metric='euclidean', approx=False, return_edges=True): 266 | ''' 267 | Build k-nearest-neighbor graph 268 | Return edge list and nearest neighbor matrix 269 | ''' 270 | 271 | t0 = time.time() 272 | if approx: 273 | try: 274 | from annoy import AnnoyIndex 275 | except: 276 | approx = False 277 | print('Could not find library "annoy" for approx. nearest neighbor search') 278 | if approx: 279 | #print('Using approximate nearest neighbor search') 280 | 281 | if dist_metric == 'cosine': 282 | dist_metric = 'angular' 283 | npc = X.shape[1] 284 | ncell = X.shape[0] 285 | annoy_index = AnnoyIndex(npc, metric=dist_metric) 286 | 287 | for i in range(ncell): 288 | annoy_index.add_item(i, list(X[i,:])) 289 | annoy_index.build(10) # 10 trees 290 | 291 | knn = [] 292 | for iCell in range(ncell): 293 | knn.append(annoy_index.get_nns_by_item(iCell, k + 1)[1:]) 294 | knn = np.array(knn, dtype=int) 295 | 296 | else: 297 | #print('Using sklearn NearestNeighbors') 298 | 299 | if dist_metric == 'cosine': 300 | nbrs = NearestNeighbors(n_neighbors=k, metric=dist_metric, algorithm='brute').fit(X) 301 | else: 302 | nbrs = NearestNeighbors(n_neighbors=k, metric=dist_metric).fit(X) 303 | knn = nbrs.kneighbors(return_distance=False) 304 | 305 | if return_edges: 306 | links = set([]) 307 | for i in range(knn.shape[0]): 308 | for j in knn[i,:]: 309 | links.add(tuple(sorted((i,j)))) 310 | 311 | t_elapse = time.time() - t0 312 | #print('kNN graph built in %.3f sec' %(t_elapse)) 313 | 314 | return links, knn 315 | return knn 316 | 317 | def build_adj_mat(edges, n_nodes): 318 | A = scipy.sparse.lil_matrix((n_nodes, n_nodes)) 319 | for e in edges: 320 | i, j = e 321 | A[i,j] = 1 322 | A[j,i] = 1 323 | return A.tocsc() 324 | 325 | ########## FORCE LAYOUT 326 | 327 | def get_force_layout(links, n_cells, n_iter=100, edgeWeightInfluence=1, barnesHutTheta=2, scalingRatio=1, gravity=0.05, jitterTolerance=1, verbose=False): 328 | from fa2 import ForceAtlas2 329 | import networkx as nx 330 | 331 | G = nx.Graph() 332 | G.add_nodes_from(range(n_cells)) 333 | G.add_edges_from(list(links)) 334 | 335 | forceatlas2 = ForceAtlas2( 336 | # Behavior alternatives 337 | outboundAttractionDistribution=False, # Dissuade hubs 338 | linLogMode=False, # NOT IMPLEMENTED 339 | adjustSizes=False, # Prevent overlap (NOT IMPLEMENTED) 340 | edgeWeightInfluence=edgeWeightInfluence, 341 | 342 | # Performance 343 | jitterTolerance=jitterTolerance, # Tolerance 344 | barnesHutOptimize=True, 345 | barnesHutTheta=barnesHutTheta, 346 | multiThreaded=False, # NOT IMPLEMENTED 347 | 348 | # Tuning 349 | scalingRatio=scalingRatio, 350 | strongGravityMode=False, 351 | gravity=gravity, 352 | # Log 353 | verbose=verbose) 354 | 355 | positions = forceatlas2.forceatlas2_networkx_layout(G, pos=None, iterations=n_iter) 356 | positions = np.array([positions[i] for i in sorted(positions.keys())]) 357 | return positions 358 | 359 | ########## CLUSTERING 360 | 361 | def get_spectral_clusters(A, k): 362 | from sklearn.cluster import SpectralClustering 363 | spec = SpectralClustering(n_clusters=k, random_state = 0, affinity = 'precomputed', assign_labels = 'discretize') 364 | return spec.fit_predict(A) 365 | 366 | 367 | def get_louvain_clusters(nodes, edges): 368 | import networkx as nx 369 | import community 370 | 371 | G = nx.Graph() 372 | G.add_nodes_from(nodes) 373 | G.add_edges_from(edges) 374 | 375 | return np.array(community.best_partition(G).values()) 376 | 377 | ########## GENE ENRICHMENT 378 | 379 | def rank_enriched_genes(E, gene_list, cell_mask, min_counts=3, min_cells=3): 380 | gix = (E[cell_mask,:]>=min_counts).sum(0).A.squeeze() >= min_cells 381 | print('%i cells in group' %(sum(cell_mask))) 382 | print('Considering %i genes' %(sum(gix))) 383 | 384 | gene_list = gene_list[gix] 385 | 386 | z = sparse_zscore(E[:,gix]) 387 | scores = z[cell_mask,:].mean(0).A.squeeze() 388 | o = np.argsort(-scores) 389 | 390 | return gene_list[o], scores[o] 391 | 392 | 393 | ########## PLOTTING STUFF 394 | 395 | def darken_cmap(cmap, scale_factor): 396 | cdat = np.zeros((cmap.N, 4)) 397 | for ii in range(cdat.shape[0]): 398 | curcol = cmap(ii) 399 | cdat[ii,0] = curcol[0] * scale_factor 400 | cdat[ii,1] = curcol[1] * scale_factor 401 | cdat[ii,2] = curcol[2] * scale_factor 402 | cdat[ii,3] = 1 403 | cmap = cmap.from_list(cmap.N, cdat) 404 | return cmap 405 | 406 | def custom_cmap(rgb_list): 407 | import matplotlib.pyplot as plt 408 | rgb_list = np.array(rgb_list) 409 | cmap = plt.cm.Reds 410 | cmap = cmap.from_list(rgb_list.shape[0],rgb_list) 411 | return cmap 412 | 413 | def plot_groups(x, y, groups, lim_buffer = 50, saving = False, fig_dir = './', fig_name = 'fig', res = 300, close_after = False, title_size = 12, point_size = 3, ncol = 5): 414 | import matplotlib.pyplot as plt 415 | 416 | n_col = int(ncol) 417 | ngroup = len(np.unique(groups)) 418 | nrow = int(np.ceil(ngroup / float(ncol))) 419 | fig = plt.figure(figsize = (14, 3 * nrow)) 420 | for ii, c in enumerate(np.unique(groups)): 421 | ax = plt.subplot(nrow, ncol, ii+1) 422 | ix = groups == c 423 | 424 | ax.scatter(x[~ix], y[~ix], s = point_size, c = [.8,.8,.8], edgecolors = '') 425 | ax.scatter(x[ix], y[ix], s = point_size, c = [0,0,0], edgecolors = '') 426 | ax.set_xticks([]) 427 | ax.set_yticks([]) 428 | ax.set_xlim([min(x) - lim_buffer, max(x) + lim_buffer]) 429 | ax.set_ylim([min(y) - lim_buffer, max(y) + lim_buffer]) 430 | 431 | ax.set_title(str(c), fontsize = title_size) 432 | 433 | fig.tight_layout() 434 | 435 | if saving: 436 | if not os.path.exists(fig_dir): 437 | os.makedirs(fig_dir) 438 | plt.savefig(fig_dir + '/' + fig_name + '.png', dpi=res) 439 | 440 | if close_after: 441 | plt.close() 442 | -------------------------------------------------------------------------------- /old_versions/v0.1/src/scrublet/scrublet.py: -------------------------------------------------------------------------------- 1 | from .helper_functions import * 2 | from sklearn.decomposition import PCA, TruncatedSVD 3 | 4 | def compute_doublet_scores(E, n_neighbors=50, sim_doublet_ratio=3, expected_doublet_rate = 0.1, use_approx_neighbors=True, total_counts_normalize=True, min_counts=3, min_cells=3, vscore_percentile=85, gene_filter=None, scaling_method = 'zscore', n_prin_comps=30, get_doublet_neighbor_parents = False): 5 | ''' Predict cell doublets 6 | 7 | Given a counts matrix `E`, calculates a doublet score between 0 and 1 by 8 | simulating doublets, performing PCA, building a k-nearest-neighbor graph, 9 | and finding the fraction of each observed transcriptome's neighbors that are 10 | simulated doublets. This 11 | 12 | Required inputs: 13 | - E: 2-D matrix with shape (n_cells, n_genes) 14 | scipy.sparse matrix or numpy array containing raw (unnormalized) 15 | transcript counts. E[i,j] is the number of copies of transcript j 16 | detected in cell i. 17 | 18 | Optional parameters: 19 | - n_neighbors: int (default = 50) 20 | Number of neighbors used to construct the kNN graph of observed 21 | transcriptomes and simulated doublets. 22 | - sim_doublet_ratio: float(default = 3) 23 | Number of doublets to simulate relative to the number of observed 24 | transcriptomes. 25 | - expected_doublet_rate: float (default = 0.1) 26 | The estimated doublet rate for the experiment. 27 | - use_approx_neighbors: boolean (default = True) 28 | If true, use approximate nearest neighbors algorithm to contstruct the 29 | kNN graph. 30 | - total_counts_normalize: boolean (default = True) 31 | If true, apply total counts normalization prior to PCA 32 | - gene_filter: list (default = None) 33 | For gene filtering prior to PCA. List of gene indices (columns of `E`) 34 | to use in PCA. 35 | - min_counts and min_cells: float (default = 3 for both) 36 | For gene filtering prior to PCA. Keep genes with at least `min_counts` 37 | counts in at least `min_cells` cells. Ignored if `gene_filter` is not 38 | `None`. 39 | - vscore_percentile: float (default = 85) 40 | For gene filtering prior to PCA. Keep only highly variable genes, i.e., 41 | those in the `vscore_percentile`-th percentile or above. Ignored if 42 | `gene_filter` is not `None`. 43 | - scaling_method: str (default = "zscore") 44 | Method for gene-level scaling of transcript counts prior to PCA. Options 45 | are "zscore", "log", and "none". 46 | - n_prin_comps: int (default = 30) 47 | Number of principal components to use for embedding cells prior to 48 | building kNN graph. 49 | - get_doublet_neighbor_parents: boolean (default = False) 50 | If true, returns the list of parent cells used to generate each observed 51 | transcriptome's doublet neighbors. 52 | 53 | Returns: a dictionary with following items: 54 | - "doublet_scores_observed_cells": 1-D `array` 55 | List of doublet scores for each observed transcriptome 56 | - "doublet_scores_simulateed_doublets": 1-D `array` 57 | List of doublet scores for each synthetic doublet 58 | - "pca_observed_cells": 2-D `array` 59 | Principal component embedding of the observed transcriptomes 60 | - "pca_simulated_doublets": 61 | Principal component embedding of the simulated doublets 62 | - "gene_filter": 63 | List of gene indices used for PCA. 64 | - "doublet_neighbor_parents": list of numpy arrays 65 | `None` if `get_doublet_neighbor_parents` is `False`. Otherwise, entry i 66 | is the list (1-D numpy array) of parent cells that generated the doublet 67 | neighbors of cell i. Cells with no doublet neighbors have an empty list. 68 | ''' 69 | 70 | # Initialize output: dictionary to store results 71 | # and useful intermediate variables 72 | output = {} 73 | 74 | # Check that input is valid 75 | valid_scaling_methods = ['zscore', 'log', 'none'] 76 | if not scaling_method in valid_scaling_methods: 77 | print('Select one of the following scaling methods:', valid_scaling_methods) 78 | return 79 | 80 | # Convert counts matrix to sparse format if necessary 81 | if not scipy.sparse.issparse(E): 82 | print('Converting to scipy.sparse.csc_matrix') 83 | E = scipy.sparse.csc_matrix(E) 84 | elif not scipy.sparse.isspmatrix_csc(E): 85 | print('Converting to scipy.sparse.csc_matrix') 86 | E = E.tocsc() 87 | 88 | # Simulate doublets 89 | print('Simulating doublets') 90 | E_doub, parent_ix = simulate_doublets_from_counts(E, sim_doublet_ratio) 91 | 92 | # Total counts normalize observed cells and simulated doublets 93 | if total_counts_normalize: 94 | print('Total counts normalizing') 95 | E = tot_counts_norm(E)[0] 96 | E_doub = tot_counts_norm(E_doub, target_mean=1e5)[0] 97 | 98 | # Filter genes (highly variable, expressed above minimum level) 99 | if gene_filter is None: 100 | print('Finding highly variable genes') 101 | gene_filter = filter_genes(E, min_vscore_pctl=vscore_percentile, min_counts=min_counts, min_cells=min_cells) 102 | else: 103 | gene_filter = np.array(gene_filter) 104 | 105 | # Total counts normalize observed cells to the same total as doublets 106 | if total_counts_normalize: 107 | E = tot_counts_norm(E, target_mean=1e5)[0] 108 | 109 | # Apply gene filter 110 | print('Filtering genes from {} to {}'.format(E.shape[1], len(gene_filter))) 111 | E = E[:, gene_filter] 112 | E_doub = E_doub[:, gene_filter] 113 | output['gene_filter'] = gene_filter 114 | 115 | # Rescale counts 116 | if scaling_method == 'log': 117 | print('Applying log normalization') 118 | E.data = np.log10(1 + E.data) 119 | E_doub.data = np.log10(1 + E_doub.data) 120 | # to do: option of TruncatedSVD to preserve sparsity 121 | pca = PCA(n_components = n_prin_comps) 122 | E_doub = E_doub.toarray() 123 | E = E.toarray() 124 | elif scaling_method == 'zscore': 125 | print('Applying z-score normalization') 126 | gene_means = E.mean(0) 127 | gene_stdevs = np.sqrt(sparse_var(E)) 128 | E = sparse_zscore(E, gene_means, gene_stdevs) 129 | E_doub = sparse_zscore(E_doub, gene_means, gene_stdevs) 130 | pca = PCA(n_components = n_prin_comps) 131 | else: 132 | # to do: option of TruncatedSVD to preserve sparsity 133 | pca = PCA(n_components = n_prin_comps) 134 | E_doub = E_doub.toarray() 135 | E = E.toarray() 136 | 137 | # Fit PCA to observed cells, then apply same transformation to 138 | # simulated doublets. 139 | print('Running PCA') 140 | pca.fit(E) 141 | E_pca = pca.transform(E) 142 | E_doub_pca = pca.transform(E_doub) 143 | doub_labels = np.concatenate((np.zeros(E_pca.shape[0], dtype=int), 144 | np.ones(E_doub_pca.shape[0], dtype=int))) 145 | 146 | output['pca_observed_cells'] = E_pca 147 | output['pca_simulated_cells'] = E_doub_pca 148 | 149 | # Calculate doublet scores using k-nearest-neighbor classifier 150 | print('Building kNN graph and calculating doublet scores') 151 | nn_outs = nearest_neighbor_classifier(np.vstack((E_pca, E_doub_pca)), 152 | doub_labels, 153 | k=n_neighbors, 154 | use_approx_nn=use_approx_neighbors, 155 | exp_doub_rate = expected_doublet_rate, 156 | get_neighbor_parents = get_doublet_neighbor_parents, 157 | parent_cells = parent_ix 158 | ) 159 | output['doublet_scores_observed_cells'] = nn_outs[0] 160 | output['doublet_scores_simulated_doublets'] = nn_outs[1] 161 | output['doublet_neighbor_parents'] = nn_outs[2] 162 | return output 163 | 164 | #========================================================================================# 165 | 166 | def plot_scrublet_results(coords, doublet_scores_obs, doublet_scores_sim, score_threshold, marker_size=5, order_points=False, scale_hist_obs='log', scale_hist_sim='linear', fig_size = (8,6)): 167 | 168 | import matplotlib.pyplot as plt 169 | from matplotlib.lines import Line2D 170 | 171 | called_doubs = doublet_scores_obs > score_threshold 172 | called_doubs_sim = doublet_scores_sim > score_threshold 173 | predictable_doub_frac = sum(called_doubs_sim) / float(len(called_doubs_sim)) 174 | called_frac = sum(called_doubs) / float(len(called_doubs)) 175 | 176 | print('{}/{} = {:.1f}% of cells are predicted doublets.'.format(sum(called_doubs), len(called_doubs), 177 | 100 * called_frac)) 178 | print('{:.1f}% of doublets are predicted to be detectable.'.format(100 * predictable_doub_frac)) 179 | print('Predicted overall doublet rate = {:.1f}%'.format(100 * called_frac / predictable_doub_frac)) 180 | 181 | fig, axs = plt.subplots(2, 2, figsize = fig_size) 182 | 183 | ax = axs[0,0] 184 | ax.hist(doublet_scores_obs, np.linspace(0, 1, 50), color = 'gray', linewidth = 0, density=True) 185 | ax.set_yscale(scale_hist_obs) 186 | yl = ax.get_ylim() 187 | ax.set_ylim(yl) 188 | ax.plot([score_threshold, score_threshold], yl, c = 'black', linewidth = 1) 189 | ax.set_title('Observed cells') 190 | ax.set_xlabel('Doublet score') 191 | ax.set_ylabel('Prob. density') 192 | 193 | ax = axs[0,1] 194 | ax.hist(doublet_scores_sim, np.linspace(0, 1, 50), color = 'gray', linewidth = 0, density=True) 195 | ax.set_yscale(scale_hist_sim) 196 | yl = ax.get_ylim() 197 | ax.set_ylim(yl) 198 | ax.plot([score_threshold, score_threshold], yl, c = 'black', linewidth = 1) 199 | ax.set_title('Simulated doublets') 200 | ax.set_xlabel('Doublet score') 201 | ax.set_ylabel('Prob. density') 202 | 203 | x = coords[:,0] 204 | y = coords[:,1] 205 | xl = (x.min() - x.ptp() * .05, x.max() + x.ptp() * 0.05) 206 | yl = (y.min() - y.ptp() * .05, y.max() + y.ptp() * 0.05) 207 | 208 | if order_points: 209 | o = np.argsort(doublet_scores_obs) 210 | else: 211 | o = np.arange(len(doublet_scores_obs)) 212 | 213 | ax = axs[1,0] 214 | pp = ax.scatter(x[o], y[o], s=marker_size, edgecolors='', c = doublet_scores_obs[o], cmap=darken_cmap(plt.cm.Reds, 0.9)) 215 | ax.set_xlim(xl) 216 | ax.set_ylim(yl) 217 | ax.set_xticks([]) 218 | ax.set_yticks([]) 219 | ax.set_title('Doublet score') 220 | 221 | ax = axs[1,1] 222 | ax.scatter(x[o], y[o], s=marker_size, edgecolors='', c=doublet_scores_obs[o] > score_threshold, cmap = custom_cmap([[.7,.7,.7], [0,0,0]])) 223 | ax.set_xlim(xl) 224 | ax.set_ylim(yl) 225 | ax.set_xticks([]) 226 | ax.set_yticks([]) 227 | ax.set_title('Predicted doublets') 228 | singlet_marker = Line2D([], [], color=[.7,.7,.7], marker='o', markersize=5, label='Singlet', linewidth=0) 229 | doublet_marker = Line2D([], [], color=[.0,.0,.0], marker='o', markersize=5, label='Doublet', linewidth=0) 230 | ax.legend(handles = [singlet_marker, doublet_marker]) 231 | 232 | fig.tight_layout() 233 | 234 | return fig, axs 235 | 236 | #========================================================================================# 237 | 238 | def simulate_doublets_from_counts(E, sim_doublet_ratio=1): 239 | ''' 240 | Simulate doublets by summing the counts of random cell pairs. 241 | 242 | Inputs: 243 | E (numpy or scipy matrix of size (num_cells, num_genes)): counts matrix, ideally without total-counts normalization. 244 | sim_doublet_ratio (float): number of doublets to simulate, as a fraction of the number of cells in E. 245 | A total of num_sim_doubs = int(sim_doublet_ratio * E[0]) doublets will be simulated. 246 | 247 | Returns: 248 | - Edoub (scipy sparse CSC matrix of size (num_cells+num_sim_doubs, num_genes)): counts matrix with the simulated doublet data appended to the original data matrix E. 249 | - doub_labels (array of size (num_cells+num_sim_doubs)): 0 if observed cell, 1 if simulated doublet 250 | - pair_ix (matrix of size(num_sim_doubs, 2)): each row gives the indices of the parent cells from E used to generate the simulated doublet 251 | ''' 252 | 253 | if not scipy.sparse.issparse(E): 254 | E = scipy.sparse.csc_matrix(E) 255 | elif not scipy.sparse.isspmatrix_csc(E): 256 | E = E.tocsc() 257 | 258 | n_obs = E.shape[0] 259 | n_doub = int(n_obs * sim_doublet_ratio) 260 | pair_ix = np.random.randint(0, n_obs, size=(n_doub, 2)) 261 | Edoub = E[pair_ix[:, 0],:] + E[pair_ix[:, 1],:] 262 | 263 | return Edoub, pair_ix 264 | 265 | #========================================================================================# 266 | 267 | def simulate_doublets_from_pca(PCdat, total_counts=None, sim_doublet_ratio=1): 268 | ''' 269 | Simulate doublets by averaging PCA coordinates of random cell pairs. 270 | Average is weighted by total counts of each parent cell, if provided. 271 | 272 | Returns: 273 | PCdoub (matrix of size (num_cells+num_sim_doubs, num_pcs)): PCA matrix with the simulated doublet PCA coordinates appended to the original data matrix PCdat. 274 | doub_labels (array of size (num_cells+num_sim_doubs)): 0 if observed cell, 1 if simulated doublet 275 | pair_ix (matrix of size(num_sim_doubs, 2)): each row gives the indices of the parent cells used to generate the simulated doublet 276 | ''' 277 | 278 | n_obs = PCdat.shape[0] 279 | n_doub = int(n_obs * sim_doublet_ratio) 280 | 281 | if total_counts is None: 282 | total_counts = np.ones(n_obs) 283 | 284 | pair_ix = np.random.randint(0, n_obs, size=(n_doub, 2)) 285 | 286 | pair_tots = np.hstack((total_counts[pair_ix[:, 0]][:,None], total_counts[pair_ix[:, 1]][:,None])) 287 | pair_tots = np.array(pair_tots, dtype=float) 288 | pair_fracs = pair_tots / np.sum(pair_tots, axis=1)[:,None] 289 | 290 | PCdoub = PCdat[pair_ix[:, 0],:] * pair_fracs[:, 0][:,None] + PCdat[pair_ix[:, 1],:] * pair_fracs[:, 1][:,None] 291 | 292 | PCdoub = np.vstack((PCdat, PCdoub)) 293 | doub_labels = np.concatenate((np.zeros(n_obs, dtype=int), np.ones(n_doub, dtype=int))) 294 | 295 | return PCdoub, doub_labels, pair_ix 296 | 297 | #========================================================================================# 298 | 299 | def nearest_neighbor_classifier(embedding, doub_labels, k=50, use_approx_nn=True, exp_doub_rate = 1.0, get_neighbor_parents = False, parent_cells = None): 300 | n_obs = sum(doub_labels == 0) 301 | n_sim = sum(doub_labels == 1) 302 | 303 | # Adjust k (number of nearest neighbors) based on the ratio of simulated to observed cells 304 | k_adj = int(round(k * (1+n_sim/float(n_obs)))) 305 | 306 | # Find k_adj nearest neighbors 307 | neighbors = get_knn_graph(embedding, k=k_adj, dist_metric='euclidean', approx=use_approx_nn, return_edges = False) 308 | 309 | # Calculate doublet score based on ratio of simulated cell neighbors vs. observed cell neighbors 310 | doub_neigh_mask = doub_labels[neighbors] == 1 311 | n_sim_neigh = doub_neigh_mask.sum(1) 312 | n_obs_neigh = doub_neigh_mask.shape[1] - n_sim_neigh 313 | doub_score = n_sim_neigh / (n_sim_neigh + n_obs_neigh * n_sim / float(n_obs) / exp_doub_rate) 314 | 315 | # get parents of doublet neighbors, if requested 316 | neighbor_parents = None 317 | if get_neighbor_parents and parent_cells is not None: 318 | neighbors = neighbors - n_obs 319 | neighbor_parents = [] 320 | for iCell in range(n_obs): 321 | this_doub_neigh = neighbors[iCell,:][neighbors[iCell,:] > -1] 322 | if len(this_doub_neigh) > 0: 323 | this_doub_neigh_parents = np.unique(parent_cells[this_doub_neigh,:].flatten()) 324 | neighbor_parents.append(this_doub_neigh_parents) 325 | else: 326 | neighbor_parents.append([]) 327 | neighbor_parents = np.array(neighbor_parents) 328 | 329 | 330 | return doub_score[doub_labels == 0], doub_score[doub_labels == 1], neighbor_parents 331 | 332 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | scikit-learn 4 | scikit-image 5 | matplotlib 6 | numba 7 | cython 8 | pandas 9 | annoy 10 | umap-learn 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name = "scrublet", 8 | packages = ['scrublet'], 9 | package_dir={'': 'src'}, 10 | version = '0.2.1', 11 | description = 'Doublet prediction in single-cell RNA-sequencing data', 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | author = 'Samuel L. Wolock', 15 | author_email = 'swolock@g.harvard.edu', 16 | url = 'https://github.com/allonkleinlab/scrublet', 17 | install_requires=['cython', 'numpy', 'scipy', 'scikit-learn', 'scikit-image', 'matplotlib', 'annoy', 'numba', 'pandas', 'umap-learn'], 18 | ) 19 | -------------------------------------------------------------------------------- /src/scrublet/__init__.py: -------------------------------------------------------------------------------- 1 | from .scrublet import Scrublet 2 | from .helper_functions import * 3 | -------------------------------------------------------------------------------- /src/scrublet/helper_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy 3 | import scipy.stats 4 | import scipy.sparse 5 | from sklearn.decomposition import PCA,TruncatedSVD 6 | from sklearn.neighbors import NearestNeighbors 7 | import time 8 | 9 | ########## PREPROCESSING PIPELINE 10 | 11 | def print_optional(string, verbose=True): 12 | if verbose: 13 | print(string) 14 | return 15 | 16 | def pipeline_normalize(self, postnorm_total=None): 17 | ''' Total counts normalization ''' 18 | if postnorm_total is None: 19 | postnorm_total = self._total_counts_obs.mean() 20 | 21 | self._E_obs_norm = tot_counts_norm(self._E_obs, target_total=postnorm_total, total_counts=self._total_counts_obs) 22 | 23 | if self._E_sim is not None: 24 | self._E_sim_norm = tot_counts_norm(self._E_sim, target_total=postnorm_total, total_counts=self._total_counts_sim) 25 | return 26 | 27 | def pipeline_get_gene_filter(self, min_counts=3, min_cells=3, min_gene_variability_pctl=85): 28 | ''' Identify highly variable genes expressed above a minimum level ''' 29 | self._gene_filter = filter_genes(self._E_obs_norm, 30 | min_counts=min_counts, 31 | min_cells=min_cells, 32 | min_vscore_pctl=min_gene_variability_pctl) 33 | return 34 | 35 | def pipeline_apply_gene_filter(self): 36 | if self._E_obs is not None: 37 | self._E_obs = self._E_obs[:,self._gene_filter] 38 | if self._E_obs_norm is not None: 39 | self._E_obs_norm = self._E_obs_norm[:,self._gene_filter] 40 | if self._E_sim is not None: 41 | self._E_sim = self._E_sim[:,self._gene_filter] 42 | if self._E_sim_norm is not None: 43 | self._E_sim_norm = self._E_sim_norm[:,self._gene_filter] 44 | return 45 | 46 | def pipeline_mean_center(self): 47 | gene_means = self._E_obs_norm.mean(0) 48 | self._E_obs_norm = self._E_obs_norm - gene_means 49 | if self._E_sim_norm is not None: 50 | self._E_sim_norm = self._E_sim_norm - gene_means 51 | return 52 | 53 | def pipeline_normalize_variance(self): 54 | gene_stdevs = np.sqrt(sparse_var(self._E_obs_norm)) 55 | self._E_obs_norm = sparse_multiply(self._E_obs_norm.T, 1/gene_stdevs).T 56 | if self._E_sim_norm is not None: 57 | self._E_sim_norm = sparse_multiply(self._E_sim_norm.T, 1/gene_stdevs).T 58 | return 59 | 60 | def pipeline_zscore(self): 61 | gene_means = self._E_obs_norm.mean(0) 62 | gene_stdevs = np.sqrt(sparse_var(self._E_obs_norm)) 63 | self._E_obs_norm = np.array(sparse_zscore(self._E_obs_norm, gene_means, gene_stdevs)) 64 | if self._E_sim_norm is not None: 65 | self._E_sim_norm = np.array(sparse_zscore(self._E_sim_norm, gene_means, gene_stdevs)) 66 | return 67 | 68 | def pipeline_log_transform(self, pseudocount=1): 69 | self._E_obs_norm = log_normalize(self._E_obs_norm, pseudocount) 70 | if self._E_sim_norm is not None: 71 | self._E_sim_norm = log_normalize(self._E_sim_norm, pseudocount) 72 | return 73 | 74 | def pipeline_truncated_svd(self, n_prin_comps=30, random_state=0): 75 | svd = TruncatedSVD(n_components=n_prin_comps, random_state=random_state).fit(self._E_obs_norm) 76 | self.set_manifold(svd.transform(self._E_obs_norm), svd.transform(self._E_sim_norm)) 77 | return 78 | 79 | def pipeline_pca(self, n_prin_comps=50, random_state=0): 80 | if scipy.sparse.issparse(self._E_obs_norm): 81 | X_obs = self._E_obs_norm.toarray() 82 | else: 83 | X_obs = self._E_obs_norm 84 | if scipy.sparse.issparse(self._E_sim_norm): 85 | X_sim = self._E_sim_norm.toarray() 86 | else: 87 | X_sim = self._E_sim_norm 88 | 89 | pca = PCA(n_components=n_prin_comps, random_state=random_state).fit(X_obs) 90 | self.set_manifold(pca.transform(X_obs), pca.transform(X_sim)) 91 | return 92 | 93 | def matrix_multiply(X, Y): 94 | if not type(X) == np.ndarray: 95 | if scipy.sparse.issparse(X): 96 | X = X.toarray() 97 | else: 98 | X = np.array(X) 99 | if not type(Y) == np.ndarray: 100 | if scipy.sparse.issparse(Y): 101 | Y = Y.toarray() 102 | else: 103 | Y = np.array(Y) 104 | return np.dot(X,Y) 105 | 106 | def log_normalize(X,pseudocount=1): 107 | X.data = np.log10(X.data + pseudocount) 108 | return X 109 | 110 | ########## LOADING DATA 111 | def load_genes(filename, delimiter='\t', column=0, skip_rows=0): 112 | gene_list = [] 113 | gene_dict = {} 114 | 115 | with open(filename) as f: 116 | for iL in range(skip_rows): 117 | f.readline() 118 | for l in f: 119 | gene = l.strip('\n').split(delimiter)[column] 120 | if gene in gene_dict: 121 | gene_dict[gene] += 1 122 | gene_list.append(gene + '__' + str(gene_dict[gene])) 123 | if gene_dict[gene] == 2: 124 | i = gene_list.index(gene) 125 | gene_list[i] = gene + '__1' 126 | else: 127 | gene_dict[gene] = 1 128 | gene_list.append(gene) 129 | return gene_list 130 | 131 | 132 | def make_genes_unique(orig_gene_list): 133 | gene_list = [] 134 | gene_dict = {} 135 | 136 | for gene in orig_gene_list: 137 | if gene in gene_dict: 138 | gene_dict[gene] += 1 139 | gene_list.append(gene + '__' + str(gene_dict[gene])) 140 | if gene_dict[gene] == 2: 141 | i = gene_list.index(gene) 142 | gene_list[i] = gene + '__1' 143 | else: 144 | gene_dict[gene] = 1 145 | gene_list.append(gene) 146 | return gene_list 147 | 148 | ########## USEFUL SPARSE FUNCTIONS 149 | 150 | def sparse_var(E, axis=0): 151 | ''' variance across the specified axis ''' 152 | 153 | mean_gene = E.mean(axis=axis).A.squeeze() 154 | tmp = E.copy() 155 | tmp.data **= 2 156 | return tmp.mean(axis=axis).A.squeeze() - mean_gene ** 2 157 | 158 | def sparse_multiply(E, a): 159 | ''' multiply each row of E by a scalar ''' 160 | 161 | nrow = E.shape[0] 162 | w = scipy.sparse.lil_matrix((nrow, nrow)) 163 | w.setdiag(a) 164 | return w * E 165 | 166 | def sparse_zscore(E, gene_mean=None, gene_stdev=None): 167 | ''' z-score normalize each column of E ''' 168 | 169 | if gene_mean is None: 170 | gene_mean = E.mean(0) 171 | if gene_stdev is None: 172 | gene_stdev = np.sqrt(sparse_var(E)) 173 | return sparse_multiply((E - gene_mean).T, 1/gene_stdev).T 174 | 175 | def subsample_counts(E, rate, original_totals, random_seed=0): 176 | if rate < 1: 177 | np.random.seed(random_seed) 178 | E.data = np.random.binomial(np.round(E.data).astype(int), rate) 179 | current_totals = E.sum(1).A.squeeze() 180 | unsampled_orig_totals = original_totals - current_totals 181 | unsampled_downsamp_totals = np.random.binomial(np.round(unsampled_orig_totals).astype(int), rate) 182 | final_downsamp_totals = current_totals + unsampled_downsamp_totals 183 | else: 184 | final_downsamp_totals = original_totals 185 | return E, final_downsamp_totals 186 | 187 | 188 | ########## GENE FILTERING 189 | 190 | def runningquantile(x, y, p, nBins): 191 | 192 | ind = np.argsort(x) 193 | x = x[ind] 194 | y = y[ind] 195 | 196 | dx = (x[-1] - x[0]) / nBins 197 | xOut = np.linspace(x[0]+dx/2, x[-1]-dx/2, nBins) 198 | 199 | yOut = np.zeros(xOut.shape) 200 | 201 | for i in range(len(xOut)): 202 | ind = np.nonzero((x >= xOut[i]-dx/2) & (x < xOut[i]+dx/2))[0] 203 | if len(ind) > 0: 204 | yOut[i] = np.percentile(y[ind], p) 205 | else: 206 | if i > 0: 207 | yOut[i] = yOut[i-1] 208 | else: 209 | yOut[i] = np.nan 210 | 211 | return xOut, yOut 212 | 213 | 214 | def get_vscores(E, min_mean=0, nBins=50, fit_percentile=0.1, error_wt=1): 215 | ''' 216 | Calculate v-score (above-Poisson noise statistic) for genes in the input counts matrix 217 | Return v-scores and other stats 218 | ''' 219 | 220 | ncell = E.shape[0] 221 | 222 | mu_gene = E.mean(axis=0).A.squeeze() 223 | gene_ix = np.nonzero(mu_gene > min_mean)[0] 224 | mu_gene = mu_gene[gene_ix] 225 | 226 | tmp = E[:,gene_ix] 227 | tmp.data **= 2 228 | var_gene = tmp.mean(axis=0).A.squeeze() - mu_gene ** 2 229 | del tmp 230 | FF_gene = var_gene / mu_gene 231 | 232 | data_x = np.log(mu_gene) 233 | data_y = np.log(FF_gene / mu_gene) 234 | 235 | x, y = runningquantile(data_x, data_y, fit_percentile, nBins) 236 | x = x[~np.isnan(y)] 237 | y = y[~np.isnan(y)] 238 | 239 | gLog = lambda input: np.log(input[1] * np.exp(-input[0]) + input[2]) 240 | h,b = np.histogram(np.log(FF_gene[mu_gene>0]), bins=200) 241 | b = b[:-1] + np.diff(b)/2 242 | max_ix = np.argmax(h) 243 | c = np.max((np.exp(b[max_ix]), 1)) 244 | errFun = lambda b2: np.sum(abs(gLog([x,c,b2])-y) ** error_wt) 245 | b0 = 0.1 246 | b = scipy.optimize.fmin(func = errFun, x0=[b0], disp=False) 247 | a = c / (1+b) - 1 248 | 249 | 250 | v_scores = FF_gene / ((1+a)*(1+b) + b * mu_gene); 251 | CV_eff = np.sqrt((1+a)*(1+b) - 1); 252 | CV_input = np.sqrt(b); 253 | 254 | return v_scores, CV_eff, CV_input, gene_ix, mu_gene, FF_gene, a, b 255 | 256 | def filter_genes(E, base_ix = [], min_vscore_pctl = 85, min_counts = 3, min_cells = 3, show_vscore_plot = False, sample_name = ''): 257 | ''' 258 | Filter genes by expression level and variability 259 | Return list of filtered gene indices 260 | ''' 261 | 262 | if len(base_ix) == 0: 263 | base_ix = np.arange(E.shape[0]) 264 | 265 | Vscores, CV_eff, CV_input, gene_ix, mu_gene, FF_gene, a, b = get_vscores(E[base_ix, :]) 266 | ix2 = Vscores>0 267 | Vscores = Vscores[ix2] 268 | gene_ix = gene_ix[ix2] 269 | mu_gene = mu_gene[ix2] 270 | FF_gene = FF_gene[ix2] 271 | min_vscore = np.percentile(Vscores, min_vscore_pctl) 272 | ix = (((E[:,gene_ix] >= min_counts).sum(0).A.squeeze() >= min_cells) & (Vscores >= min_vscore)) 273 | 274 | if show_vscore_plot: 275 | import matplotlib.pyplot as plt 276 | x_min = 0.5*np.min(mu_gene) 277 | x_max = 2*np.max(mu_gene) 278 | xTh = x_min * np.exp(np.log(x_max/x_min)*np.linspace(0,1,100)) 279 | yTh = (1 + a)*(1+b) + b * xTh 280 | plt.figure(figsize=(8, 6)); 281 | plt.scatter(np.log10(mu_gene), np.log10(FF_gene), c = [.8,.8,.8], alpha = 0.3, edgecolors=''); 282 | plt.scatter(np.log10(mu_gene)[ix], np.log10(FF_gene)[ix], c = [0,0,0], alpha = 0.3, edgecolors=''); 283 | plt.plot(np.log10(xTh),np.log10(yTh)); 284 | plt.title(sample_name) 285 | plt.xlabel('log10(mean)'); 286 | plt.ylabel('log10(Fano factor)'); 287 | plt.show() 288 | 289 | return gene_ix[ix] 290 | 291 | ########## CELL NORMALIZATION 292 | 293 | def tot_counts_norm(E, total_counts = None, exclude_dominant_frac = 1, included = [], target_total = None): 294 | ''' 295 | Cell-level total counts normalization of input counts matrix, excluding overly abundant genes if desired. 296 | Return normalized counts, average total counts, and (if exclude_dominant_frac < 1) list of genes used to calculate total counts 297 | ''' 298 | 299 | E = E.tocsc() 300 | ncell = E.shape[0] 301 | if total_counts is None: 302 | if len(included) == 0: 303 | if exclude_dominant_frac == 1: 304 | tots_use = E.sum(axis=1) 305 | else: 306 | tots = E.sum(axis=1) 307 | wtmp = scipy.sparse.lil_matrix((ncell, ncell)) 308 | wtmp.setdiag(1. / tots) 309 | included = np.asarray(~(((wtmp * E) > exclude_dominant_frac).sum(axis=0) > 0))[0,:] 310 | tots_use = E[:,included].sum(axis = 1) 311 | print('Excluded %i genes from normalization' %(np.sum(~included))) 312 | else: 313 | tots_use = E[:,included].sum(axis = 1) 314 | else: 315 | tots_use = total_counts.copy() 316 | 317 | if target_total is None: 318 | target_total = np.mean(tots_use) 319 | 320 | w = scipy.sparse.lil_matrix((ncell, ncell)) 321 | w.setdiag(float(target_total) / tots_use) 322 | Enorm = w * E 323 | 324 | return Enorm.tocsc() 325 | 326 | ########## DIMENSIONALITY REDUCTION 327 | 328 | def get_pca(E, base_ix=[], numpc=50, keep_sparse=False, normalize=True, random_state=0): 329 | ''' 330 | Run PCA on the counts matrix E, gene-level normalizing if desired 331 | Return PCA coordinates 332 | ''' 333 | # If keep_sparse is True, gene-level normalization maintains sparsity 334 | # (no centering) and TruncatedSVD is used instead of normal PCA. 335 | 336 | if len(base_ix) == 0: 337 | base_ix = np.arange(E.shape[0]) 338 | 339 | if keep_sparse: 340 | if normalize: 341 | zstd = np.sqrt(sparse_var(E[base_ix,:])) 342 | Z = sparse_multiply(E.T, 1 / zstd).T 343 | else: 344 | Z = E 345 | pca = TruncatedSVD(n_components=numpc, random_state=random_state) 346 | 347 | else: 348 | if normalize: 349 | zmean = E[base_ix,:].mean(0) 350 | zstd = np.sqrt(sparse_var(E[base_ix,:])) 351 | Z = sparse_multiply((E - zmean).T, 1/zstd).T 352 | else: 353 | Z = E 354 | pca = PCA(n_components=numpc, random_state=random_state) 355 | 356 | pca.fit(Z[base_ix,:]) 357 | return pca.transform(Z) 358 | 359 | 360 | def preprocess_and_pca(E, total_counts_normalize=True, norm_exclude_abundant_gene_frac=1, min_counts=3, min_cells=5, min_vscore_pctl=85, gene_filter=None, num_pc=50, sparse_pca=False, zscore_normalize=True, show_vscore_plot=False): 361 | ''' 362 | Total counts normalize, filter genes, run PCA 363 | Return PCA coordinates and filtered gene indices 364 | ''' 365 | 366 | if total_counts_normalize: 367 | print('Total count normalizing') 368 | E = tot_counts_norm(E, exclude_dominant_frac = norm_exclude_abundant_gene_frac)[0] 369 | 370 | if gene_filter is None: 371 | print('Finding highly variable genes') 372 | gene_filter = filter_genes(E, min_vscore_pctl=min_vscore_pctl, min_counts=min_counts, min_cells=min_cells, show_vscore_plot=show_vscore_plot) 373 | 374 | print('Using %i genes for PCA' %len(gene_filter)) 375 | PCdat = get_pca(E[:,gene_filter], numpc=num_pc, keep_sparse=sparse_pca, normalize=zscore_normalize) 376 | 377 | return PCdat, gene_filter 378 | 379 | ########## GRAPH CONSTRUCTION 380 | 381 | def get_knn_graph(X, k=5, dist_metric='euclidean', approx=False, return_edges=True, random_seed=0): 382 | ''' 383 | Build k-nearest-neighbor graph 384 | Return edge list and nearest neighbor matrix 385 | ''' 386 | 387 | t0 = time.time() 388 | if approx: 389 | try: 390 | from annoy import AnnoyIndex 391 | except: 392 | approx = False 393 | print('Could not find library "annoy" for approx. nearest neighbor search') 394 | if approx: 395 | #print('Using approximate nearest neighbor search') 396 | 397 | if dist_metric == 'cosine': 398 | dist_metric = 'angular' 399 | npc = X.shape[1] 400 | ncell = X.shape[0] 401 | annoy_index = AnnoyIndex(npc, metric=dist_metric) 402 | annoy_index.set_seed(random_seed) 403 | 404 | for i in range(ncell): 405 | annoy_index.add_item(i, list(X[i,:])) 406 | annoy_index.build(10) # 10 trees 407 | 408 | knn = [] 409 | for iCell in range(ncell): 410 | knn.append(annoy_index.get_nns_by_item(iCell, k + 1)[1:]) 411 | knn = np.array(knn, dtype=int) 412 | 413 | else: 414 | #print('Using sklearn NearestNeighbors') 415 | 416 | if dist_metric == 'cosine': 417 | nbrs = NearestNeighbors(n_neighbors=k, metric=dist_metric, algorithm='brute').fit(X) 418 | else: 419 | nbrs = NearestNeighbors(n_neighbors=k, metric=dist_metric).fit(X) 420 | knn = nbrs.kneighbors(return_distance=False) 421 | 422 | if return_edges: 423 | links = set([]) 424 | for i in range(knn.shape[0]): 425 | for j in knn[i,:]: 426 | links.add(tuple(sorted((i,j)))) 427 | 428 | t_elapse = time.time() - t0 429 | #print('kNN graph built in %.3f sec' %(t_elapse)) 430 | 431 | return links, knn 432 | return knn 433 | 434 | def build_adj_mat(edges, n_nodes): 435 | A = scipy.sparse.lil_matrix((n_nodes, n_nodes)) 436 | for e in edges: 437 | i, j = e 438 | A[i,j] = 1 439 | A[j,i] = 1 440 | return A.tocsc() 441 | 442 | ########## 2-D EMBEDDINGS 443 | 444 | def get_umap(X, n_neighbors=10, min_dist=0.1, metric='euclidean', random_state=0): 445 | import umap 446 | return umap.UMAP(n_neighbors=n_neighbors, min_dist=min_dist, metric=metric, random_state=random_state).fit_transform(X) 447 | 448 | def get_tsne(X, angle=0.5, perplexity=30, random_state=0, verbose=False): 449 | from sklearn.manifold import TSNE 450 | return TSNE(angle=angle, perplexity=perplexity, random_state=random_state, verbose=verbose).fit_transform(X) 451 | 452 | def get_force_layout(X, n_neighbors=5, approx_neighbors=False, n_iter=300, verbose=False): 453 | edges = get_knn_graph(X, k=n_neighbors, approx=approx_neighbors, return_edges=True)[0] 454 | return run_force_layout(edges, X.shape[0], verbose=verbose) 455 | 456 | def run_force_layout(links, n_cells, n_iter=100, edgeWeightInfluence=1, barnesHutTheta=2, scalingRatio=1, gravity=0.05, jitterTolerance=1, verbose=False): 457 | from fa2 import ForceAtlas2 458 | import networkx as nx 459 | 460 | G = nx.Graph() 461 | G.add_nodes_from(range(n_cells)) 462 | G.add_edges_from(list(links)) 463 | 464 | forceatlas2 = ForceAtlas2( 465 | # Behavior alternatives 466 | outboundAttractionDistribution=False, # Dissuade hubs 467 | linLogMode=False, # NOT IMPLEMENTED 468 | adjustSizes=False, # Prevent overlap (NOT IMPLEMENTED) 469 | edgeWeightInfluence=edgeWeightInfluence, 470 | 471 | # Performance 472 | jitterTolerance=jitterTolerance, # Tolerance 473 | barnesHutOptimize=True, 474 | barnesHutTheta=barnesHutTheta, 475 | multiThreaded=False, # NOT IMPLEMENTED 476 | 477 | # Tuning 478 | scalingRatio=scalingRatio, 479 | strongGravityMode=False, 480 | gravity=gravity, 481 | # Log 482 | verbose=verbose) 483 | 484 | positions = forceatlas2.forceatlas2_networkx_layout(G, pos=None, iterations=n_iter) 485 | positions = np.array([positions[i] for i in sorted(positions.keys())]) 486 | return positions 487 | 488 | ########## CLUSTERING 489 | 490 | def get_spectral_clusters(A, k): 491 | from sklearn.cluster import SpectralClustering 492 | spec = SpectralClustering(n_clusters=k, random_state = 0, affinity = 'precomputed', assign_labels = 'discretize') 493 | return spec.fit_predict(A) 494 | 495 | 496 | def get_louvain_clusters(nodes, edges): 497 | import networkx as nx 498 | import community 499 | 500 | G = nx.Graph() 501 | G.add_nodes_from(nodes) 502 | G.add_edges_from(edges) 503 | 504 | return np.array(list(community.best_partition(G).values())) 505 | 506 | ########## GENE ENRICHMENT 507 | 508 | def rank_enriched_genes(E, gene_list, cell_mask, min_counts=3, min_cells=3, verbose=False): 509 | gix = (E[cell_mask,:]>=min_counts).sum(0).A.squeeze() >= min_cells 510 | print_optional('%i cells in group' %(sum(cell_mask)), verbose) 511 | print_optional('Considering %i genes' %(sum(gix)), verbose) 512 | 513 | gene_list = gene_list[gix] 514 | 515 | z = sparse_zscore(E[:,gix]) 516 | scores = z[cell_mask,:].mean(0).A.squeeze() 517 | o = np.argsort(-scores) 518 | 519 | return gene_list[o], scores[o] 520 | 521 | 522 | ########## PLOTTING STUFF 523 | 524 | def darken_cmap(cmap, scale_factor): 525 | cdat = np.zeros((cmap.N, 4)) 526 | for ii in range(cdat.shape[0]): 527 | curcol = cmap(ii) 528 | cdat[ii,0] = curcol[0] * scale_factor 529 | cdat[ii,1] = curcol[1] * scale_factor 530 | cdat[ii,2] = curcol[2] * scale_factor 531 | cdat[ii,3] = 1 532 | cmap = cmap.from_list(cmap.N, cdat) 533 | return cmap 534 | 535 | def custom_cmap(rgb_list): 536 | import matplotlib.pyplot as plt 537 | rgb_list = np.array(rgb_list) 538 | cmap = plt.cm.Reds 539 | cmap = cmap.from_list(rgb_list.shape[0],rgb_list) 540 | return cmap 541 | 542 | def plot_groups(x, y, groups, lim_buffer = 50, saving = False, fig_dir = './', fig_name = 'fig', res = 300, close_after = False, title_size = 12, point_size = 3, ncol = 5): 543 | import matplotlib.pyplot as plt 544 | 545 | n_col = int(ncol) 546 | ngroup = len(np.unique(groups)) 547 | nrow = int(np.ceil(ngroup / float(ncol))) 548 | fig = plt.figure(figsize = (14, 3 * nrow)) 549 | for ii, c in enumerate(np.unique(groups)): 550 | ax = plt.subplot(nrow, ncol, ii+1) 551 | ix = groups == c 552 | 553 | ax.scatter(x[~ix], y[~ix], s = point_size, c = [.8,.8,.8], edgecolors = '') 554 | ax.scatter(x[ix], y[ix], s = point_size, c = [0,0,0], edgecolors = '') 555 | ax.set_xticks([]) 556 | ax.set_yticks([]) 557 | ax.set_xlim([min(x) - lim_buffer, max(x) + lim_buffer]) 558 | ax.set_ylim([min(y) - lim_buffer, max(y) + lim_buffer]) 559 | 560 | ax.set_title(str(c), fontsize = title_size) 561 | 562 | fig.tight_layout() 563 | 564 | if saving: 565 | if not os.path.exists(fig_dir): 566 | os.makedirs(fig_dir) 567 | plt.savefig(fig_dir + '/' + fig_name + '.png', dpi=res) 568 | 569 | if close_after: 570 | plt.close() 571 | -------------------------------------------------------------------------------- /src/scrublet/scrublet.py: -------------------------------------------------------------------------------- 1 | from .helper_functions import * 2 | from sklearn.decomposition import PCA, TruncatedSVD 3 | import matplotlib.pyplot as plt 4 | 5 | class Scrublet(): 6 | def __init__(self, counts_matrix, total_counts=None, sim_doublet_ratio=2.0, n_neighbors=None, expected_doublet_rate=0.1, stdev_doublet_rate=0.02, random_state=0): 7 | ''' Initialize Scrublet object with counts matrix and doublet prediction parameters 8 | 9 | Parameters 10 | ---------- 11 | counts_matrix : scipy sparse matrix or ndarray, shape (n_cells, n_genes) 12 | Matrix containing raw (unnormalized) UMI-based transcript counts. 13 | Converted into a scipy.sparse.csc_matrix. 14 | 15 | total_counts : ndarray, shape (n_cells,), optional (default: None) 16 | Array of total UMI counts per cell. If `None`, this is calculated 17 | as the row sums of `counts_matrix`. 18 | 19 | sim_doublet_ratio : float, optional (default: 2.0) 20 | Number of doublets to simulate relative to the number of observed 21 | transcriptomes. 22 | 23 | n_neighbors : int, optional (default: None) 24 | Number of neighbors used to construct the KNN graph of observed 25 | transcriptomes and simulated doublets. If `None`, this is 26 | set to round(0.5 * sqrt(n_cells)) 27 | 28 | expected_doublet_rate : float, optional (default: 0.1) 29 | The estimated doublet rate for the experiment. 30 | 31 | stdev_doublet_rate : float, optional (default: 0.02) 32 | Uncertainty in the expected doublet rate. 33 | 34 | random_state : int, optional (default: 0) 35 | Random state for doublet simulation, approximate 36 | nearest neighbor search, and PCA/TruncatedSVD. 37 | 38 | Attributes 39 | ---------- 40 | predicted_doublets_ : ndarray, shape (n_cells,) 41 | Boolean mask of predicted doublets in the observed 42 | transcriptomes. 43 | 44 | doublet_scores_obs_ : ndarray, shape (n_cells,) 45 | Doublet scores for observed transcriptomes. 46 | 47 | doublet_scores_sim_ : ndarray, shape (n_doublets,) 48 | Doublet scores for simulated doublets. 49 | 50 | doublet_errors_obs_ : ndarray, shape (n_cells,) 51 | Standard error in the doublet scores for observed 52 | transcriptomes. 53 | 54 | doublet_errors_sim_ : ndarray, shape (n_doublets,) 55 | Standard error in the doublet scores for simulated 56 | doublets. 57 | 58 | threshold_: float 59 | Doublet score threshold for calling a transcriptome 60 | a doublet. 61 | 62 | z_scores_ : ndarray, shape (n_cells,) 63 | Z-score conveying confidence in doublet calls. 64 | Z = `(doublet_score_obs_ - threhsold_) / doublet_errors_obs_` 65 | 66 | detected_doublet_rate_: float 67 | Fraction of observed transcriptomes that have been called 68 | doublets. 69 | 70 | detectable_doublet_fraction_: float 71 | Estimated fraction of doublets that are detectable, i.e., 72 | fraction of simulated doublets with doublet scores above 73 | `threshold_` 74 | 75 | overall_doublet_rate_: float 76 | Estimated overall doublet rate, 77 | `detected_doublet_rate_ / detectable_doublet_fraction_`. 78 | Should agree (roughly) with `expected_doublet_rate`. 79 | 80 | manifold_obs_: ndarray, shape (n_cells, n_features) 81 | The single-cell "manifold" coordinates (e.g., PCA coordinates) 82 | for observed transcriptomes. Nearest neighbors are found using 83 | the union of `manifold_obs_` and `manifold_sim_` (see below). 84 | 85 | manifold_sim_: ndarray, shape (n_doublets, n_features) 86 | The single-cell "manifold" coordinates (e.g., PCA coordinates) 87 | for simulated doublets. Nearest neighbors are found using 88 | the union of `manifold_obs_` (see above) and `manifold_sim_`. 89 | 90 | doublet_parents_ : ndarray, shape (n_doublets, 2) 91 | Indices of the observed transcriptomes used to generate the 92 | simulated doublets. 93 | 94 | doublet_neighbor_parents_ : list, length n_cells 95 | A list of arrays of the indices of the doublet neighbors of 96 | each observed transcriptome (the ith entry is an array of 97 | the doublet neighbors of transcriptome i). 98 | ''' 99 | 100 | if not scipy.sparse.issparse(counts_matrix): 101 | counts_matrix = scipy.sparse.csc_matrix(counts_matrix) 102 | elif not scipy.sparse.isspmatrix_csc(counts_matrix): 103 | counts_matrix = counts_matrix.tocsc() 104 | 105 | # initialize counts matrices 106 | self._E_obs = counts_matrix 107 | self._E_sim = None 108 | self._E_obs_norm = None 109 | self._E_sim_norm = None 110 | 111 | if total_counts is None: 112 | self._total_counts_obs = self._E_obs.sum(1).A.squeeze() 113 | else: 114 | self._total_counts_obs = total_counts 115 | 116 | self._gene_filter = np.arange(self._E_obs.shape[1]) 117 | self._embeddings = {} 118 | 119 | self.sim_doublet_ratio = sim_doublet_ratio 120 | self.n_neighbors = n_neighbors 121 | self.expected_doublet_rate = expected_doublet_rate 122 | self.stdev_doublet_rate = stdev_doublet_rate 123 | self.random_state = random_state 124 | 125 | if self.n_neighbors is None: 126 | self.n_neighbors = int(round(0.5*np.sqrt(self._E_obs.shape[0]))) 127 | 128 | ######## Core Scrublet functions ######## 129 | 130 | def scrub_doublets(self, synthetic_doublet_umi_subsampling=1.0, use_approx_neighbors=True, distance_metric='euclidean', get_doublet_neighbor_parents=False, min_counts=3, min_cells=3, min_gene_variability_pctl=85, log_transform=False, mean_center=True, normalize_variance=True, n_prin_comps=30, verbose=True): 131 | ''' Standard pipeline for preprocessing, doublet simulation, and doublet prediction 132 | 133 | Automatically sets a threshold for calling doublets, but it's best to check 134 | this by running plot_histogram() afterwards and adjusting threshold 135 | with call_doublets(threshold=new_threshold) if necessary. 136 | 137 | Arguments 138 | --------- 139 | synthetic_doublet_umi_subsampling : float, optional (defuault: 1.0) 140 | Rate for sampling UMIs when creating synthetic doublets. If 1.0, 141 | each doublet is created by simply adding the UMIs from two randomly 142 | sampled observed transcriptomes. For values less than 1, the 143 | UMI counts are added and then randomly sampled at the specified 144 | rate. 145 | 146 | use_approx_neighbors : bool, optional (default: True) 147 | Use approximate nearest neighbor method (annoy) for the KNN 148 | classifier. 149 | 150 | distance_metric : str, optional (default: 'euclidean') 151 | Distance metric used when finding nearest neighbors. For list of 152 | valid values, see the documentation for annoy (if `use_approx_neighbors` 153 | is True) or sklearn.neighbors.NearestNeighbors (if `use_approx_neighbors` 154 | is False). 155 | 156 | get_doublet_neighbor_parents : bool, optional (default: False) 157 | If True, return the parent transcriptomes that generated the 158 | doublet neighbors of each observed transcriptome. This information can 159 | be used to infer the cell states that generated a given 160 | doublet state. 161 | 162 | min_counts : float, optional (default: 3) 163 | Used for gene filtering prior to PCA. Genes expressed at fewer than 164 | `min_counts` in fewer than `min_cells` (see below) are excluded. 165 | 166 | min_cells : int, optional (default: 3) 167 | Used for gene filtering prior to PCA. Genes expressed at fewer than 168 | `min_counts` (see above) in fewer than `min_cells` are excluded. 169 | 170 | min_gene_variability_pctl : float, optional (default: 85.0) 171 | Used for gene filtering prior to PCA. Keep the most highly variable genes 172 | (in the top min_gene_variability_pctl percentile), as measured by 173 | the v-statistic [Klein et al., Cell 2015]. 174 | 175 | log_transform : bool, optional (default: False) 176 | If True, log-transform the counts matrix (log10(1+TPM)). 177 | `sklearn.decomposition.TruncatedSVD` will be used for dimensionality 178 | reduction, unless `mean_center` is True. 179 | 180 | mean_center : bool, optional (default: True) 181 | If True, center the data such that each gene has a mean of 0. 182 | `sklearn.decomposition.PCA` will be used for dimensionality 183 | reduction. 184 | 185 | normalize_variance : bool, optional (default: True) 186 | If True, normalize the data such that each gene has a variance of 1. 187 | `sklearn.decomposition.TruncatedSVD` will be used for dimensionality 188 | reduction, unless `mean_center` is True. 189 | 190 | n_prin_comps : int, optional (default: 30) 191 | Number of principal components used to embed the transcriptomes prior 192 | to k-nearest-neighbor graph construction. 193 | 194 | verbose : bool, optional (default: True) 195 | If True, print progress updates. 196 | 197 | Sets 198 | ---- 199 | doublet_scores_obs_, doublet_errors_obs_, 200 | doublet_scores_sim_, doublet_errors_sim_, 201 | predicted_doublets_, z_scores_ 202 | threshold_, detected_doublet_rate_, 203 | detectable_doublet_fraction_, overall_doublet_rate_, 204 | doublet_parents_, doublet_neighbor_parents_ 205 | ''' 206 | t0 = time.time() 207 | 208 | self._E_sim = None 209 | self._E_obs_norm = None 210 | self._E_sim_norm = None 211 | self._gene_filter = np.arange(self._E_obs.shape[1]) 212 | 213 | print_optional('Preprocessing...', verbose) 214 | pipeline_normalize(self) 215 | pipeline_get_gene_filter(self, min_counts=min_counts, min_cells=min_cells, min_gene_variability_pctl=min_gene_variability_pctl) 216 | pipeline_apply_gene_filter(self) 217 | 218 | print_optional('Simulating doublets...', verbose) 219 | self.simulate_doublets(sim_doublet_ratio=self.sim_doublet_ratio, synthetic_doublet_umi_subsampling=synthetic_doublet_umi_subsampling) 220 | pipeline_normalize(self, postnorm_total=1e6) 221 | if log_transform: 222 | pipeline_log_transform(self) 223 | if mean_center and normalize_variance: 224 | pipeline_zscore(self) 225 | elif mean_center: 226 | pipeline_mean_center(self) 227 | elif normalize_variance: 228 | pipeline_normalize_variance(self) 229 | 230 | if mean_center: 231 | print_optional('Embedding transcriptomes using PCA...', verbose) 232 | pipeline_pca(self, n_prin_comps=n_prin_comps, random_state=self.random_state) 233 | else: 234 | print_optional('Embedding transcriptomes using Truncated SVD...', verbose) 235 | pipeline_truncated_svd(self, n_prin_comps=n_prin_comps, random_state=self.random_state) 236 | 237 | print_optional('Calculating doublet scores...', verbose) 238 | self.calculate_doublet_scores( 239 | use_approx_neighbors=use_approx_neighbors, 240 | distance_metric=distance_metric, 241 | get_doublet_neighbor_parents=get_doublet_neighbor_parents 242 | ) 243 | self.call_doublets(verbose=verbose) 244 | 245 | t1=time.time() 246 | print_optional('Elapsed time: {:.1f} seconds'.format(t1 - t0), verbose) 247 | return self.doublet_scores_obs_, self.predicted_doublets_ 248 | 249 | def simulate_doublets(self, sim_doublet_ratio=None, synthetic_doublet_umi_subsampling=1.0): 250 | ''' Simulate doublets by adding the counts of random observed transcriptome pairs. 251 | 252 | Arguments 253 | --------- 254 | sim_doublet_ratio : float, optional (default: None) 255 | Number of doublets to simulate relative to the number of observed 256 | transcriptomes. If `None`, self.sim_doublet_ratio is used. 257 | 258 | synthetic_doublet_umi_subsampling : float, optional (defuault: 1.0) 259 | Rate for sampling UMIs when creating synthetic doublets. If 1.0, 260 | each doublet is created by simply adding the UMIs from two randomly 261 | sampled observed transcriptomes. For values less than 1, the 262 | UMI counts are added and then randomly sampled at the specified 263 | rate. 264 | 265 | Sets 266 | ---- 267 | doublet_parents_ 268 | ''' 269 | 270 | if sim_doublet_ratio is None: 271 | sim_doublet_ratio = self.sim_doublet_ratio 272 | else: 273 | self.sim_doublet_ratio = sim_doublet_ratio 274 | 275 | n_obs = self._E_obs.shape[0] 276 | n_sim = int(n_obs * sim_doublet_ratio) 277 | 278 | np.random.seed(self.random_state) 279 | pair_ix = np.random.randint(0, n_obs, size=(n_sim, 2)) 280 | 281 | E1 = self._E_obs[pair_ix[:,0],:] 282 | E2 = self._E_obs[pair_ix[:,1],:] 283 | tots1 = self._total_counts_obs[pair_ix[:,0]] 284 | tots2 = self._total_counts_obs[pair_ix[:,1]] 285 | if synthetic_doublet_umi_subsampling < 1: 286 | self._E_sim, self._total_counts_sim = subsample_counts(E1+E2, synthetic_doublet_umi_subsampling, tots1+tots2, random_seed=self.random_state) 287 | else: 288 | self._E_sim = E1+E2 289 | self._total_counts_sim = tots1+tots2 290 | self.doublet_parents_ = pair_ix 291 | return 292 | 293 | def set_manifold(self, manifold_obs, manifold_sim): 294 | ''' Set the manifold coordinates used in k-nearest-neighbor graph construction 295 | 296 | Arguments 297 | --------- 298 | manifold_obs: ndarray, shape (n_cells, n_features) 299 | The single-cell "manifold" coordinates (e.g., PCA coordinates) 300 | for observed transcriptomes. Nearest neighbors are found using 301 | the union of `manifold_obs` and `manifold_sim` (see below). 302 | 303 | manifold_sim: ndarray, shape (n_doublets, n_features) 304 | The single-cell "manifold" coordinates (e.g., PCA coordinates) 305 | for simulated doublets. Nearest neighbors are found using 306 | the union of `manifold_obs` (see above) and `manifold_sim`. 307 | 308 | Sets 309 | ---- 310 | manifold_obs_, manifold_sim_, 311 | ''' 312 | 313 | self.manifold_obs_ = manifold_obs 314 | self.manifold_sim_ = manifold_sim 315 | return 316 | 317 | def calculate_doublet_scores(self, use_approx_neighbors=True, distance_metric='euclidean', get_doublet_neighbor_parents=False): 318 | ''' Calculate doublet scores for observed transcriptomes and simulated doublets 319 | 320 | Requires that manifold_obs_ and manifold_sim_ have already been set. 321 | 322 | Arguments 323 | --------- 324 | use_approx_neighbors : bool, optional (default: True) 325 | Use approximate nearest neighbor method (annoy) for the KNN 326 | classifier. 327 | 328 | distance_metric : str, optional (default: 'euclidean') 329 | Distance metric used when finding nearest neighbors. For list of 330 | valid values, see the documentation for annoy (if `use_approx_neighbors` 331 | is True) or sklearn.neighbors.NearestNeighbors (if `use_approx_neighbors` 332 | is False). 333 | 334 | get_doublet_neighbor_parents : bool, optional (default: False) 335 | If True, return the parent transcriptomes that generated the 336 | doublet neighbors of each observed transcriptome. This information can 337 | be used to infer the cell states that generated a given 338 | doublet state. 339 | 340 | Sets 341 | ---- 342 | doublet_scores_obs_, doublet_scores_sim_, 343 | doublet_errors_obs_, doublet_errors_sim_, 344 | doublet_neighbor_parents_ 345 | 346 | ''' 347 | 348 | self._nearest_neighbor_classifier( 349 | k=self.n_neighbors, 350 | exp_doub_rate=self.expected_doublet_rate, 351 | stdev_doub_rate=self.stdev_doublet_rate, 352 | use_approx_nn=use_approx_neighbors, 353 | distance_metric=distance_metric, 354 | get_neighbor_parents=get_doublet_neighbor_parents 355 | ) 356 | return self.doublet_scores_obs_ 357 | 358 | def _nearest_neighbor_classifier(self, k=40, use_approx_nn=True, distance_metric='euclidean', exp_doub_rate=0.1, stdev_doub_rate=0.03, get_neighbor_parents=False): 359 | manifold = np.vstack((self.manifold_obs_, self.manifold_sim_)) 360 | doub_labels = np.concatenate((np.zeros(self.manifold_obs_.shape[0], dtype=int), 361 | np.ones(self.manifold_sim_.shape[0], dtype=int))) 362 | 363 | n_obs = np.sum(doub_labels == 0) 364 | n_sim = np.sum(doub_labels == 1) 365 | 366 | # Adjust k (number of nearest neighbors) based on the ratio of simulated to observed cells 367 | k_adj = int(round(k * (1+n_sim/float(n_obs)))) 368 | 369 | # Find k_adj nearest neighbors 370 | neighbors = get_knn_graph(manifold, k=k_adj, dist_metric=distance_metric, approx=use_approx_nn, return_edges=False, random_seed=self.random_state) 371 | 372 | # Calculate doublet score based on ratio of simulated cell neighbors vs. observed cell neighbors 373 | doub_neigh_mask = doub_labels[neighbors] == 1 374 | n_sim_neigh = doub_neigh_mask.sum(1) 375 | n_obs_neigh = doub_neigh_mask.shape[1] - n_sim_neigh 376 | 377 | rho = exp_doub_rate 378 | r = n_sim / float(n_obs) 379 | nd = n_sim_neigh.astype(float) 380 | ns = n_obs_neigh.astype(float) 381 | N = float(k_adj) 382 | 383 | # Bayesian 384 | q=(nd+1)/(N+2) 385 | Ld = q*rho/r/(1-rho-q*(1-rho-rho/r)) 386 | 387 | se_q = np.sqrt(q*(1-q)/(N+3)) 388 | se_rho = stdev_doub_rate 389 | 390 | se_Ld = q*rho/r / (1-rho-q*(1-rho-rho/r))**2 * np.sqrt((se_q/q*(1-rho))**2 + (se_rho/rho*(1-q))**2) 391 | 392 | self.doublet_scores_obs_ = Ld[doub_labels == 0] 393 | self.doublet_scores_sim_ = Ld[doub_labels == 1] 394 | self.doublet_errors_obs_ = se_Ld[doub_labels==0] 395 | self.doublet_errors_sim_ = se_Ld[doub_labels==1] 396 | 397 | # get parents of doublet neighbors, if requested 398 | neighbor_parents = None 399 | if get_neighbor_parents: 400 | parent_cells = self.doublet_parents_ 401 | neighbors = neighbors - n_obs 402 | neighbor_parents = [] 403 | for iCell in range(n_obs): 404 | this_doub_neigh = neighbors[iCell,:][neighbors[iCell,:] > -1] 405 | if len(this_doub_neigh) > 0: 406 | this_doub_neigh_parents = np.unique(parent_cells[this_doub_neigh,:].flatten()) 407 | neighbor_parents.append(this_doub_neigh_parents) 408 | else: 409 | neighbor_parents.append([]) 410 | self.doublet_neighbor_parents_ = np.array(neighbor_parents) 411 | return 412 | 413 | def call_doublets(self, threshold=None, verbose=True): 414 | ''' Call trancriptomes as doublets or singlets 415 | 416 | Arguments 417 | --------- 418 | threshold : float, optional (default: None) 419 | Doublet score threshold for calling a transcriptome 420 | a doublet. If `None`, this is set automatically by looking 421 | for the minimum between the two modes of the `doublet_scores_sim_` 422 | histogram. It is best practice to check the threshold visually 423 | using the `doublet_scores_sim_` histogram and/or based on 424 | co-localization of predicted doublets in a 2-D embedding. 425 | 426 | verbose : bool, optional (default: True) 427 | If True, print summary statistics. 428 | 429 | Sets 430 | ---- 431 | predicted_doublets_, z_scores_, threshold_, 432 | detected_doublet_rate_, detectable_doublet_fraction, 433 | overall_doublet_rate_ 434 | ''' 435 | 436 | if threshold is None: 437 | # automatic threshold detection 438 | # http://scikit-image.org/docs/dev/api/skimage.filters.html 439 | from skimage.filters import threshold_minimum 440 | try: 441 | threshold = threshold_minimum(self.doublet_scores_sim_) 442 | if verbose: 443 | print("Automatically set threshold at doublet score = {:.2f}".format(threshold)) 444 | except: 445 | self.predicted_doublets_ = None 446 | if verbose: 447 | print("Warning: failed to automatically identify doublet score threshold. Run `call_doublets` with user-specified threshold.") 448 | return self.predicted_doublets_ 449 | 450 | Ld_obs = self.doublet_scores_obs_ 451 | Ld_sim = self.doublet_scores_sim_ 452 | se_obs = self.doublet_errors_obs_ 453 | Z = (Ld_obs - threshold) / se_obs 454 | self.predicted_doublets_ = Ld_obs > threshold 455 | self.z_scores_ = Z 456 | self.threshold_ = threshold 457 | self.detected_doublet_rate_ = (Ld_obs>threshold).sum() / float(len(Ld_obs)) 458 | self.detectable_doublet_fraction_ = (Ld_sim>threshold).sum() / float(len(Ld_sim)) 459 | self.overall_doublet_rate_ = self.detected_doublet_rate_ / self.detectable_doublet_fraction_ 460 | 461 | if verbose: 462 | print('Detected doublet rate = {:.1f}%'.format(100*self.detected_doublet_rate_)) 463 | print('Estimated detectable doublet fraction = {:.1f}%'.format(100*self.detectable_doublet_fraction_)) 464 | print('Overall doublet rate:') 465 | print('\tExpected = {:.1f}%'.format(100*self.expected_doublet_rate)) 466 | print('\tEstimated = {:.1f}%'.format(100*self.overall_doublet_rate_)) 467 | 468 | return self.predicted_doublets_ 469 | 470 | ######## Viz functions ######## 471 | 472 | def plot_histogram(self, scale_hist_obs='log', scale_hist_sim='linear', fig_size = (8,3)): 473 | ''' Plot histogram of doublet scores for observed transcriptomes and simulated doublets 474 | 475 | The histogram for simulated doublets is useful for determining the correct doublet 476 | score threshold. To set threshold to a new value, T, run call_doublets(threshold=T). 477 | 478 | ''' 479 | 480 | fig, axs = plt.subplots(1, 2, figsize = fig_size) 481 | 482 | ax = axs[0] 483 | ax.hist(self.doublet_scores_obs_, np.linspace(0, 1, 50), color='gray', linewidth=0, density=True) 484 | ax.set_yscale(scale_hist_obs) 485 | yl = ax.get_ylim() 486 | ax.set_ylim(yl) 487 | ax.plot(self.threshold_ * np.ones(2), yl, c='black', linewidth=1) 488 | ax.set_title('Observed transcriptomes') 489 | ax.set_xlabel('Doublet score') 490 | ax.set_ylabel('Prob. density') 491 | 492 | ax = axs[1] 493 | ax.hist(self.doublet_scores_sim_, np.linspace(0, 1, 50), color='gray', linewidth=0, density=True) 494 | ax.set_yscale(scale_hist_sim) 495 | yl = ax.get_ylim() 496 | ax.set_ylim(yl) 497 | ax.plot(self.threshold_ * np.ones(2), yl, c = 'black', linewidth = 1) 498 | ax.set_title('Simulated doublets') 499 | ax.set_xlabel('Doublet score') 500 | ax.set_ylabel('Prob. density') 501 | 502 | fig.tight_layout() 503 | 504 | return fig, axs 505 | 506 | def set_embedding(self, embedding_name, coordinates): 507 | ''' Add a 2-D embedding for the observed transcriptomes ''' 508 | self._embeddings[embedding_name] = coordinates 509 | return 510 | 511 | def plot_embedding(self, embedding_name, score='raw', marker_size=5, order_points=False, fig_size=(8,4), color_map=None): 512 | ''' Plot doublet predictions on 2-D embedding of observed transcriptomes ''' 513 | 514 | #from matplotlib.lines import Line2D 515 | if embedding_name not in self._embeddings: 516 | print('Cannot find "{}" in embeddings. First add the embedding using `set_embedding`.'.format(embedding_name)) 517 | return 518 | 519 | # TO DO: check if self.predicted_doublets exists; plot raw scores only if it doesn't 520 | 521 | fig, axs = plt.subplots(1, 2, figsize = fig_size) 522 | 523 | x = self._embeddings[embedding_name][:,0] 524 | y = self._embeddings[embedding_name][:,1] 525 | xl = (x.min() - x.ptp() * .05, x.max() + x.ptp() * 0.05) 526 | yl = (y.min() - y.ptp() * .05, y.max() + y.ptp() * 0.05) 527 | 528 | ax = axs[1] 529 | if score == 'raw': 530 | color_dat = self.doublet_scores_obs_ 531 | vmin = color_dat.min() 532 | vmax = color_dat.max() 533 | if color_map is None: 534 | cmap_use = darken_cmap(plt.cm.Reds, 0.9) 535 | else: 536 | cmap_use = color_map 537 | elif score == 'zscore': 538 | color_dat = self.z_scores_ 539 | vmin = -color_dat.max() 540 | vmax = color_dat.max() 541 | if color_map is None: 542 | cmap_use = darken_cmap(plt.cm.RdBu_r, 0.9) 543 | else: 544 | cmap_use = color_map 545 | if order_points: 546 | o = np.argsort(color_dat) 547 | else: 548 | o = np.arange(len(color_dat)) 549 | pp = ax.scatter(x[o], y[o], s=marker_size, edgecolors='', c = color_dat[o], 550 | cmap=cmap_use, vmin=vmin, vmax=vmax) 551 | ax.set_xlim(xl) 552 | ax.set_ylim(yl) 553 | ax.set_xticks([]) 554 | ax.set_yticks([]) 555 | ax.set_title('Doublet score') 556 | ax.set_xlabel(embedding_name + ' 1') 557 | ax.set_ylabel(embedding_name + ' 2') 558 | fig.colorbar(pp, ax=ax) 559 | 560 | ax = axs[0] 561 | called_doubs = self.predicted_doublets_ 562 | ax.scatter(x[o], y[o], s=marker_size, edgecolors='', c=called_doubs[o], cmap=custom_cmap([[.7,.7,.7], [0,0,0]])) 563 | ax.set_xlim(xl) 564 | ax.set_ylim(yl) 565 | ax.set_xticks([]) 566 | ax.set_yticks([]) 567 | ax.set_title('Predicted doublets') 568 | #singlet_marker = Line2D([], [], color=[.7,.7,.7], marker='o', markersize=5, label='Singlet', linewidth=0) 569 | #doublet_marker = Line2D([], [], color=[.0,.0,.0], marker='o', markersize=5, label='Doublet', linewidth=0) 570 | #ax.legend(handles = [singlet_marker, doublet_marker]) 571 | ax.set_xlabel(embedding_name + ' 1') 572 | ax.set_ylabel(embedding_name + ' 2') 573 | 574 | fig.tight_layout() 575 | 576 | return fig, axs 577 | 578 | 579 | 580 | 581 | 582 | 583 | --------------------------------------------------------------------------------