Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/_static/custom.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Custom CSS for grplot documentation GitHub-inspired theme */
/* Custom CSS for grplot documentation: GitHub-inspired theme */

/* ── Code blocks ── */
[data-theme="dark"] .highlight {
Expand Down Expand Up @@ -168,7 +168,7 @@ article img {
box-shadow: 0 2px 12px rgba(140, 149, 159, 0.25);
}

/* ── Admonitions note, tip, warning ── */
/* ── Admonitions: note, tip, warning ── */
.admonition {
border-radius: 0 6px 6px 0;
padding: 0.75em 1em;
Expand Down
Binary file added docs/_static/plots/dashboard_matplotlib_tips.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
212 changes: 212 additions & 0 deletions docs/comparison.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
grplot vs. Matplotlib/Seaborn
=============================

Rather than assert that grplot reduces boilerplate, this page shows the same
dashboard implemented both ways so you can judge the difference directly.

Six-panel statistical dashboard
--------------------------------

The dashboard below combines six different chart families (a histogram
with KDE and summary statistics, an ECDF, a treemap, a pie chart, a Pareto
chart, and an annotated box + strip plot), each requiring its own
statistical-annotation logic (quantile lines and a stats box, a twin-axis
cumulative curve, confidence-interval whiskers).

Using grplot
~~~~~~~~~~~~

.. code-block:: python

from grplot import plot2d
import grplot_seaborn as gs

gs.set_theme(context='notebook', style='darkgrid', palette='deep')

tips = gs.load_dataset('tips')
ax = plot2d(plot={'[1,1]': 'histplot', '[1,2]': 'ecdfplot',
'[2,1]': 'treemapsplot', '[2,2]': 'pieplot',
'[3,1]': 'paretoplot', '[3,2]': 'boxplot+stripplot'},
Nx=2, Ny=3, df=tips, filter=(tips['total_bill'] > 10),
figsize=[18, 16], fontsize=12, legend_fontsize=11,
sep={'total_bill': '.c',
'.': ['Count', 'Proportion', '[2,1]', '[2,2]', 'Cumulative Percentage']},
statdesc={'[1,1]': {'total_bill': 'general'},
'[3,2]': {'total_bill': 'boxplot'}},
text={'Count': 'h', True: ['[2,1]', '[2,2]'], '[3,1]': {'total_bill': 'h+i'}},
tick_add={'total_bill': 'Rp(_)'},
title={'[1,1]': 'Histogram Count vs total_bill',
'[1,2]': 'ECDF Proportion vs total_bill',
'[2,1]': 'Treemaps of day',
'[2,2]': 'Pie of day',
'[3,1]': 'Pareto total_bill vs day',
'[3,2]': 'Box day vs total_bill'},
alpha={'[1,1]': 0.75, '[3,1]': 0.75},
kde=True)

**32 lines.**

.. image:: _static/plots/dashboard_2d.png
:alt: Six-panel statistical dashboard generated by a single plot2d call
:align: center

Using Matplotlib/Seaborn
~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import seaborn as sns
import squarify # pip install squarify (for the treemap)

sns.set_theme(context='notebook', style='darkgrid', palette='deep')
tips = sns.load_dataset('tips')
df = tips[tips['total_bill'] > 10].copy()
fig, axes = plt.subplots(3, 2, figsize=(18, 16))
rp = lambda v: f"Rp{v:,.2f}".replace(",", ".")

# Histogram + KDE
ax = axes[0, 0]
tb = df['total_bill']
counts, bins, patches = ax.hist(tb, bins=10, alpha=0.75, edgecolor='white')
for c, left, right in zip(counts, bins[:-1], bins[1:]):
if c > 0:
ax.text((left + right) / 2, c + 0.5, f"{int(c)}", ha='center', fontsize=12)
ax2 = ax.twinx()
sns.kdeplot(tb, ax=ax2, color='steelblue')
ax2.set_yticks([])
stats = {
'count': len(tb), 'non zero count': (tb != 0).sum(), 'unique count': tb.nunique(),
'std': tb.std(), 'range': tb.max() - tb.min(), 'min': tb.min(),
'q1': tb.quantile(0.25), 'median': tb.median(), 'mean': tb.mean(),
'q3': tb.quantile(0.75), 'max': tb.max(),
}
for key in ['min', 'q1', 'median', 'mean', 'q3', 'max']:
color = {'min': 'gray', 'q1': 'blue', 'median': 'purple',
'mean': 'orange', 'q3': 'green', 'max': 'red'}[key]
ls = '--' if key not in ('median', 'mean') else ':'
ax.axvline(stats[key], color=color, linestyle=ls, linewidth=1)
txt = "\n".join(
[f"count: {stats['count']}", f"non zero count: {stats['non zero count']}",
f"unique count: {stats['unique count']}", f"std: {rp(stats['std'])}",
f"range: {rp(stats['range'])}"] +
[f"{k}: {rp(stats[k])}" for k in ['min', 'q1', 'median', 'mean', 'q3', 'max']]
)
ax.text(0.98, 0.97, txt, transform=ax.transAxes, fontsize=11,
va='top', ha='right', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
ax.xaxis.set_major_formatter(mticker.FuncFormatter(lambda v, _: rp(v)))
ax.set_title('Histogram Count vs total_bill')
ax.set_ylabel('Count')

# ECDF
ax = axes[0, 1]
sns.ecdfplot(tb, ax=ax)
ax.xaxis.set_major_formatter(mticker.FuncFormatter(lambda v, _: rp(v)))
ax.set_title('ECDF Proportion vs total_bill')
ax.set_ylabel('Proportion')

# Treemap
ax = axes[1, 0]
day_counts = df['day'].value_counts()
colors = sns.color_palette('deep', len(day_counts))
squarify.plot(sizes=day_counts.values,
label=[f"{d}\n{c}" for d, c in day_counts.items()],
color=colors, ax=ax, edgecolor='white')
ax.set_title('Treemaps of day')
ax.axis('off')

# Pie
ax = axes[1, 1]
day_counts_pie = df['day'].value_counts()
ax.pie(day_counts_pie.values, labels=day_counts_pie.index,
autopct='%1.1f%%', colors=sns.color_palette('deep'))
ax.set_title('Pie of day')

# Pareto
ax = axes[2, 0]
means = df.groupby('day', observed=True)['total_bill'].mean().sort_values(ascending=False)
cum_pct = means.cumsum() / means.sum() * 100
ax.bar(means.index, means.values, alpha=0.75)
for i, v in enumerate(means.values):
ax.text(i, v / 2, rp(v), ha='center', fontsize=11)
ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda v, _: rp(v)))
ax.set_ylabel('total_bill')
ax3 = ax.twinx()
ax3.plot(means.index, cum_pct.values, color='black', marker='D')
for i, v in enumerate(cum_pct.values):
ax3.text(i, v + 3, f"{v:.1f}%", ha='center', fontsize=11)
ax3.set_ylim(0, 110)
ax3.yaxis.set_major_formatter(mticker.PercentFormatter())
ax3.set_ylabel('Cumulative Percentage')
ax3.grid(False)
ax.set_title('Pareto total_bill vs day')

# Box + strip
ax = axes[2, 1]
order = ['Thur', 'Fri', 'Sat', 'Sun']
sns.boxplot(data=df, x='total_bill', y='day', order=order, ax=ax, orient='h')
sns.stripplot(data=df, x='total_bill', y='day', order=order, ax=ax,
color='black', size=3, alpha=0.6)
ax.xaxis.set_major_formatter(mticker.FuncFormatter(lambda v, _: rp(v)))
ax.set_title('Box day vs total_bill')
q1, q3 = tb.quantile(0.25), tb.quantile(0.75)
iqr = q3 - q1
lw = tb[tb >= q1 - 1.5 * iqr].min()
uw = tb[tb <= q3 + 1.5 * iqr].max()
se = tb.std() / np.sqrt(len(tb))
box_stats = {
'min': tb.min(), 'lower whisker': lw, 'q1': q1,
'95% CI low': tb.mean() - 1.96 * se, 'median': tb.median(),
'mean': tb.mean(), '95% CI hi': tb.mean() + 1.96 * se,
'q3': q3, 'upper whisker': uw, 'max': tb.max(),
}
txt = "\n".join(f"{k}: {rp(v)}" for k, v in box_stats.items())
ax.text(1.02, 0.5, txt, transform=ax.transAxes, fontsize=10,
va='center', ha='left', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))

