# plotnine: a grammar of graphics for Python, built on ggplot2's model

> plotnine brings the layered grammar of graphics to Python, mapping dataframe columns to visual properties and adding components with the + operator. It suits analysts who already think in ggplot2 terms and are willing to accept a pandas-shaped dependency stack.

**has2k1/plotnine** — A Grammar of Graphics for Python

- Repository: https://github.com/has2k1/plotnine
- Website: https://plotnine.org
- Stars: 4,769 · Forks: 255
- Language: Python
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/has2k1-plotnine

## What plotnine actually solves for a Python analyst

Most Python plotting libraries ask you to name a chart type and then pass data into it. plotnine inverts that. You declare a dataset, declare which columns map to which visual properties, and then add layers. The README describes it as "an implementation of a grammar of graphics in Python based on ggplot2", and that lineage is the whole point: the API is deliberately close to ggplot2, so where plotnine's own coverage is thin, the README points readers at the ggplot2 reference documentation instead.

The audience is therefore fairly narrow but real. If you have written ggplot2 before, the mental model transfers almost unchanged: ggplot(mtcars, aes("wt", "mpg")) + geom_point() is a complete plot, and every later decision is another term in the sum. If you have only ever used imperative plotting, the first hour will feel indirect, because nothing is drawn until the object is evaluated. The payoff shows up in composite figures. Faceting, a smoothing layer and a colour mapping are three additions rather than three rewrites.

## How the grammar is assembled: data, mappings, geoms, stats, facets

The README's worked example is the clearest description of the data flow. It starts with the mtcars dataset imported from plotnine.data, then builds up in five documented steps. Step one is a scatter plot. Step two maps a third column to colour using the string "factor(gear)", so the mapping accepts expressions as well as bare column names. Step three adds stat_smooth(method="lm"), which fits a linear model per group and draws confidence intervals. Step four adds facet_wrap("gear") to split the figure into panels by that column. Step five swaps the visual theme, showing theme_xkcd() and theme_tufte() as drop-in replacements.

That sequence is the architecture in miniature. A ggplot object holds the data and the aesthetic mapping; geom_* functions contribute drawing layers; stat_* functions contribute computed layers; facet_* functions partition the panels; theme_* functions control non-data ink. Because each piece is an object added to the previous one, the same plot definition can be re-themed or re-faceted without touching the data mapping. The cost is that errors often surface late, at draw time, rather than when the offending term is written.

## Installing plotnine and drawing the README's first plot

The README gives pip as the primary route and notes that the plain install "should be sufficient for most". Optional extras exist for testing, documentation and development, and conda and pixi are listed as alternatives. The project metadata requires Python 3.11 or newer and pulls in matplotlib>=3.10.0, pandas>=2.2.0, numpy>=1.25.0, scipy>=1.15.0, statsmodels>=0.14.6, contourpy>=1.2.0 and mizani~=0.14.5.

Install the base package:

```bash
pip install plotnine
```

If you want the optional companion packages used in tests and examples, the README lists a separate extra target:

```bash
pip install 'plotnine[extra]'
```

Then reproduce the first documented example. The star import brings the grammar functions into scope, and mtcars comes from the bundled datasets:

```python
from plotnine import *
from plotnine.data import mtcars

(
    ggplot(mtcars, aes("wt", "mpg"))
    + geom_point()
)
```

What you should see is a scatter plot of weight against miles per gallon. Note the parentheses around the expression: each + adds a layer, and wrapping the whole thing keeps the line continuation readable. From there the README's next four steps add colour, a linear smooth, facets and a theme, one term at a time.

## Where plotnine is the wrong tool

The grammar is not free. Every plotnine figure is rendered through matplotlib, so anything matplotlib cannot draw, plotnine cannot draw either. If your target is an interactive browser chart with hover tooltips and pan-and-zoom, this is the wrong layer: the output is a static image, and the README makes no claim otherwise. Teams that need web-native output should look elsewhere before they start.

There is also a dependency weight to accept. A base install brings pandas, numpy, scipy, statsmodels, matplotlib, contourpy and mizani. If you are producing one bar chart inside a small service, that is a lot of surface area for the result. And the ggplot2 heritage cuts both ways: the README itself admits that where plotnine "lacks in coverage" you should consult ggplot2's documentation, which means some R idioms you know may simply not exist in the Python port yet. Finally, the test suite compares generated images against baseline images, and the README warns that small differences in text rendering can throw off those comparisons, with only very small differences tolerated. That is a maintenance reality for anyone running the suite on a different platform than the one the baselines were made on.

## plotnine vs seaborn vs matplotlib: different layers, not just different syntax

The comparison people reach for first is plotnine vs matplotlib. They are not peers. matplotlib is the rendering engine underneath; plotnine is a declarative layer on top of it. Choosing plotnine does not remove matplotlib, it changes what you write. If you need precise control over individual artists, axes positions or a bespoke layout, matplotlib gives you that directly and plotnine will get in the way.

