PlotSmith
PlotSmith is a layered plotting library for ML models with strict architectural boundaries. It provides clean, minimalist visualizations following best practices for analytical figures.
Features
- 4-Layer Architecture: Strict separation of concerns with clear boundaries
- Minimalist Styling: Clean, publication-ready plots with serif fonts and minimal clutter
- Type-Safe: Full type hints and immutable data structures
- Comprehensive Chart Types:
- Time Series: Time series plots with confidence bands
- Statistical: Histograms, box plots, violin plots, scatter plots
- Categorical: Bar charts, lollipop charts, dumbbell charts, range charts
- Specialized: Waterfall charts, waffle charts, slope charts, metric displays
- ML Analysis: Residual plots, backtest visualization, model comparison, forecast comparison
- Correlation: Correlation heatmaps
- ML-Focused: Built for model evaluation, backtesting, and analysis workflows
Installation
pip install plotsmith
Quick Start
import pandas as pd
import numpy as np
from plotsmith import plot_timeseries, plot_residuals, plot_heatmap
# Time series with confidence bands
data = pd.Series([1, 2, 3, 4, 5], index=pd.date_range('2020-01-01', periods=5))
lower = data - 0.5
upper = data + 0.5
fig, ax = plot_timeseries(
data,
bands={'95% CI': (lower, upper)},
title='Time Series with Confidence Bands'
)
# Residual analysis
actual = np.array([1, 2, 3, 4, 5])
predicted = np.array([1.1, 2.2, 2.9, 4.1, 4.8])
fig, ax = plot_residuals(actual, predicted, plot_type='scatter')
# Correlation heatmap
corr_matrix = df.corr()
fig, ax = plot_heatmap(corr_matrix, annotate=True, cmap='RdYlGn')
Architecture
PlotSmith follows a strict 4-layer architecture with clear boundaries:
Layer 1: Objects (plotsmith.objects)
Immutable dataclasses representing plot-ready data. No matplotlib imports.
SeriesView- Time series dataBandView- Confidence bandsScatterView- Scatter plot dataHistogramView- Histogram dataBarView- Bar chart dataHeatmapView- 2D heatmap dataFigureSpec- Figure styling specification
Layer 2: Primitives (plotsmith.primitives)
Drawing functions that accept only Layer 1 objects. All matplotlib calls here.
draw_series(),draw_scatter(),draw_histogram(),draw_bar(),draw_heatmap()minimal_axes()- Minimalist axes stylingtidy_axes()- Minimalist axes cleanupstyle_line_plot(),style_scatter_plot(),style_bar_plot()event_line(),direct_label(),note(),accent_point()
Layer 3: Tasks (plotsmith.tasks)
Translate user intent into Layer 1 objects. Can import pandas/numpy, not matplotlib.
TimeseriesPlotTask- Convert Series/DataFrame to viewsBacktestPlotTask- Convert results table to scatter viewsHistogramPlotTask- Convert data to histogram viewsBarPlotTask- Convert data to bar viewsHeatmapPlotTask- Convert 2D data to heatmap viewsResidualsPlotTask- Convert actual/predicted to residual views
Layer 4: Workflows (plotsmith.workflows)
Public API entry points. Orchestrate tasks → primitives → save/show.
plot_timeseries()- Time series plottingplot_backtest()- Backtest results visualizationplot_histogram()- Histogram plotting (supports overlaid)plot_bar()- Bar chart plottingplot_heatmap()- Heatmap/correlation matrix plottingplot_residuals()- Residual analysis plotsplot_model_comparison()- Compare multiple model predictionsfigure()- Create styled figuresmall_multiples()- Create grid of small-multiple axes
Workflows
Time Series Plotting
from plotsmith import plot_timeseries
import pandas as pd
# Single series
data = pd.Series([1, 2, 3, 4, 5], index=pd.date_range('2020-01-01', periods=5))
fig, ax = plot_timeseries(data, title='My Time Series')
# Multiple series (DataFrame)
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]},
index=pd.date_range('2020-01-01', periods=3))
fig, ax = plot_timeseries(df)
# With confidence bands
bands = {'CI': (lower_series, upper_series)}
fig, ax = plot_timeseries(data, bands=bands, save_path='output.png')
Residual Analysis
from plotsmith import plot_residuals
# Scatter plot (predicted vs actual)
fig, ax = plot_residuals(actual, predicted, plot_type='scatter', add_perfect_line=True)
# Time series (residuals over time)
fig, ax = plot_residuals(actual, predicted, x=dates, plot_type='series')
Model Comparison
from plotsmith import plot_model_comparison
models = {
'LSTM': lstm_predictions,
'XGBoost': xgb_predictions,
'SARIMA': sarima_predictions
}
fig, ax = plot_model_comparison(
actual_data,
models,
test_start_idx=100, # Optional: mark test period start
title='Model Comparison'
)
Heatmaps
from plotsmith import plot_heatmap
import pandas as pd
# Correlation matrix
corr = df.corr()
fig, ax = plot_heatmap(
corr,
annotate=True,
fmt='.2f',
cmap='RdYlGn',
vmin=-1,
vmax=1
)
# Any 2D array
data = np.random.rand(10, 10)
fig, ax = plot_heatmap(data, title='Custom Heatmap')
Histograms
from plotsmith import plot_histogram
# Single histogram
fig, ax = plot_histogram(data, bins=30, title='Distribution')
# Overlaid histograms
fig, ax = plot_histogram(
[normal_data, anomaly_data],
labels=['Normal', 'Anomalies'],
colors=['blue', 'red'],
alpha=0.7
)
Bar Charts
from plotsmith import plot_bar
categories = ['A', 'B', 'C', 'D']
values = [10, 20, 15, 25]
fig, ax = plot_bar(
categories,
values,
force_zero=True, # Honest scale
title='Bar Chart'
)
Backtest Results
from plotsmith import plot_backtest
results = pd.DataFrame({
'y_true': [1, 2, 3, 4, 5],
'y_pred': [1.1, 2.2, 2.9, 4.1, 4.8],
'fold_id': [0, 0, 1, 1, 2]
})
fig, ax = plot_backtest(results, fold_id_col='fold_id')
Styling Helpers
PlotSmith provides minimalist styling by default and includes helpers for customization:
from plotsmith import minimal_axes, tidy_axes, event_line, direct_label, ACCENT
fig, ax = plt.subplots()
minimal_axes(ax) # Minimalist styling (serif font, clean spines)
# or
tidy_axes(ax) # Minimalist style (gray spines, clean ticks)
# Add event markers
event_line(ax, x=2020, text='Policy Change', color=ACCENT)
# Direct labels
direct_label(ax, x=5, y=10, text='Peak', use_accent=True)
Examples
See examples/basic_timeseries.py for a complete example:
python examples/basic_timeseries.py
This generates examples/out/basic_timeseries.png with a time series plot including confidence bands.
Architecture Principles
- Strict Boundaries: Each layer can only import from layers below it
- Immutable Data: All view objects are frozen dataclasses
- Validation: Shape and alignment checks before drawing
- Separation of Concerns: Data representation (Layer 1) is separate from drawing (Layer 2)
- Type Safety: Full type hints throughout
Requirements
- Python 3.12+ (only)
- matplotlib >= 3.5.0, < 4.0.0
- numpy >= 1.20.0, < 3.0.0
- pandas >= 1.3.0, < 3.0.0
Development
# Install in development mode
pip install -e ".[dev]"
# Run tests
pytest
# Run with coverage
pytest --cov=plotsmith --cov-report=html
License
MIT License - see LICENSE file for details.
Contributing
See CONTRIBUTING.md for guidelines.
Citation
If you use PlotSmith in your research, please cite:
@software{plotsmith,
title={PlotSmith: A Layered Plotting Library for ML Models},
author={Jones, Kyle T.},
year={2025},
url={https://github.com/kylejones200/plotsmith}
}
Metadata
Release files for plotsmith 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| plotsmith-0.2.0.tar.gz | 48.3 kB | With provenance | Details for plotsmith-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| plotsmith-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for plotsmith-0.2.0-py3-none-any.whl |
Total release size: 86.2 kB
Release files / plotsmith-0.2.0.tar.gz
| Download URL | plotsmith-0.2.0.tar.gz |
|---|---|
| Size | 48.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1536fae23bacca23eaa044d5675899faa503c06fa6c45bff65024a2e75de0b44
|
|
BLAKE2b-256 checksum How to use checksums |
2b5c7954638ab515c3d520dc94b1e19d263a8142f4b3a50bbe8b9e738123e310
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 12, 2026.
Transparency logRelease files / plotsmith-0.2.0-py3-none-any.whl
| Download URL | plotsmith-0.2.0-py3-none-any.whl |
|---|---|
| Size | 37.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a8ef80d771b9cc68b81b26b0820dd0c8b9720e36152f09cc7bbf8c9250fa6f81
|
|
BLAKE2b-256 checksum How to use checksums |
1eee57cfac67126e2b3991fba7505d835a3413d953d422c1b41d60d0b618bf56
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 12, 2026.
Transparency logSecurity for plotsmith 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for plotsmith-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 12, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.