Skip to main content

Visualization for Scientific Data: Creating Effective Scientific Plots

Created: December 17, 2025 5 min read

Effective visualization is crucial for communicating scientific findings. This guide covers creating publication-quality plots and interactive visualizations. See Python Guide for more context. See Python Guide for more context. See Python Guide for more context.

Matplotlib for Scientific Plots

Basic Scientific Plots

import matplotlib.pyplot as plt
import numpy as np

# Generate data
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)

# Create figure with subplots
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

# Line plot
axes[0, 0].plot(x, y1, 'b-', linewidth=2, label='sin(x)')
axes[0, 0].plot(x, y2, 'r--', linewidth=2, label='cos(x)')
axes[0, 0].set_xlabel('x')
axes[0, 0].set_ylabel('y')
axes[0, 0].set_title('Trigonometric Functions')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)

# Scatter plot
axes[0, 1].scatter(x, y1, s=50, alpha=0.6, label='sin(x)')
axes[0, 1].scatter(x, y2, s=50, alpha=0.6, label='cos(x)')
axes[0, 1].set_xlabel('x')
axes[0, 1].set_ylabel('y')
axes[0, 1].set_title('Scatter Plot')
axes[0, 1].legend()

# Histogram
axes[1, 0].hist(y1, bins=20, alpha=0.7, edgecolor='black')
axes[1, 0].set_xlabel('Value')
axes[1, 0].set_ylabel('Frequency')
axes[1, 0].set_title('Histogram')

# Error bars
y_err = np.random.normal(0, 0.1, len(x))
axes[1, 1].errorbar(x, y1, yerr=y_err, fmt='o', capsize=5, capthick=2)
axes[1, 1].set_xlabel('x')
axes[1, 1].set_ylabel('y')
axes[1, 1].set_title('Error Bars')

plt.tight_layout()
plt.savefig('scientific_plots.png', dpi=300, bbox_inches='tight')
plt.show()

Publication-Quality Plots

import matplotlib.pyplot as plt
import numpy as np

# Set style for publication
plt.style.use('seaborn-v0_8-darkgrid')

# Create figure
fig, ax = plt.subplots(figsize=(10, 6))

# Generate data
x = np.linspace(0, 10, 100)
y = np.sin(x) * np.exp(-x/10)

# Plot with publication settings
ax.plot(x, y, 'b-', linewidth=2.5, label='$y = \sin(x) e^{-x/10}$')
ax.fill_between(x, y, alpha=0.3)

# Formatting
ax.set_xlabel('Time (s)', fontsize=12, fontweight='bold')
ax.set_ylabel('Amplitude (V)', fontsize=12, fontweight='bold')
ax.set_title('Damped Oscillation', fontsize=14, fontweight='bold')
ax.legend(fontsize=11, loc='upper right')
ax.grid(True, alpha=0.3)

# Adjust tick labels
ax.tick_params(labelsize=10)

# Save with high DPI
plt.tight_layout()
plt.savefig('publication_plot.pdf', dpi=300, bbox_inches='tight')
plt.savefig('publication_plot.png', dpi=300, bbox_inches='tight')
plt.show()

3D Plots

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np

# Create 3D plot
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')

# Generate data
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))

# Surface plot
surf = ax.plot_surface(X, Y, Z, cmap='viridis', alpha=0.8)

# Add colorbar
fig.colorbar(surf, ax=ax, label='Z value')

# Labels
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('3D Surface Plot')

plt.show()

Seaborn for Statistical Plots

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

# Create sample data
data = pd.DataFrame({
    'Group': ['A']*50 + ['B']*50 + ['C']*50,
    'Value': np.concatenate([
        np.random.normal(10, 2, 50),
        np.random.normal(12, 2, 50),
        np.random.normal(11, 3, 50)
    ])
})

# Create figure
fig, axes = plt.subplots(2, 2, figsize=(12, 10))

# Box plot
sns.boxplot(data=data, x='Group', y='Value', ax=axes[0, 0])
axes[0, 0].set_title('Box Plot')

# Violin plot
sns.violinplot(data=data, x='Group', y='Value', ax=axes[0, 1])
axes[0, 1].set_title('Violin Plot')

# Strip plot
sns.stripplot(data=data, x='Group', y='Value', ax=axes[1, 0], size=6)
axes[1, 0].set_title('Strip Plot')

# Bar plot with error bars
sns.barplot(data=data, x='Group', y='Value', ax=axes[1, 1], errorbar='sd')
axes[1, 1].set_title('Bar Plot with Error Bars')

plt.tight_layout()
plt.show()

Plotly for Interactive Plots

import plotly.graph_objects as go
import plotly.express as px
import numpy as np
import pandas as pd

