Skip to main content
v2026.11,986 entries · CC-BY 4.0

plotnine: Grammar of Graphics Data Visualization in Python

A guide to plotnine: installation, layered grammar of graphics syntax, publication themes, statistical plots with error bars, and manuscript figure export.

Written and maintained by CASRAI Editorial Board

Published

On this page

What it is used for in research

plotnine is an open-source data visualization library for Python based on Leland Wilkinson’s Grammar of Graphics and heavily inspired by the R library ggplot2. In academic and scientific research, data visualization is not merely cosmetic; it is a foundational analytical tool for exploratory data analysis, hypothesis testing, quality control, and scholarly dissemination. While imperative Python visualization packages require researchers to manually manipulate canvas coordinates, plotnine implements a declarative approach where visual properties are mapped directly from variables in tidy data frames.

Researchers use plotnine to construct complex, multi-layered statistical figures without writing verbose boilerplate. Typical research workflows supported by plotnine include:

  • Multivariate analysis: Mapping independent experimental variables to distinct visual channels, such as position, color, point shape, and line dash types, ensuring clear differentiation across experimental cohorts.
  • Small multiples (faceting): Subsetting large datasets across treatment arms, genetic strains, or observation time points into synchronized multi-panel layouts using facet_wrap or facet_grid.
  • Statistical transformation and error aggregation: Overlaying central tendencies (means, medians) with parametric error bars, confidence ribbons, and linear or non-linear trend curves directly on top of raw observations.
  • Publication-ready manuscript figures: Generating reproducible figures configured for print publication standards (including 300+ DPI raster outputs and scalable vector PDF/SVG formats) with minimalist academic themes like theme_bw(), theme_classic(), and theme_tufte().

Because plotnine operates natively on structured tabular data, it integrates seamlessly with scientific data pipelines. Researchers often pair plotnine with data preparation workflows like adding columns in pandas or subsetting experimental records. Using declarative scripts rather than interactive spreadsheet software reinforces reproducible research practices, making it possible to re-render figures automatically whenever underlying laboratory datasets are updated.

Install or access (official source only)

Plotnine is published as an open-source Python package on the Python Package Index (PyPI) and Conda Forge under the permissive MIT License. It requires Python version 3.10 or higher. The core package depends on standard scientific Python libraries, including NumPy (≥1.23.5), pandas (≥2.2.0), Matplotlib (≥3.8.0), SciPy (≥1.8.0), statsmodels (≥0.14.6), and Mizani (~0.14.0).

To install the official release using Python’s standard package installer (pip), run:

pip install plotnine

For modern environments utilizing uv or pixi, use the respective official commands:

# Using uv
uv pip install plotnine

# Using pixi
pixi add plotnine

If you manage your scientific software environment via Conda, install plotnine from the community-maintained conda-forge channel:

conda install -c conda-forge plotnine

For specialized research tasks—such as non-overlapping label layout, geospatial mapping, Gaussian process smoothing, or locally weighted scatterplot smoothing (LOESS)—plotnine provides an official extra bundle containing adjustText, geopandas, scikit-learn, and scikit-misc:

# Install optional research dependencies
pip install 'plotnine[extra]'

Step-by-step task (numbered, with menu names or commands exactly as in the current docs)

