AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Visualization

skill-sshtomar-claude-code-skills-social-science-visualization · by sshtomar

Publication-quality statistical visualization with mandatory best practices. Use when creating plots, charts, figures, or any data visualization. Enforces accessibility, uncertainty display, proper labeling, and statistical accuracy.

No reviews yet
0 installs
17 views
0.0% view→install

Install

$ agentstack add skill-sshtomar-claude-code-skills-social-science-visualization

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-sshtomar-claude-code-skills-social-science-visualization)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Visualization? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Visualization is not decoration—it is statistical communication. A well-designed plot reveals patterns that tables cannot, while a poor plot misleads more effectively than wrong numbers. Every visualization is an argument about data, and this skill enforces the standards that make those arguments honest, accessible, and reproducible.

The difference between amateur and professional visualization is not aesthetics but statistical integrity: showing uncertainty, avoiding perceptual tricks, ensuring accessibility, and documenting what you've done. This skill enforces visualization practices that meet publication standards while preventing common manipulations.

"The purpose of visualization is insight, not pictures" - Ben Shneiderman

FUNDAMENTAL RULES:

  1. Every estimate needs uncertainty bands
  2. Every axis needs units
  3. Every color scheme needs accessibility testing
  4. Every truncated axis needs justification
  5. Every plot needs to be reproducible

Complete Axis Labeling MUST label all axes with variable names AND units in parentheses Cleveland (1985) found unlabeled axes were the #1 source of misinterpretation in published figures Readers misinterpret scale, leading to order-of-magnitude errors in interpretation

Uncertainty Visualization ALL point estimates MUST show confidence intervals or standard errors Cumming et al. (2007) demonstrate that showing uncertainty reduces overconfidence in conclusions by 40% False precision, overconfident claims, inability to distinguish signal from noise

Accessibility Compliance MUST use colorblind-safe palettes, test with simulator, provide non-color redundancy 8% of men and 0.5% of women have color vision deficiency (Neitz & Neitz, 2011) Excludes ~4% of readers, may violate journal/grant accessibility requirements

Resolution and Format Standards Minimum 144 DPI for screens, 300 DPI for print, vector format for publication Journal standards require 300+ DPI; low resolution causes rejection at submission Desk rejection from journals, pixelated figures in presentations

Honest Scaling Y-axis MUST start at zero for bar charts, ratios, and percentages unless explicitly justified Huff (1954) "How to Lie with Statistics" - truncated axes are the most common manipulation Exaggerated effects, misleading comparisons, ethical violations

Return Figure Objects for Display MUST return figure objects (fig, plt.gcf(), or plt.gca()) as the last expression in the cell. NEVER use plt.show() - it prevents inline display in notebooks and breaks reproducibility Notebook environments (Jupyter, marimo) automatically display the last expression. plt.show() blocks execution, prevents inline display, and makes figures inaccessible for further manipulation. Returning figure objects enables notebook display, programmatic access, and proper figure management Figures don't display in notebooks, code execution blocked, figures cannot be saved or manipulated programmatically, breaks notebook workflow

When creating statistical visualizations:

  1. Choose plot type based on data structure and question
  2. Set up proper figure dimensions and resolution
  3. Plot data with appropriate aesthetics
  4. Add ALL required labels with units
  5. Include uncertainty measures (CI, SE, prediction bands)
  6. Test accessibility (colorblind simulation)
  7. Add statistical annotations (p-values, R², n)
  8. Save in multiple formats (PNG for viewing, PDF for publication)
  9. Return figure object (fig, plt.gcf(), or plt.gca()) as last expression - NEVER use plt.show()
  10. Document code for reproducibility
# CRITICAL: Publication-quality visualization template

