Skip to content
Codeloom
Pandas

Pandas Plotting: Built-in Visualization Guide

Create effective charts directly from Pandas DataFrames with plot(), including line, bar, histogram, scatter, box plots, and customization tips.

·6 min read · By Codeloom
Beginner 12 min read

What you'll learn

  • How to create common chart types directly from DataFrames
  • When to use line, bar, scatter, histogram, and box plots
  • How to customize axes, labels, colors, and layout
  • Tips for making publication-ready charts with minimal code

Prerequisites

  • Basic Pandas knowledge (DataFrames, Series)
  • Pandas and matplotlib installed (pip install pandas matplotlib)

Pandas has a built-in plotting interface that wraps matplotlib. It is not as flexible as Seaborn or Plotly, but it is the fastest way to visualize your data during analysis. One line of code gets you a chart. A few more lines make it presentable.

Setup

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

# Sample data: monthly sales
sales = pd.DataFrame({
    "month": pd.date_range("2025-01-01", periods=12, freq="ME"),
    "revenue": [45000, 48000, 52000, 49000, 55000, 61000,
                58000, 63000, 67000, 62000, 71000, 78000],
    "expenses": [38000, 39000, 41000, 40000, 42000, 44000,
                 43000, 45000, 46000, 44000, 47000, 49000],
    "region": ["East", "East", "East", "West", "West", "West",
               "East", "East", "East", "West", "West", "West"],
})
sales = sales.set_index("month")

# Employee data
employees = pd.DataFrame({
    "name": [f"Emp_{i}" for i in range(50)],
    "department": np.random.choice(["Engineering", "Marketing", "Sales", "Support"], 50),
    "salary": np.random.normal(75000, 15000, 50).astype(int),
    "experience_years": np.random.uniform(1, 15, 50).round(1),
    "performance_score": np.random.uniform(2.5, 5.0, 50).round(2),
})

Line plots

Line plots are the default. Best for time series and continuous data.

# Basic line plot
sales["revenue"].plot()
plt.title("Monthly Revenue")
plt.ylabel("Revenue ($)")
plt.show()
# Multiple lines
sales[["revenue", "expenses"]].plot()
plt.title("Revenue vs Expenses")
plt.ylabel("Amount ($)")
plt.show()
# Customized line plot
sales[["revenue", "expenses"]].plot(
    figsize=(10, 6),
    linewidth=2,
    style=["o-", "s--"],          # Circle markers solid, square markers dashed
    color=["#2ecc71", "#e74c3c"],
)
plt.title("Revenue vs Expenses (2025)", fontsize=14)
plt.ylabel("Amount ($)", fontsize=12)
plt.xlabel("")
plt.legend(["Revenue", "Expenses"], fontsize=11)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Area plot

A filled line plot. Good for showing volume over time.

sales[["revenue", "expenses"]].plot.area(alpha=0.5, figsize=(10, 6))
plt.title("Revenue and Expenses Over Time")
plt.ylabel("Amount ($)")
plt.show()
# Stacked area (default)
sales[["revenue", "expenses"]].plot.area(stacked=True)
plt.show()

# Unstacked
sales[["revenue", "expenses"]].plot.area(stacked=False, alpha=0.4)
plt.show()

Bar plots

Bar plots compare discrete categories.

# Average salary by department
dept_salary = employees.groupby("department")["salary"].mean().sort_values()
dept_salary.plot.bar(figsize=(8, 5), color="steelblue")
plt.title("Average Salary by Department")
plt.ylabel("Salary ($)")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Horizontal bar plot

Better when category labels are long.

dept_salary.plot.barh(figsize=(8, 5), color="steelblue")
plt.title("Average Salary by Department")
plt.xlabel("Salary ($)")
plt.tight_layout()
plt.show()

Grouped bar plot

# Revenue and expenses side by side
monthly_summary = sales[["revenue", "expenses"]].head(6)
monthly_summary.plot.bar(figsize=(10, 6), width=0.8)
plt.title("Monthly Revenue vs Expenses")
plt.ylabel("Amount ($)")
plt.xticks(rotation=45)
plt.legend(loc="upper left")
plt.tight_layout()
plt.show()