This walkthrough demonstrates how to build and export a publication-ready multi-group experimental figure using plotnine in Python. The procedure structures tidy data, layers statistical geometries, facets by experimental condition, and exports a 300 DPI vector graphic for journal submission.

  1. Prepare and import data: Load pandas and plotnine in your Python script or notebook. Ensure your dataset is arranged in tidy format, where each row is an observation and each column represents a variable.
    import pandas as pd
    import numpy as np
    from plotnine import *
    
    # Construct sample experimental dataset
    np.random.seed(42)
    df = pd.DataFrame({
        "dose": np.tile(["0 uM", "10 uM", "50 uM"], 40),
        "cell_line": np.repeat(["Line_A", "Line_B"], 60),
        "viability": np.concatenate([
            np.random.normal(98, 4, 20), np.random.normal(75, 6, 20), np.random.normal(42, 8, 20),
            np.random.normal(95, 5, 20), np.random.normal(88, 5, 20), np.random.normal(65, 7, 20)
        ])
    })
  2. Initialize the ggplot canvas and aesthetics: Call ggplot(), passing the DataFrame as the first argument and aes() to map variables to aesthetic channels. In Python, wrap multi-line plot expressions in parentheses to ensure correct syntax parsing.
    p = (
        ggplot(df, aes(x="dose", y="viability", fill="dose"))
    )
  3. Add geometric layers and distribution plots: Layer geometric objects sequentially using the + operator. Add a jittered scatter layer to show raw observations and a boxplot layer to depict summary distributions. If comparing complex multimodal data, researchers can also evaluate a raincloud plot for detailed density visualization.
    p = (
        ggplot(df, aes(x="dose", y="viability", fill="dose"))
        + geom_boxplot(alpha=0.7, outlier_shape="", width=0.5)
        + geom_jitter(width=0.15, size=1.5, alpha=0.6, color="#333333")
    )
  4. Subdivide panels using faceting: Split the visualization across experimental lines without writing manual subplots loops by appending facet_wrap().
    p = (
        p
        + facet_wrap("~cell_line")
    )
  5. Apply publication theme and descriptive labels: Polish the visual design by applying a clean academic theme (such as theme_bw() or theme_classic()), removing superfluous grid lines, and defining clear axis labels with units via labs().
    p = (
        p
        + labs(
            title="Cell Viability Across Compound Concentrations",
            x="Compound Concentration",
            y="Relative Viability (%)",
            fill="Dose Cohort"
        )
        + theme_bw()
        + theme(
            figure_size=(8, 4.5),
            text=element_text(family="sans", size=10),
            axis_title=element_text(weight="bold"),
            panel_grid_minor=element_blank(),
            strip_background=element_rect(fill="#f0f0f0")
        )
    )
  6. Save high-resolution output for manuscript submission: Call the save() method on your plot object to render and write the figure to disk. Specify target dimensions and resolution suitable for journal figures (typically 300 DPI for raster or vector PDF).
    # Export vector PDF for publication
    p.save("figure_viability.pdf", width=8, height=4.5, units="in", dpi=300)
    
    # Export high-resolution PNG for presentation
    p.save("figure_viability.png", width=8, height=4.5, units="in", dpi=300)

Output and how to read it

The resulting graphic consists of distinct visual regions configured through plotnine’s Grammar of Graphics pipeline:

  • Plot Canvas and Panels: Because facet_wrap("~cell_line") was declared, the canvas is split into two synchronized side-by-side subplots (Line_A and Line_B). Both panels share identical y-axis scales (0 to 120% relative viability), allowing direct visual comparison of drug sensitivity between cell lines without scale bias.
  • Aesthetic Mappings: The horizontal axis displays the three discrete categorical concentration steps (0 μM, 10 μM, 50 μM). The vertical axis shows the continuous measurement response. Color fills identify dose cohorts, accompanied by an automatically generated legend.
  • Layered Geometries: The background boxplots depict median lines, interquartile ranges (IQR, 25th to 75th percentiles), and whiskers extending to 1.5 × IQR. Semi-transparent jittered points overlay the boxes, presenting true raw data point density and revealing sample variance that summary statistics might mask.
  • Typography and Margins: The applied theme_bw() removes the default gray background canvas, rendering a clean white plotting region bounded by subtle borders and crisp tick marks, satisfying academic publisher styling guidelines.

Troubleshooting

The table below summarizes common issues encountered when building visualizations in plotnine, along with verified diagnostic explanations and solutions.

Observed Error or Warning Root Cause Verified Solution
TypeError: unsupported operand type(s) for + In Python, placing the + operator at the start of a newline without wrapping the entire plot expression in parentheses breaks Python syntax parsing. Enclose the entire multi-line plot expression in outer parentheses: (ggplot(df, aes(...)) + geom_point()).
PlotnineWarning: Method 'loess' requires scikit-misc Calling geom_smooth(method='loess') requires the optional C-extension package scikit-misc, which is not bundled with core plotnine. Install the optional dependencies bundle using pip install 'plotnine[extra]' or conda install -c conda-forge scikit-misc.
Continuous value supplied to discrete scale A numeric column (such as cohort IDs [1, 2, 3]) is passed to a discrete aesthetic mapping like color or fill without explicit categorical casting. Convert the column to a string or category before plotting: df['cohort'] = df['cohort'].astype(str), or wrap it inside the aesthetic: aes(color='factor(cohort)').
TclError: no display name and no $DISPLAY environment variable Executing plotnine scripts on a remote headless compute cluster or high-performance computing (HPC) node without an active X11 graphical display. Set the non-interactive Matplotlib backend at the top of your script before importing plotnine: import matplotlib; matplotlib.use('Agg').
Overlapping text labels in scatter plots Plotting multiple text annotations with geom_text causes labels to collide in dense coordinate clusters. Install adjustText via pip install 'plotnine[extra]' and replace geom_text() with geom_label() or use adjust_text parameter options.

