Metadata-Version: 2.4
Name: sinaplot
Version: 0.1.0
Summary: SinaPlot for pandas and Matplotlib, with sample-size-scaled violins
Author: okumuralab
License-Expression: MIT
Project-URL: Homepage, https://github.com/okumuralab/sinaplot
Project-URL: Documentation, https://github.com/okumuralab/sinaplot#readme
Project-URL: Issues, https://github.com/okumuralab/sinaplot/issues
Project-URL: Source, https://github.com/okumuralab/sinaplot
Keywords: data visualization,matplotlib,pandas,sinaplot,statistics
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: matplotlib>=3.6
Requires-Dist: numpy>=1.23
Requires-Dist: pandas>=1.5
Requires-Dist: scipy>=1.9
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: ruff>=0.9; extra == "dev"
Requires-Dist: twine>=5; extra == "dev"
Dynamic: license-file

# sinaplot

[![PyPI](https://img.shields.io/pypi/v/sinaplot)](https://pypi.org/project/sinaplot/)
[![CI](https://github.com/okumuralab/sinaplot/actions/workflows/ci.yml/badge.svg)](https://github.com/okumuralab/sinaplot/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

`sinaplot` draws [SinaPlots](https://doi.org/10.1101/028191) from pandas data
with Matplotlib. A SinaPlot shows every observation while using the width of
the point distribution to represent the estimated density.

This implementation has two features intended to make comparisons more
truthful:

- The area of each violin is proportional to the number of observations in
  that group. A larger group therefore has a larger envelope instead of every
  violin being normalized independently.
- `jitter_method="even"` spreads nearby points in display space, reducing
  overlap and producing a more even visual density.

![Example SinaPlot](https://raw.githubusercontent.com/okumuralab/sinaplot/main/docs/example.png)

## Installation

```console
pip install sinaplot
```

Python 3.10 or later is required.

## Quick start

```python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from sinaplot import sinaplot

rng = np.random.default_rng(7)
data = pd.DataFrame(
    {
        "condition": np.repeat(["Control", "Treatment A", "Treatment B"], [50, 90, 130]),
        "response": np.concatenate(
            [
                rng.normal(0.0, 0.8, 50),
                rng.normal(0.7, 1.0, 90),
                np.r_[rng.normal(-0.2, 0.45, 65), rng.normal(1.7, 0.55, 65)],
            ]
        ),
    }
)

fig, ax = plt.subplots(figsize=(7, 4.5))
sinaplot(
    x="condition",
    y="response",
    data=data,
    jitter_method="even",
    random_seed=42,
    ax=ax,
)
fig.tight_layout()
plt.show()
```

`sinaplot` returns the Matplotlib `Axes`, so normal Matplotlib customization
continues to work:

```python
ax = sinaplot(
    "condition",
    "response",
    data,
    order=["Treatment B", "Treatment A", "Control"],
    color="tab:purple",
    violin_kws={"alpha": 0.2},
    scatter_kws={"edgecolor": "white", "linewidth": 0.3},
)
ax.set_title("Response by condition")
```

## Main options

| Option | Meaning |
| --- | --- |
| `violin=True` | Draw the sample-size-scaled density envelope. |
| `max_width=0.8` | Set the maximum full width in category-axis units. |
| `jitter_method="random"` | Randomly place points inside the envelope. |
| `jitter_method="even"` | Iteratively spread nearby points to reduce overlap. |
| `random_seed=None` | Make either placement method reproducible. |
| `order=None` | Set an explicit category order. Ordered pandas categoricals are respected automatically. |
| `bw_method=None` | Pass a KDE bandwidth method to SciPy. |
| `violin_kws`, `scatter_kws` | Pass styling options to Matplotlib. |

Rows with missing categories or missing/non-finite numerical values are
omitted. Groups with fewer than two distinct values cannot have a kernel
density estimate and are represented by a horizontal line.

## How area scaling works

For each group, the displayed kernel density is numerically normalized over
the plotted range and then multiplied by the number of finite observations in
that group. All groups use one common horizontal scale. Consequently, violin
area is proportional to sample size, while the local width still represents
density.

The points are always constrained to the corresponding density envelope.
With `jitter_method="even"`, an iterative repulsion calculation in display
coordinates gives nearby points more uniform spacing. Set `random_seed` when
you need exactly reproducible coordinates.

## Development

```console
git clone https://github.com/okumuralab/sinaplot.git
cd sinaplot
python -m pip install -e ".[test]"
python -m pytest
```

Build the wheel and source distribution with:

```console
python -m pip install build
python -m build
```

## Reference

Sidiropoulos N, Sohi SH, Pedersen TL, Porse BT, Winther O, Rapin N, Bagger FO.
“SinaPlot: an enhanced chart for simple and truthful representation of single
observations over multiple classes.”
*Journal of Computational and Graphical Statistics* 27(3), 673–676 (2018).
[doi:10.1080/10618600.2017.1366914](https://doi.org/10.1080/10618600.2017.1366914).
Preprint: [doi:10.1101/028191](https://doi.org/10.1101/028191).

This project is an independent Python implementation and is not affiliated
with the authors of the paper or the R packages that implement SinaPlot.

## License

MIT
