├── .coverage ├── .github ├── pull_request_template.md └── workflows │ └── CI.yml ├── .pre-commit-config.yaml ├── Dockerfile ├── LICENSE ├── README.md ├── codecov.yml ├── diffexpr ├── __init__.py ├── py_deseq.py └── py_pathway.py ├── environment.yml ├── example ├── deseq_example.ipynb └── jupyter.png ├── poetry.lock ├── pyproject.toml ├── requirements.txt ├── setup.R ├── setup.py └── test ├── data ├── ercc.tsv ├── quant1 │ └── abundance.h5 └── quant2 │ └── abundance.h5 ├── deseq.R ├── test_deseq.py └── test_pathways.py /.coverage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wckdouglas/diffexpr/c78aba6a8ec7b26331f7e258d5e2cf1c5d559eed/.coverage -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Description of what this PR does 2 | 3 | ## Checklist 4 | - [ ] CI pass? 5 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: "CI" 5 | 6 | env: 7 | REGISTRY: ghcr.io 8 | IMAGE_NAME: ${{ github.repository }} 9 | 10 | on: 11 | push: 12 | branches: [ "*" ] 13 | pull_request: 14 | types: [opened, ready_for_review] 15 | 16 | concurrency: 17 | group: ${{ github.ref }} 18 | cancel-in-progress: true 19 | 20 | 21 | jobs: 22 | build-and-ci: 23 | name: miniconda build 24 | runs-on: ubuntu-latest 25 | strategy: 26 | matrix: 27 | python-version: [3.6, 3.7, 3.8] 28 | env: 29 | SETUPTOOLS_USE_DISTUTILS: stdlib 30 | defaults: 31 | run: 32 | shell: bash -l {0} 33 | 34 | steps: 35 | - uses: actions/checkout@v2 36 | - uses: conda-incubator/setup-miniconda@v2 37 | with: 38 | activate-environment: test-environment 39 | python-version: ${{ matrix.python-version }} 40 | channels: bioconda,default,anaconda,r,conda-forge 41 | allow-softlinks: true 42 | channel-priority: 'flexible' 43 | show-channel-urls: true 44 | use-only-tar-bz2: true 45 | auto-update-conda: true 46 | 47 | - name: setup conda 48 | run: | 49 | conda config --set always_yes yes --set changeps1 no 50 | conda info -a 51 | conda list 52 | conda config --show-sources 53 | conda config --show 54 | 55 | - name: Install dependencies 56 | run: | 57 | conda install mamba 58 | mamba install pandas tzlocal rpy2 biopython ReportLab pytest-cov codecov bioconductor-deseq2 gfortran_linux-64 bioconductor-apeglm 59 | 60 | - name: hack for missing sysconfigdata (py3.7 and py3.8, doing nothing really) 61 | if: matrix.python-version != '3.6' 62 | run: | 63 | ls $CONDA_PREFIX/lib/python${{ matrix.python-version }}/_sysconfigdata_x86_64_conda*.py 64 | 65 | - name: hack for missing sysconfigdata (py3.6) 66 | if: matrix.python-version == '3.6' 67 | run: | 68 | ls $CONDA_PREFIX/lib/python${{ matrix.python-version }}/_sysconfigdata_x86_64*.py 69 | cp $CONDA_PREFIX/lib/python${{ matrix.python-version }}/_sysconfigdata_x86_64_conda_cos6_linux_gnu.py $CONDA_PREFIX/lib/python${{ matrix.python-version }}/_sysconfigdata_x86_64_conda_linux_gnu.py 70 | 71 | - name: install package 72 | run: | 73 | conda activate test-environment 74 | pip install . 75 | 76 | - name: install R packages 77 | run: | 78 | Rscript setup.R 79 | 80 | - name: Test with pytest 81 | run: | 82 | coverage run -m pytest -vvv 83 | 84 | - name: codecov 85 | run: | 86 | bash <(curl -s https://codecov.io/bash) 87 | 88 | build-and-push-image: 89 | runs-on: ubuntu-latest 90 | needs: build-and-ci 91 | permissions: 92 | contents: read 93 | packages: write 94 | 95 | steps: 96 | - name: Checkout repository 97 | uses: actions/checkout@v3 98 | 99 | - name: Log in to the Container registry 100 | uses: docker/login-action@v2 101 | with: 102 | registry: ${{ env.REGISTRY }} 103 | username: ${{ github.actor }} 104 | password: ${{ secrets.GITHUB_TOKEN }} 105 | 106 | - name: Set up Docker Buildx 107 | uses: docker/setup-buildx-action@v2 108 | 109 | - name: Build and push Docker image 110 | if: github.ref_name == 'master' 111 | uses: docker/build-push-action@v3 112 | with: 113 | context: . 114 | push: true 115 | target: diffexpr 116 | cache-from: type=gha 117 | cache-to: type=gha,mode=max 118 | tags: | 119 | ${{ env.REGISTRY }}/${{ github.repository }}/diffexpr:${{ github.ref_name }} 120 | ${{ env.REGISTRY }}/${{ github.repository }}/diffexpr:${{ github.sha }} 121 | 122 | - name: Build and push Dev Docker image 123 | uses: docker/build-push-action@v3 124 | with: 125 | context: . 126 | push: true 127 | target: diffexpr_dev 128 | cache-from: type=gha 129 | cache-to: type=gha,mode=max 130 | tags: | 131 | ${{ env.REGISTRY }}/${{ github.repository }}/diffexpr-dev:${{ github.ref_name }} 132 | ${{ env.REGISTRY }}/${{ github.repository }}/diffexpr-dev:${{ github.sha }} 133 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 22.10.0 4 | hooks: 5 | - id: black 6 | args: ["-l", "120"] 7 | language_version: python3 8 | - repo: https://github.com/PyCQA/isort 9 | rev: 5.10.1 10 | hooks: 11 | - id: isort 12 | args: ["--profile", "black"] 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9.16-bullseye AS base 2 | 3 | # installation of R and associated packages (DESeq2) 4 | RUN apt-get update \ 5 | && apt-get install -y r-base r-base-dev r-bioc-deseq2 r-bioc-rhdf5 libcurl4-openssl-dev libxml2-dev libssl-dev \ 6 | && apt-get clean \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | # installation of python dependencies 10 | RUN pip install pandas tzlocal \ 11 | biopython ReportLab pytest rpy2 12 | 13 | 14 | # At this stage, we will build the diffexpr package 15 | # and any R packages that are not installed by apt-get 16 | # e.g. apeglm 17 | FROM base AS diffexpr 18 | 19 | COPY . /opt/diffexpr 20 | RUN python /opt/diffexpr/setup.py install 21 | RUN /usr/bin/Rscript -e "BiocManager::install(c('apeglm'))" 22 | 23 | # make sure python kernel knows about the diffexpr package 24 | # TODO: do we really need this? 25 | ENV PYTHONPATH "${PYTHONPATH}:/opt/diffexpr" 26 | 27 | # run a test to make sure things are installed correctly 28 | WORKDIR /opt/diffexpr 29 | RUN pytest -vvv 30 | 31 | # for the dev stage, we will install packages that help 32 | # differential expression analyses 33 | FROM diffexpr AS diffexpr_dev 34 | 35 | # jupyter lab and plotting 36 | RUN pip install jupyterlab matplotlib seaborn 37 | 38 | # R kernel for debugging 39 | RUN /usr/bin/Rscript -e "install.packages('IRkernel'); IRkernel::installspec(user = FALSE)" 40 | 41 | # again, run a test to make sure things at this stage are installed correctly 42 | WORKDIR /opt/diffexpr 43 | RUN pytest -vvv 44 | 45 | # docker run will spin up a jupyter lab instance at port 1234 46 | # docker run -p 1234:1234 should be allow access from outside the 47 | # container 48 | WORKDIR / 49 | CMD ["jupyter-lab", "--allow-root", "--ip", "0.0.0.0", "--port", "1234"] 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Douglas Wu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # diffexpr # 2 | [![CI](https://github.com/wckdouglas/diffexpr/workflows/CI/badge.svg)](https://github.com/wckdouglas/diffexpr/actions) [![codecov](https://codecov.io/gh/wckdouglas/diffexpr/branch/master/graph/badge.svg)](https://codecov.io/gh/wckdouglas/diffexpr) 3 | 4 | A python package using `rpy2` to port [DESeq2](https://bioconductor.org/packages/release/bioc/html/DESeq2.html) into python. 5 | 6 | ## Installation ## 7 | Dependencies are `pandas` (python), `rpy2` (python), and `DESeq2` (R) 8 | Best way to setup the environment should be via [docker](#docker), 9 | but it should also be possible to install the dependency packages using 10 | [conda](https://docs.conda.io/en/latest/): 11 | 12 | ``` 13 | conda config --add channels defaults 14 | conda config --add channels bioconda 15 | conda config --add channels conda-forge 16 | conda create -q -n diffexpr python=3.6 \ 17 | pandas tzlocal rpy2 biopython ReportLab pytest-cov \ 18 | bioconductor-deseq2 codecov 19 | conda activate diffexpr # activate diffexpr environment 20 | Rscript setup.R #to install DESeq2 correctly 21 | python setup.py install 22 | ``` 23 | 24 | ## Docker ## 25 | 26 | We build two docker images in our [CI workflow](https://github.com/wckdouglas/diffexpr/blob/98166d9ee7c078520dfb55535634a5cdeaf477cf/.github/workflows/CI.yml#L106-L128): 27 | 1. diffexpr (`ghcr.io/wckdouglas/diffexpr/diffexpr`): contains minimal dependencies for executing code in this package 28 | 2. *diffexpr-dev* (`ghcr.io/wckdouglas/diffexpr/diffexpr-dev`): is the same as `diffexpr`, but with additional python packages (`matplotlib`, `seaborn`, and `jupyterlab`) for using this package in jupyter notebook analysis (see [below](#example) for how to spin up the jupyterlab instance from within the container), feel free to file an issue or put a PR to include your favorite packages! 29 | 30 | ## Example ## 31 | An example of running DESeq2 in *python* using `diffexpr` package is provided [here](https://github.com/wckdouglas/diffexp/blob/master/example/deseq_example.ipynb). 32 | 33 | This should be reproducible by: 34 | 35 | ```bash 36 | git clone https://github.com/wckdouglas/diffexpr.git 37 | cd diffexpr 38 | docker run \ 39 | -p 1234:1234 \ 40 | --mount type=bind,source="$(pwd)",target=/jupyter \ 41 | ghcr.io/wckdouglas/diffexpr/diffexpr-dev:master 42 | ``` 43 | 44 | and go to http://localhost:1234 to access the jupyter lab instance 45 | 46 | The `--mount type=bind,source="$(pwd)",target=/jupyter` option will mount the local filesystem (current directory) at `/jupyter`, such that the container has access to all the files under the current directory, the example notebook is under `/jupyter/example/deseq_example.ipynb`: 47 | 48 | ![](example/jupyter.png) 49 | 50 | 51 | ## Citation ## 52 | :bangbang: Please cite the original [DESeq2 paper](https://genomebiology.biomedcentral.com/articles/10.1186/s13059-014-0550-8) if you used this package in your work: 53 | 54 | ``` 55 | @article{Love2014, 56 | doi = {10.1186/s13059-014-0550-8}, 57 | url = {https://doi.org/10.1186/s13059-014-0550-8}, 58 | year = {2014}, 59 | month = dec, 60 | publisher = {Springer Science and Business Media {LLC}}, 61 | volume = {15}, 62 | number = {12}, 63 | author = {Michael I Love and Wolfgang Huber and Simon Anders}, 64 | title = {Moderated estimation of fold change and dispersion for {RNA}-seq data with {DESeq}2}, 65 | journal = {Genome Biology} 66 | } 67 | ``` 68 | 69 | ## Alternatives ## 70 | 71 | [pyDESeq2](https://github.com/owkin/PyDESeq2) is a pure python implementation of DESeq2. 72 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: "70...100" 8 | 9 | parsers: 10 | gcov: 11 | branch_detection: 12 | conditional: yes 13 | loop: yes 14 | method: no 15 | macro: no 16 | 17 | comment: 18 | layout: "reach,diff,flags,tree" 19 | behavior: default 20 | require_changes: no 21 | -------------------------------------------------------------------------------- /diffexpr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wckdouglas/diffexpr/c78aba6a8ec7b26331f7e258d5e2cf1c5d559eed/diffexpr/__init__.py -------------------------------------------------------------------------------- /diffexpr/py_deseq.py: -------------------------------------------------------------------------------- 1 | """ 2 | Running DESeq2 from python via rpy2 3 | 4 | Adopted from: https://stackoverflow.com/questions/41821100/running-deseq2-through-rpy2 5 | 6 | If there are any functions that is missing for your need, feel free to file an 7 | [issue](https://github.com/wckdouglas/diffexpr/issues) or even better, make a 8 | [PR](https://github.com/wckdouglas/diffexpr/pulls)! 9 | 10 | A jupyter notebook with examples can be found at: 11 | https://github.com/wckdouglas/diffexpr/blob/master/example/deseq_example.ipynb 12 | """ 13 | 14 | import logging 15 | from typing import Dict 16 | 17 | import numpy as np 18 | import pandas as pd 19 | import rpy2.robjects as robjects 20 | from rpy2.robjects import Formula, pandas2ri 21 | from rpy2.robjects.conversion import localconverter 22 | from rpy2.robjects.packages import importr 23 | 24 | # setup logger 25 | logging.basicConfig(level=logging.INFO) 26 | logger = logging.getLogger("DESeq2") 27 | 28 | # R packages as python objects 29 | r_utils = importr("utils") 30 | deseq = importr("DESeq2") 31 | tximport = importr("tximport") 32 | multicore = importr("BiocParallel") 33 | summarized_experiment = importr("SummarizedExperiment") 34 | 35 | # get version of deseq2 36 | _DESEQ2_VERSION_INT = r_utils.packageVersion("DESeq2") 37 | DESEQ2_VERSION = ".".join(map(str, robjects.conversion.rpy2py(_DESEQ2_VERSION_INT)[0])) 38 | 39 | # a R function to make matrix into dataframe 40 | to_dataframe = robjects.r("function(x) data.frame(x)") 41 | 42 | 43 | class py_DESeq2: 44 | """ 45 | DESeq2 object through rpy2 46 | 47 | Args: 48 | count_matrix (Union[pd.DataFrame, Dict[str,str]): should be a pandas dataframe with each column as count, and a id column for gene id, 49 | unless kallisto=True, then this is expected to be a dictionary of key: sample name, value: abundance.h5 file 50 | design_matrix (pd.DataFrame): an design matrix in the form of pandas dataframe, see DESeq2 manual, samplenames as rownames 51 | design_formula (str): see DESeq2 manual, example: "~ treatment"" 52 | gene_column (str): column name of gene id columns (default: "id") 53 | threads (int): how many threads to used in running deseq, if threads > 1 is provided, 54 | `parallel=True` will be used in `DESeq2::DESeq`, `DESeq2::results`, and `DESeq2::lfcShrink` 55 | (default: 1) 56 | 57 | 58 | count_matrix example:: 59 | 60 | id sampleA sampleB 61 | geneA 5 1 62 | geneB 4 5 63 | geneC 1 2 64 | 65 | Design matrix example:: 66 | 67 | treatment 68 | sampleA1 A 69 | sampleA2 A 70 | sampleB1 B 71 | sampleB2 B 72 | 73 | """ 74 | 75 | def __init__( 76 | self, count_matrix, design_matrix, design_formula, gene_column="id", threads=1, kallisto=False, tx2gene=None 77 | ): 78 | if not isinstance(threads, int): 79 | raise ValueError("threads must be an integer") 80 | multicore.register(multicore.MulticoreParam(threads)) 81 | 82 | # set up the deseq2 object 83 | self.dds = None 84 | self.result = None 85 | self.deseq_result = None 86 | self.resLFC = None 87 | self.comparison = None 88 | self.normalized_count_df = None 89 | self.parallel = threads > 1 90 | self.gene_id = None 91 | self.gene_column = None 92 | 93 | if kallisto: 94 | if tx2gene is None: 95 | raise ValueError("tx2gene must be specified") 96 | self.from_kallisto(count_matrix, design_matrix, design_formula, tx2gene) 97 | else: 98 | self.init_matrix(count_matrix, design_matrix, design_formula, gene_column) 99 | 100 | def init_matrix(self, count_matrix, design_matrix, design_formula, gene_column): 101 | """ 102 | Initialize deseq from count matrix 103 | 104 | Args: 105 | count_matrix (pd.DataFrame): 106 | design_matrix (pd.DataFrame): 107 | design_formula (str): 108 | gene_column (str): 109 | """ 110 | # input validation 111 | for df in [count_matrix, design_matrix]: 112 | if not isinstance(df, pd.DataFrame): 113 | raise ValueError("count_matrix and design_matrix should be pd.DataFrame type") 114 | 115 | if gene_column not in count_matrix.columns: 116 | raise ValueError("The given gene_column name is not a column in count_matrix dataframe") 117 | 118 | self.gene_column = gene_column 119 | self.gene_id = count_matrix[self.gene_column] 120 | self.samplenames = count_matrix.columns[count_matrix.columns != self.gene_column] 121 | with localconverter(robjects.default_converter + pandas2ri.converter): 122 | self.count_matrix = robjects.conversion.py2rpy(count_matrix.set_index(self.gene_column)) 123 | self.design_matrix = robjects.conversion.py2rpy(design_matrix) 124 | self.design_formula = Formula(design_formula) 125 | self.dds = deseq.DESeqDataSetFromMatrix( 126 | countData=self.count_matrix, colData=self.design_matrix, design=self.design_formula 127 | ) 128 | 129 | def from_kallisto( 130 | self, h5_file_list: Dict[str, str], design_matrix: pd.DataFrame, design_formula: str, tx2gene: pd.DataFrame 131 | ): 132 | """ 133 | Initialize deseq from Tximport kallisto files 134 | 135 | :param h5_file_list: dictionary of key: sample name, value: abundance.h5 file 136 | :param design_matrix: an design matrix in the form of pandas dataframe, see DESeq2 manual, samplenames as rownames 137 | :param str design_formula: see DESeq2 manual, example: "~ treatment"" 138 | """ 139 | files = robjects.StrVector(list(h5_file_list.values())) 140 | files.names = list(h5_file_list.keys()) 141 | self.design_formula = Formula(design_formula) 142 | with localconverter(robjects.default_converter + pandas2ri.converter): 143 | self.design_matrix = robjects.conversion.py2rpy(design_matrix) 144 | tx2gene = robjects.conversion.py2rpy(tx2gene) 145 | self.txi = tximport.tximport( 146 | files, type="kallisto", txOut=False, tx2gene=tx2gene, countsFromAbundance="scaledTPM" 147 | ) 148 | logger.info(f"Read kallisto files: {files}") 149 | self.dds = deseq.DESeqDataSetFromTximport(self.txi, colData=self.design_matrix, design=self.design_formula) 150 | 151 | def run_deseq(self, **kwargs): 152 | """ 153 | actually running deseq2 and setup the dds and comparison 154 | fields in the object 155 | 156 | From DESeq2 manual: 157 | 158 | DESeq( 159 | object, 160 | test = c("Wald", "LRT"), 161 | fitType = c("parametric", "local", "mean", "glmGamPoi"), 162 | sfType = c("ratio", "poscounts", "iterate"), 163 | betaPrior, 164 | full = design(object), 165 | reduced, 166 | quiet = FALSE, 167 | minReplicatesForReplace = 7, 168 | modelMatrixType, 169 | useT = FALSE, 170 | minmu = if (fitType == "glmGamPoi") 1e-06 else 0.5, 171 | parallel = FALSE, 172 | BPPARAM = bpparam() 173 | ) 174 | 175 | Args: 176 | **kwargs: Any keyword arguments for DESeq 177 | Returns: 178 | NoneType 179 | 180 | """ 181 | 182 | for key, value in kwargs.items(): 183 | if key == "reduced": 184 | kwargs[key] = Formula(value) 185 | if key == "parallel": 186 | raise ValueError("parallel is inferred from the provided thread count") 187 | self.dds = deseq.DESeq(self.dds, parallel=self.parallel, **kwargs) 188 | self.comparison = list(deseq.resultsNames(self.dds)) 189 | 190 | def get_deseq_result(self, contrast=None, **kwargs): 191 | """ 192 | DESeq2: result(dds, contrast) 193 | making a dds.deseq_result pandas dataframe 194 | 195 | Args: 196 | contrast (list[str]): list of string annotating the contrast to compute 197 | (see DESeq2 manual http://bioconductor.org/packages/devel/bioc/vignettes/DESeq2/inst/doc/DESeq2.html#contrasts) 198 | **kwargs (Dict[str,Any]): any other parameters to pass into DESeq2::results function 199 | Returns: 200 | NoneType 201 | """ 202 | 203 | if contrast: 204 | if len(contrast) == 3: 205 | r_contrast = robjects.vectors.StrVector(np.array(contrast)) 206 | else: 207 | if len(contrast) != 2: 208 | raise ValueError("Contrast must be length of 3 or 2") 209 | r_contrast = robjects.ListVector({None: con for con in contrast}) 210 | logger.info("Using contrast: %s" % contrast) 211 | self.result = deseq.results(self.dds, contrast=r_contrast, parallel=self.parallel, **kwargs) # Robject 212 | else: 213 | self.result = deseq.results(self.dds, **kwargs) # R object 214 | self.deseq_result = to_dataframe(self.result) # R dataframe 215 | with localconverter(robjects.default_converter + pandas2ri.converter): 216 | self.deseq_result = robjects.conversion.rpy2py(self.deseq_result) ## back to pandas dataframe 217 | 218 | if self.gene_column is not None: 219 | self.deseq_result[self.gene_column] = self.gene_id.values 220 | 221 | def normalized_count(self): 222 | """ 223 | Returns a normalized count data frame 224 | 225 | Returns: 226 | pd.DataFrame: a dataframe in the format of DESeq2::Counts(dds, normalized=TRUE) in R 227 | """ 228 | normalized_count_matrix = deseq.counts_DESeqDataSet(self.dds, normalized=True) 229 | normalized_count_matrix = to_dataframe(normalized_count_matrix) 230 | # switch back to python 231 | with localconverter(robjects.default_converter + pandas2ri.converter): 232 | self.normalized_count_df = robjects.conversion.rpy2py(normalized_count_matrix) 233 | 234 | if self.gene_column is not None: 235 | self.normalized_count_df[self.gene_column] = self.gene_id.values 236 | logger.info("Normalizing counts") 237 | return self.normalized_count_df 238 | 239 | def lfcShrink(self, coef, method="apeglm", **kwargs): 240 | """ 241 | Perform LFC shrinkage on the DDS object 242 | see: http://bioconductor.org/packages/devel/bioc/vignettes/DESeq2/inst/doc/DESeq2.html 243 | 244 | Be sure to check dds.comparison to see which coef (1-base because it's passing into the R code) 245 | to use 246 | 247 | Args: 248 | coef (int): 1-based index for selecting which of dds.comparison to show 249 | method (str): DESeq2 lfcshrink method ("apeglm", "ashr", "normal") 250 | 251 | Returns: 252 | pandas.DataFrame: a deseq2 result table 253 | """ 254 | lfc = deseq.lfcShrink(self.dds, res=self.result, coef=coef, type=method, parallel=self.parallel, **kwargs) 255 | with localconverter(robjects.default_converter + pandas2ri.converter): 256 | lfc = robjects.conversion.rpy2py(to_dataframe(lfc)) 257 | 258 | return lfc.reset_index().rename(columns={"index": self.gene_column}) 259 | 260 | def vst(self, blind=True, fit_type="parametric"): 261 | """ 262 | deseq varianceStabilizingTransformation 263 | see: https://rdrr.io/bioc/DESeq2/man/varianceStabilizingTransformation.html 264 | 265 | essentially running R code: 266 | 267 | >>> vsd = DESeq2::varianceStabilizingTransformation(dds, blind=True, fitType="parametric") 268 | >>> SummarizedExperiment::assay(vsd) 269 | 270 | Example: 271 | 272 | >>> dds = py_DESeq2( 273 | count_matrix=df, 274 | design_matrix=sample_df, 275 | design_formula="~ batch + sample", 276 | gene_column="id", 277 | ) 278 | >>> dds.vst(blind=True, fit_type="parametric") 279 | 280 | 281 | Args: 282 | blind (bool): whether to blind the transformation to the experimental design 283 | fit_type (str): should be either "parametric", "local", "mean" 284 | Returns: 285 | pandas.DataFrame: a vst transformed count table 286 | """ 287 | if self.dds is None: 288 | raise ValueError("Empty DESeq object") 289 | 290 | acceptable_fit_types = set(["parametric", "local", "mean"]) 291 | if fit_type not in acceptable_fit_types: 292 | raise ValueError(f"fit_type must be {acceptable_fit_types}") 293 | 294 | vst_matrix = summarized_experiment.assay( 295 | deseq.varianceStabilizingTransformation(self.dds, blind=blind, fitType=fit_type) 296 | ) 297 | vst_df = to_dataframe(vst_matrix) 298 | with localconverter(robjects.default_converter + pandas2ri.converter): 299 | vst_counts = robjects.conversion.rpy2py(vst_df) 300 | 301 | logger.info("Processed variance stablizing transformation") 302 | return vst_counts.reset_index().rename(columns={"index": self.gene_column}) 303 | 304 | def rlog(self, blind=True, fit_type="parametric"): 305 | """ 306 | deseq rlog 307 | see: https://rdrr.io/bioc/DESeq2/man/rlog.html 308 | 309 | TODO: DESeq2 version of this function accepts two additional optional arguments 310 | 'intercept' and 'betaPriorVar' that have not been explicitly ported here. 311 | 312 | essentially running R code: 313 | 314 | >>> rld = DESeq2::rlog(dds, blind=True, fitType="parametric") 315 | >>> SummarizedExperiment::assay(rld) 316 | 317 | Example: 318 | 319 | >>> dds = py_DESeq2( 320 | count_matrix=df, 321 | design_matrix=sample_df, 322 | design_formula="~ batch + sample", 323 | gene_column="id", 324 | ) 325 | >>> dds.rlog(blind=True, fit_type="parametric") 326 | 327 | 328 | Args: 329 | blind (bool): whether to blind the transformation to the experimental design 330 | fit_type (str): should be either "parametric", "local", "mean" 331 | Returns: 332 | pandas.DataFrame: a rlog transformed count table 333 | """ 334 | if self.dds is None: 335 | raise ValueError("Empty DESeq object") 336 | 337 | acceptable_fit_types = set(["parametric", "local", "mean"]) 338 | if fit_type not in acceptable_fit_types: 339 | raise ValueError(f"fit_type must be {acceptable_fit_types}") 340 | 341 | rlog_matrix = summarized_experiment.assay(deseq.rlog(self.dds, blind=blind, fitType=fit_type)) 342 | rlog_df = to_dataframe(rlog_matrix) 343 | with localconverter(robjects.default_converter + pandas2ri.converter): 344 | rlog_counts = robjects.conversion.rpy2py(rlog_df) 345 | 346 | logger.info("Processed rlog transformation") 347 | return rlog_counts.reset_index().rename(columns={"index": self.gene_column}) 348 | 349 | @property 350 | def deseq2_version(self): 351 | """ 352 | Return DESeq2 version number 353 | 354 | :return: string 355 | """ 356 | return DESEQ2_VERSION 357 | -------------------------------------------------------------------------------- /diffexpr/py_pathway.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from Bio.Graphics.KGML_vis import KGMLCanvas 3 | from Bio.KEGG.KGML import KGML_parser 4 | from Bio.KEGG.REST import * 5 | 6 | 7 | def gene_is_enriched(enriched_genes, possible_names): 8 | """ 9 | check if any gene name in the box matched the enriched genes 10 | return the first matched gene name 11 | """ 12 | gene_names = possible_names.replace(" ", "").split(",") 13 | for gn in gene_names: 14 | if gn in enriched_genes: 15 | return gn 16 | 17 | 18 | class pathwayview: 19 | """ 20 | Like pathwayview in R, plotting a pathway and labeling the changed genes 21 | 22 | Usage: 23 | args: 24 | enriched_genes: a list of enriched genes that are in the pathway 25 | pathway_id: KEGG pathway ID (e.g. hsa04110) 26 | figurename: Must be pdf file 27 | 28 | returns: 29 | pathway: a biopython KEGG KGML object 30 | 31 | Example: 32 | 33 | enriched_genes = 'GADD45A,PLK1,TTK,CDC6,CDC25C,CDC25A'.split(',') 34 | pv = pathwayview() 35 | pathway = pv.plot_pathway(enriched_genes = enriched_genes, 36 | pathway_id = 'hsa04110', 37 | figurename = 'pathway.pdf') 38 | 39 | 40 | # changing labeling colors: 41 | pv.enriched_color = '#00000' #must be hex codes 42 | 43 | 44 | """ 45 | 46 | def __init__(self, fontsize=12): 47 | self.enriched_box_color = "#FFB600" # oragne 48 | self.non_enriched_box_color = "#FFFFFF" # white 49 | self.enriched_text_color = "#000000" # black 50 | self.non_enriched_text_color = "#000000" 51 | self.fontsize = fontsize 52 | 53 | def set_color( 54 | self, 55 | enriched_box_color="#FFB600", 56 | non_enriched_box_color="#FFFFFF", 57 | enriched_text_color="#000000", 58 | non_enriched_text_color="#000000", 59 | ): 60 | """ 61 | Color control, inputs must be HEX color code 62 | """ 63 | self.enriched_box_color = enriched_box_color # oragne 64 | self.non_enriched_box_color = non_enriched_box_color # white 65 | self.enriched_text_color = enriched_text_color # black 66 | self.non_enriched_text_color = non_enriched_text_color 67 | 68 | def plot_pathway(self, enriched_genes, pathway_id="hsa05322", figurename=None): 69 | 70 | # config figure name 71 | if not figurename: 72 | figurename = "%s.pdf" % pathway_id 73 | assert figurename.endswith(".pdf") 74 | 75 | # fetch pathway 76 | pathway = KGML_parser.read(kegg_get(pathway_id, "kgml")) 77 | 78 | # change color for pathway elements 79 | for entry in pathway.entries.values(): 80 | possible_gene_names = entry.graphics[0].name 81 | matched_name = gene_is_enriched(enriched_genes, possible_gene_names) 82 | if matched_name: 83 | entry.graphics[0].bgcolor = self.enriched_box_color # set box color 84 | entry.graphics[0].fgcolor = self.enriched_text_color # set text color 85 | entry.graphics[0].name = matched_name 86 | else: 87 | entry.graphics[0].bgcolor = self.non_enriched_box_color 88 | entry.graphics[0].fgcolor = self.non_enriched_text_color 89 | entry.graphics[0].name = entry.graphics[0].name.split(",")[0] 90 | 91 | canvas = KGMLCanvas(pathway, import_imagemap=True, fontsize=self.fontsize) 92 | canvas.draw(figurename) 93 | print("Drawn: ", figurename) 94 | return pathway 95 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: base 2 | channels: 3 | - conda-forge 4 | - r 5 | - anaconda 6 | - default 7 | - bioconda 8 | - defaults 9 | dependencies: 10 | - _libgcc_mutex=0.1=conda_forge 11 | - _openmp_mutex=4.5=2_gnu 12 | - _r-mutex=1.0.1=anacondar_1 13 | - argcomplete=2.0.0=pyhd8ed1ab_0 14 | - attrs=22.1.0=pyh71513ae_1 15 | - binutils_impl_linux-64=2.36.1=h193b22a_2 16 | - binutils_linux-64=2.36=hf3e587d_10 17 | - bioconductor-annotate=1.76.0=r42hdfd78af_0 18 | - bioconductor-annotationdbi=1.60.0=r42hdfd78af_0 19 | - bioconductor-biobase=2.58.0=r42hc0cfd56_0 20 | - bioconductor-biocgenerics=0.44.0=r42hdfd78af_0 21 | - bioconductor-biocparallel=1.32.0=r42hc247a5b_0 22 | - bioconductor-biostrings=2.66.0=r42hc0cfd56_0 23 | - bioconductor-data-packages=20221112=hdfd78af_0 24 | - bioconductor-delayedarray=0.24.0=r42hc0cfd56_0 25 | - bioconductor-deseq2=1.38.0=r42hc247a5b_0 26 | - bioconductor-genefilter=1.80.0=r42h38f54d8_0 27 | - bioconductor-geneplotter=1.76.0=r42hdfd78af_0 28 | - bioconductor-genomeinfodb=1.34.1=r42hdfd78af_0 29 | - bioconductor-genomeinfodbdata=1.2.9=r42hdfd78af_0 30 | - bioconductor-genomicranges=1.50.0=r42hc0cfd56_0 31 | - bioconductor-iranges=2.32.0=r42hc0cfd56_0 32 | - bioconductor-keggrest=1.38.0=r42hdfd78af_0 33 | - bioconductor-matrixgenerics=1.10.0=r42hdfd78af_0 34 | - bioconductor-s4vectors=0.36.0=r42hc0cfd56_0 35 | - bioconductor-summarizedexperiment=1.28.0=r42hdfd78af_0 36 | - bioconductor-xvector=0.38.0=r42hc0cfd56_0 37 | - bioconductor-zlibbioc=1.44.0=r42hc0cfd56_0 38 | - biopython=1.80=py39hb9d737c_0 39 | - blas=1.1=openblas 40 | - brotlipy=0.7.0=py39h27cfd23_1003 41 | - bwidget=1.9.14=ha770c72_1 42 | - bzip2=1.0.8=h7f98852_4 43 | - c-ares=1.18.1=h7f98852_0 44 | - ca-certificates=2022.9.24=ha878542_0 45 | - cairo=1.16.0=h18b612c_1001 46 | - certifi=2022.9.24=pyhd8ed1ab_0 47 | - cffi=1.15.0=py39hd667e15_1 48 | - charset-normalizer=2.0.4=pyhd3eb1b0_0 49 | - codecov=2.1.12=pyhd8ed1ab_0 50 | - colorama=0.4.4=pyhd3eb1b0_0 51 | - conda=4.12.0=py39h06a4308_0 52 | - conda-content-trust=0.1.1=pyhd3eb1b0_0 53 | - conda-package-handling=1.8.1=py39h7f8727e_0 54 | - coverage=6.5.0=py39hb9d737c_1 55 | - cryptography=36.0.0=py39h9ce1e76_0 56 | - curl=7.86.0=h2283fc2_1 57 | - exceptiongroup=1.0.4=pyhd8ed1ab_0 58 | - expat=2.5.0=h27087fc_0 59 | - fmt=9.1.0=h924138e_0 60 | - font-ttf-dejavu-sans-mono=2.37=hab24e00_0 61 | - font-ttf-inconsolata=3.000=h77eed37_0 62 | - font-ttf-source-code-pro=2.038=h77eed37_0 63 | - font-ttf-ubuntu=0.83=hab24e00_0 64 | - fontconfig=2.14.1=hc2a2eb6_0 65 | - fonts-conda-ecosystem=1=0 66 | - fonts-conda-forge=1=0 67 | - freetype=2.12.1=hca18f0e_1 68 | - fribidi=1.0.10=h36c2ea0_0 69 | - gcc_impl_linux-64=11.2.0=h82a94d6_16 70 | - gcc_linux-64=11.2.0=h39a9532_10 71 | - gettext=0.21.1=h27087fc_0 72 | - gfortran_impl_linux-64=11.2.0=h7a446d4_16 73 | - gfortran_linux-64=11.2.0=h777b47f_10 74 | - glib=2.74.1=h6239696_0 75 | - glib-tools=2.74.1=h6239696_0 76 | - graphite2=1.3.14=h295c915_1 77 | - gxx_impl_linux-64=11.2.0=h82a94d6_16 78 | - gxx_linux-64=11.2.0=hacbe6df_10 79 | - harfbuzz=4.3.0=hd55b92a_0 80 | - icu=58.2=hf484d3e_1000 81 | - idna=3.3=pyhd3eb1b0_0 82 | - importlib-metadata=4.11.4=py39hf3d152e_0 83 | - importlib_metadata=4.11.4=hd8ed1ab_0 84 | - iniconfig=1.1.1=pyh9f0ad1d_0 85 | - jinja2=3.1.2=pyhd8ed1ab_1 86 | - jpeg=9e=h166bdaf_2 87 | - jq=1.6=h36c2ea0_1000 88 | - kernel-headers_linux-64=2.6.32=he073ed8_15 89 | - keyutils=1.6.1=h166bdaf_0 90 | - krb5=1.19.3=h08a2579_0 91 | - lcms2=2.14=h6ed2654_0 92 | - ld_impl_linux-64=2.36.1=hea4e1c9_2 93 | - lerc=4.0.0=h27087fc_0 94 | - libarchive=3.5.2=hada088e_3 95 | - libblas=3.9.0=16_linux64_openblas 96 | - libcblas=3.9.0=16_linux64_openblas 97 | - libcurl=7.86.0=h2283fc2_1 98 | - libdeflate=1.14=h166bdaf_0 99 | - libedit=3.1.20191231=he28a2e2_2 100 | - libev=4.33=h516909a_1 101 | - libffi=3.4.2=h7f98852_5 102 | - libgcc-devel_linux-64=11.2.0=h0952999_16 103 | - libgcc-ng=12.2.0=h65d4601_19 104 | - libgfortran-ng=12.2.0=h69a702a_19 105 | - libgfortran5=12.2.0=h337968e_19 106 | - libglib=2.74.1=h7a41b64_0 107 | - libgomp=12.2.0=h65d4601_19 108 | - libiconv=1.17=h166bdaf_0 109 | - liblapack=3.9.0=16_linux64_openblas 110 | - libmamba=1.0.0=h9eff5f0_2 111 | - libmambapy=1.0.0=py39h37259de_2 112 | - libnghttp2=1.47.0=hff17c54_1 113 | - libnsl=2.0.0=h7f98852_0 114 | - libopenblas=0.3.21=pthreads_h78a6416_3 115 | - libpng=1.6.39=h753d276_0 116 | - libsanitizer=11.2.0=he4da1e4_16 117 | - libsolv=0.7.22=h6239696_0 118 | - libsqlite=3.40.0=h753d276_0 119 | - libssh2=1.10.0=hf14f497_3 120 | - libstdcxx-devel_linux-64=11.2.0=h0952999_16 121 | - libstdcxx-ng=12.2.0=h46fd767_19 122 | - libtiff=4.4.0=h55922b4_4 123 | - libuuid=2.32.1=h7f98852_1000 124 | - libwebp-base=1.2.4=h166bdaf_0 125 | - libxcb=1.13=h7f98852_1004 126 | - libxml2=2.9.14=h74e7548_0 127 | - libzlib=1.2.13=h166bdaf_4 128 | - lz4-c=1.9.3=h9c3ff4c_1 129 | - lzo=2.10=h516909a_1000 130 | - make=4.3=hd18ef5c_1 131 | - mamba=1.0.0=py39hc5d2bb1_2 132 | - markupsafe=2.1.1=py39hb9d737c_2 133 | - ncurses=6.3=h7f8727e_2 134 | - numpy=1.23.5=py39h3d75532_0 135 | - oniguruma=6.9.8=h166bdaf_0 136 | - openblas=0.3.21=pthreads_h320a7e8_3 137 | - openjpeg=2.5.0=h7d73246_1 138 | - openssl=3.0.7=h166bdaf_0 139 | - packaging=21.3=pyhd8ed1ab_0 140 | - pandas=1.5.2=py39h4661b88_0 141 | - pango=1.50.7=hbd2fdc8_0 142 | - pcre2=10.37=hc3806b6_1 143 | - pillow=9.2.0=py39hf3a2cdf_3 144 | - pip=21.2.4=py39h06a4308_0 145 | - pixman=0.38.0=h516909a_1003 146 | - pluggy=1.0.0=py39hf3d152e_4 147 | - pthread-stubs=0.4=h36c2ea0_1001 148 | - pybind11-abi=4=hd8ed1ab_3 149 | - pycosat=0.6.3=py39h27cfd23_0 150 | - pycparser=2.21=pyhd3eb1b0_0 151 | - pyopenssl=22.0.0=pyhd3eb1b0_0 152 | - pyparsing=3.0.9=pyhd8ed1ab_0 153 | - pysocks=1.7.1=py39h06a4308_0 154 | - pytest=7.2.0=py39hf3d152e_1 155 | - pytest-cov=4.0.0=pyhd8ed1ab_0 156 | - python=3.9.15=hba424b6_0_cpython 157 | - python-dateutil=2.8.2=pyhd8ed1ab_0 158 | - python-tzdata=2022.6=pyhd8ed1ab_0 159 | - python_abi=3.9=2_cp39 160 | - pytz=2022.6=pyhd8ed1ab_0 161 | - pytz-deprecation-shim=0.1.0.post0=py39hf3d152e_3 162 | - pyyaml=6.0=py39hb9d737c_5 163 | - r-askpass=1.1=r42h06615bd_3 164 | - r-backports=1.4.1=r42h06615bd_1 165 | - r-base=4.2.0=h1ae530e_0 166 | - r-bh=1.78.0_0=r42hc72bb7e_1 167 | - r-bit=4.0.5=r42h06615bd_0 168 | - r-bit64=4.0.5=r42h06615bd_1 169 | - r-bitops=1.0_7=r42h06615bd_1 170 | - r-blob=1.2.3=r42hc72bb7e_1 171 | - r-brio=1.1.3=r42h06615bd_1 172 | - r-cachem=1.0.6=r42h06615bd_1 173 | - r-callr=3.7.3=r42hc72bb7e_0 174 | - r-cli=3.4.1=r42h7525677_1 175 | - r-codetools=0.2_18=r42hc72bb7e_1 176 | - r-colorspace=2.0_3=r42h06615bd_1 177 | - r-cpp11=0.4.3=r42hc72bb7e_0 178 | - r-crayon=1.5.2=r42hc72bb7e_1 179 | - r-curl=4.3.3=r42h06615bd_1 180 | - r-dbi=1.1.3=r42hc72bb7e_1 181 | - r-desc=1.4.2=r42hc72bb7e_1 182 | - r-diffobj=0.3.5=r42h06615bd_1 183 | - r-digest=0.6.30=r42h7525677_0 184 | - r-ellipsis=0.3.2=r42h06615bd_1 185 | - r-evaluate=0.18=r42hc72bb7e_0 186 | - r-fansi=1.0.3=r42h06615bd_1 187 | - r-farver=2.1.1=r42h7525677_1 188 | - r-fastmap=1.1.0=r42h7525677_1 189 | - r-formatr=1.12=r42hc72bb7e_1 190 | - r-fs=1.5.2=r42h7525677_2 191 | - r-futile.logger=1.4.3=r42hc72bb7e_1004 192 | - r-futile.options=1.0.1=r42hc72bb7e_1003 193 | - r-ggplot2=3.4.0=r42hc72bb7e_0 194 | - r-glue=1.6.2=r42h06615bd_1 195 | - r-gtable=0.3.1=r42hc72bb7e_1 196 | - r-httr=1.4.4=r42hc72bb7e_1 197 | - r-isoband=0.2.6=r42h7525677_1 198 | - r-jsonlite=1.8.3=r42h06615bd_0 199 | - r-labeling=0.4.2=r42hc72bb7e_2 200 | - r-lambda.r=1.2.4=r42hc72bb7e_2 201 | - r-lattice=0.20_45=r42h06615bd_1 202 | - r-lifecycle=1.0.3=r42hc72bb7e_1 203 | - r-locfit=1.5_9.6=r42h06615bd_1 204 | - r-magrittr=2.0.3=r42h06615bd_1 205 | - r-mass=7.3_58.1=r42h06615bd_1 206 | - r-matrix=1.5_3=r42h5f7b363_0 207 | - r-matrixstats=0.63.0=r42h06615bd_0 208 | - r-memoise=2.0.1=r42hc72bb7e_1 209 | - r-mgcv=1.8_41=r42h5f7b363_0 210 | - r-mime=0.12=r42h06615bd_1 211 | - r-munsell=0.5.0=r42hc72bb7e_1005 212 | - r-nlme=3.1_160=r42h8da6f51_0 213 | - r-openssl=2.0.4=r42h1f3e0c5_0 214 | - r-pillar=1.8.1=r42hc72bb7e_1 215 | - r-pkgconfig=2.0.3=r42hc72bb7e_2 216 | - r-pkgload=1.3.2=r42hc72bb7e_0 217 | - r-plogr=0.2.0=r42hc72bb7e_1004 218 | - r-png=0.1_7=r42h06615bd_1006 219 | - r-praise=1.0.0=r42hc72bb7e_1006 220 | - r-processx=3.8.0=r42h06615bd_0 221 | - r-ps=1.7.2=r42h06615bd_0 222 | - r-r6=2.5.1=r42hc72bb7e_1 223 | - r-rcolorbrewer=1.1_3=r42h785f33e_1 224 | - r-rcpp=1.0.9=r42h7525677_2 225 | - r-rcpparmadillo=0.11.4.2.1=r42h9f5de39_0 226 | - r-rcurl=1.98_1.9=r42h06615bd_1 227 | - r-rematch2=2.1.2=r42hc72bb7e_2 228 | - r-rlang=1.0.6=r42h7525677_1 229 | - r-rprojroot=2.0.3=r42hc72bb7e_1 230 | - r-rsqlite=2.2.19=r42h7525677_0 231 | - r-scales=1.2.1=r42hc72bb7e_1 232 | - r-snow=0.4_4=r42hc72bb7e_1 233 | - r-survival=3.4_0=r42h06615bd_1 234 | - r-sys=3.4.1=r42h06615bd_0 235 | - r-testthat=3.1.5=r42h7525677_1 236 | - r-tibble=3.1.8=r42h06615bd_1 237 | - r-utf8=1.2.2=r42h06615bd_1 238 | - r-vctrs=0.5.1=r42h7525677_0 239 | - r-viridislite=0.4.1=r42hc72bb7e_1 240 | - r-waldo=0.4.0=r42hc72bb7e_1 241 | - r-withr=2.5.0=r42hc72bb7e_1 242 | - r-xml=3.99_0.11=r42h2b86b34_2 243 | - r-xtable=1.8_4=r42hc72bb7e_4 244 | - readline=8.1.2=h7f8727e_1 245 | - reportlab=3.5.68=py39he59360d_1 246 | - reproc=14.2.3=h7f98852_0 247 | - reproc-cpp=14.2.3=h9c3ff4c_0 248 | - requests=2.27.1=pyhd3eb1b0_0 249 | - rpy2=3.5.6=py39r42h2ae25f5_0 250 | - ruamel_yaml=0.15.100=py39h27cfd23_0 251 | - setuptools=61.2.0=py39h06a4308_0 252 | - simplegeneric=0.8.1=py_1 253 | - six=1.16.0=pyhd3eb1b0_1 254 | - sqlite=3.38.2=hc218d9a_0 255 | - sysroot_linux-64=2.12=he073ed8_15 256 | - tk=8.6.12=h27826a3_0 257 | - tktable=2.10=hb7b940f_3 258 | - toml=0.10.2=pyhd8ed1ab_0 259 | - tomli=2.0.1=pyhd8ed1ab_0 260 | - tqdm=4.63.0=pyhd3eb1b0_0 261 | - tzdata=2022a=hda174b7_0 262 | - tzlocal=4.2=py39hf3d152e_2 263 | - urllib3=1.26.8=pyhd3eb1b0_0 264 | - wheel=0.37.1=pyhd3eb1b0_0 265 | - xmltodict=0.13.0=pyhd8ed1ab_0 266 | - xorg-kbproto=1.0.7=h7f98852_1002 267 | - xorg-libice=1.0.10=h7f98852_0 268 | - xorg-libsm=1.2.3=hd9c2040_1000 269 | - xorg-libx11=1.7.2=h7f98852_0 270 | - xorg-libxau=1.0.9=h7f98852_0 271 | - xorg-libxdmcp=1.1.3=h7f98852_0 272 | - xorg-libxext=1.3.4=h7f98852_1 273 | - xorg-libxrender=0.9.10=h7f98852_1003 274 | - xorg-renderproto=0.11.1=h7f98852_1002 275 | - xorg-xextproto=7.3.0=h7f98852_1002 276 | - xorg-xproto=7.0.31=h7f98852_1007 277 | - xz=5.2.6=h166bdaf_0 278 | - yaml=0.2.5=h7b6447c_0 279 | - yaml-cpp=0.7.0=h27087fc_2 280 | - yq=2.13.0=pyhd8ed1ab_0 281 | - zipp=3.11.0=pyhd8ed1ab_0 282 | - zlib=1.2.13=h166bdaf_4 283 | - zstd=1.5.2=ha4553b6_0 284 | - pip: 285 | - diffexp==0.1 286 | prefix: /opt/conda 287 | -------------------------------------------------------------------------------- /example/deseq_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Porting DESeq into python using rpy2#\n", 8 | "\n", 9 | "I will use a small example of [ERCC transcript](https://www.thermofisher.com/order/catalog/product/4456740) from [samples A and B in MAQC data](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3272078/)." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "%load_ext autoreload\n", 19 | "%autoreload 2\n", 20 | "import pandas as pd \n", 21 | "import numpy as np" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "We will read the table and it should only contains count data of ERCC spikeins (rows) and 3 replicates from each of samples A and B (columns)." 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 2, 34 | "metadata": {}, 35 | "outputs": [ 36 | { 37 | "data": { 38 | "text/html": [ 39 | "
\n", 40 | "\n", 53 | "\n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | "
idA_1A_2A_3B_1B_2B_3
0ERCC-00002111461106261107547333944199252186947
1ERCC-000036735538752651393785848596
2ERCC-00004176731398315462506532223353
3ERCC-00009466944314211693941553647
4ERCC-00012020000
\n", 119 | "
" 120 | ], 121 | "text/plain": [ 122 | " id A_1 A_2 A_3 B_1 B_2 B_3\n", 123 | "0 ERCC-00002 111461 106261 107547 333944 199252 186947\n", 124 | "1 ERCC-00003 6735 5387 5265 13937 8584 8596\n", 125 | "2 ERCC-00004 17673 13983 15462 5065 3222 3353\n", 126 | "3 ERCC-00009 4669 4431 4211 6939 4155 3647\n", 127 | "4 ERCC-00012 0 2 0 0 0 0" 128 | ] 129 | }, 130 | "execution_count": 2, 131 | "metadata": {}, 132 | "output_type": "execute_result" 133 | } 134 | ], 135 | "source": [ 136 | "df = pd.read_table('../test/data/ercc.tsv')\n", 137 | "df.head()" 138 | ] 139 | }, 140 | { 141 | "cell_type": "markdown", 142 | "metadata": {}, 143 | "source": [ 144 | "And here, we will create a design matrix based on the samples in the count table. Note that the sample name has to be used as the ```pd.DataFrame``` index" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": 3, 150 | "metadata": {}, 151 | "outputs": [ 152 | { 153 | "data": { 154 | "text/html": [ 155 | "
\n", 156 | "\n", 169 | "\n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | "
samplenamesamplereplicate
samplename
A_1A_1A1
A_2A_2A2
A_3A_3A3
B_1B_1B1
B_2B_2B2
B_3B_3B3
\n", 223 | "
" 224 | ], 225 | "text/plain": [ 226 | " samplename sample replicate\n", 227 | "samplename \n", 228 | "A_1 A_1 A 1\n", 229 | "A_2 A_2 A 2\n", 230 | "A_3 A_3 A 3\n", 231 | "B_1 B_1 B 1\n", 232 | "B_2 B_2 B 2\n", 233 | "B_3 B_3 B 3" 234 | ] 235 | }, 236 | "execution_count": 3, 237 | "metadata": {}, 238 | "output_type": "execute_result" 239 | } 240 | ], 241 | "source": [ 242 | "sample_df = pd.DataFrame({'samplename': df.columns}) \\\n", 243 | " .query('samplename != \"id\"')\\\n", 244 | " .assign(sample = lambda d: d.samplename.str.extract('([AB])_', expand=False)) \\\n", 245 | " .assign(replicate = lambda d: d.samplename.str.extract('_([123])', expand=False)) \n", 246 | "sample_df.index = sample_df.samplename\n", 247 | "sample_df" 248 | ] 249 | }, 250 | { 251 | "cell_type": "markdown", 252 | "metadata": {}, 253 | "source": [ 254 | "Running DESeq2 is jsut like how it is run in ```R```, but instead of the row.name being gene ID for the count table, we can jsut tell the function which column is the gene ID:" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": 4, 260 | "metadata": {}, 261 | "outputs": [ 262 | { 263 | "name": "stderr", 264 | "output_type": "stream", 265 | "text": [ 266 | "WARNING:rpy2.rinterface_lib.callbacks:R[write to console]: estimating size factors\n", 267 | "\n", 268 | "WARNING:rpy2.rinterface_lib.callbacks:R[write to console]: estimating dispersions\n", 269 | "\n", 270 | "WARNING:rpy2.rinterface_lib.callbacks:R[write to console]: gene-wise dispersion estimates\n", 271 | "\n", 272 | "WARNING:rpy2.rinterface_lib.callbacks:R[write to console]: mean-dispersion relationship\n", 273 | "\n", 274 | "WARNING:rpy2.rinterface_lib.callbacks:R[write to console]: final dispersion estimates\n", 275 | "\n", 276 | "WARNING:rpy2.rinterface_lib.callbacks:R[write to console]: fitting model and testing\n", 277 | "\n", 278 | "INFO:DESeq2:Using contrast: ['sample', 'B', 'A']\n" 279 | ] 280 | }, 281 | { 282 | "data": { 283 | "text/html": [ 284 | "
\n", 285 | "\n", 298 | "\n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | " \n", 362 | " \n", 363 | "
baseMeanlog2FoldChangelfcSEstatpvaluepadjid
ERCC-00002167917.3427290.8088570.04760616.9905379.650176e-651.102877e-63ERCC-00002
ERCC-000037902.6340730.5217310.0588788.8612527.912104e-194.868987e-18ERCC-00003
ERCC-0000410567.048228-2.3301220.055754-41.7927640.000000e+000.000000e+00ERCC-00004
ERCC-000094672.573043-0.1956600.061600-3.1762861.491736e-033.616329e-03ERCC-00009
ERCC-000120.384257-1.5654914.047562-0.3867746.989237e-01NaNERCC-00012
\n", 364 | "
" 365 | ], 366 | "text/plain": [ 367 | " baseMean log2FoldChange lfcSE stat pvalue \\\n", 368 | "ERCC-00002 167917.342729 0.808857 0.047606 16.990537 9.650176e-65 \n", 369 | "ERCC-00003 7902.634073 0.521731 0.058878 8.861252 7.912104e-19 \n", 370 | "ERCC-00004 10567.048228 -2.330122 0.055754 -41.792764 0.000000e+00 \n", 371 | "ERCC-00009 4672.573043 -0.195660 0.061600 -3.176286 1.491736e-03 \n", 372 | "ERCC-00012 0.384257 -1.565491 4.047562 -0.386774 6.989237e-01 \n", 373 | "\n", 374 | " padj id \n", 375 | "ERCC-00002 1.102877e-63 ERCC-00002 \n", 376 | "ERCC-00003 4.868987e-18 ERCC-00003 \n", 377 | "ERCC-00004 0.000000e+00 ERCC-00004 \n", 378 | "ERCC-00009 3.616329e-03 ERCC-00009 \n", 379 | "ERCC-00012 NaN ERCC-00012 " 380 | ] 381 | }, 382 | "execution_count": 4, 383 | "metadata": {}, 384 | "output_type": "execute_result" 385 | } 386 | ], 387 | "source": [ 388 | "from diffexpr.py_deseq import py_DESeq2\n", 389 | "\n", 390 | "dds = py_DESeq2(count_matrix = df,\n", 391 | " design_matrix = sample_df,\n", 392 | " design_formula = '~ replicate + sample',\n", 393 | " gene_column = 'id') # <- telling DESeq2 this should be the gene ID column\n", 394 | " \n", 395 | "dds.run_deseq() \n", 396 | "dds.get_deseq_result(contrast = ['sample','B','A'])\n", 397 | "res = dds.deseq_result \n", 398 | "res.head()" 399 | ] 400 | }, 401 | { 402 | "cell_type": "code", 403 | "execution_count": 5, 404 | "metadata": {}, 405 | "outputs": [ 406 | { 407 | "name": "stderr", 408 | "output_type": "stream", 409 | "text": [ 410 | "INFO:DESeq2:Normalizing counts\n" 411 | ] 412 | }, 413 | { 414 | "data": { 415 | "text/html": [ 416 | "
\n", 417 | "\n", 430 | "\n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | " \n", 453 | " \n", 454 | " \n", 455 | " \n", 456 | " \n", 457 | " \n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | " \n", 465 | " \n", 466 | " \n", 467 | " \n", 468 | " \n", 469 | " \n", 470 | " \n", 471 | " \n", 472 | " \n", 473 | " \n", 474 | " \n", 475 | " \n", 476 | " \n", 477 | " \n", 478 | " \n", 479 | " \n", 480 | " \n", 481 | " \n", 482 | " \n", 483 | " \n", 484 | " \n", 485 | " \n", 486 | " \n", 487 | " \n", 488 | " \n", 489 | " \n", 490 | " \n", 491 | " \n", 492 | " \n", 493 | " \n", 494 | " \n", 495 | " \n", 496 | " \n", 497 | " \n", 498 | " \n", 499 | " \n", 500 | " \n", 501 | " \n", 502 | " \n", 503 | " \n", 504 | " \n", 505 | " \n", 506 | " \n", 507 | " \n", 508 | " \n", 509 | " \n", 510 | " \n", 511 | " \n", 512 | " \n", 513 | " \n", 514 | " \n", 515 | " \n", 516 | " \n", 517 | " \n", 518 | " \n", 519 | " \n", 520 | " \n", 521 | " \n", 522 | " \n", 523 | " \n", 524 | " \n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | " \n", 530 | " \n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | " \n", 537 | " \n", 538 | " \n", 539 | " \n", 540 | " \n", 541 | " \n", 542 | " \n", 543 | " \n", 544 | " \n", 545 | " \n", 546 | " \n", 547 | " \n", 548 | " \n", 549 | " \n", 550 | " \n", 551 | " \n", 552 | " \n", 553 | " \n", 554 | " \n", 555 | "
A_1A_2A_3B_1B_2B_3id
ERCC-00002115018.353297122494.471246128809.545168218857.357008207880.854689214443.474968ERCC-00002
ERCC-000036949.9520866209.9708896305.9151389133.9116288955.7407549860.313944ERCC-00003
ERCC-0000418237.04576316119.18005118518.9097553319.4562963361.5327013846.164804ERCC-00004
ERCC-000094818.0142975107.9229645043.5344054547.6223574334.9374224183.406812ERCC-00009
ERCC-000120.0000002.3055400.0000000.0000000.0000000.000000ERCC-00012
........................
ERCC-001642.0638311.1527705.9885233.2768571.0433062.294163ERCC-00164
ERCC-00165269.329992246.692736287.449123513.811202484.094095489.803869ERCC-00165
ERCC-001681.0319163.4583090.0000004.5876004.1732251.147082ERCC-00168
ERCC-00170137.244785148.707304135.34062926.87022910.43306232.118286ERCC-00170
ERCC-001718707.3044849622.1694848818.6995557691.4391097691.2535926892.813691ERCC-00171
\n", 556 | "

92 rows × 7 columns

\n", 557 | "
" 558 | ], 559 | "text/plain": [ 560 | " A_1 A_2 A_3 B_1 \\\n", 561 | "ERCC-00002 115018.353297 122494.471246 128809.545168 218857.357008 \n", 562 | "ERCC-00003 6949.952086 6209.970889 6305.915138 9133.911628 \n", 563 | "ERCC-00004 18237.045763 16119.180051 18518.909755 3319.456296 \n", 564 | "ERCC-00009 4818.014297 5107.922964 5043.534405 4547.622357 \n", 565 | "ERCC-00012 0.000000 2.305540 0.000000 0.000000 \n", 566 | "... ... ... ... ... \n", 567 | "ERCC-00164 2.063831 1.152770 5.988523 3.276857 \n", 568 | "ERCC-00165 269.329992 246.692736 287.449123 513.811202 \n", 569 | "ERCC-00168 1.031916 3.458309 0.000000 4.587600 \n", 570 | "ERCC-00170 137.244785 148.707304 135.340629 26.870229 \n", 571 | "ERCC-00171 8707.304484 9622.169484 8818.699555 7691.439109 \n", 572 | "\n", 573 | " B_2 B_3 id \n", 574 | "ERCC-00002 207880.854689 214443.474968 ERCC-00002 \n", 575 | "ERCC-00003 8955.740754 9860.313944 ERCC-00003 \n", 576 | "ERCC-00004 3361.532701 3846.164804 ERCC-00004 \n", 577 | "ERCC-00009 4334.937422 4183.406812 ERCC-00009 \n", 578 | "ERCC-00012 0.000000 0.000000 ERCC-00012 \n", 579 | "... ... ... ... \n", 580 | "ERCC-00164 1.043306 2.294163 ERCC-00164 \n", 581 | "ERCC-00165 484.094095 489.803869 ERCC-00165 \n", 582 | "ERCC-00168 4.173225 1.147082 ERCC-00168 \n", 583 | "ERCC-00170 10.433062 32.118286 ERCC-00170 \n", 584 | "ERCC-00171 7691.253592 6892.813691 ERCC-00171 \n", 585 | "\n", 586 | "[92 rows x 7 columns]" 587 | ] 588 | }, 589 | "execution_count": 5, 590 | "metadata": {}, 591 | "output_type": "execute_result" 592 | } 593 | ], 594 | "source": [ 595 | "dds.normalized_count() #DESeq2 normalized count" 596 | ] 597 | }, 598 | { 599 | "cell_type": "code", 600 | "execution_count": 6, 601 | "metadata": {}, 602 | "outputs": [ 603 | { 604 | "data": { 605 | "text/plain": [ 606 | "['Intercept', 'replicate_2_vs_1', 'replicate_3_vs_1', 'sample_B_vs_A']" 607 | ] 608 | }, 609 | "execution_count": 6, 610 | "metadata": {}, 611 | "output_type": "execute_result" 612 | } 613 | ], 614 | "source": [ 615 | "dds.comparison # show coefficients for GLM" 616 | ] 617 | }, 618 | { 619 | "cell_type": "code", 620 | "execution_count": 7, 621 | "metadata": {}, 622 | "outputs": [ 623 | { 624 | "name": "stderr", 625 | "output_type": "stream", 626 | "text": [ 627 | "WARNING:rpy2.rinterface_lib.callbacks:R[write to console]: using 'apeglm' for LFC shrinkage. If used in published research, please cite:\n", 628 | " Zhu, A., Ibrahim, J.G., Love, M.I. (2018) Heavy-tailed prior distributions for\n", 629 | " sequence count data: removing the noise and preserving large differences.\n", 630 | " Bioinformatics. https://doi.org/10.1093/bioinformatics/bty895\n", 631 | "\n" 632 | ] 633 | }, 634 | { 635 | "data": { 636 | "text/html": [ 637 | "
\n", 638 | "\n", 651 | "\n", 652 | " \n", 653 | " \n", 654 | " \n", 655 | " \n", 656 | " \n", 657 | " \n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | " \n", 672 | " \n", 673 | " \n", 674 | " \n", 675 | " \n", 676 | " \n", 677 | " \n", 678 | " \n", 679 | " \n", 680 | " \n", 681 | " \n", 682 | " \n", 683 | " \n", 684 | " \n", 685 | " \n", 686 | " \n", 687 | " \n", 688 | " \n", 689 | " \n", 690 | " \n", 691 | " \n", 692 | " \n", 693 | " \n", 694 | " \n", 695 | " \n", 696 | " \n", 697 | " \n", 698 | " \n", 699 | " \n", 700 | " \n", 701 | " \n", 702 | " \n", 703 | " \n", 704 | " \n", 705 | " \n", 706 | " \n", 707 | " \n", 708 | " \n", 709 | " \n", 710 | "
idbaseMeanlog2FoldChangelfcSEpvaluepadj
0ERCC-00002167917.3427290.8073160.0476099.650176e-651.102877e-63
1ERCC-000037902.6340730.5199440.0588237.912104e-194.868987e-18
2ERCC-0000410567.048228-2.3280370.0557830.000000e+000.000000e+00
3ERCC-000094672.573043-0.1945940.0614661.491736e-033.616329e-03
4ERCC-000120.384257-0.0523260.8206966.989237e-01NaN
\n", 711 | "
" 712 | ], 713 | "text/plain": [ 714 | " id baseMean log2FoldChange lfcSE pvalue \\\n", 715 | "0 ERCC-00002 167917.342729 0.807316 0.047609 9.650176e-65 \n", 716 | "1 ERCC-00003 7902.634073 0.519944 0.058823 7.912104e-19 \n", 717 | "2 ERCC-00004 10567.048228 -2.328037 0.055783 0.000000e+00 \n", 718 | "3 ERCC-00009 4672.573043 -0.194594 0.061466 1.491736e-03 \n", 719 | "4 ERCC-00012 0.384257 -0.052326 0.820696 6.989237e-01 \n", 720 | "\n", 721 | " padj \n", 722 | "0 1.102877e-63 \n", 723 | "1 4.868987e-18 \n", 724 | "2 0.000000e+00 \n", 725 | "3 3.616329e-03 \n", 726 | "4 NaN " 727 | ] 728 | }, 729 | "execution_count": 7, 730 | "metadata": {}, 731 | "output_type": "execute_result" 732 | } 733 | ], 734 | "source": [ 735 | "# from the last cell, we see the arrangement of coefficients, \n", 736 | "# so that we can now use \"coef\" for lfcShrink\n", 737 | "# the comparison we want to focus on is 'sample_B_vs_A', so coef = 4 will be used\n", 738 | "lfc_res = dds.lfcShrink(coef=4, method='apeglm')\n", 739 | "lfc_res.head()" 740 | ] 741 | } 742 | ], 743 | "metadata": { 744 | "kernelspec": { 745 | "display_name": "Python 3 (ipykernel)", 746 | "language": "python", 747 | "name": "python3" 748 | }, 749 | "language_info": { 750 | "codemirror_mode": { 751 | "name": "ipython", 752 | "version": 3 753 | }, 754 | "file_extension": ".py", 755 | "mimetype": "text/x-python", 756 | "name": "python", 757 | "nbconvert_exporter": "python", 758 | "pygments_lexer": "ipython3", 759 | "version": "3.9.15" 760 | } 761 | }, 762 | "nbformat": 4, 763 | "nbformat_minor": 4 764 | } 765 | -------------------------------------------------------------------------------- /example/jupyter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wckdouglas/diffexpr/c78aba6a8ec7b26331f7e258d5e2cf1c5d559eed/example/jupyter.png -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "atomicwrites" 3 | version = "1.4.0" 4 | description = "Atomic file writes." 5 | category = "main" 6 | optional = false 7 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 8 | 9 | [[package]] 10 | name = "attrs" 11 | version = "20.3.0" 12 | description = "Classes Without Boilerplate" 13 | category = "main" 14 | optional = false 15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 16 | 17 | [package.extras] 18 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] 19 | docs = ["furo", "sphinx", "zope.interface"] 20 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 21 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 22 | 23 | [[package]] 24 | name = "biopython" 25 | version = "1.78" 26 | description = "Freely available tools for computational molecular biology." 27 | category = "main" 28 | optional = false 29 | python-versions = ">=3.6" 30 | 31 | [package.dependencies] 32 | numpy = "*" 33 | 34 | [[package]] 35 | name = "certifi" 36 | version = "2020.12.5" 37 | description = "Python package for providing Mozilla's CA Bundle." 38 | category = "main" 39 | optional = false 40 | python-versions = "*" 41 | 42 | [[package]] 43 | name = "cffi" 44 | version = "1.14.5" 45 | description = "Foreign Function Interface for Python calling C code." 46 | category = "main" 47 | optional = false 48 | python-versions = "*" 49 | 50 | [package.dependencies] 51 | pycparser = "*" 52 | 53 | [[package]] 54 | name = "chardet" 55 | version = "4.0.0" 56 | description = "Universal encoding detector for Python 2 and 3" 57 | category = "main" 58 | optional = false 59 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 60 | 61 | [[package]] 62 | name = "codecov" 63 | version = "2.1.11" 64 | description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" 65 | category = "main" 66 | optional = false 67 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 68 | 69 | [package.dependencies] 70 | coverage = "*" 71 | requests = ">=2.7.9" 72 | 73 | [[package]] 74 | name = "colorama" 75 | version = "0.4.4" 76 | description = "Cross-platform colored terminal text." 77 | category = "main" 78 | optional = false 79 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 80 | 81 | [[package]] 82 | name = "coverage" 83 | version = "5.5" 84 | description = "Code coverage measurement for Python" 85 | category = "main" 86 | optional = false 87 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 88 | 89 | [package.extras] 90 | toml = ["toml"] 91 | 92 | [[package]] 93 | name = "idna" 94 | version = "2.10" 95 | description = "Internationalized Domain Names in Applications (IDNA)" 96 | category = "main" 97 | optional = false 98 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 99 | 100 | [[package]] 101 | name = "importlib-metadata" 102 | version = "3.7.2" 103 | description = "Read metadata from Python packages" 104 | category = "main" 105 | optional = false 106 | python-versions = ">=3.6" 107 | 108 | [package.dependencies] 109 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 110 | zipp = ">=0.5" 111 | 112 | [package.extras] 113 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 114 | testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 115 | 116 | [[package]] 117 | name = "iniconfig" 118 | version = "1.1.1" 119 | description = "iniconfig: brain-dead simple config-ini parsing" 120 | category = "main" 121 | optional = false 122 | python-versions = "*" 123 | 124 | [[package]] 125 | name = "jinja2" 126 | version = "2.11.3" 127 | description = "A very fast and expressive template engine." 128 | category = "main" 129 | optional = false 130 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 131 | 132 | [package.dependencies] 133 | MarkupSafe = ">=0.23" 134 | 135 | [package.extras] 136 | i18n = ["Babel (>=0.8)"] 137 | 138 | [[package]] 139 | name = "markupsafe" 140 | version = "1.1.1" 141 | description = "Safely add untrusted strings to HTML/XML markup." 142 | category = "main" 143 | optional = false 144 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 145 | 146 | [[package]] 147 | name = "numpy" 148 | version = "1.19.5" 149 | description = "NumPy is the fundamental package for array computing with Python." 150 | category = "main" 151 | optional = false 152 | python-versions = ">=3.6" 153 | 154 | [[package]] 155 | name = "packaging" 156 | version = "20.9" 157 | description = "Core utilities for Python packages" 158 | category = "main" 159 | optional = false 160 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 161 | 162 | [package.dependencies] 163 | pyparsing = ">=2.0.2" 164 | 165 | [[package]] 166 | name = "pandas" 167 | version = "1.0.1" 168 | description = "Powerful data structures for data analysis, time series, and statistics" 169 | category = "main" 170 | optional = false 171 | python-versions = ">=3.6.1" 172 | 173 | [package.dependencies] 174 | numpy = ">=1.13.3" 175 | python-dateutil = ">=2.6.1" 176 | pytz = ">=2017.2" 177 | 178 | [package.extras] 179 | test = ["pytest (>=4.0.2)", "pytest-xdist", "hypothesis (>=3.58)"] 180 | 181 | [[package]] 182 | name = "pillow" 183 | version = "8.1.2" 184 | description = "Python Imaging Library (Fork)" 185 | category = "main" 186 | optional = false 187 | python-versions = ">=3.6" 188 | 189 | [[package]] 190 | name = "pluggy" 191 | version = "0.13.1" 192 | description = "plugin and hook calling mechanisms for python" 193 | category = "main" 194 | optional = false 195 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 196 | 197 | [package.dependencies] 198 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 199 | 200 | [package.extras] 201 | dev = ["pre-commit", "tox"] 202 | 203 | [[package]] 204 | name = "py" 205 | version = "1.10.0" 206 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 207 | category = "main" 208 | optional = false 209 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 210 | 211 | [[package]] 212 | name = "pycparser" 213 | version = "2.20" 214 | description = "C parser in Python" 215 | category = "main" 216 | optional = false 217 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 218 | 219 | [[package]] 220 | name = "pyparsing" 221 | version = "2.4.7" 222 | description = "Python parsing module" 223 | category = "main" 224 | optional = false 225 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 226 | 227 | [[package]] 228 | name = "pytest" 229 | version = "6.2.2" 230 | description = "pytest: simple powerful testing with Python" 231 | category = "main" 232 | optional = false 233 | python-versions = ">=3.6" 234 | 235 | [package.dependencies] 236 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 237 | attrs = ">=19.2.0" 238 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 239 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 240 | iniconfig = "*" 241 | packaging = "*" 242 | pluggy = ">=0.12,<1.0.0a1" 243 | py = ">=1.8.2" 244 | toml = "*" 245 | 246 | [package.extras] 247 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 248 | 249 | [[package]] 250 | name = "pytest-cov" 251 | version = "2.11.1" 252 | description = "Pytest plugin for measuring coverage." 253 | category = "dev" 254 | optional = false 255 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 256 | 257 | [package.dependencies] 258 | coverage = ">=5.2.1" 259 | pytest = ">=4.6" 260 | 261 | [package.extras] 262 | testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] 263 | 264 | [[package]] 265 | name = "python-dateutil" 266 | version = "2.8.1" 267 | description = "Extensions to the standard Python datetime module" 268 | category = "main" 269 | optional = false 270 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 271 | 272 | [package.dependencies] 273 | six = ">=1.5" 274 | 275 | [[package]] 276 | name = "pytz" 277 | version = "2021.1" 278 | description = "World timezone definitions, modern and historical" 279 | category = "main" 280 | optional = false 281 | python-versions = "*" 282 | 283 | [[package]] 284 | name = "reportlab" 285 | version = "3.5.65" 286 | description = "The Reportlab Toolkit" 287 | category = "main" 288 | optional = false 289 | python-versions = ">=2.7, >=3.6, <4" 290 | 291 | [package.dependencies] 292 | pillow = ">=4.0.0" 293 | 294 | [package.extras] 295 | rlpycairo = ["rlPyCairo (>=0.0.5)"] 296 | 297 | [[package]] 298 | name = "requests" 299 | version = "2.25.1" 300 | description = "Python HTTP for Humans." 301 | category = "main" 302 | optional = false 303 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 304 | 305 | [package.dependencies] 306 | certifi = ">=2017.4.17" 307 | chardet = ">=3.0.2,<5" 308 | idna = ">=2.5,<3" 309 | urllib3 = ">=1.21.1,<1.27" 310 | 311 | [package.extras] 312 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] 313 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 314 | 315 | [[package]] 316 | name = "rpy2" 317 | version = "3.0.0" 318 | description = "Python interface to the R language (embedded R)" 319 | category = "main" 320 | optional = false 321 | python-versions = "*" 322 | 323 | [package.dependencies] 324 | cffi = ">=1.0.0" 325 | jinja2 = "*" 326 | pytest = "*" 327 | simplegeneric = "*" 328 | 329 | [[package]] 330 | name = "simplegeneric" 331 | version = "0.8.1" 332 | description = "Simple generic functions (similar to Python's own len(), pickle.dump(), etc.)" 333 | category = "main" 334 | optional = false 335 | python-versions = "*" 336 | 337 | [[package]] 338 | name = "six" 339 | version = "1.15.0" 340 | description = "Python 2 and 3 compatibility utilities" 341 | category = "main" 342 | optional = false 343 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 344 | 345 | [[package]] 346 | name = "toml" 347 | version = "0.10.2" 348 | description = "Python Library for Tom's Obvious, Minimal Language" 349 | category = "main" 350 | optional = false 351 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 352 | 353 | [[package]] 354 | name = "typing-extensions" 355 | version = "3.7.4.3" 356 | description = "Backported and Experimental Type Hints for Python 3.5+" 357 | category = "main" 358 | optional = false 359 | python-versions = "*" 360 | 361 | [[package]] 362 | name = "tzlocal" 363 | version = "2.1" 364 | description = "tzinfo object for the local timezone" 365 | category = "main" 366 | optional = false 367 | python-versions = "*" 368 | 369 | [package.dependencies] 370 | pytz = "*" 371 | 372 | [[package]] 373 | name = "urllib3" 374 | version = "1.26.3" 375 | description = "HTTP library with thread-safe connection pooling, file post, and more." 376 | category = "main" 377 | optional = false 378 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 379 | 380 | [package.extras] 381 | brotli = ["brotlipy (>=0.6.0)"] 382 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 383 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 384 | 385 | [[package]] 386 | name = "zipp" 387 | version = "3.4.1" 388 | description = "Backport of pathlib-compatible object wrapper for zip files" 389 | category = "main" 390 | optional = false 391 | python-versions = ">=3.6" 392 | 393 | [package.extras] 394 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 395 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 396 | 397 | [metadata] 398 | lock-version = "1.1" 399 | python-versions = "^3.6.1" 400 | content-hash = "c9d6fe96949af4e87868a6c5cbe8b93e411e9439bd6213fccc138694a65be86a" 401 | 402 | [metadata.files] 403 | atomicwrites = [ 404 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 405 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 406 | ] 407 | attrs = [ 408 | {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, 409 | {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, 410 | ] 411 | biopython = [ 412 | {file = "biopython-1.78-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0b9fbb0d3022dc22716da108b8a81b80d952cd97ac1f106de491dce850f92f62"}, 413 | {file = "biopython-1.78-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f5021a398c898b9cf6815cc5171c146a601b935b55364c53e6516a2545ab740c"}, 414 | {file = "biopython-1.78-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:195f099c2c0c39518b6df921ab2b3cc43a601896018fc61909ac8385d5878866"}, 415 | {file = "biopython-1.78-cp36-cp36m-win32.whl", hash = "sha256:75b55000793f6b76334b8e80dc7e6d8cd2b019af917aa431cea6646e8e696c7f"}, 416 | {file = "biopython-1.78-cp36-cp36m-win_amd64.whl", hash = "sha256:f1076653937947773768455556b1d24acad9575759e9089082f32636b09add54"}, 417 | {file = "biopython-1.78-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e0af107cc62a905d13d35dd7b38f335a37752ede45e4617139e84409a6a88dc4"}, 418 | {file = "biopython-1.78-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4565c97fab16c5697d067b821b6a1da0ec3ef36a9c96cf103ac7b4a94eb9f9ba"}, 419 | {file = "biopython-1.78-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0df5cddef2819c975e6508adf5d85aa046e449df5420d02b04871c7836b41273"}, 420 | {file = "biopython-1.78-cp37-cp37m-win32.whl", hash = "sha256:5c0b369f91a76b8e5e36624d075585c3f0f088ea4a6e3d015c48f08e48ce0114"}, 421 | {file = "biopython-1.78-cp37-cp37m-win_amd64.whl", hash = "sha256:cc3b0b78022d14f11d508038a288a189d03c97c476d6636c7b6f98bd8bc8462b"}, 422 | {file = "biopython-1.78-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:010142a8ec2549ff0649edd497658964ef1a18eefdb9fd942ec1e81b292ce2d9"}, 423 | {file = "biopython-1.78-cp38-cp38-manylinux1_i686.whl", hash = "sha256:194528eda6856a4c68f840ca0bcc9b544a5edee3548b97521084e7ac38c833ca"}, 424 | {file = "biopython-1.78-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ada611f12ee3b0bef7308ef41ee7b94898613b369ab44e0268d74bd1d6a06920"}, 425 | {file = "biopython-1.78-cp38-cp38-win32.whl", hash = "sha256:48d424453a5512a1d1d41a4acabdfe5291da1f491a2d3606f2b0e4fbd63aeda6"}, 426 | {file = "biopython-1.78-cp38-cp38-win_amd64.whl", hash = "sha256:2bd5a630be2a8e593094f7b1717fc962eda8931b68542b97fbf9bd8e2ac1e08d"}, 427 | {file = "biopython-1.78.tar.gz", hash = "sha256:1ee0a0b6c2376680fea6642d5080baa419fd73df104a62d58a8baf7a8bbe4564"}, 428 | ] 429 | certifi = [ 430 | {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, 431 | {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, 432 | ] 433 | cffi = [ 434 | {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, 435 | {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, 436 | {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, 437 | {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, 438 | {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, 439 | {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, 440 | {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, 441 | {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, 442 | {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, 443 | {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, 444 | {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, 445 | {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, 446 | {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, 447 | {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, 448 | {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, 449 | {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, 450 | {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, 451 | {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, 452 | {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, 453 | {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, 454 | {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, 455 | {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, 456 | {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, 457 | {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, 458 | {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, 459 | {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, 460 | {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, 461 | {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, 462 | {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, 463 | {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, 464 | {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, 465 | {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, 466 | {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, 467 | {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, 468 | {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, 469 | {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, 470 | {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, 471 | ] 472 | chardet = [ 473 | {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, 474 | {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, 475 | ] 476 | codecov = [ 477 | {file = "codecov-2.1.11-py2.py3-none-any.whl", hash = "sha256:ba8553a82942ce37d4da92b70ffd6d54cf635fc1793ab0a7dc3fecd6ebfb3df8"}, 478 | {file = "codecov-2.1.11-py3.8.egg", hash = "sha256:e95901d4350e99fc39c8353efa450050d2446c55bac91d90fcfd2354e19a6aef"}, 479 | {file = "codecov-2.1.11.tar.gz", hash = "sha256:6cde272454009d27355f9434f4e49f238c0273b216beda8472a65dc4957f473b"}, 480 | ] 481 | colorama = [ 482 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 483 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 484 | ] 485 | coverage = [ 486 | {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, 487 | {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, 488 | {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, 489 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, 490 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, 491 | {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, 492 | {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, 493 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, 494 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, 495 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, 496 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, 497 | {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, 498 | {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, 499 | {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, 500 | {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, 501 | {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, 502 | {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, 503 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, 504 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, 505 | {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, 506 | {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, 507 | {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, 508 | {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, 509 | {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, 510 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, 511 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, 512 | {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, 513 | {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, 514 | {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, 515 | {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, 516 | {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, 517 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, 518 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, 519 | {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, 520 | {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, 521 | {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, 522 | {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, 523 | {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, 524 | {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, 525 | {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, 526 | {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, 527 | {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, 528 | {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, 529 | {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, 530 | {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, 531 | {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, 532 | {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, 533 | {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, 534 | {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, 535 | {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, 536 | {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, 537 | {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, 538 | ] 539 | idna = [ 540 | {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, 541 | {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, 542 | ] 543 | importlib-metadata = [ 544 | {file = "importlib_metadata-3.7.2-py3-none-any.whl", hash = "sha256:407d13f55dc6f2a844e62325d18ad7019a436c4bfcaee34cda35f2be6e7c3e34"}, 545 | {file = "importlib_metadata-3.7.2.tar.gz", hash = "sha256:18d5ff601069f98d5d605b6a4b50c18a34811d655c55548adc833e687289acde"}, 546 | ] 547 | iniconfig = [ 548 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 549 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 550 | ] 551 | jinja2 = [ 552 | {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, 553 | {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, 554 | ] 555 | markupsafe = [ 556 | {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, 557 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, 558 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, 559 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, 560 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, 561 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, 562 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, 563 | {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, 564 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, 565 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, 566 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, 567 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, 568 | {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, 569 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, 570 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, 571 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, 572 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, 573 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, 574 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, 575 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, 576 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, 577 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, 578 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, 579 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, 580 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, 581 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, 582 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, 583 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, 584 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, 585 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, 586 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, 587 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, 588 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, 589 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, 590 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, 591 | {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, 592 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, 593 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, 594 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, 595 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, 596 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, 597 | {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, 598 | {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, 599 | {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, 600 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, 601 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, 602 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, 603 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, 604 | {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, 605 | {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, 606 | {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, 607 | {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, 608 | ] 609 | numpy = [ 610 | {file = "numpy-1.19.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc6bd4fd593cb261332568485e20a0712883cf631f6f5e8e86a52caa8b2b50ff"}, 611 | {file = "numpy-1.19.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aeb9ed923be74e659984e321f609b9ba54a48354bfd168d21a2b072ed1e833ea"}, 612 | {file = "numpy-1.19.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8b5e972b43c8fc27d56550b4120fe6257fdc15f9301914380b27f74856299fea"}, 613 | {file = "numpy-1.19.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:43d4c81d5ffdff6bae58d66a3cd7f54a7acd9a0e7b18d97abb255defc09e3140"}, 614 | {file = "numpy-1.19.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a4646724fba402aa7504cd48b4b50e783296b5e10a524c7a6da62e4a8ac9698d"}, 615 | {file = "numpy-1.19.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:2e55195bc1c6b705bfd8ad6f288b38b11b1af32f3c8289d6c50d47f950c12e76"}, 616 | {file = "numpy-1.19.5-cp36-cp36m-win32.whl", hash = "sha256:39b70c19ec771805081578cc936bbe95336798b7edf4732ed102e7a43ec5c07a"}, 617 | {file = "numpy-1.19.5-cp36-cp36m-win_amd64.whl", hash = "sha256:dbd18bcf4889b720ba13a27ec2f2aac1981bd41203b3a3b27ba7a33f88ae4827"}, 618 | {file = "numpy-1.19.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:603aa0706be710eea8884af807b1b3bc9fb2e49b9f4da439e76000f3b3c6ff0f"}, 619 | {file = "numpy-1.19.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cae865b1cae1ec2663d8ea56ef6ff185bad091a5e33ebbadd98de2cfa3fa668f"}, 620 | {file = "numpy-1.19.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:36674959eed6957e61f11c912f71e78857a8d0604171dfd9ce9ad5cbf41c511c"}, 621 | {file = "numpy-1.19.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:06fab248a088e439402141ea04f0fffb203723148f6ee791e9c75b3e9e82f080"}, 622 | {file = "numpy-1.19.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6149a185cece5ee78d1d196938b2a8f9d09f5a5ebfbba66969302a778d5ddd1d"}, 623 | {file = "numpy-1.19.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:50a4a0ad0111cc1b71fa32dedd05fa239f7fb5a43a40663269bb5dc7877cfd28"}, 624 | {file = "numpy-1.19.5-cp37-cp37m-win32.whl", hash = "sha256:d051ec1c64b85ecc69531e1137bb9751c6830772ee5c1c426dbcfe98ef5788d7"}, 625 | {file = "numpy-1.19.5-cp37-cp37m-win_amd64.whl", hash = "sha256:a12ff4c8ddfee61f90a1633a4c4afd3f7bcb32b11c52026c92a12e1325922d0d"}, 626 | {file = "numpy-1.19.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf2402002d3d9f91c8b01e66fbb436a4ed01c6498fffed0e4c7566da1d40ee1e"}, 627 | {file = "numpy-1.19.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1ded4fce9cfaaf24e7a0ab51b7a87be9038ea1ace7f34b841fe3b6894c721d1c"}, 628 | {file = "numpy-1.19.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:012426a41bc9ab63bb158635aecccc7610e3eff5d31d1eb43bc099debc979d94"}, 629 | {file = "numpy-1.19.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:759e4095edc3c1b3ac031f34d9459fa781777a93ccc633a472a5468587a190ff"}, 630 | {file = "numpy-1.19.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a9d17f2be3b427fbb2bce61e596cf555d6f8a56c222bd2ca148baeeb5e5c783c"}, 631 | {file = "numpy-1.19.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:99abf4f353c3d1a0c7a5f27699482c987cf663b1eac20db59b8c7b061eabd7fc"}, 632 | {file = "numpy-1.19.5-cp38-cp38-win32.whl", hash = "sha256:384ec0463d1c2671170901994aeb6dce126de0a95ccc3976c43b0038a37329c2"}, 633 | {file = "numpy-1.19.5-cp38-cp38-win_amd64.whl", hash = "sha256:811daee36a58dc79cf3d8bdd4a490e4277d0e4b7d103a001a4e73ddb48e7e6aa"}, 634 | {file = "numpy-1.19.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c843b3f50d1ab7361ca4f0b3639bf691569493a56808a0b0c54a051d260b7dbd"}, 635 | {file = "numpy-1.19.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d6631f2e867676b13026e2846180e2c13c1e11289d67da08d71cacb2cd93d4aa"}, 636 | {file = "numpy-1.19.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7fb43004bce0ca31d8f13a6eb5e943fa73371381e53f7074ed21a4cb786c32f8"}, 637 | {file = "numpy-1.19.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2ea52bd92ab9f768cc64a4c3ef8f4b2580a17af0a5436f6126b08efbd1838371"}, 638 | {file = "numpy-1.19.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:400580cbd3cff6ffa6293df2278c75aef2d58d8d93d3c5614cd67981dae68ceb"}, 639 | {file = "numpy-1.19.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:df609c82f18c5b9f6cb97271f03315ff0dbe481a2a02e56aeb1b1a985ce38e60"}, 640 | {file = "numpy-1.19.5-cp39-cp39-win32.whl", hash = "sha256:ab83f24d5c52d60dbc8cd0528759532736b56db58adaa7b5f1f76ad551416a1e"}, 641 | {file = "numpy-1.19.5-cp39-cp39-win_amd64.whl", hash = "sha256:0eef32ca3132a48e43f6a0f5a82cb508f22ce5a3d6f67a8329c81c8e226d3f6e"}, 642 | {file = "numpy-1.19.5-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a0d53e51a6cb6f0d9082decb7a4cb6dfb33055308c4c44f53103c073f649af73"}, 643 | {file = "numpy-1.19.5.zip", hash = "sha256:a76f502430dd98d7546e1ea2250a7360c065a5fdea52b2dffe8ae7180909b6f4"}, 644 | ] 645 | packaging = [ 646 | {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, 647 | {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, 648 | ] 649 | pandas = [ 650 | {file = "pandas-1.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:74a470d349d52b9d00a2ba192ae1ee22155bb0a300fd1ccb2961006c3fa98ed3"}, 651 | {file = "pandas-1.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e2140e1bbf9c46db9936ee70f4be6584d15ff8dc3dfff1da022d71227d53bad3"}, 652 | {file = "pandas-1.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d10e83866b48c0cdb83281f786564e2a2b51a7ae7b8a950c3442ad3c9e36b48c"}, 653 | {file = "pandas-1.0.1-cp36-cp36m-win32.whl", hash = "sha256:303827f0bb40ff610fbada5b12d50014811efcc37aaf6ef03202dc3054bfdda1"}, 654 | {file = "pandas-1.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6f38969e2325056f9959efbe06c27aa2e94dd35382265ad0703681d993036052"}, 655 | {file = "pandas-1.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2530aea4fe46e8df7829c3f05e0a0f821c893885d53cb8ac9b89cc67c143448c"}, 656 | {file = "pandas-1.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3b019e3ea9f5d0cfee0efabae2cfd3976874e90bcc3e97b29600e5a9b345ae3d"}, 657 | {file = "pandas-1.0.1-cp37-cp37m-win32.whl", hash = "sha256:23e177d43e4bf68950b0f8788b6a2fef2f478f4ec94883acb627b9264522a98a"}, 658 | {file = "pandas-1.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a9fbe41663416bb70ed05f4e16c5f377519c0dc292ba9aa45f5356e37df03a38"}, 659 | {file = "pandas-1.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f9a509f6f11fa8b9313002ebdf6f690a7aa1dd91efd95d90185371a0d68220e"}, 660 | {file = "pandas-1.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:942b5d04762feb0e55b2ad97ce2b254a0ffdd344b56493b04a627266e24f2d82"}, 661 | {file = "pandas-1.0.1-cp38-cp38-win32.whl", hash = "sha256:7d77034e402165b947f43050a8a415aa3205abfed38d127ea66e57a2b7b5a9e0"}, 662 | {file = "pandas-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:5036d4009012a44aa3e50173e482b664c1fae36decd277c49e453463798eca4e"}, 663 | {file = "pandas-1.0.1.tar.gz", hash = "sha256:3c07765308f091d81b6735d4f2242bb43c332cc3461cae60543df6b10967fe27"}, 664 | ] 665 | pillow = [ 666 | {file = "Pillow-8.1.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:5cf03b9534aca63b192856aa601c68d0764810857786ea5da652581f3a44c2b0"}, 667 | {file = "Pillow-8.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f91b50ad88048d795c0ad004abbe1390aa1882073b1dca10bfd55d0b8cf18ec5"}, 668 | {file = "Pillow-8.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5762ebb4436f46b566fc6351d67a9b5386b5e5de4e58fdaa18a1c83e0e20f1a8"}, 669 | {file = "Pillow-8.1.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e2cd8ac157c1e5ae88b6dd790648ee5d2777e76f1e5c7d184eaddb2938594f34"}, 670 | {file = "Pillow-8.1.2-cp36-cp36m-win32.whl", hash = "sha256:72027ebf682abc9bafd93b43edc44279f641e8996fb2945104471419113cfc71"}, 671 | {file = "Pillow-8.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d1d6bca39bb6dd94fba23cdb3eeaea5e30c7717c5343004d900e2a63b132c341"}, 672 | {file = "Pillow-8.1.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:90882c6f084ef68b71bba190209a734bf90abb82ab5e8f64444c71d5974008c6"}, 673 | {file = "Pillow-8.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:89e4c757a91b8c55d97c91fa09c69b3677c227b942fa749e9a66eef602f59c28"}, 674 | {file = "Pillow-8.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8c4e32218c764bc27fe49b7328195579581aa419920edcc321c4cb877c65258d"}, 675 | {file = "Pillow-8.1.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a01da2c266d9868c4f91a9c6faf47a251f23b9a862dce81d2ff583135206f5be"}, 676 | {file = "Pillow-8.1.2-cp37-cp37m-win32.whl", hash = "sha256:30d33a1a6400132e6f521640dd3f64578ac9bfb79a619416d7e8802b4ce1dd55"}, 677 | {file = "Pillow-8.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:71b01ee69e7df527439d7752a2ce8fb89e19a32df484a308eca3e81f673d3a03"}, 678 | {file = "Pillow-8.1.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:5a2d957eb4aba9d48170b8fe6538ec1fbc2119ffe6373782c03d8acad3323f2e"}, 679 | {file = "Pillow-8.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:87f42c976f91ca2fc21a3293e25bd3cd895918597db1b95b93cbd949f7d019ce"}, 680 | {file = "Pillow-8.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:15306d71a1e96d7e271fd2a0737038b5a92ca2978d2e38b6ced7966583e3d5af"}, 681 | {file = "Pillow-8.1.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:71f31ee4df3d5e0b366dd362007740106d3210fb6a56ec4b581a5324ba254f06"}, 682 | {file = "Pillow-8.1.2-cp38-cp38-win32.whl", hash = "sha256:98afcac3205d31ab6a10c5006b0cf040d0026a68ec051edd3517b776c1d78b09"}, 683 | {file = "Pillow-8.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:328240f7dddf77783e72d5ed79899a6b48bc6681f8d1f6001f55933cb4905060"}, 684 | {file = "Pillow-8.1.2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bead24c0ae3f1f6afcb915a057943ccf65fc755d11a1410a909c1fefb6c06ad1"}, 685 | {file = "Pillow-8.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81b3716cc9744ffdf76b39afb6247eae754186838cedad0b0ac63b2571253fe6"}, 686 | {file = "Pillow-8.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:63cd413ac52ee3f67057223d363f4f82ce966e64906aea046daf46695e3c8238"}, 687 | {file = "Pillow-8.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:8565355a29655b28fdc2c666fd9a3890fe5edc6639d128814fafecfae2d70910"}, 688 | {file = "Pillow-8.1.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1940fc4d361f9cc7e558d6f56ff38d7351b53052fd7911f4b60cd7bc091ea3b1"}, 689 | {file = "Pillow-8.1.2-cp39-cp39-win32.whl", hash = "sha256:46c2bcf8e1e75d154e78417b3e3c64e96def738c2a25435e74909e127a8cba5e"}, 690 | {file = "Pillow-8.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:aeab4cd016e11e7aa5cfc49dcff8e51561fa64818a0be86efa82c7038e9369d0"}, 691 | {file = "Pillow-8.1.2-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:74cd9aa648ed6dd25e572453eb09b08817a1e3d9f8d1bd4d8403d99e42ea790b"}, 692 | {file = "Pillow-8.1.2-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:e5739ae63636a52b706a0facec77b2b58e485637e1638202556156e424a02dc2"}, 693 | {file = "Pillow-8.1.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:903293320efe2466c1ab3509a33d6b866dc850cfd0c5d9cc92632014cec185fb"}, 694 | {file = "Pillow-8.1.2-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:5daba2b40782c1c5157a788ec4454067c6616f5a0c1b70e26ac326a880c2d328"}, 695 | {file = "Pillow-8.1.2-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:1f93f2fe211f1ef75e6f589327f4d4f8545d5c8e826231b042b483d8383e8a7c"}, 696 | {file = "Pillow-8.1.2-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:6efac40344d8f668b6c4533ae02a48d52fd852ef0654cc6f19f6ac146399c733"}, 697 | {file = "Pillow-8.1.2-pp37-pypy37_pp73-win32.whl", hash = "sha256:f36c3ff63d6fc509ce599a2f5b0d0732189eed653420e7294c039d342c6e204a"}, 698 | {file = "Pillow-8.1.2.tar.gz", hash = "sha256:b07c660e014852d98a00a91adfbe25033898a9d90a8f39beb2437d22a203fc44"}, 699 | ] 700 | pluggy = [ 701 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 702 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 703 | ] 704 | py = [ 705 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 706 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 707 | ] 708 | pycparser = [ 709 | {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, 710 | {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, 711 | ] 712 | pyparsing = [ 713 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 714 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 715 | ] 716 | pytest = [ 717 | {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"}, 718 | {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"}, 719 | ] 720 | pytest-cov = [ 721 | {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, 722 | {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"}, 723 | ] 724 | python-dateutil = [ 725 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, 726 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, 727 | ] 728 | pytz = [ 729 | {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, 730 | {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, 731 | ] 732 | reportlab = [ 733 | {file = "reportlab-3.5.65-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:fd6712a8a6dca12181a3a12316f97810927861e77f2a98029efd2c5cfc8546dc"}, 734 | {file = "reportlab-3.5.65-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:ee711804acdaf3ea7f0f2cd27f19478af993e730df8c8d923a678eb0e2572fba"}, 735 | {file = "reportlab-3.5.65-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4c42e85851f969e21fa4d6414587b7544e877ce685e2495d7d422589c70b6281"}, 736 | {file = "reportlab-3.5.65-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d8fefd07072bfae2715283a821fb1acf8fc4946cf925509d5cc2af791c611809"}, 737 | {file = "reportlab-3.5.65-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:bdf751289efee4891f4f354ce9122da8de8258a40f328b3f11540c4888363337"}, 738 | {file = "reportlab-3.5.65-cp36-cp36m-win32.whl", hash = "sha256:f0634740b099b69caed081acd89692996b5504c59f86f39781b6bebc82b267f5"}, 739 | {file = "reportlab-3.5.65-cp36-cp36m-win_amd64.whl", hash = "sha256:d810bffd4bcd50fdcb2bab0d1fe9ea4e6187ed5237687e41c6ade6c884b00c1e"}, 740 | {file = "reportlab-3.5.65-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:46745826657d35f86843487f4bc6f6f805f61260428f8ee13642bf6372f9df55"}, 741 | {file = "reportlab-3.5.65-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bc62187181582772688d65c557ad6a40a4c3bb8d1f74de463d35ea81983e9b75"}, 742 | {file = "reportlab-3.5.65-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:58bec163f727c1c60515fc4704a961b3b4ccf2c76b4e6ec1a457ea7ed0c2d756"}, 743 | {file = "reportlab-3.5.65-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d92834993bf998853a04946729266a3276965e7b13f7423212f1c1abdfc4a1c7"}, 744 | {file = "reportlab-3.5.65-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:9ec95808b742ce70c1dab28b2c5bef9093816b92315b948419c2c6968658f9cc"}, 745 | {file = "reportlab-3.5.65-cp37-cp37m-win32.whl", hash = "sha256:b9494986f35d82350b0ce0c29704a49a3945421b789dff92e93fbd3de554fa34"}, 746 | {file = "reportlab-3.5.65-cp37-cp37m-win_amd64.whl", hash = "sha256:07f9d9c0360cb8fc780ca05264faa68b90583cd28dbdf2cda6bda34379b6e66c"}, 747 | {file = "reportlab-3.5.65-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:81898de0a0be2c8318468ae0ae1590f828805e9b7fd68e5a50667dce8b942171"}, 748 | {file = "reportlab-3.5.65-cp38-cp38-manylinux1_i686.whl", hash = "sha256:99aeee49a61c85f1af1087e9e418f3d0c2352c4dd0f0abbfac17ae6c467185aa"}, 749 | {file = "reportlab-3.5.65-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3ec70873d99c14570e2a9c44b86c8c01526871e7af5ee4b2855246db15cb0c9f"}, 750 | {file = "reportlab-3.5.65-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c12432575c793b8cd8552fddc219bbf2813541c64d02854ae345a108fb875b9d"}, 751 | {file = "reportlab-3.5.65-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2cf692ae7af995b499a31a3f58f2001d98e310e03f74812bcb97a08078239c0"}, 752 | {file = "reportlab-3.5.65-cp38-cp38-win32.whl", hash = "sha256:f92388e30bf6b5d2eceb3d7b05ee2df856635f74ce7d950a8f45d2b70c685a5b"}, 753 | {file = "reportlab-3.5.65-cp38-cp38-win_amd64.whl", hash = "sha256:6f007142f2b166f52cbb3e5d23319e3e496c429831e53b904e6db28c3370f279"}, 754 | {file = "reportlab-3.5.65-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:8707cc21a769150154bf4634dca6e9581ae24a05f0fb81a84fcc1143b1cbbfde"}, 755 | {file = "reportlab-3.5.65-cp39-cp39-manylinux1_i686.whl", hash = "sha256:27a831da0d17153e33c985bd7a88307e206c5a28778cddb755d5372598d12637"}, 756 | {file = "reportlab-3.5.65-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fe5d98cdac07dd702bcd49f5723aacdd0af8c84d70fc82a5cc3781e52aedad52"}, 757 | {file = "reportlab-3.5.65-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:ba2d10f368c9ea1e76c84b3bb6b9982eb5a8f243c434e821c505b75ca8d85852"}, 758 | {file = "reportlab-3.5.65-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:289539f7888239343ef7ebcd30c55e6204ef78d5f70e1547fdeb854a2da8bfa1"}, 759 | {file = "reportlab-3.5.65-cp39-cp39-win32.whl", hash = "sha256:cdf8ff72cd6fa9303744c8409fb81ef7720da2e034c369762c2fdf496462179e"}, 760 | {file = "reportlab-3.5.65-cp39-cp39-win_amd64.whl", hash = "sha256:4a784ecdf3008f533e5a032b96c395e8592ed5e679baaf5ef4dcc136b01c72e9"}, 761 | {file = "reportlab-3.5.65.tar.gz", hash = "sha256:b2c7eedb4d19db63301c27ad1076086a099fd4c8ca0a6f62f6e9ed749fa5908f"}, 762 | ] 763 | requests = [ 764 | {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, 765 | {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, 766 | ] 767 | rpy2 = [ 768 | {file = "rpy2-3.0.0.tar.gz", hash = "sha256:34efc2935d9015527837d6b1de29641863d184b19d39ad415d5384be8a015bce"}, 769 | ] 770 | simplegeneric = [ 771 | {file = "simplegeneric-0.8.1.zip", hash = "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173"}, 772 | ] 773 | six = [ 774 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 775 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 776 | ] 777 | toml = [ 778 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 779 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 780 | ] 781 | typing-extensions = [ 782 | {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, 783 | {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, 784 | {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, 785 | ] 786 | tzlocal = [ 787 | {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"}, 788 | {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, 789 | ] 790 | urllib3 = [ 791 | {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, 792 | {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, 793 | ] 794 | zipp = [ 795 | {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, 796 | {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, 797 | ] 798 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "diffexpr" 3 | version = "0.1.0" 4 | description = "DESeq2 port in python via rpy2" 5 | authors = ["Douglas Wu "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.6.1" 10 | rpy2 = "3.0.0" 11 | pandas = "1.0.1" 12 | tzlocal = "^2.1" 13 | biopython = "^1.78" 14 | reportlab = "^3.5.65" 15 | codecov = "^2.1.11" 16 | 17 | [tool.poetry.dev-dependencies] 18 | pytest = "6.2.2" 19 | pytest-cov = "2.11.1" 20 | 21 | [build-system] 22 | requires = ["poetry-core>=1.0.0"] 23 | build-backend = "poetry.core.masonry.api" 24 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file may be used to create an environment using: 2 | # $ conda create --name --file 3 | # platform: osx-64 4 | appnope=0.1.0=py38h32f6830_1001 5 | backcall=0.1.0=py_0 6 | ca-certificates=2020.4.5.1=hecc5488_0 7 | certifi=2020.4.5.1=py38h32f6830_0 8 | decorator=4.4.2=py_0 9 | entrypoints=0.3=py38h32f6830_1001 10 | ipykernel=5.3.0=py38h23f93f0_0 11 | ipython=7.15.0=py38h32f6830_0 12 | ipython_genutils=0.2.0=py_1 13 | jedi=0.17.0=py38h32f6830_0 14 | jupyter_client=6.1.3=py_0 15 | jupyter_core=4.6.3=py38h32f6830_1 16 | libcxx=10.0.0=h1af66ff_2 17 | libffi=3.2.1=h4a8c4bd_1007 18 | libsodium=1.0.17=h01d97ff_0 19 | ncurses=6.1=h0a44026_1002 20 | openssl=1.1.1g=h0b31af3_0 21 | parso=0.7.0=pyh9f0ad1d_0 22 | pexpect=4.8.0=py38h32f6830_1 23 | pickleshare=0.7.5=py38h32f6830_1001 24 | pip=20.1.1=py_1 25 | prompt-toolkit=3.0.5=py_0 26 | ptyprocess=0.6.0=py_1001 27 | pygments=2.6.1=py_0 28 | python=3.8.2=hd5f0129_7_cpython 29 | python-dateutil=2.8.1=py_0 30 | python_abi=3.8=1_cp38 31 | pyzmq=19.0.1=py38h1fcdcd6_0 32 | readline=8.0=hcfe32e1_0 33 | setuptools=47.1.1=py38h32f6830_0 34 | six=1.15.0=pyh9f0ad1d_0 35 | sqlite=3.30.1=h93121df_0 36 | tk=8.6.10=hbbe82c9_0 37 | tornado=6.0.4=py38h64e0658_1 38 | traitlets=4.3.3=py38h32f6830_1 39 | wcwidth=0.2.2=pyh9f0ad1d_0 40 | wheel=0.34.2=py_1 41 | xz=5.2.5=h0b31af3_0 42 | zeromq=4.3.2=h6de7cb9_2 43 | zlib=1.2.11=h0b31af3_1006 44 | -------------------------------------------------------------------------------- /setup.R: -------------------------------------------------------------------------------- 1 | install.packages(c('BiocManager','Hmisc', 'RcppEigen','RcppNumerical'), 2 | dependencies='Depends', 3 | repo = "http://cran.us.r-project.org") 4 | BiocManager::install(c('DESeq2','apeglm', 'rhdf5', 'tximport')) 5 | 6 | 7 | library(DESeq2) 8 | library(apeglm) 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup, Extension 2 | 3 | 4 | def check_package(package = 'rpy2'): 5 | import importlib 6 | try: 7 | importlib.import_module(package) 8 | except ImportError as e: 9 | raise ImportError("Requires {package} to " 10 | "be installed before running setup.py (pip install {package})"\ 11 | .format(package = package)) 12 | finally: 13 | pass 14 | 15 | _ = [check_package(p) for p in ['rpy2','numpy','pandas']] 16 | 17 | 18 | #try: 19 | # import rpy2 20 | #except ImportError: 21 | # raise ImportError("Requires rpy2 to " 22 | # "be installed before running setup.py (pip install cython)") 23 | #try: 24 | # import numpy as np 25 | #except ImportError: 26 | # raise ImportError("Requires numpy to " 27 | # "be installed before running setup.py (pip install numpy)") 28 | #try: 29 | # import pandas 30 | #except ImportError: 31 | # raise ImportError("Requires pandas to " 32 | # "be installed before running setup.py (pip install pandas)") 33 | 34 | setup( 35 | name = 'diffexp', 36 | version = '0.1', 37 | description = 'Tools for porting differential expression DESeq to python', 38 | url = '', 39 | author = 'Douglas C. Wu', 40 | author_email = 'wckdouglas@gmail.com', 41 | license = 'MIT', 42 | packages = find_packages(), 43 | zip_safe = False, 44 | install_requires=[ 45 | 'pandas', 46 | 'rpy2'] 47 | ) 48 | -------------------------------------------------------------------------------- /test/data/ercc.tsv: -------------------------------------------------------------------------------- 1 | id A_1 A_2 A_3 B_1 B_2 B_3 2 | ERCC-00002 111461 106261 107547 333944 199252 186947 3 | ERCC-00003 6735 5387 5265 13937 8584 8596 4 | ERCC-00004 17673 13983 15462 5065 3222 3353 5 | ERCC-00009 4669 4431 4211 6939 4155 3647 6 | ERCC-00012 0 2 0 0 0 0 7 | ERCC-00013 3 0 3 14 6 5 8 | ERCC-00014 36 39 34 93 78 68 9 | ERCC-00016 0 3 1 2 1 1 10 | ERCC-00017 1 0 0 0 0 0 11 | ERCC-00019 115 95 103 28 33 20 12 | ERCC-00022 631 633 712 1933 1208 1180 13 | ERCC-00024 1 0 0 3 1 1 14 | ERCC-00025 635 587 581 1000 632 559 15 | ERCC-00028 18 10 14 10 4 8 16 | ERCC-00031 17 9 13 14 9 5 17 | ERCC-00033 31 31 45 11 4 2 18 | ERCC-00034 73 66 64 128 73 67 19 | ERCC-00035 553 530 450 846 527 475 20 | ERCC-00039 20 18 17 49 10 14 21 | ERCC-00040 1 0 1 12 4 2 22 | ERCC-00041 1 0 1 0 1 0 23 | ERCC-00042 2205 1999 1943 2828 1675 1737 24 | ERCC-00043 4350 4039 3831 9568 6165 5766 25 | ERCC-00044 616 635 575 1462 861 815 26 | ERCC-00046 12373 11902 12537 29554 18817 17744 27 | ERCC-00048 0 0 1 1 1 0 28 | ERCC-00051 68 48 50 56 35 28 29 | ERCC-00053 191 153 155 211 115 116 30 | ERCC-00054 11 15 10 14 23 8 31 | ERCC-00057 6 0 0 2 0 0 32 | ERCC-00058 8 9 13 26 15 9 33 | ERCC-00059 54 31 27 134 91 81 34 | ERCC-00060 1057 949 1064 1350 803 682 35 | ERCC-00061 0 0 0 3 0 0 36 | ERCC-00062 443 369 346 100 70 64 37 | ERCC-00067 10 6 7 11 8 5 38 | ERCC-00069 16 9 12 33 23 16 39 | ERCC-00071 210 170 223 418 262 241 40 | ERCC-00073 8 5 3 6 3 2 41 | ERCC-00074 45268 41189 38646 87012 53603 50788 42 | ERCC-00075 0 0 0 0 0 0 43 | ERCC-00076 543 533 561 1375 818 807 44 | ERCC-00077 9 7 7 30 5 8 45 | ERCC-00078 252 179 169 597 344 339 46 | ERCC-00079 235 201 199 592 403 388 47 | ERCC-00081 0 1 1 0 0 1 48 | ERCC-00083 0 0 0 0 0 0 49 | ERCC-00084 181 197 182 475 264 223 50 | ERCC-00085 30 11 26 19 8 12 51 | ERCC-00086 1 3 0 3 2 1 52 | ERCC-00092 1649 1507 1698 581 358 391 53 | ERCC-00095 368 301 303 113 63 57 54 | ERCC-00096 92836 89607 89178 144482 86830 85932 55 | ERCC-00097 5 3 1 1 2 1 56 | ERCC-00098 0 0 1 8 0 0 57 | ERCC-00099 124 130 107 232 172 123 58 | ERCC-00104 6 1 1 1 1 2 59 | ERCC-00108 4936 4349 4727 1954 1019 1080 60 | ERCC-00109 3 2 1 6 1 0 61 | ERCC-00111 1804 1626 1854 4007 2349 2461 62 | ERCC-00112 711 705 773 2326 1223 1321 63 | ERCC-00113 19212 18555 17141 41027 25217 23896 64 | ERCC-00116 5947 5823 5669 2300 1424 1256 65 | ERCC-00117 0 0 0 2 0 1 66 | ERCC-00120 0 1 0 3 2 1 67 | ERCC-00123 0 0 0 0 0 0 68 | ERCC-00126 41 35 32 49 49 45 69 | ERCC-00130 174360 163701 149265 58753 36667 31609 70 | ERCC-00131 475 422 460 149 118 116 71 | ERCC-00134 4 5 4 1 0 0 72 | ERCC-00136 8585 8136 7795 3065 1826 1601 73 | ERCC-00137 1 0 2 4 3 3 74 | ERCC-00138 1 4 1 0 1 2 75 | ERCC-00142 2 0 0 0 4 0 76 | ERCC-00143 15 20 14 28 24 29 77 | ERCC-00144 79 64 76 38 10 19 78 | ERCC-00145 4210 4116 3844 8384 5364 4846 79 | ERCC-00147 7 2 9 0 1 1 80 | ERCC-00148 45 33 19 53 28 20 81 | ERCC-00150 12 11 15 15 10 17 82 | ERCC-00154 10 21 19 7 9 7 83 | ERCC-00156 1 0 0 0 1 0 84 | ERCC-00157 65 65 59 142 100 88 85 | ERCC-00158 6 2 1 2 6 3 86 | ERCC-00160 68 65 62 210 131 113 87 | ERCC-00162 113 71 116 195 111 143 88 | ERCC-00163 38 56 33 130 65 72 89 | ERCC-00164 2 1 5 5 1 2 90 | ERCC-00165 261 214 240 784 464 427 91 | ERCC-00168 1 3 0 7 4 1 92 | ERCC-00170 133 129 113 41 10 28 93 | ERCC-00171 8438 8347 7363 11736 7372 6009 94 | -------------------------------------------------------------------------------- /test/data/quant1/abundance.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wckdouglas/diffexpr/c78aba6a8ec7b26331f7e258d5e2cf1c5d559eed/test/data/quant1/abundance.h5 -------------------------------------------------------------------------------- /test/data/quant2/abundance.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wckdouglas/diffexpr/c78aba6a8ec7b26331f7e258d5e2cf1c5d559eed/test/data/quant2/abundance.h5 -------------------------------------------------------------------------------- /test/deseq.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | library(DESeq2) 4 | 5 | df <- read.table('data/ercc.tsv', header=T) 6 | 7 | sample_df <- data.frame(samplename = names(df)[2:ncol(df)]) 8 | sample_df$sample <- substr(sample_df$samplename, 1,1) 9 | sample_df$batch <- substr(sample_df$samplename, 3,3) 10 | row.names(sample_df) <- sample_df$samplename 11 | sample_df$sample <- factor(sample_df$sample) 12 | 13 | 14 | count_matrix <- subset(df, , -id) 15 | row.names(count_matrix) <- df$id 16 | dds <- DESeqDataSetFromMatrix(countData = count_matrix, 17 | colData = sample_df, 18 | design = ~ batch + sample) 19 | dds <- DESeq(dds) 20 | res <- results(dds) 21 | res$id = row.names(res) 22 | write.table(data.frame(res), 23 | file = 'data/R_deseq.tsv', 24 | sep='\t', 25 | row.names=F, 26 | quote=F) 27 | 28 | dds <- DESeq(dds, test="LRT", reduced=~ batch) 29 | res <- results(dds) 30 | res$id = row.names(res) 31 | write.table(data.frame(res), 32 | file = 'data/R_deseq_reduced.tsv', 33 | sep='\t', 34 | row.names=F, 35 | quote=F) -------------------------------------------------------------------------------- /test/test_deseq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import re 4 | import shlex 5 | import subprocess 6 | import warnings 7 | 8 | import numpy as np 9 | import pandas as pd 10 | import pytest 11 | 12 | from diffexpr.py_deseq import py_DESeq2 13 | 14 | warnings.filterwarnings("ignore") 15 | test_data_path = os.path.dirname(os.path.realpath(__file__)) + "/data" 16 | 17 | 18 | @pytest.fixture(scope="module", name="run_r") 19 | def fixture_run_r(): 20 | os.chdir(os.path.dirname(test_data_path)) 21 | os.system("Rscript deseq.R") 22 | 23 | 24 | def count_matrix(): 25 | """ 26 | id A_1 A_2 A_3 B_1 B_2 B_3 27 | 0 ERCC-00002 111461 106261 107547 333944 199252 186947 28 | 1 ERCC-00003 6735 5387 5265 13937 8584 8596 29 | 2 ERCC-00004 17673 13983 15462 5065 3222 3353 30 | 3 ERCC-00009 4669 4431 4211 6939 4155 3647 31 | 4 ERCC-00012 0 2 0 0 0 0 32 | """ 33 | return pd.read_csv(test_data_path + "/ercc.tsv", sep="\t") 34 | 35 | 36 | def sample_metadata(): 37 | df = count_matrix() 38 | sample_df = ( 39 | pd.DataFrame({"samplename": df.columns}) 40 | .query('samplename != "id"') 41 | .assign(sample=lambda d: d["samplename"].str.extract("([AB])_", expand=False)) 42 | .assign(batch=lambda d: d["samplename"].str.extract("_([123])", expand=False)) 43 | .pipe(lambda d: d.set_index(d["samplename"])) 44 | # this duplicates the "samplename" column into index, needed for `setup_deseq` 45 | ) 46 | return sample_df 47 | 48 | 49 | @pytest.fixture(scope="module") 50 | def setup_deseq(): 51 | df = count_matrix() 52 | sample_df = sample_metadata() 53 | 54 | dds = py_DESeq2( 55 | count_matrix=df, 56 | design_matrix=sample_df, 57 | design_formula="~ batch + sample", 58 | gene_column="id", 59 | threads=2, 60 | ) 61 | 62 | return df, dds 63 | 64 | 65 | @pytest.mark.parametrize("matrix", [("count"), ("metadata")]) 66 | def test_deseq_not_dataframe_exception(matrix): 67 | df = pd.read_csv(test_data_path + "/ercc.tsv", sep="\t") 68 | sample_df = sample_metadata() 69 | if matrix == "count": 70 | df = df.values 71 | elif matrix == "metadata": 72 | sample_df = sample_df.values 73 | 74 | with pytest.raises(ValueError, match="should be pd.DataFrame type"): 75 | py_DESeq2( 76 | count_matrix=df, 77 | design_matrix=sample_df, 78 | design_formula="~ batch + sample", 79 | gene_column="id", 80 | ) 81 | 82 | 83 | def test_deseq_no_id_exception(): 84 | df = pd.read_csv(test_data_path + "/ercc.tsv", sep="\t") 85 | sample_df = sample_metadata() 86 | with pytest.raises(ValueError, match="The given gene_column name is not a column"): 87 | py_DESeq2( 88 | count_matrix=df, 89 | design_matrix=sample_df, 90 | design_formula="~ batch + sample", 91 | gene_column="id1", 92 | ) 93 | 94 | 95 | def test_deseq(setup_deseq): 96 | df, dds = setup_deseq 97 | dds.run_deseq() 98 | dds.get_deseq_result() 99 | res = dds.deseq_result 100 | assert res.query("padj < 0.05").shape == (35, 7) 101 | 102 | dds.get_deseq_result(contrast=["sample", "B", "A"]) 103 | res = dds.deseq_result 104 | assert res.query("padj < 0.05").shape == (35, 7) 105 | 106 | lfc_res = dds.lfcShrink(coef=4, method="apeglm") 107 | assert lfc_res[lfc_res.padj < 0.05].shape[0] == 35 108 | 109 | res.to_csv(test_data_path + "/py_deseq.tsv", index=False, sep="\t") 110 | 111 | dds.run_deseq(test="LRT", reduced="~ batch") 112 | dds.get_deseq_result() 113 | res = dds.deseq_result 114 | res.to_csv(test_data_path + "/py_deseq_reduced.tsv", index=False, sep="\t") 115 | 116 | 117 | def test_normalized_count(setup_deseq): 118 | df, dds = setup_deseq 119 | norm_count_df = dds.normalized_count() 120 | assert norm_count_df.shape == df.shape 121 | 122 | 123 | @pytest.mark.parametrize( 124 | "blind,fit_type", 125 | [(True, "parametric"), (True, "local"), (True, "mean"), (False, "parametric"), (False, "local"), (False, "mean")], 126 | ) 127 | def test_vst(setup_deseq, blind, fit_type): 128 | df, dds = setup_deseq 129 | vst = dds.vst(blind=blind, fit_type=fit_type) 130 | assert vst.shape == df.shape 131 | 132 | 133 | @pytest.mark.parametrize( 134 | "blind,fit_type", 135 | [(True, "parametric"), (True, "local"), (True, "mean"), (False, "parametric"), (False, "local"), (False, "mean")], 136 | ) 137 | def test_rlog(setup_deseq, blind, fit_type): 138 | df, dds = setup_deseq 139 | rlog = dds.rlog(blind=blind, fit_type=fit_type) 140 | assert rlog.shape == df.shape 141 | 142 | 143 | @pytest.mark.parametrize( 144 | "case,r_table,py_table", 145 | [ 146 | ("deseq", "R_deseq.tsv", "py_deseq.tsv"), 147 | ("deseq reduced", "R_deseq_reduced.tsv", "py_deseq_reduced.tsv"), 148 | ], 149 | ) 150 | def test_result(run_r, case, r_table, py_table): 151 | os.chdir(os.path.dirname(test_data_path)) 152 | 153 | py_tab = pd.read_table(os.path.join(test_data_path, py_table)) 154 | r_tab = pd.read_table(os.path.join(test_data_path, r_table)) 155 | 156 | for col in py_tab.columns: 157 | if py_tab.columns.dtype == "float64": 158 | assert np.all(np.isclose(py_tab[col].fillna(0), r_tab[col].fillna(0))), f"{case} failed" 159 | 160 | 161 | def test_deseq2_version(setup_deseq): 162 | _, dds = setup_deseq 163 | 164 | # extract R deseq2 version 165 | re_ver = re.compile("\d+.\d+.\d+") 166 | cmd = """ 167 | Rscript -e "packageVersion('DESeq2')" 168 | """ 169 | version_output = subprocess.check_output(shlex.split(cmd)).decode() 170 | version = re_ver.findall(version_output)[0] 171 | 172 | # let's see if we extracted the correct version 173 | assert dds.deseq2_version == version 174 | 175 | 176 | def test_kallisto(): 177 | 178 | import pandas as pd 179 | 180 | from diffexpr import py_deseq 181 | 182 | h5_list = { 183 | "quant1": f"{test_data_path}/quant1/abundance.h5", 184 | "quant2": f"{test_data_path}/quant2/abundance.h5", 185 | } 186 | sample_df = pd.DataFrame( 187 | { 188 | "sample": ["quant1", "quant2"], 189 | "condition": ["1", "2"], 190 | } 191 | ).set_index("sample") 192 | design = "~ condition" 193 | 194 | transcripts = """ 195 | ENST00000513300.5 196 | ENST00000282507.7 197 | ENST00000504685.5 198 | ENST00000243108.4 199 | ENST00000303450.4 200 | ENST00000243082.4 201 | ENST00000303406.4 202 | ENST00000303460.4 203 | ENST00000243056.4 204 | ENST00000312492.2 205 | ENST00000040584.5 206 | ENST00000430889.2 207 | ENST00000394331.3 208 | ENST00000243103.3 209 | """ 210 | tx = transcripts.strip().split("\n") 211 | gene = list(map(lambda x: x.split(".")[-1], tx)) 212 | 213 | tx2gene = pd.DataFrame({"TXNAME": tx, "GENEID": gene}) 214 | 215 | dds = py_deseq.py_DESeq2( 216 | count_matrix=h5_list, design_matrix=sample_df, design_formula=design, tx2gene=tx2gene, kallisto=True 217 | ) 218 | -------------------------------------------------------------------------------- /test/test_pathways.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from Bio.KEGG.KGML.KGML_pathway import Pathway 4 | 5 | from diffexpr.py_pathway import pathwayview 6 | 7 | 8 | def test_plotpathway(): 9 | enriched_genes = "GADD45A,PLK1,TTK,CDC6,CDC25C,CDC25A".split(",") 10 | pv = pathwayview() 11 | pathway = pv.plot_pathway(enriched_genes=enriched_genes, pathway_id="hsa04110", figurename="pathway.pdf") 12 | assert os.path.isfile("pathway.pdf") 13 | assert isinstance(pathway, Pathway) 14 | --------------------------------------------------------------------------------