LogoPaperBanana
  • Features
  • Paper VisualizerNEW
  • Image Edit
  • Pricing
  • Blog
How to Make a Grouped Bar Chart for a Research Paper (Matplotlib Code + CSV Shortcut)
2026/11/10

How to Make a Grouped Bar Chart for a Research Paper (Matplotlib Code + CSV Shortcut)

A complete, copy-paste matplotlib recipe for publication-quality grouped bar charts: long-format CSV, error bars, colorblind-safe colors, column-width sizing, embedded fonts, and fixes for the most common layout problems.

A grouped bar chart (also called a clustered bar chart) is the standard way to compare several methods across several datasets or settings: one group per dataset, one bar per method. It shows up in almost every experiments section, and it is also one of the easiest figures to get subtly wrong. Labels collide, error bars hide the numbers, the legend covers a bar, or the PDF fails the venue's font check.

This guide gives you a complete matplotlib script you can paste and run, explains the parts that matter for a paper, and lists the fixes for the problems you are most likely to hit. At the end there is a shortcut if you would rather start from a CSV and a one-sentence description.

When a grouped bar chart is the right choice

Use a grouped bar chart when:

  • You compare a small number of methods (usually 2–5) across a small number of categories (datasets, tasks, model sizes).
  • The categories are discrete. If the x-axis is continuous, such as training steps or noise level, a line chart is clearer.
  • Readers care about relative differences within each group more than the exact numbers. If exact numbers matter most, a table may be better, or you can add value labels as shown below.

If you have more than about 5 bars per group or more than about 6 groups, consider splitting the figure, switching to a horizontal layout, or using a table.

Step 1: Put your data in long format

Keep one row per measurement. This "long" format is easy to export from experiment logs and easy to reshape:

results.csv
method,dataset,accuracy,std
Baseline,CIFAR-10,91.2,0.3
Ours,CIFAR-10,94.7,0.2
Ours+Aug,CIFAR-10,95.3,0.2
Baseline,CIFAR-100,68.4,0.5
Ours,CIFAR-100,74.9,0.4
Ours+Aug,CIFAR-100,76.8,0.3
Baseline,Tiny-ImageNet,55.1,0.6
Ours,Tiny-ImageNet,61.8,0.5
Ours+Aug,Tiny-ImageNet,63.5,0.4

The numbers above are example data for this tutorial, not real benchmark results.

Step 2: The complete matplotlib script

This script was tested with matplotlib 3.11 and pandas. It writes both a vector PDF for LaTeX and a 300 DPI PNG.

grouped_bar.py
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

df = pd.read_csv("results.csv")
datasets = ["CIFAR-10", "CIFAR-100", "Tiny-ImageNet"]  # x-axis order
methods = ["Baseline", "Ours", "Ours+Aug"]              # bar order in each group

mean = df.pivot(index="dataset", columns="method", values="accuracy").loc[datasets, methods]
std = df.pivot(index="dataset", columns="method", values="std").loc[datasets, methods]

plt.rcParams.update({
    "font.family": "serif",
    "font.size": 9,
    "axes.spines.top": False,
    "axes.spines.right": False,
    "pdf.fonttype": 42,   # embed TrueType fonts (many venues require this)
    "ps.fonttype": 42,
})

colors = ["#0072B2", "#E69F00", "#009E73"]  # Okabe-Ito, colorblind-safe
hatches = ["", "//", "xx"]                  # still readable in grayscale

x = np.arange(len(datasets))
n = len(methods)
width = 0.8 / n

fig, ax = plt.subplots(figsize=(3.5, 2.6))  # single-column width in inches
for i, method in enumerate(methods):
    offset = (i - (n - 1) / 2) * width
    ax.bar(
        x + offset, mean[method], width,
        yerr=std[method], capsize=2, error_kw={"elinewidth": 0.8},
        label=method, color=colors[i], hatch=hatches[i],
        edgecolor="black", linewidth=0.5,
    )
    # Put value labels above the error bar, not on top of it.
    for xi, m, s in zip(x + offset, mean[method], std[method]):
        ax.text(xi, m + s + 1.5, f"{m:.1f}", ha="center", va="bottom", fontsize=5.5)

