website/blog/2026-03-03-typing-pandas.md
At time of writing, pandas is one of the most widely used Python libraries. It is downloaded about half-a-billion times per month from PyPI, is supported by nearly all Python data science packages, and is generally required learning in data science curriculums. Despite modern alternatives existing, pandas' impact cannot be minimised or understated.
In order to improve the developer experience for pandas' users across the ecosystem, we at Quansight Labs (with support from the Pyrefly team at Meta) decided to focus on improving pandas' typing. Why? Because better type hints mean:
By supporting the pandas community, pandas' public API is now type-complete (as measured by Pyright), up from 47% when we started the effort last year. We'll tell the story of how it happened - but first, we need to talk more about type completeness, and how we measure it.
<!-- truncate -->Pyright has a nifty little feature which helps us calculate the type-completeness of a library's public API. The general idea is:
pandas.DataFrame, pandas.read_csv, pandas.Series, ...Foo which has some missing type annotations, and a function def bar() -> Foo: ..., then the function bar also counts as type-unknown because it returns a type-unknown symbol (Foo).Type-completeness is different from just calculating the percentage of missing type annotations, as it's biased towards heavily-used classes. In pandas, for example, Series appears as an argument and return type to at least some function in all of pandas' methods - therefore, no matter how type-complete the rest of pandas is, if Series isn't type-complete, then pandas' overall type-completess score will remain low.
By default, Pyright includes all public symbols. In practice, there are some pandas paths which are considered public according to Python's usual standards, but which pandas considers private, such as:
pandas.tests.pandas.conftest.pandas.core which aren't publictly re-exported in other places such as pandas.api.We therefore amend Pyright's calculation to exclude these "technically public but not really" paths. This gave us a more useful measure of what part of the pandas API which users are expected to interact with is actually type-annotated.
Investigating sources of missing type-completeness in pandas was quite a circular exercise. For example, suppose that DataFrame and Series were type-complete, but Index had an untyped attribute. Here is what would happen:
Index would be reported as "partially unknown" because of its untyped attribute.DataFrame would be reported as "partially unknown" because its method .index returns Index, which is partially unknown.Series is reported as "partially unknown" because its method to_frame returns DataFrame, which is partially unknown.It was clear, therefore, that incremental progress would be difficult. Because of how intertwined pandas' classes all are, we expected the type-completeness score to flatline for several months before suddenly spiking. And that's exactly what happened! Progress flat-lined at around 60-70%, before spiking up to 100%.
pandas-stubs uses the ruff linter to enforce code quality standards. Ruff is highly configurable and comes with many optional ones, one of which is any-type (ANN401). A prerequisite to type-completeness is that types be present everywhere. In order to track progress, the ANN401 rule was enabled across the codebase, with a few exclusions which were then addressed gradually. The general rule was: if you make a pull request which types a certain part of the codebase, then remove the ANN401 exclusion for that part of the codebase so that it stays fully-typed in the future.
Measuring type-completeness with Pyright is:
The situation for pandas is the latter, meaning that some extra work is needed to ensure type-completeness stays high in CI. In fact, it's even more complicated because there are parts of pandas which are technically public (according to Python's usual rules) but which pandas considers private! So, quite some work to get around Pyright's default score is needed.
The general idea is:
py.typed file.pyright against the pandas package in that temporary virtual environment.The full script can be viewed in the pandas-stubs repo.
Pyright's --verifytypes feature takes about 2 and a half minutes to run in pandas-stubs. There's room of improvement here - so much so, that the Pyrefly team is working on a pyrefly report which would work similarly. The pyrefly report API is not yet considered stable, so for now pandas-stubs uses Pyright's --verifytypes command, but hopefully a faster tool is on the horizon!
I'm proud of how Quansight Labs (my employer), Meta, and open source communities were able to come together to make this happen. We plan to continue this work in other targeted open source projects, to keep supporting pandas and NumPy with the work we've already done, to improve typing support in IDEs such as Marimo, and to make pyrefly report production-ready.
Have any requests? Let us know on Discourse