Stacked bar plot

monthly_summary.plot.bar(stacked=True, figsize=(10, 6))
plt.title("Revenue and Expenses (Stacked)")
plt.ylabel("Amount ($)")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Histograms

Histograms show the distribution of a single numeric variable.

# Salary distribution
employees["salary"].plot.hist(bins=15, figsize=(8, 5), edgecolor="black")
plt.title("Salary Distribution")
plt.xlabel("Salary ($)")
plt.ylabel("Frequency")
plt.show()
# Compare distributions across departments
employees.groupby("department")["salary"].plot.hist(
    alpha=0.5, bins=12, legend=True, figsize=(10, 6)
)
plt.title("Salary Distribution by Department")
plt.xlabel("Salary ($)")
plt.show()
# Multiple histograms in subplots
employees[["salary", "experience_years", "performance_score"]].plot.hist(
    subplots=True,
    layout=(1, 3),
    figsize=(15, 4),
    bins=12,
    edgecolor="black",
)
plt.tight_layout()
plt.show()

KDE plot (smoothed distribution)

employees["salary"].plot.kde(figsize=(8, 5))
plt.title("Salary Density")
plt.xlabel("Salary ($)")
plt.show()
# Histogram with KDE overlay
ax = employees["salary"].plot.hist(bins=15, density=True, alpha=0.6, edgecolor="black")
employees["salary"].plot.kde(ax=ax, linewidth=2)
plt.title("Salary Distribution with KDE")
plt.xlabel("Salary ($)")
plt.show()

Scatter plots

Scatter plots show relationships between two numeric variables.

employees.plot.scatter(
    x="experience_years",
    y="salary",
    figsize=(8, 6),
    alpha=0.6,
)
plt.title("Experience vs Salary")
plt.xlabel("Years of Experience")
plt.ylabel("Salary ($)")
plt.show()
# Color by a third variable
employees.plot.scatter(
    x="experience_years",
    y="salary",
    c="performance_score",
    colormap="viridis",
    figsize=(8, 6),
    alpha=0.7,
)
plt.title("Experience vs Salary (colored by performance)")
plt.xlabel("Years of Experience")
plt.ylabel("Salary ($)")
plt.show()
# Size by a third variable
employees.plot.scatter(
    x="experience_years",
    y="salary",
    s=employees["performance_score"] * 40,  # Scale for visibility
    alpha=0.5,
    figsize=(8, 6),
)
plt.title("Experience vs Salary (sized by performance)")
plt.show()

Box plots

Box plots show the distribution summary: median, quartiles, and outliers.

# Single box plot
employees["salary"].plot.box(figsize=(6, 6))
plt.title("Salary Distribution")
plt.ylabel("Salary ($)")
plt.show()
# Box plot by category
employees.boxplot(column="salary", by="department", figsize=(10, 6))
plt.title("Salary Distribution by Department")
plt.suptitle("")  # Remove the automatic title
plt.ylabel("Salary ($)")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# Multiple variables
employees[["salary", "experience_years"]].plot.box(
    subplots=True,
    layout=(1, 2),
    figsize=(12, 5),
)
plt.tight_layout()
plt.show()

Pie charts

Pie charts show proportions. Use them sparingly and only with a few categories.

dept_counts = employees["department"].value_counts()
dept_counts.plot.pie(
    autopct="%1.1f%%",
    figsize=(8, 8),
    startangle=90,
    colors=["#3498db", "#2ecc71", "#e74c3c", "#f39c12"],
)
plt.title("Employee Distribution by Department")
plt.ylabel("")  # Remove the default ylabel
plt.show()

Subplots

Multiple plots in a grid

fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# Top left: line plot
sales[["revenue", "expenses"]].plot(ax=axes[0, 0])
axes[0, 0].set_title("Revenue vs Expenses")

# Top right: bar plot
dept_salary = employees.groupby("department")["salary"].mean()
dept_salary.plot.bar(ax=axes[0, 1], color="steelblue")
axes[0, 1].set_title("Avg Salary by Department")
axes[0, 1].tick_params(axis="x", rotation=45)

