$npx -y skills add langchain-ai/deepagents --skill data-visualizationUse for creating publication-quality charts and multi-panel analysis summaries. Triggers when tasks involve visualizing data, plotting results, creating charts, or producing visual reports from analysis output.
| 1 | # Data Visualization Skill |
| 2 | |
| 3 | Create publication-quality analytical charts using matplotlib and seaborn in a headless GPU sandbox. Charts are saved as PNG files to `/workspace/` for retrieval. |
| 4 | |
| 5 | ## When to Use This Skill |
| 6 | |
| 7 | Use this skill when: |
| 8 | - Visualizing results from cuDF analysis or cuML models |
| 9 | - Creating charts (bar, line, scatter, heatmap, histogram, box plot) |
| 10 | - Building multi-panel analysis summaries |
| 11 | - The user asks for visual output, plots, graphs, or charts |
| 12 | - Presenting statistical findings with figures |
| 13 | |
| 14 | ## Initialization (REQUIRED) |
| 15 | |
| 16 | MUST call `matplotlib.use('Agg')` BEFORE importing pyplot. This enables headless rendering. |
| 17 | |
| 18 | ```python |
| 19 | import matplotlib |
| 20 | matplotlib.use('Agg') # Headless backend — MUST be before pyplot import |
| 21 | import matplotlib.pyplot as plt |
| 22 | import numpy as np |
| 23 | |
| 24 | # Publication-quality defaults |
| 25 | plt.rcParams.update({ |
| 26 | 'figure.dpi': 100, |
| 27 | 'savefig.dpi': 300, |
| 28 | 'font.size': 11, |
| 29 | 'axes.labelsize': 12, |
| 30 | 'axes.titlesize': 14, |
| 31 | 'xtick.labelsize': 10, |
| 32 | 'ytick.labelsize': 10, |
| 33 | 'legend.fontsize': 10, |
| 34 | 'figure.constrained_layout.use': True, |
| 35 | }) |
| 36 | |
| 37 | # Colorblind-safe palette (Okabe-Ito) |
| 38 | COLORS = ['#0173B2', '#DE8F05', '#029E73', '#D55E00', '#CC78BC', |
| 39 | '#CA9161', '#FBAFE4', '#949494', '#ECE133', '#56B4E9'] |
| 40 | ``` |
| 41 | |
| 42 | ## Saving Charts |
| 43 | |
| 44 | Always save to `/workspace/` with these settings: |
| 45 | |
| 46 | ```python |
| 47 | plt.savefig('/workspace/chart_name.png', dpi=300, bbox_inches='tight', |
| 48 | facecolor='white', edgecolor='none') |
| 49 | plt.close() |
| 50 | # IMPORTANT: call read_file("/workspace/<chart>.png") to display inline |
| 51 | ``` |
| 52 | |
| 53 | - `dpi=300` for print quality |
| 54 | - `bbox_inches='tight'` removes excess whitespace |
| 55 | - `facecolor='white'` ensures white background |
| 56 | - Always call `plt.close()` after saving to free memory |
| 57 | |
| 58 | ## Displaying Charts (REQUIRED) |
| 59 | |
| 60 | After saving any chart, you MUST call `read_file` on it to display it inline in the conversation: |
| 61 | |
| 62 | ``` |
| 63 | read_file("/workspace/chart_name.png") |
| 64 | ``` |
| 65 | |
| 66 | Users cannot see charts unless you do this. Every chart you save MUST be followed by a `read_file` call. |
| 67 | |
| 68 | ## Quick Reference |
| 69 | |
| 70 | ### Bar Chart (from groupby results) |
| 71 | |
| 72 | ```python |
| 73 | # After: result = to_pd(df.groupby("category")["value"].mean()) |
| 74 | fig, ax = plt.subplots(figsize=(8, 5)) |
| 75 | |
| 76 | bars = ax.bar(result.index, result.values, color=COLORS[:len(result)], |
| 77 | edgecolor='black', linewidth=0.8) |
| 78 | |
| 79 | for bar in bars: |
| 80 | height = bar.get_height() |
| 81 | ax.text(bar.get_x() + bar.get_width()/2., height, |
| 82 | f'{height:.1f}', ha='center', va='bottom', fontsize=9) |
| 83 | |
| 84 | ax.set_ylabel('Mean Value', fontweight='bold') |
| 85 | ax.set_xlabel('Category', fontweight='bold') |
| 86 | ax.set_title('Average Value by Category', fontweight='bold') |
| 87 | ax.grid(axis='y', alpha=0.3, linestyle='--') |
| 88 | ax.set_axisbelow(True) |
| 89 | |
| 90 | plt.savefig('/workspace/bar_chart.png', dpi=300, bbox_inches='tight', |
| 91 | facecolor='white', edgecolor='none') |
| 92 | plt.close() |
| 93 | # IMPORTANT: call read_file("/workspace/<chart>.png") to display inline |
| 94 | ``` |
| 95 | |
| 96 | ### Line Chart (trends over time) |
| 97 | |
| 98 | ```python |
| 99 | fig, ax = plt.subplots(figsize=(10, 5)) |
| 100 | |
| 101 | for i, col in enumerate(columns_to_plot): |
| 102 | ax.plot(df["date"], df[col], label=col, color=COLORS[i], linewidth=2, |
| 103 | marker='o', markersize=3, markevery=max(1, len(df)//20)) |
| 104 | |
| 105 | ax.set_ylabel('Values', fontweight='bold') |
| 106 | ax.set_xlabel('Date', fontweight='bold') |
| 107 | ax.set_title('Trends Over Time', fontweight='bold') |
| 108 | ax.legend(frameon=True, shadow=False) |
| 109 | ax.grid(True, alpha=0.3, linestyle='--') |
| 110 | ax.set_axisbelow(True) |
| 111 | plt.xticks(rotation=45, ha='right') |
| 112 | |
| 113 | plt.savefig('/workspace/line_chart.png', dpi=300, bbox_inches='tight', |
| 114 | facecolor='white', edgecolor='none') |
| 115 | plt.close() |
| 116 | # IMPORTANT: call read_file("/workspace/<chart>.png") to display inline |
| 117 | ``` |
| 118 | |
| 119 | ### Scatter Plot — Continuous Color (correlations) |
| 120 | |
| 121 | ```python |
| 122 | fig, ax = plt.subplots(figsize=(8, 6)) |
| 123 | |
| 124 | scatter = ax.scatter(df["x"], df["y"], c=df["value"], cmap='viridis', |
| 125 | s=40, alpha=0.7, edgecolors='black', linewidth=0.3) |
| 126 | plt.colorbar(scatter, ax=ax, label='Value') |
| 127 | |
| 128 | # Optional: trend line |
| 129 | z = np.polyfit(df["x"], df["y"], 1) |
| 130 | ax.plot(df["x"].sort_values(), np.poly1d(z)(df["x"].sort_values()), |
| 131 | "r--", linewidth=2, label=f'y={z[0]:.2f}x+{z[1]:.2f}') |
| 132 | |
| 133 | ax.set_xlabel('X', fontweight='bold') |
| 134 | ax.set_ylabel('Y', fontweight='bold') |
| 135 | ax.set_title('Correlation Analysis', fontweight='bold') |
| 136 | ax.legend() |
| 137 | ax.grid(True, alpha=0.3, linestyle='--') |
| 138 | |
| 139 | plt.savefig('/workspace/scatter_correlation.png', dpi=300, bbox_inches='tight', |
| 140 | facecolor='white', edgecolor='none') |
| 141 | plt.close() |
| 142 | # IMPORTANT: call read_file("/workspace/<chart>.png") to display inline |
| 143 | ``` |
| 144 | |
| 145 | ### Scatter Plot — Categorical Color (clusters) |
| 146 | |
| 147 | ```python |
| 148 | fig, ax = plt.subplots(figsize=(8, 6)) |
| 149 | |
| 150 | for i, label in enumerate(sorted(df["cluster"].unique())): |
| 151 | mask = df["cluster"] == l |