The more interesting comparison is plotnine vs seaborn. Both sit above matplotlib and both work from dataframes, but they organise the work differently. seaborn exposes named chart functions with parameters, so you pick the function that matches the chart you want and configure it. plotnine exposes composable grammar components, so you describe the mapping and then add layers until the plot is right. For a standard chart, seaborn's approach is shorter. For a figure that needs a statistical layer, a facet grid and a custom theme together, plotnine's additive model tends to stay readable where a function signature with many keyword arguments does not. Neither is strictly better; the deciding question is whether your figures are variations on known chart types or compositions you build incrementally.

## Maintenance, releases and the MIT licence

plotnine is not archived, and the last push to the repository was on 2026-09-22. Recent releases include v0.15.8 on 2026-08-14, v0.15.7 on 2026-06-13 and v0.15.6 on 2026-06-10, so the release cadence over that window is roughly one per month or two. The version constraint on mizani (~=0.14.5), plotnine's own scaling and statistics package, means upgrades of that companion library are held within the 0.14 series, which limits how much can shift underfoot in a minor release.

Upgrade cost is mostly tied to the underlying stack rather than to plotnine itself. The pyproject floors are aggressive: pandas>=2.2.0, matplotlib>=3.10.0, numpy>=1.25.0, scipy>=1.15.0 and statsmodels>=0.14.6. A project pinned to older versions of any of those will have to move them before it can move plotnine. The test extra pins matplotlib>=3.11.0 specifically because the baseline images use text metrics from that version, which tells you image-diff tests are sensitive to the rendering stack and not just to plotnine's code.

The licence is MIT, declared in pyproject.toml as license = {file = "LICENSE"} and classified as "License :: OSI Approved :: MIT License". MIT is permissive and permits commercial use and modification, but this is a description of the repository metadata, not legal advice; check the LICENSE file and your own obligations.

## Contributing examples and reporting bugs

The README is unusually specific about what it wants from contributors, which is a useful signal about the project's priorities. Documentation examples are solicited, but with two stated criteria: simple-looking plots that otherwise require a trick or two, and plots that are part of a data analytic narrative showing off a geom or stat "at their differential best". Submissions go to the separate plotnine-examples repository rather than into the main tree.

For bugs, the README directs you to check the existing issues first and file a new one only if the problem has not been reported. Fixes are welcome. The repository layout includes a code-of-conduct.md and a CITATION.bib, and the Makefile exposes targets such as lint, test, coverage and doc, with ruff used for both formatting and linting. If you intend to run the test suite locally, be aware of the baseline-image caveat mentioned above: text rendering differences between platforms can cause failures that are not real regressions.

## Conclusion

Adopt plotnine if your team already reasons in ggplot2 terms, works mostly with pandas dataframes, and wants faceting and statistical layers without hand-assembling matplotlib artists. Do not adopt it if you need an interactive web canvas, a huge gallery of chart types, or a dependency footprint smaller than pandas plus matplotlib plus scipy plus statsmodels. Before committing, check that your pandas version satisfies the pyproject constraint of pandas>=2.2.0, confirm your Python is 3.11 or newer, and run the README's five-step mtcars example end to end to see whether the layered style fits how your team writes plotting code.

## FAQ

### What are the key differences between plotnine and Seaborn?

Both build on matplotlib and take dataframes, but they organise the work differently. Seaborn exposes named chart functions you configure with parameters, while plotnine exposes grammar components you add together with the + operator, starting from ggplot(data, aes(...)) and layering geoms, stats, facets and themes.

### Does plotnine work with polars?

The README does not state this directly. polars appears only in the optional extra dependency group in pyproject.toml, alongside packages such as geopandas and scikit-learn, so it is listed as a companion package rather than as a documented input type.

### How do I install plotnine in Python?

The README gives pip install plotnine as the base install and notes it should be sufficient for most users. It also lists pip install 'plotnine[extra]' for optional packages, conda install -c conda-forge plotnine, and a pixi route using pixi add python plotnine.

### What is plotnine in Python?

plotnine is described in the README as an implementation of a grammar of graphics in Python based on ggplot2. It lets you compose plots by mapping variables in a dataframe to visual characteristics such as position, colour and size.

### How do I import plotnine in Python?

The README's example uses a star import, from plotnine import *, followed by from plotnine.data import mtcars for the bundled dataset. The star import brings the grammar functions such as ggplot, aes and geom_point into scope.

### How do I install plotnine in Jupyter?

The README does not give Jupyter-specific installation steps. The documented routes are pip install plotnine, pip install 'plotnine[extra]', conda install -c conda-forge plotnine, and pixi add python plotnine, all of which install the package into the active environment.

## Sources

- [has2k1/plotnine on GitHub](https://github.com/has2k1/plotnine)
- [License: MIT](https://github.com/has2k1/plotnine/blob/main/LICENSE)
- [Project website](https://plotnine.org)
- [README](https://github.com/has2k1/plotnine/blob/main/README.md)
- [Releases](https://github.com/has2k1/plotnine/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/has2k1-plotnine