# Bottom left: histogram
employees["salary"].plot.hist(ax=axes[1, 0], bins=15, edgecolor="black")
axes[1, 0].set_title("Salary Distribution")

# Bottom right: scatter
employees.plot.scatter(
    x="experience_years", y="salary", ax=axes[1, 1], alpha=0.5
)
axes[1, 1].set_title("Experience vs Salary")

plt.tight_layout()
plt.show()

Using the subplots parameter

# Pandas can create subplots automatically for each column
employees[["salary", "experience_years", "performance_score"]].plot(
    subplots=True,
    layout=(1, 3),
    figsize=(15, 4),
    sharex=False,
)
plt.tight_layout()
plt.show()

Customization reference

Colors

# Named colors
df.plot(color="steelblue")

# Hex colors
df.plot(color="#e74c3c")

# Color list for multiple series
df.plot(color=["#2ecc71", "#e74c3c", "#3498db"])

# Colormaps
df.plot(colormap="viridis")    # Sequential
df.plot(colormap="Set2")       # Categorical
df.plot(colormap="coolwarm")   # Diverging

Figure size and DPI

df.plot(figsize=(12, 6))  # Width x Height in inches

# For saving high-resolution images
fig = df.plot(figsize=(12, 6)).get_figure()
fig.savefig("chart.png", dpi=150, bbox_inches="tight")

Axis formatting

ax = df.plot()

# Labels and title
ax.set_title("My Chart", fontsize=14, fontweight="bold")
ax.set_xlabel("X Axis", fontsize=12)
ax.set_ylabel("Y Axis", fontsize=12)

# Limits
ax.set_xlim(0, 100)
ax.set_ylim(0, 200000)

# Grid
ax.grid(True, alpha=0.3, linestyle="--")

# Format y-axis as currency
import matplotlib.ticker as mticker
ax.yaxis.set_major_formatter(mticker.StrMethodFormatter("${x:,.0f}"))

# Rotate tick labels
ax.tick_params(axis="x", rotation=45)

Legend

ax = df.plot()
ax.legend(loc="upper right", fontsize=10, framealpha=0.9)

# Move legend outside the plot
ax.legend(bbox_to_anchor=(1.05, 1), loc="upper left")

Annotations

ax = sales["revenue"].plot(figsize=(10, 6))

# Annotate maximum value
max_idx = sales["revenue"].idxmax()
max_val = sales["revenue"].max()
ax.annotate(
    f"Peak: ${max_val:,.0f}",
    xy=(max_idx, max_val),
    xytext=(max_idx - pd.Timedelta(days=60), max_val + 3000),
    arrowprops=dict(arrowstyle="->", color="red"),
    fontsize=11,
    color="red",
)
plt.show()

When to use something else

Pandas plotting is great for quick exploratory analysis. For specific use cases, consider other tools.

Use Seaborn when you need statistical plots (violin plots, pair plots, heatmaps) or when you want attractive defaults with less customization code.

import seaborn as sns
sns.heatmap(employees[["salary", "experience_years", "performance_score"]].corr(),
            annot=True, cmap="coolwarm")

Use Plotly when you need interactive charts (zoom, hover, pan) or when your charts will be embedded in a web application.

import plotly.express as px
fig = px.scatter(employees, x="experience_years", y="salary",
                 color="department", hover_data=["name"])
fig.show()

Use matplotlib directly when you need full control over every visual element, complex multi-panel figures, or custom geometries.

Wrapping Up

Pandas plotting covers 80% of your visualization needs during data analysis with minimal code. The .plot() method on DataFrames and Series creates line plots by default, and .plot.bar(), .plot.hist(), .plot.scatter(), and .plot.box() handle the other common chart types. For customization, grab the matplotlib axes object and use standard matplotlib methods. Save polished versions with fig.savefig(). For anything more complex - statistical visualizations, interactive charts, or custom layouts - graduate to Seaborn, Plotly, or raw matplotlib.