plt.tight_layout()
plt.savefig('dashboard.png', dpi=100, bbox_inches='tight')

**101 lines**, roughly 3x longer, because each panel's statistical
annotation (the stats box, the twin-axis cumulative line, the confidence
interval) is written out by hand instead of being generated from a
declarative argument.

.. image:: _static/plots/dashboard_matplotlib_tips.png
:alt: Equivalent six-panel dashboard built manually with Matplotlib and Seaborn
:align: center

Where the difference disappears
--------------------------------

This advantage is not universal, and it would be misleading to imply
otherwise. For a grid of *one repeated chart type* (nine box plots across a
metric/cluster grid, for example), grplot (37 lines) and a short Matplotlib
loop (34 lines) come out roughly the same length. grplot's boilerplate
reduction is concentrated in dashboards that mix several chart families, each
needing its own statistical-annotation code; it offers no particular
advantage when a simple loop over one chart type already does the job.

.. list-table::
:header-rows: 1
:widths: 60 15 20 15

* - Dashboard
- grplot
- Matplotlib/Seaborn
- Ratio
* - 6 heterogeneous panels (as above)
- 32 lines
- 101 lines
- 3.2×
* - 9 homogeneous panels (boxplot only, repeated across a metric/cluster grid)
- 37 lines
- 34 lines
- ~1.0×

