skills/data-visualization/SKILL.md
Python data visualization for notebooks, scripts, and reports using matplotlib, seaborn, and Plotly. Use when: plotting analysis results in Jupyter/Marimo notebooks, choosing between static and interactive charts, creating publication- quality figures, building dashboard-style multi-panel layouts, exporting charts for reports or presentations, auditing existing plots for readability and accessibility, or improving visual consistency across an analysis project.
npx skillsauth add michaelsvanbeek/personal-agent-skills data-visualizationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
For React web application charts (Recharts, dark mode theming, CSS token integration), see the charts skill instead. This skill covers Python-native visualization for data science workflows.
| Library | Best For | Output | Install |
|---------|----------|--------|---------|
| matplotlib | Full control, publication figures, custom layouts | Static (PNG, SVG, PDF) | uv add matplotlib |
| seaborn | Statistical plots, distribution analysis, beautiful defaults | Static (built on matplotlib) | uv add seaborn |
| Plotly | Interactive exploration, hover tooltips, dashboards | Interactive HTML, static export | uv add plotly |
| Altair | Declarative interactive exploration, web integration, layered charts | Interactive HTML (Vega-Lite) | uv add altair |
| Chart Type | Use When | Library |
|------------|----------|---------|
| Line | Trends over time | All |
| Bar | Comparing categories | All |
| Histogram | Distribution of a single variable | seaborn (histplot) |
| KDE / Density | Smoothed distribution estimate | seaborn (kdeplot) |
| Box / Violin | Distribution comparison across groups | seaborn (boxplot, violinplot) |
| Scatter | Correlation between two variables | All |
| Heatmap | Correlation matrix, pivot table intensity | seaborn (heatmap) |
| Pair plot | All pairwise relationships in a dataset | seaborn (pairplot) — limit to ≤8 columns |
| Facet grid | Same chart repeated across subgroups | seaborn (FacetGrid) or Plotly (facet_col) |
Apply at the top of every notebook or script:
import matplotlib.pyplot as plt
import seaborn as sns
# Use seaborn's clean theme with matplotlib
sns.set_theme(
style="whitegrid",
palette="colorblind", # accessible by default
font_scale=1.1,
rc={
"figure.figsize": (10, 6),
"axes.titlesize": 14,
"axes.labelsize": 12,
"figure.dpi": 150,
"savefig.dpi": 300,
"savefig.bbox": "tight",
},
)
"colorblind" palette — accessible to colorblind readers."Blues", "Greens" — for ordered data (low → high)."RdBu", "coolwarm" — for data with a meaningful center (e.g., correlation)."colorblind", "Set2" — for unordered groups.# Categorical comparison
sns.barplot(data=df, x="category", y="value", palette="colorblind")
# Sequential heatmap
sns.heatmap(corr_matrix, cmap="Blues", annot=True, fmt=".2f")
# Diverging (centered at 0)
sns.heatmap(corr_matrix, cmap="RdBu", center=0, annot=True, fmt=".2f")
Rule: Never use "rainbow" or "jet" colormaps — they are perceptually non-uniform and inaccessible.
Every chart must have:
"Revenue (USD)", not "Y".import matplotlib.ticker as mticker
fig, ax = plt.subplots()
sns.barplot(data=df, x="month", y="revenue", ax=ax)
ax.set_title("Monthly Revenue by Region")
ax.set_xlabel("Month")
ax.set_ylabel("Revenue (USD)")
# Format y-axis as currency
ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f"${x:,.0f}"))
# Rotate x-labels if needed (last resort — prefer shorter labels)
ax.tick_params(axis="x", rotation=45)
plt.tight_layout()
from matplotlib.ticker import FuncFormatter, EngFormatter
# Thousands separator: 1,000,000
fmt_thousands = FuncFormatter(lambda x, _: f"{x:,.0f}")
# Engineering notation: 1M, 2.5k
fmt_engineering = EngFormatter(places=1)
# Percentage: 45.2%
fmt_pct = FuncFormatter(lambda x, _: f"{x:.1%}")
fig, ax = plt.subplots(figsize=(12, 5))
sns.lineplot(data=df, x="date", y="value", hue="category", ax=ax)
ax.set_title("Daily Active Users by Platform")
ax.set_ylabel("Users")
ax.legend(title="Platform", bbox_to_anchor=(1.02, 1), loc="upper left")
plt.tight_layout()
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Histogram + KDE
sns.histplot(data=df, x="amount", hue="plan", kde=True, ax=axes[0])
axes[0].set_title("Transaction Amount Distribution")
# Box plot comparison
sns.boxplot(data=df, x="plan", y="amount", ax=axes[1])
axes[1].set_title("Amount by Plan")
plt.tight_layout()
# Compute correlation on numeric columns
corr = df.select(pl.col(pl.NUMERIC_DTYPES)).to_pandas().corr()
fig, ax = plt.subplots(figsize=(8, 8))
mask = [[i < j for j in range(len(corr))] for i in range(len(corr))]
sns.heatmap(corr, mask=mask, annot=True, fmt=".2f", cmap="RdBu", center=0, ax=ax)
ax.set_title("Feature Correlation Matrix")
plt.tight_layout()
g = sns.FacetGrid(df.to_pandas(), col="region", col_wrap=3, height=4)
g.map_dataframe(sns.histplot, x="amount", bins=30)
g.set_titles("{col_name}")
g.set_axis_labels("Amount (USD)", "Count")
g.tight_layout()
Use Plotly when interactivity adds value (hover details, zoom, filtering):
import plotly.express as px
fig = px.scatter(
df.to_pandas(),
x="spend",
y="revenue",
color="category",
size="users",
hover_data=["name"],
title="Spend vs Revenue by Category",
labels={"spend": "Ad Spend (USD)", "revenue": "Revenue (USD)"},
)
fig.update_layout(template="plotly_white")
fig.show()
template="plotly_white" for clean, readable charts.labels={} to override column names with human-readable labels.hover_data for context that doesn't fit on axes.fig.write_image("chart.png", scale=2).Use Altair for declarative, composable interactive charts — especially for exploratory analysis and web integration:
import altair as alt
chart = alt.Chart(df.to_pandas()).mark_circle(size=60).encode(
x=alt.X("spend:Q", title="Ad Spend (USD)"),
y=alt.Y("revenue:Q", title="Revenue (USD)"),
color=alt.Color("category:N", title="Category"),
tooltip=["name:N", "spend:Q", "revenue:Q"],
).interactive().properties(
width=600,
height=400,
title="Spend vs Revenue by Category"
)
chart.show()
x, y, color, size) for declarative mapping — more readable than positional arguments..interactive() to enable zoom and pan.| (side-by-side) or & (stacked) for dashboard layouts.alt.condition() for conditional encoding based on selection.# Layered chart with conditional color
selection = alt.selection_point(fields=["category"])
base = alt.Chart(df.to_pandas()).encode(
x="spend:Q",
y="revenue:Q",
color=alt.condition(selection, "category:N", alt.value("lightgray")),
).add_params(selection)
base.mark_circle(size=60) | base.mark_line()
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
sns.lineplot(data=df, x="date", y="revenue", ax=axes[0, 0])
axes[0, 0].set_title("Revenue Over Time")
sns.barplot(data=top_10, x="revenue", y="product", ax=axes[0, 1])
axes[0, 1].set_title("Top 10 Products")
sns.histplot(data=df, x="order_value", bins=50, ax=axes[1, 0])
axes[1, 0].set_title("Order Value Distribution")
sns.heatmap(pivot_table, ax=axes[1, 1], cmap="Blues")
axes[1, 1].set_title("Heatmap")
fig.suptitle("Sales Dashboard — Q1 2026", fontsize=16, y=1.02)
plt.tight_layout()
from plotly.subplots import make_subplots
import plotly.graph_objects as go
fig = make_subplots(rows=1, cols=2, subplot_titles=("Revenue", "Users"))
fig.add_trace(go.Scatter(x=df["date"], y=df["revenue"], name="Revenue"), row=1, col=1)
fig.add_trace(go.Bar(x=df["date"], y=df["users"], name="Users"), row=1, col=2)
fig.update_layout(template="plotly_white", title="Key Metrics")
fig.show()
# PNG (raster) — good for slides and documents
fig.savefig("output/chart.png", dpi=300, bbox_inches="tight")
# SVG (vector) — good for web, scales without pixelation
fig.savefig("output/chart.svg", bbox_inches="tight")
# PDF (vector) — good for print
fig.savefig("output/chart.pdf", bbox_inches="tight")
Requires kaleido: uv add kaleido.
fig.write_image("output/chart.png", scale=2, width=1200, height=600)
fig.write_html("output/chart.html", include_plotlyjs="cdn")
# Export to interactive HTML
chart.save("output/chart.html")
# Export to static PNG (requires `altair_saver` or `selenium + geckodriver`)
chart.save("output/chart.png")
# Export to JSON specification for embedding in web apps
chart.to_json()
dpi=300 or scale=2."colorblind" palette as the default — it is distinguishable by people with common color vision deficiencies.# Accessible line chart: different markers AND colors
markers = ["o", "s", "D", "^", "v"]
for i, (name, group) in enumerate(df.group_by("category")):
ax.plot(group["x"], group["y"], marker=markers[i % len(markers)], label=name)
development
TypeScript coding standards and type safety conventions. Use when: creating TypeScript files, defining interfaces and types, writing type-safe code, reviewing TypeScript for type correctness, auditing a codebase for type safety gaps, eliminating any or ts-ignore usage, or improving strict-mode compliance. Covers strict typing, avoiding any and ts-ignore, discriminated unions, Zod runtime validation, immutability patterns, and proper type definitions.
testing
Writing clear, actionable tickets in any issue tracker (Jira, Linear, GitHub Issues, ServiceNow, etc.). Use when: creating epics, stories, tasks, bugs, or spikes; writing acceptance criteria; decomposing work for a sprint; linking dependencies between tickets; auditing backlog items for clarity; or coaching a team on ticket quality. Covers title conventions, description templates, acceptance criteria, decomposition rules, dependency linking, and org-specific pluggable configuration.
development
Testing strategy, patterns, and evaluation for software and LLM/AI systems. Use when: writing tests, choosing test boundaries, designing test data, structuring test suites, evaluating LLM outputs, building evaluation pipelines, setting coverage thresholds, auditing test coverage gaps in existing projects, or improving test quality and structure.
development
Writing effective status updates for different audiences and cadences. Use when: writing a weekly status update, preparing a monthly summary, drafting a quarterly review, sending updates to leadership, sharing progress with stakeholders, or improving the clarity and impact of team communications. Covers weekly, monthly, and quarterly formats tailored for upward, lateral, and downward communication.