# Interactive line plot
x = np.linspace(0, 10, 100)
y = np.sin(x)

fig = go.Figure()
fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='sin(x)'))
fig.update_layout(
    title='Interactive Plot',
    xaxis_title='x',
    yaxis_title='y',
    hovermode='x unified'
)
fig.show()

# Interactive scatter plot with hover info
data = pd.DataFrame({
    'x': np.random.randn(100),
    'y': np.random.randn(100),
    'size': np.random.randint(10, 100, 100),
    'color': np.random.randint(0, 10, 100)
})

fig = px.scatter(
    data,
    x='x',
    y='y',
    size='size',
    color='color',
    hover_data=['x', 'y', 'size'],
    title='Interactive Scatter Plot'
)
fig.show()

# 3D scatter plot
fig = px.scatter_3d(
    data,
    x='x',
    y='y',
    z='size',
    color='color',
    title='3D Scatter Plot'
)
fig.show()

Heatmaps and Correlation Plots

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

# Create correlation matrix
data = pd.DataFrame(
    np.random.randn(100, 5),
    columns=['A', 'B', 'C', 'D', 'E']
)
corr = data.corr()

# Heatmap
fig, ax = plt.subplots(figsize=(8, 6))
sns.heatmap(corr, annot=True, fmt='.2f', cmap='coolwarm', center=0,
            square=True, linewidths=1, cbar_kws={"shrink": 0.8}, ax=ax)
ax.set_title('Correlation Matrix')
plt.tight_layout()
plt.show()

# Clustermap
sns.clustermap(corr, cmap='coolwarm', center=0, figsize=(8, 8))
plt.show()

Subplots and Layouts

import matplotlib.pyplot as plt
import numpy as np

# Create complex layout
fig = plt.figure(figsize=(14, 10))
gs = fig.add_gridspec(3, 3, hspace=0.3, wspace=0.3)

# Different sized subplots
ax1 = fig.add_subplot(gs[0, :2])
ax2 = fig.add_subplot(gs[0, 2])
ax3 = fig.add_subplot(gs[1:, 0])
ax4 = fig.add_subplot(gs[1:, 1:])

# Plot data
x = np.linspace(0, 10, 100)

ax1.plot(x, np.sin(x))
ax1.set_title('Large Plot 1')

ax2.plot(x, np.cos(x))
ax2.set_title('Small Plot')

ax3.hist(np.random.randn(1000), bins=30)
ax3.set_title('Histogram')

ax4.scatter(np.random.randn(100), np.random.randn(100))
ax4.set_title('Large Plot 2')

plt.show()

Saving Figures

import matplotlib.pyplot as plt
import numpy as np

# Create plot
fig, ax = plt.subplots()
x = np.linspace(0, 10, 100)
ax.plot(x, np.sin(x))

# Save in different formats
plt.savefig('plot.png', dpi=300, bbox_inches='tight')  # PNG
plt.savefig('plot.pdf', dpi=300, bbox_inches='tight')  # PDF
plt.savefig('plot.svg', dpi=300, bbox_inches='tight')  # SVG
plt.savefig('plot.eps', dpi=300, bbox_inches='tight')  # EPS

# Save with specific settings
plt.savefig('plot_high_quality.png',
    dpi=600,
    bbox_inches='tight',
    facecolor='white',
    edgecolor='none',
    transparent=False
)

Best Practices

  1. Clear labels: Always label axes with units
  2. Legends: Include legends for multiple lines
  3. Color choice: Use colorblind-friendly palettes
  4. Font sizes: Use readable font sizes
  5. Grid lines: Use grid for easier reading
  6. Aspect ratio: Choose appropriate aspect ratios
  7. High DPI: Save at 300+ DPI for publication

Common Pitfalls

Bad Practice:

# Don't: Unclear labels
plt.plot(x, y)
plt.xlabel('x')
plt.ylabel('y')

# Don't: Too many colors
for i in range(10):
    plt.plot(x, y[i])

# Don't: Low resolution
plt.savefig('plot.png', dpi=72)

Good Practice:

# Do: Clear, descriptive labels
plt.plot(x, y)
plt.xlabel('Time (seconds)')
plt.ylabel('Temperature (°C)')

# Do: Limited, distinct colors
colors = ['blue', 'red', 'green']
for i, color in enumerate(colors):
    plt.plot(x, y[i], color=color, label=f'Series {i+1}')

# Do: High resolution
plt.savefig('plot.png', dpi=300, bbox_inches='tight')

Conclusion

Effective scientific visualization communicates findings clearly. Master matplotlib for static plots, seaborn for statistical plots, and plotly for interactive visualizations. Follow best practices for publication-quality figures.

Resources

Comments

Share this article

Scan to read on mobile