In both cases, ``plot2d`` returns the underlying Matplotlib ``Axes`` array,
so any further per-panel customization is still available through the normal
Matplotlib API on top of the generated baseline.
4 changes: 2 additions & 2 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Configuration

The following configurations help you optimize grplot for your specific environment.

Image Quality Dots per Inch
Image Quality (Dots per Inch)
-----------------------------

By default, `grplot` ensures figures use a high-resolution DPI of 300. This
Expand All @@ -19,7 +19,7 @@ set it before creating any plots, for example:
Unsupported de Python Locale Solution
-------------------------------------

Not all environments support the `de` Python locale, which can break the matplotlib axis formatter for thousand separators. The alternative implementationdirectly drawing the stringwill always work. To use it, pass the following argument before plotting:
Not all environments support the `de` Python locale, which can break the matplotlib axis formatter for thousand separators. The alternative implementation (directly drawing the string) will always work. To use it, pass the following argument before plotting:

.. code-block:: python

Expand Down
30 changes: 28 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ grplot

.. container:: hero-tagline

Publication-ready statistical plots **one function, zero boilerplate.**
Publication-ready statistical plots: **one function, zero boilerplate.**


.. |pypi_ver| image:: https://img.shields.io/pypi/v/grplot.svg
Expand Down Expand Up @@ -34,14 +34,39 @@ grplot

|pypi_ver| |license| |anaconda| |downloads| |coverage|

.. code-block:: python

from grplot import plot2d
import grplot_seaborn as gs

gs.set_theme(context='notebook', style='darkgrid', palette='deep')

tips = gs.load_dataset('tips')
ax = plot2d(plot={'[1,1]': 'histplot', '[1,2]': 'ecdfplot',
'[2,1]': 'treemapsplot', '[2,2]': 'pieplot',
'[3,1]': 'paretoplot', '[3,2]': 'boxplot+stripplot'},
Nx=2, Ny=3, df=tips, filter=(tips['total_bill'] > 10),
kde=True) # see Dashboard-Like for the full, annotated call