ax.set_xticks(x, ["CIFAR-10", "CIFAR-100", "Tiny-\nImageNet"])  # wrap long names
ax.set_ylabel("Top-1 accuracy (%)")
ax.set_ylim(0, 105)
ax.yaxis.grid(True, linestyle=":", linewidth=0.5)
ax.set_axisbelow(True)
ax.legend(loc="upper center", bbox_to_anchor=(0.5, -0.24), ncol=n, frameon=False)

fig.tight_layout()
fig.savefig("grouped_bar.pdf", bbox_inches="tight")            # vector, for LaTeX
fig.savefig("grouped_bar.png", dpi=300, bbox_inches="tight")   # raster, 300 DPI

This is the output, rendered at 300 DPI:

Grouped bar chart produced by the script: three datasets, three methods, error bars, value labels and a legend below the axis

What each part is doing

The offset formula. Each method's bars are shifted by (i - (n - 1) / 2) * width, which centers the group on the tick no matter how many methods you have. With width = 0.8 / n, the bars in one group take up 80% of the space between ticks, leaving a visible gap between groups.

Explicit ordering. pivot(...).loc[datasets, methods] fixes the order of groups and bars. Without it, pandas sorts alphabetically, which usually puts your own method in the wrong place.

Colors and hatches. The three colors come from the Okabe–Ito palette, which stays distinguishable for the most common forms of color vision deficiency. The hatches keep the bars distinguishable when the paper is printed in grayscale.

Size in inches, not pixels. figsize=(3.5, 2.6) matches a single column in most two-column templates (roughly 3.3–3.5 inches wide). Designing at the final size means a 9 pt font in matplotlib appears as 9 pt in the PDF, close to the body text. For a full-width figure, use about 7 inches.

Embedded fonts. pdf.fonttype: 42 makes matplotlib embed TrueType fonts instead of Type 3 fonts. Submission checkers for many conferences flag Type 3 fonts, and this setting avoids that problem.

Vector first. Include the PDF in LaTeX with \includegraphics[width=\columnwidth]{grouped_bar.pdf}. Keep the 300 DPI PNG for slides, Word, or journals that ask for raster images.

Common problems and how to fix them

Here is the first version of the same script, before the layout fixes:

First attempt: the CIFAR-100 and Tiny-ImageNet labels overlap, and the value labels sit on top of the error bars

Three problems are visible, and they are the ones people hit most often:

  1. Overlapping x-axis labels. At single-column width, long category names run into each other. Wrap them with \n, shorten them (and explain the abbreviation in the caption), or rotate them with ax.set_xticks(x, labels, rotation=20, ha="right").
  2. Value labels covering the error bars. ax.bar_label places text at the top of the bar, which is exactly where the error bar starts. Place labels at mean + std plus a small margin, as the final script does, or drop value labels and report the numbers in a table.
  3. Crowded numbers. If neighboring labels touch, reduce their font size, use one decimal place, or label only the best result in each group.

Other issues worth checking:

  • The legend covers a bar. Move it below the axis with bbox_to_anchor, as above, or place it above the plot with loc="lower center", bbox_to_anchor=(0.5, 1.0).
  • The y-axis does not start at zero. Bar length encodes the value, so a truncated axis exaggerates differences. If the differences are small and important, show them with a dot plot or a table instead of cutting the axis.
  • The error bars are unexplained. State in the caption whether they show standard deviation, standard error, or a 95% confidence interval, and how many runs they are based on.
  • Blurry figures in the PDF. This usually means a low-resolution PNG was scaled up. Use the vector PDF, or export the PNG at the final printed size and at least 300 DPI.

Pre-submission checklist

  • Group and bar order is intentional (your method is easy to find).
  • Colors are colorblind-safe, and the bars are still distinguishable in grayscale.
  • The font size in the figure is close to the caption's font size at the final printed width.
  • Axis labels include units, and the y-axis starts at zero.
  • The caption says what the error bars mean and how many runs were used.
  • Fonts are embedded (no Type 3 fonts in the PDF).
  • The same method uses the same color in every figure in the paper.

Prefer seaborn?

If your data is already in long format, seaborn can draw the same grouping in one call:

import seaborn as sns

ax = sns.barplot(data=df, x="dataset", y="accuracy", hue="method",
                 palette=["#0072B2", "#E69F00", "#009E73"], errorbar=None)