@app.cell
def create_publication_figure(df, x_var, y_var, group_var=None):
    """
    Every visualization must meet publication standards.
    This template enforces labeling, uncertainty, accessibility, and
    reproducibility requirements that prevent common mistakes.
    """
    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd
    import seaborn as sns
    import os
    from matplotlib import cm

    # MANDATORY: Set publication-quality defaults
    plt.rcParams.update({
        'font.size': 11,
        'font.family': 'sans-serif',
        'axes.labelsize': 12,
        'axes.titlesize': 13,
        'xtick.labelsize': 10,
        'ytick.labelsize': 10,
        'legend.fontsize': 10,
        'figure.titlesize': 14,
        'axes.linewidth': 1.5,
        'axes.grid': True,
        'grid.alpha': 0.3,
    })

    # MANDATORY: Colorblind-safe palette
    # Source: Wong, B. (2011) Nature Methods 8, 441
    colorblind_colors = [
        '#0173B2',  # Blue
        '#DE8F05',  # Orange
        '#029E73',  # Green
        '#CC78BC',  # Light purple
        '#ECE133',  # Yellow
        '#56B4E9',  # Light blue
        '#F0E442',  # Light yellow
    ]

    # Create figure with specific size for journal column width
    # Single column: 3.5", double column: 7"
    fig, ax = plt.subplots(figsize=(7, 5), dpi=144)

    # Get data statistics for annotations
    n_obs = len(df)
    x_mean = df[x_var].mean()
    y_mean = df[y_var].mean()

    if group_var is None:
        # Single group with confidence band
        ax.scatter(df[x_var], df[y_var], alpha=0.6, s=50,
                  color=colorblind_colors[0], edgecolor='black', linewidth=0.5)

        # Add regression line with confidence interval
        from scipy import stats
        slope, intercept, r_value, p_value, std_err = stats.linregress(df[x_var], df[y_var])

        x_line = np.linspace(df[x_var].min(), df[x_var].max(), 100)
        y_line = slope * x_line + intercept

        # Calculate confidence interval for regression line
        from scipy.stats import t
        predict_se = std_err * np.sqrt(1/n_obs + (x_line - x_mean)**2 / ((df[x_var] - x_mean)**2).sum())
        margin = t.ppf(0.975, n_obs - 2) * predict_se

        ax.plot(x_line, y_line, color=colorblind_colors[0], linewidth=2,
               label=f'Fit (R² = {r_value**2:.3f})')
        ax.fill_between(x_line, y_line - margin, y_line + margin,
                       alpha=0.2, color=colorblind_colors[0], label='95% CI')

        # Add statistical annotation
        ax.text(0.05, 0.95, f'n = {n_obs}\np = {p_value:.3f}',
               transform=ax.transAxes, verticalalignment='top',
               bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))

    else:
        # Multiple groups with distinct colors and markers
        markers = ['o', 's', '^', 'D', 'v', '']
        groups = df[group_var].unique()

        for i, group in enumerate(groups):
            subset = df[df[group_var] == group]
            color = colorblind_colors[i % len(colorblind_colors)]
            marker = markers[i % len(markers)]

            # Plot with confidence intervals
            x_vals = subset.groupby(x_var)[y_var].mean()
            x_sems = subset.groupby(x_var)[y_var].sem()

            ax.errorbar(x_vals.index, x_vals.values, yerr=1.96*x_sems.values,
                       marker=marker, markersize=8, linewidth=2, capsize=5,
                       color=color, label=f'{group} (n={len(subset)})',
                       alpha=0.8)

    # MANDATORY: Complete axis labeling with units
    # Extract units from variable names if formatted as "variable (unit)"
    x_label = x_var if '(' in x_var else f"{x_var} (units)"
    y_label = y_var if '(' in y_var else f"{y_var} (units)"

    ax.set_xlabel(x_label, fontweight='bold')
    ax.set_ylabel(y_label, fontweight='bold')

    # MANDATORY: Informative title
    if group_var:
        ax.set_title(f'{y_var} vs {x_var} by {group_var}\n(Mean ± 95% CI)',
                    fontweight='bold', pad=20)
    else:
        ax.set_title(f'{y_var} vs {x_var}\n(with 95% Confidence Band)',
                    fontweight='bold', pad=20)

    # MANDATORY: Grid for readability
    ax.grid(True, alpha=0.3, linestyle='--')

    # MANDATORY: Legend with sample sizes
    if group_var or True:  # Always show legend for clarity
        ax.legend(loc='best', frameon=True, fancybox=True, shadow=True)

    # Check if y-axis should start at zero (for ratios, percentages, counts)
    if any(keyword in y_var.lower() for keyword in ['percent', 'ratio', 'proportion', 'count']):
        y_min, y_max = ax.get_ylim()
        if y_min > 0:
            ax.set_ylim(bottom=0, top=y_max * 1.1)
            ax.annotate('Note: Y-axis starts at zero', xy=(0.5, -0.15),
                       xycoords='axes fraction', ha='center', fontsize=9, style='italic')

    # Add data source and timestamp for reproducibility
    fig.text(0.99, 0.01, f'Data: {n_obs} observations | Generated: {pd.Timestamp.now().strftime("%Y-%m-%d")}',
            ha='right', fontsize=8, style='italic', alpha=0.7)

    # Tight layout to prevent label cutoff
    plt.tight_layout()

    # MANDATORY: Save in multiple formats
    os.makedirs("./images", exist_ok=True)

    # Screen viewing (PNG at 144 DPI)
    plt.savefig("./images/figure_screen.png", dpi=144, bbox_inches="tight")

    # Publication (PDF vector format)
    plt.savefig("./images/figure_publication.pdf", bbox_inches="tight")

    # High-res for presentations (PNG at 300 DPI)
    plt.savefig("./images/figure_highres.png", dpi=300, bbox_inches="tight")

    print("Figure saved in three formats:")
    print("  ./images/figure_screen.png (144 DPI for screens)")
    print("  ./images/figure_publication.pdf (vector for journals)")
    print("  ./images/figure_highres.png (300 DPI for presentations)")

    # CRITICAL: Return figure for inline display in notebook
    # Do NOT call plt.show() - it prevents inline display and blocks execution
    # Do NOT call plt.close() - this would prevent display
    # MUST return figure object (fig, plt.gcf(), or plt.gca()) as last expression
    return fig,