.. image:: _static/plots/dashboard_2d.png
:alt: Six-panel statistical dashboard generated by a single plot2d call
:align: center

*A histogram with KDE and summary statistics, an ECDF, a treemap, a pie
chart, a Pareto chart, and an annotated box + strip plot: six different
chart families, fully annotated, produced by one* ``plot2d`` *call. See*
:doc:`comparison` *for the equivalent Matplotlib/Seaborn code side by side,*
:doc:`plots/dashboard` *for this example's full write-up, and*
:doc:`plot_types` *for a worked example of every individual chart type.*

.. container:: feature-grid

.. hlist::
:columns: 2

- 📐 Smart figure sizing & multi-panel layout
- 💱 Thousand separators & currency symbols
- 📊 20+ plot types scatter, box, violin & more
- 📊 20+ plot types: scatter, box, violin & more
- 🏷️ Automatic bar labels & point annotations
- 📈 On-plot statistical summaries
- 🔄 Tick rotation & unit labels
Expand All @@ -61,6 +86,7 @@ grplot
:hidden:

introduction
comparison
configuration
plot_types

Expand Down
4 changes: 2 additions & 2 deletions docs/installation.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Installation
============

Pip PyPI
Pip (PyPI)
----------

.. code-block:: bash
Expand All @@ -18,7 +18,7 @@ Conda
How to Update
-------------

Pip PyPI
Pip (PyPI)
~~~~~~~~~~

.. code-block:: bash
Expand Down
21 changes: 15 additions & 6 deletions docs/introduction.rst
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
Introduction
============

grplot introduces powerful features through an intuitive, hierarchical argument system. Every feature is directly accessible via ``plot2d`` parameters, allowing you to configure everything from high-level data inputs to the finest label details in a single function call.
grplot reduces the boilerplate of producing annotated, publication-ready
statistical figures: subplot layout, tick/number formatting, inset
statistical summaries, and value-label annotations are configured through
``plot2d`` parameters instead of separate Matplotlib/Seaborn calls per
figure. This page documents the hierarchical argument system and
configuration options that apply across *every* plot type. For a direct,
line-by-line comparison against equivalent Matplotlib/Seaborn code, see
:doc:`comparison`. For worked, runnable examples of each individual chart
with rendered output, see :doc:`plot_types`; for multi-panel dashboards
combining several chart types in one call, see :doc:`plots/dashboard`.

Understanding the Argument System
---------------------------------

Arguments in grplot are processed at four different levels of granularity:

- **Ordinary arguments** Apply to the entire figure (e.g., ``df``, ``figsize``, ``Nx``).
- **Axes arguments** Apply to specific subplots (e.g., ``plot``, ``filter``, ``title``).
- **Axes-plot arguments** Apply to specific plots within a subplot (e.g., ``zorder``).
- **Axes-axislabel arguments** Apply to specific axes and labels (e.g., ``lim``, ``sep``).
- **Ordinary arguments**: Apply to the entire figure (e.g., ``df``, ``figsize``, ``Nx``).
- **Axes arguments**: Apply to specific subplots (e.g., ``plot``, ``filter``, ``title``).
- **Axes-plot arguments**: Apply to specific plots within a subplot (e.g., ``zorder``).
- **Axes-axislabel arguments**: Apply to specific axes and labels (e.g., ``lim``, ``sep``).

💡 **Global Rule: The X and Y Counterparts**

Expand Down Expand Up @@ -291,7 +300,7 @@ Manage system resources and save your output. Both are Ordinary arguments.
* - ``optimizer``
- str
- ``'perf'``
- ``'perf'``/``'pandas'``fast, more memory. ``'saver'``/``'numpy'`` slower, less memory.
- ``'perf'``/``'pandas'``: fast, more memory. ``'saver'``/``'numpy'``: slower, less memory.
* - ``saveas``
- str or None
- ``None``
Expand Down
Loading