With one row per measurement and no repeated runs, set errorbar=None. If your CSV contains one row per run, seaborn can compute the error bars itself (for example, errorbar="sd"). You will still want the sizing, font and export settings from the script above.

Shortcut: from CSV to figure with PaperBanana

If you would rather not write and debug the plotting code yourself, the PaperBanana Paper Visualizer has a Data Plot mode built for this:

  1. Paste your CSV or JSON data.
  2. Describe the figure in one sentence, for example: "Grouped bar chart of accuracy by dataset, one bar per method, value labels on top, legend below the x-axis."
  3. PaperBanana writes matplotlib code, runs it in an isolated sandbox, renders the figure at 300 DPI, and has a critic review the result over one or more rounds.

You can download the image and copy the generated code. That means you can rerun the code locally, export a vector PDF, and adjust anything you like. This is the actual output for the same data used in this guide:

Grouped bar chart generated by PaperBanana's Data Plot mode from the same CSV

Data Plot mode does not use an image model, so the numbers in the figure come straight from your data. It costs 10 credits per critique round, and rounds that are not needed are refunded automatically. See pricing for details.

FAQ

Should I use a grouped bar chart or a table? Use a chart when the main message is a pattern, such as "our method wins on every dataset" or "the gap grows on harder datasets". Use a table when readers need exact numbers. Many papers do both: a chart in the main text and a full table in the appendix.

How many bars per group is too many? More than about 5 bars per group becomes hard to read at column width. Split the comparison into two figures, or move the less important baselines to the appendix.

Standard deviation or standard error? Either is acceptable if you say which one you used. Standard deviation shows the spread across runs. Standard error and confidence intervals show how precisely the mean is estimated. Always report the number of runs.

Can I make the bars horizontal? Yes. Replace ax.bar with ax.barh, swap the roles of the x and y axes, and use ax.set_yticks. Horizontal bars work well when category names are long.

For general guidance on paper figures, see How to Create Publication-Quality Figures for Your Research Paper and our comparison of TikZ, matplotlib and AI tools.

All Posts

Author

avatar for PaperBanana Team
PaperBanana Team

Categories

  • Research
When a grouped bar chart is the right choiceStep 1: Put your data in long formatStep 2: The complete matplotlib scriptWhat each part is doingCommon problems and how to fix themPre-submission checklistPrefer seaborn?Shortcut: from CSV to figure with PaperBananaFAQ

More Posts

TikZ vs Matplotlib vs AI: The Best Tools for Academic Paper Figures in 2026
Research

TikZ vs Matplotlib vs AI: The Best Tools for Academic Paper Figures in 2026

An honest comparison of tools for creating research paper figures. We compare TikZ, matplotlib, draw.io, Figma, BioRender, and AI generators like PaperBanana across learning curve, output quality, and workflow speed.

avatar for PaperBanana Team
PaperBanana Team
2026/03/10
PaperBanana is back: GPT Image 2.6, Nano Banana 2.1 and plot mode
Research

PaperBanana is back: GPT Image 2.6, Nano Banana 2.1 and plot mode

PaperBanana returns on November 10, 2026 with two image models, 2K output, isolated 300 DPI plotting, and per-round billing with automatic unused-round refunds.

avatar for PaperBanana Team
PaperBanana Team
2026/11/10
PaperBanana: Completing the Final Piece of the AI Scientist Puzzle
Research

PaperBanana: Completing the Final Piece of the AI Scientist Puzzle

In the wave of AI reshaping scientific research, PaperBanana fills the visual gap in automated research workflows. Deep dive into the agentic framework, benchmark evaluation, and future implications for fully autonomous AI scientists.

avatar for PaperBanana Team
PaperBanana Team
2025/02/05

Stay Updated

Join the PaperBanana community

Get notified about new features, research tips, and early access opportunities

LogoPaperBanana

AI-Powered Academic Illustrations for Researchers

GitHubGitHubEmail
Product
  • Features
  • Pricing
  • FAQ
Resources
  • Blog
Company
  • About
  • Contact
  • Waitlist
Legal
  • Refund Policy
  • Cookie Policy
  • Privacy Policy
  • Terms of Service
© 2026 PaperBanana All Rights Reserved.Support: support@paperbanana.onlinePowered by Nano Banana 2.1 / GPT Image 2.6 + Gemini 3.8 Flash