CellTypist is logistic regression with two defaults you inherit silently
A tool for semi-automatic cell type classification
At a glance
- What is it?
- A cell type annotation tool for single-cell RNA data built on classifiers trained by stochastic gradient descent, distributed as pickle files in a home directory cache. What it does not do is pick a model for you.
- Who is it for?
- Use CellTypist when you need fast reproducible immune-focused annotation and want a model you can inspect cell type by cell type, and treat it as a starting annotation rather than ground truth since the decision matrix is exposed but the training data behind any single pickle is not.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 142 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two defaults stack when you omit the model argument
The classifier family is fixed and narrow: logistic regression optimised by the stochastic gradient descent algorithm, with models serialised in a binary pickle format. Leaving the model argument out of the annotate call does not select a general model, it selects a specific immune model named Immune_All_Low.pkl, and the same default applies both to the annotate function and to models.Model.load. So a first script that passes only an input file runs an immune-focused classifier over whatever tissue you gave it, and reports the result without indicating that a default was involved. The built-in set concentrates on immune sub-populations, which is the intended use and also the boundary of the thing. Choosing a different default is a one-line change:
predictions = celltypist.annotate(input_file, model = 'Immune_All_Low.pkl')Models are listed before they are chosen, through models_description, and inspected after loading, through model.cell_types and model.features.
on_the_fly narrows the model list instead of widening it
The listing function has an argument whose name reads backwards from its effect. Called with the default of on_the_fly set to False, models_description shows every possible model, including ones you have never downloaded. Called with it set to True, the same call gives you an overview limited to the models already present on disk:
models.models_description(on_the_fly = True)That means the local view is the one that requires the argument. Reading it as on the fly meaning live querying would lead you to expect a wider set. Downloading is separate and granular. You can pull one model by name, a list of models by passing a list, or refresh what you have with force_update set to True:
models.download_models(model = ['Immune_All_Low.pkl', 'Immune_All_High.pkl'], force_update = True)Since each model averages about one megabyte, the documentation encourages downloading all of them, which makes the refresh path the one you will actually use over time.
CELLTYPIST_FOLDER is the only piece of global state
Models are cached on disk rather than bundled into the package. By default a folder named .celltypist is created in the user's home directory to hold them, and the location is redirected by exporting an environment variable in a shell configuration file:
#In the shell configuration file.
export CELLTYPIST_FOLDER='/path/to/model/folder/'This is the setting to get right on a shared machine, inside a container, or on a cluster node, because two environments resolving to different folders will silently hold different model versions. It is also why the container image pins the variable itself rather than relying on a default. The install path for that image sets the folder to /opt/celltypist, prepends its bin directory to PATH, builds a virtual environment there, and then runs celltypist --update-models during the build so the model set is baked into the layer. The consequence is that models in a container age with the image, and a long-lived container will need its own refresh.
Raw counts only, and gene-by-cell doubles your flags
Input handling is strict in one place and forgiving in another. The data must be a raw count matrix of reads or UMIs, not a normalised or scaled matrix, which is the requirement most often missed when the input comes out of an existing pipeline. Formats accepted are .txt, .csv, .tsv, .tab, .mtx and .mtx.gz, laid out either cell-by-gene or gene-by-cell. The second layout needs transpose_input set to True, and .mtx additionally needs gene_file and cell_file naming the files that hold gene and cell names. So the worst case is three extra arguments:
predictions = celltypist.annotate(input_file, model = 'Immune_All_Low.pkl', transpose_input = True, gene_file = '/path/to/gene/file.txt', cell_file = '/path/to/cell/file.txt')There is also a suggestion that cuts against the instinct to filter: non-expressed genes are advised to be kept in the table, because their absence of signal is itself evidence when cells are compared against the model. Dropping them removes the negative half of the comparison.
The result separates scores from probabilities
annotate returns an AnnotationResult instance and that object keeps three distinct views rather than collapsing to one label per cell. predicted_labels is the assignment you will write into your table. decision_matrix is the raw decision score of every cell against every cell type the model knows about, so it is rectangular and lets you see a runner-up rather than only the winner. A third matrix holds the probability that each cell belongs to each type, obtained by transforming the decision matrix. Keeping both means you can set your own threshold on the probability matrix instead of accepting whatever decision rule produced the labels, which matters when a rare population is the thing you care about and one wrong label costs you a cell. Printing the result object gives the summary.
The container targets 3.9 while the package claims 3.6
The packaging metadata and the shipped container disagree about the floor. setup.py sets python_requires to 3.6 or greater and registers a single generic classifier for Python 3, with no per-minor-version entries. The requirements file then lists lower bounds only, with no upper bounds anywhere: numpy at 1.19.0, pandas at 1.0.5, scikit-learn at 0.24.1, openpyxl at 3.0.4, click at 7.1.2, requests at 2.23.0, scanpy at 1.7.0 and leidenalg at 0.9.0. The Dockerfile starts from python:3.9-slim. So the officially supported floor is older than anything the project itself builds on, and an install with no pins will pull current scanpy and scikit-learn releases rather than the ones the classifiers were fitted alongside. The classifier Development Status is declared as 5, Production or Stable, and the console entry point is wired to celltypist.command_line:main.
Three notebooks cover the three ways people actually use it
The intended workflows are signposted by three hosted notebooks rather than by prose. The first is plain cell type classification. The second is multi-label classification, which is the mode to reach for when one cell can legitimately carry more than one identity and forcing a single label discards information. The third is large-scale cross-dataset label transfer, described as best practice, which is a different problem from annotating one dataset because the model has to stay meaningful across studies with different sampling and different pipelines. All three run on Colab and reference files from the repository rather than bundling data, so the count matrix you bring is your own responsibility. Model information is also published separately on the CellTypist portal, which maintains its own model listing alongside the in-package description call.
Editorial conclusion
Use CellTypist when you need fast reproducible immune-focused annotation and want a model you can inspect cell type by cell type, and treat it as a starting annotation rather than ground truth since the decision matrix is exposed but the training data behind any single pickle is not. Before your first run, set CELLTYPIST_FOLDER deliberately, confirm your input is raw counts rather than normalised values, and pass the model argument explicitly instead of inheriting Immune_All_Low.pkl. Check whether your numpy, scanpy and scikit-learn floors resolve to versions the classifiers were not fitted against.
Frequently asked questions
what is celltypist
An automated cell type annotation tool for single-cell RNA-seq datasets built on logistic regression classifiers optimised by the stochastic gradient descent algorithm. It predicts with built-in models, currently focused on immune sub-populations, or with models you supply.
how to install celltypist
Two routes are documented: pip install celltypist, or conda install -c bioconda -c conda-forge celltypist. setup.py declares python_requires of 3.6 or greater and lists dependency lower bounds with no upper bounds.
how to use celltypist
Import celltypist and its models submodule, download a model with models.download_models, then call celltypist.annotate on a count table in .txt, .csv, .tsv, .tab, .mtx or .mtx.gz. Raw counts are required, and gene-by-cell input needs transpose_input set to True.
celltypist vs singler
The README does not compare the two. It describes CellTypist only in terms of its own mechanism, logistic regression classifiers trained by stochastic gradient descent and distributed as pickle model files.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/teichlab-celltypist)