Source code for plotnine.positions.position_jitterdodge
from contextlib import suppress
from copy import copy
from ..exceptions import PlotnineError
from ..mapping.aes import SCALED_AESTHETICS
from ..utils import jitter, resolution
from .position import position
from .position_dodge import position_dodge
# Adjust position by simultaneously dodging and jittering
[docs]class position_jitterdodge(position):
"""
Dodge and jitter to minimise overlap
Useful when aligning points generated through
:class:`~plotnine.geoms.geom_point` with dodged a
:class:`~plotnine.geoms.geom_boxplot`.
Parameters
----------
jitter_width : float
Proportion to jitter in horizontal direction.
Default is ``0.4`` of the resolution of the data.
jitter_height : float
Proportion to jitter in vertical direction.
Default is ``0.0`` of the resolution of the data.
dodge_width : float
Amount to dodge in horizontal direction.
Default is ``0.75``
random_state : int or ~numpy.random.RandomState, optional
Seed or Random number generator to use. If ``None``, then
numpy global generator :class:`numpy.random` is used.
"""
REQUIRED_AES = {"x", "y"}
strategy = staticmethod(position_dodge.strategy)
def __init__(
self,
jitter_width=None,
jitter_height=0,
dodge_width=0.75,
random_state=None,
):
self.params = {
"jitter_width": jitter_width,
"jitter_height": jitter_height,
"dodge_width": dodge_width,
"random_state": random_state,
}
def setup_params(self, data):
params = copy(self.params)
width = params["jitter_width"]
if width is None:
width = resolution(data["x"]) * 0.4
# Adjust the x transformation based on the number
# of dodge variables
dvars = SCALED_AESTHETICS - self.REQUIRED_AES
dodge_columns = data.columns.intersection(list(dvars))
if len(dodge_columns) == 0:
raise PlotnineError(
"'position_jitterdodge' requires at least one "
"aesthetic to dodge by."
)
s = set()
for col in dodge_columns:
with suppress(AttributeError):
s.update(data[col].cat.categories)
ndodge = len(s)
params["jitter_width"] = width / (ndodge + 2)
params["width"] = params["dodge_width"]
return params
@classmethod
def compute_panel(cls, data, scales, params):
trans_x = None # pyright: ignore
trans_y = None # pyright: ignore
if params["jitter_width"] > 0:
def trans_x(x):
return jitter(
x,
amount=params["jitter_width"],
random_state=params["random_state"],
)
if params["jitter_height"] > 0:
def trans_y(y):
return jitter(
y,
amount=params["jitter_height"],
random_state=params["random_state"],
)
# dodge, then jitter
data = cls.collide(data, params=params)
data = cls.transform_position(data, trans_x, trans_y)
return data