Visualizing treatment effects with proper uncertainty bands

@app.cell
def plot_treatment_effects():
    #Treatment effects must show uncertainty to enable
    # proper inference. This example shows the gold standard for
    # presenting experimental results with multiple treatments.

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

    # Simulated treatment effects from an experiment
    treatments = ['Control', 'Treatment A', 'Treatment B', 'Treatment C']
    effects = [0, 2.3, 3.7, 1.8]
    std_errors = [0.5, 0.6, 0.8, 0.7]
    sample_sizes = [102, 98, 95, 101]
    p_values = [1.000, 0.001, 0.0001, 0.042]

    # MANDATORY: Colorblind-safe palette
    colors = ['#808080', '#0173B2', '#029E73', '#DE8F05']

    fig, ax = plt.subplots(figsize=(10, 6), dpi=144)

    # Calculate 95% confidence intervals
    ci_lower = [e - 1.96*se for e, se in zip(effects, std_errors)]
    ci_upper = [e + 1.96*se for e, se in zip(effects, std_errors)]

    # Create coefficient plot (forest plot style)
    y_positions = range(len(treatments))

    for i, (treatment, effect, lower, upper, n, p, color) in enumerate(
        zip(treatments, effects, ci_lower, ci_upper, sample_sizes, p_values, colors)
    ):
        # Plot point estimate
        ax.scatter(effect, i, s=150, color=color, zorder=3,
                  edgecolors='black', linewidth=1.5)

        # Plot confidence interval
        ax.plot([lower, upper], [i, i], linewidth=3, color=color, alpha=0.7)

        # Add caps to CI
        cap_width = 0.05
        ax.plot([lower, lower], [i-cap_width, i+cap_width], linewidth=2, color=color)
        ax.plot([upper, upper], [i-cap_width, i+cap_width], linewidth=2, color=color)

        # Add text annotations with sample size and p-value
        significance = '***' if p 

Forest plots are the gold standard for showing treatment effects. Always include: point estimates, confidence intervals, sample sizes, p-values, and a reference line at zero.

Time series with intervention points and uncertainty bands

