Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Publication-quality data visualization with matplotlib, seaborn, and plotly
.claude/skills/brycewang-stanford-python-dataviz-guide/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-09 | ✗→✓ | ▲ Improved | 105% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 95% | 0% |
| case-07 | ✓→✓ | = Same ✓ | 119% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 96% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 109% | 0% |
Data visualization is how researchers communicate quantitative findings. A well-designed figure can convey complex relationships instantly, while a poor one buries the signal in clutter. Python's visualization ecosystem -- anchored by matplotlib, seaborn, and plotly -- provides everything needed to produce publication-quality figures for journals, conferences, and presentations.
This guide covers the three major Python visualization libraries, their strengths and trade-offs, and concrete recipes for the chart types researchers use most frequently. Each example is designed to be copy-paste ready and customizable for your specific dataset and venue requirements.
The emphasis is on producing figures that meet journal standards: correct DPI, appropriate font sizes, accessible color palettes, and vector-format exports. We also cover interactive visualization with plotly for exploratory analysis and supplementary materials.
Matplotlib is the most flexible Python plotting library. Nearly every other visualization tool in the Python ecosystem builds on it.
pythonimport matplotlib.pyplot as plt import matplotlib as mpl # Publication-quality defaults plt.rcParams.update({ 'figure.figsize': (6, 4), 'figure.dpi': 150, 'savefig.dpi': 300, 'savefig.bbox': 'tight', 'font.size': 11, 'font.family': 'serif', 'font.serif': ['Times New Roman'], 'axes.labelsize': 12, 'axes.titlesize': 13, 'xtick.labelsize': 10, 'ytick.labelsize': 10, 'legend.fontsize': 10, 'lines.linewidth': 1.5, 'lines.markersize': 6, 'axes.grid': True, 'grid.alpha': 0.3, })
pythonimport numpy as np epochs = np.arange(1, 51) acc_mean = 1 - 0.5 * np.exp(-epochs / 10) acc_std = 0.03 * np.exp(-epochs / 20) fig, ax = plt.subplots() ax.plot(epochs, acc_mean, label='Our Method', color='#2563EB') ax.fill_between(epochs, acc_mean - acc_std, acc_mean + acc_std, alpha=0.2, color='#2563EB') ax.set_xlabel('Epoch') ax.set_ylabel('Accuracy') ax.set_ylim(0.4, 1.0) ax.legend(frameon=False) fig.savefig('accuracy_curve.pdf') # Vector format for papers
pythonfig, axes = plt.subplots(1, 3, figsize=(15, 4), sharey=True) for ax, dataset, color in zip(axes, ['CIFAR-10', 'ImageNet', 'COCO'], ['#2563EB', '#DC2626', '#16A34A']): x = np.random.randn(200) ax.hist(x, bins=30, color=color, alpha=0.7, edgecolor='white') ax.set_title(dataset) ax.set_xlabel('Score Distribution') axes[0].set_ylabel('Count') plt.tight_layout() fig.savefig('multi_panel.pdf')
Seaborn excels at statistical graphics with minimal code. It handles data frames natively and produces polished output by default.
pythonimport seaborn as sns import pandas as pd data = pd.DataFrame({ 'Method': ['Baseline', 'Baseline', 'Ours', 'Ours', 'Ours+FT', 'Ours+FT'], 'Metric': ['BLEU', 'ROUGE'] * 3, 'Score': [34.2, 45.1, 41.8, 52.3, 48.5, 58.7] }) fig, ax = plt.subplots(figsize=(8, 5)) sns.barplot(data=data, x='Metric', y='Score', hue='Method', palette=['#94A3B8', '#3B82F6', '#EF4444'], ax=ax) ax.set_ylabel('Score') ax.legend(title='Method', frameon=False) fig.savefig('comparison.pdf')
pythoncorr_matrix = pd.DataFrame( np.random.randn(8, 8), columns=[f'Feature {i}' for i in range(8)] ).corr() fig, ax = plt.subplots(figsize=(8, 7)) sns.heatmap(corr_matrix, annot=True, fmt='.2f', cmap='RdBu_r', center=0, square=True, linewidths=0.5, ax=ax) ax.set_title('Feature Correlation Matrix') fig.savefig('heatmap.pdf')
pythondf = pd.DataFrame({ 'Group': np.repeat(['Control', 'Treatment A', 'Treatment B'], 100), 'Value': np.concatenate([ np.random.normal(50, 10, 100), np.random.normal(55, 8, 100), np.random.normal(60, 12, 100) ]) }) fig, ax = plt.subplots(figsize=(8, 5)) sns.violinplot(data=df, x='Group', y='Value', palette='Set2', inner='box', ax=ax) ax.set_ylabel('Measurement') fig.savefig('violin.pdf')
Plotly is ideal for exploratory analysis and HTML-based supplementary materials.
pythonimport plotly.express as px df = px.data.gapminder().query("year == 2007") fig = px.scatter(df, x="gdpPercap", y="lifeExp", size="pop", color="continent", hover_name="country", log_x=True, size_max=60, title="GDP vs Life Expectancy (2007)") fig.write_html("interactive_scatter.html") fig.write_image("scatter.pdf") # Requires kaleido
| Data Relationship | Recommended Chart | Library | |-------------------|-------------------|---------| | Trend over time | Line plot | matplotlib | | Distribution | Histogram, violin, box | seaborn | | Comparison (categories) | Bar chart, grouped bar | seaborn | | Correlation (2 vars) | Scatter plot | matplotlib/plotly | | Correlation (matrix) | Heatmap | seaborn | | Part-to-whole | Stacked bar (not pie) | matplotlib | | High-dimensional | PCA/t-SNE scatter | plotly | | Geospatial | Choropleth | plotly |
sns.color_palette("colorblind") or use tools like ColorBrewer.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-07 | pass→pass | 13,229 | 16,904 | +28% | 1 | 1 | 0% | 2,313 | 5,062 | +119% | 0 | 0 | — |
case-01 | fail→fail | 23,651 | 16,382 | -31% | 1 | 1 | 0% | 3,796 | 4,514 | +19% | 0 | 0 | — |
case-02 | pass→pass | 14,096 | 16,380 | +16% | 1 | 1 | 0% | 2,469 | 4,837 | +96% | 0 | 0 | — |
case-03 | fail→fail | 12,565 | 15,650 | +25% | 1 | 1 | 0% | 2,179 | 4,794 | +120% | 0 | 0 | — |
case-04 | pass→pass | 14,140 | 12,996 | -8% | 1 | 1 | 0% | 2,083 | 4,361 | +109% | 0 | 0 | — |
case-05 | pass→pass | 12,832 | 24,266 | +89% | 1 | 1 | 0% | 2,457 | 6,014 | +145% | 0 | 0 | — |
case-06 | pass→pass | 12,892 | 12,092 | -6% | 1 | 1 | 0% | 2,226 | 4,257 | +91% | 0 | 0 | — |
case-08 | pass→pass | 12,381 | 14,734 | +19% | 1 | 1 | 0% | 1,955 | 4,626 | +137% | 0 | 0 | — |
case-09 | fail→pass | 8,251 | 7,202 | -13% | 1 | 1 | 0% | 1,681 | 3,449 | +105% | 0 | 0 | — |
case-10 | pass→pass | 11,328 | 5,177 | -54% | 1 | 1 | 0% | 1,837 | 2,963 | +61% | 0 | 0 | — |
case-11 | pass→pass | 9,918 | 7,497 | -24% | 1 | 1 | 0% | 2,078 | 3,588 | +73% | 0 | 0 | — |
case-12 | pass→pass | 9,911 | 4,611 | -53% | 1 | 1 | 0% | 1,861 | 2,880 | +55% | 0 | 0 | — |
case-13 | fail→pass | 7,765 | 5,857 | -25% | 1 | 1 | 0% | 1,674 | 3,266 | +95% | 0 | 0 | — |
case-14 | pass→pass | 5,883 | 6,318 | +7% | 1 | 1 | 0% | 1,150 | 3,220 | +180% | 0 | 0 | — |
case-15 | pass→pass | 2,865 | 2,753 | -4% | 1 | 1 | 0% | 524 | 2,459 | +369% | 0 | 0 | — |
case-16 | pass→pass | 18,476 | 22,152 | +20% | 1 | 1 | 0% | 3,108 | 5,796 | +86% | 0 | 0 | — |
case-17 | pass→pass | 12,650 | 15,794 | +25% | 1 | 1 | 0% | 2,304 | 4,834 | +110% | 0 | 0 | — |
case-18 | pass→pass | 9,387 | 6,111 | -35% | 1 | 1 | 0% | 1,592 | 3,017 | +90% | 0 | 0 | — |
case-19 | fail→fail | 12,851 | 14,299 | +11% | 1 | 1 | 0% | 2,000 | 4,190 | +110% | 0 | 0 | — |
case-20 | pass→pass | 12,181 | 17,690 | +45% | 1 | 1 | 0% | 2,412 | 5,238 | +117% | 0 | 0 | — |
case-21 | pass→pass | 16,577 | 17,611 | +6% | 1 | 1 | 0% | 3,050 | 5,420 | +78% | 0 | 0 | — |
case-22 | pass→pass | 11,699 | 16,144 | +38% | 1 | 1 | 0% | 2,291 | 5,070 | +121% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +9 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.