Free alternatives (table)

While plotnine offers a pure Grammar of Graphics implementation in Python, researchers can choose among several mature open-source visualization systems depending on language preference, interactivity needs, and analysis workflows. For imperative figures and basic bar charts, researchers frequently use a bar plot in Matplotlib as an entry point.

Visualization Tool Language / Ecosystem Grammar Paradigm Primary Research Strengths License
plotnine Python Grammar of Graphics (strict ggplot2 port) Declarative layered syntax, automatic faceting, seamless pandas/polars integration, publication themes. MIT License
Matplotlib Python Imperative / Object-Oriented Low-level canvas manipulation, arbitrary figure geometry control, widespread adoption across scientific libraries. PSF License
Seaborn Python Statistical wrappers over Matplotlib Convenient high-level statistical plotting (pairplots, jointplots, violin distributions) with pandas DataFrame bindings. BSD 3-Clause
Altair Python Grammar of Graphics (Vega-Lite spec) JSON-based declarative specifications, interactive web visualizations, linked brush filtering in Jupyter environments. BSD 3-Clause
ggplot2 R Grammar of Graphics (original implementation) Extensive statistical ecosystem, vast extension library (ggrepel, patchwork), native tidyverse integration in R. MIT License

When software packages and open science analysis tools are documented for scientific reproducibility, authors often submit reference implementations to scholarly venues such as the Journal of Open Source Software (JOSS).

FAQ

Is plotnine identical to R’s ggplot2?

Plotnine intentionally replicates the syntax, naming conventions, and layer architecture of R’s ggplot2, making it straightforward for researchers transitioning between R and Python to apply their existing knowledge. Key differences arise from language idioms: plotnine requires quoting column names inside aes() (e.g., aes(x="variable") rather than unquoted symbols) and requires outer parentheses when chaining multi-line layers with + to conform to Python grammar.

How do I save a publication-quality figure in plotnine?

To export a figure suitable for journal submission, invoke the save() method on your ggplot instance: p.save("figure1.pdf", width=7, height=5, units="in", dpi=300). Plotnine leverages Matplotlib’s export backend, enabling direct export to vector formats (PDF, SVG, EPS) that maintain infinite scalability without pixelation, as well as high-resolution raster files (PNG, TIFF) rendered at 300 or 600 DPI.

Can plotnine create interactive plots?

Plotnine is designed primarily for producing static, publication-quality figures and documents. If your research workflow demands browser-based interactive exploration, tooltips, or dynamic brushing, consider complementary open-source Python packages such as Altair or Plotly.

Does plotnine support both pandas and Polars DataFrames?

Yes. While plotnine was originally engineered for pandas DataFrames, recent versions support Apache Arrow-backed and Polars DataFrames, enabling researchers handling large experimental cohorts or omics matrices to pass tidy tables directly to ggplot() without conversion.

Sources (official docs)

Follow CASRAI

Research-administration guidance, standards updates and independent tool reviews.

Ask CASRAI · free to try

Ask about plotnine: Grammar of Graphics Data Visualization in Python

Ask your first 2 questions free below. Subscribers get 150 a day for $29 a month.

An AI assistant specialized in research administration. It cites the sources behind every answer, labels web answers and says when it can't answer.

Answers draw on CASRAI's guides and dictionary plus the federal and funder documents we index: Federal Register, Grants.gov, Regulations.gov and UKRI.

Works on this site and inside Claude, Cursor and the AI tools you already use.

Everything CASRAI publishes — this page, the dictionary, the guides and the news — stays free to read, with no account and no card.

Referenced across the research world

University of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logoUniversity of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logo
  • University of Cambridge logo
  • Columbia University logo
  • Crossref logo
  • University of Edinburgh logo
  • Harvard University logo
  • University of Oxford logo
  • Princeton University logo
  • Stanford School of Medicine logo
  • University College London logo
  • ORCID logo

View CASRAI adoption →