```python
@app.cell
def plot_time_series_with_intervention():
    #Time series plots must clearly show when interventions
    # occurred and quantify uncertainty around trends. This example shows
    # best practices for interrupted time series visualization.

    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd
    from scipy import stats
    import os

    # Generate example time series data
    np.random.seed(42)
    dates = pd.date_range('2020-01-01', '2023-12-31', freq='M')
    n_points = len(dates)

    # Pre-intervention trend
    pre_intervention = 36  # Month 36 is intervention
    trend_pre = 100 + 2 * np.arange(pre_intervention)
    noise_pre = np.random.normal(0, 10, pre_intervention)

    # Post-intervention (level shift + trend change)
    level_shift = 20
    trend_change = -1.5
    trend_post = (100 + 2 * pre_intervention + level_shift +
                 trend_change * np.arange(n_points - pre_intervention))
    noise_post = np.random.normal(0, 12, n_points - pre_intervention)

    # Combine
    values = np.concatenate([trend_pre + noise_pre, trend_post + noise_post])

    df = pd.DataFrame({
        'date': dates,
        'value': values,
        'period': ['Pre' if i 

For interrupted time series: Always mark the intervention clearly, show pre/post trends separately, quantify level and slope changes, and include uncertainty bands.

Multi-panel publication-ready figure with shared aesthetics

```python
@app.cell
def create_publication_panel_figure():
    #Multi-panel figures are standard in publications.
    # This example shows how to create a complex figure with multiple
    # related plots that share formatting and meet journal standards.

    import matplotlib.pyplot as plt
    import matplotlib.gridspec as gridspec
    import numpy as np
    import pandas as pd
    import seaborn as sns
    from scipy import stats
    import os

    # Generate example data
    np.random.seed(123)
    n = 200
    df = pd.DataFrame({
        'x': np.random.normal(50, 15, n),
        'y': np.random.normal(100, 20, n),
        'group': np.random.choice(['Control', 'Treatment'], n),
        'covariate': np.random.uniform(0, 100, n)
    })

    # Add treatment effect
    treatment_mask = df['group'] == 'Treatment'
    df.loc[treatment_mask, 'y'] += 10 + 0.3 * df.loc[treatment_mask, 'covariate']

    # MANDATORY: Set up publication-quality figure
    # For Nature/Science: width = 180mm (7.09 inches) for full page
    fig = plt.figure(figsize=(7, 8), dpi=300)

    # Use GridSpec for complex layout
    gs = gridspec.GridSpec(3, 2, height_ratios=[1, 1, 0.8],
                          width_ratios=[1, 1], hspace=0.3, wspace=0.3)

    # Colorblind-safe colors
    colors = {'Control': '#0173B2', 'Treatment': '#DE8F05'}

    # PANEL A: Distribution comparison
    ax_a = fig.add_subplot(gs[0, :])

    for group in ['Control', 'Treatment']:
        subset = df[df['group'] == group]['y']

        # Histogram with KDE
        counts, bins, _ = ax_a.hist(subset, bins=20, alpha=0.5,
                                    label=group, color=colors[group],
                                    edgecolor='black', linewidth=0.5)

        # Add KDE
        kde = stats.gaussian_kde(subset)
        x_range = np.linspace(subset.min(), subset.max(), 100)
        kde_values = kde(x_range) * len(subset) * (bins[1] - bins[0])
        ax_a.plot(x_range, kde_values, color=colors[group],
                 linewidth=2, alpha=0.8)

        # Add mean line
        mean_val = subset.mean()
        ax_a.axvline(mean_val, color=colors[group], linestyle='--',
                    linewidth=2, alpha=0.7)

        # Add text with stats
        ax_a.text(mean_val, ax_a.get_ylim()[1]*0.9,
                 f'μ={mean_val:.1f}',
                 ha='center', fontsize=8, color=colors[group])

    ax_a.set_xlabel('Outcome (units)', fontweight='bold')
    ax_a.set_ylabel('Frequency', fontweight='bold')
    ax_a.set_title('A. Distribution by Group', fontweight='bold', loc='left')
    ax_a.legend(loc='upper right')
    ax_a.grid(True, alpha=0.3)

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [sshtomar](https://github.com/sshtomar)
- **Source:** [sshtomar/claude-code-skills-social-science](https://github.com/sshtomar/claude-code-skills-social-science)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.