Skip to content

Commit

Permalink
Exclude outliers and mark on axis edges
Browse files Browse the repository at this point in the history
  • Loading branch information
mmore500 committed Nov 20, 2023
1 parent ba4b0c1 commit 54ae9d9
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 3 deletions.
2 changes: 2 additions & 0 deletions conduitpylib/viz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from ._beleaguerment_regplot import beleaguerment_regplot
from ._beleaguerment_facetplot import beleaguerment_facetplot
from ._compact_xaxis_units import compact_xaxis_units
from ._draw_edge_markers import draw_edge_markers
from ._DrawBatched import DrawBatched
from ._errplot import errplot, facet_errplot
from ._facet_barplot import facet_barplot
Expand Down Expand Up @@ -38,6 +39,7 @@
"beleaguerment_facetplot",
"beleaguerment_regplot",
"compact_xaxis_units",
"draw_edge_markers",
"DrawBatched",
"errplot",
"facet_barplot",
Expand Down
7 changes: 5 additions & 2 deletions conduitpylib/viz/_calc_performance_semantics_axis_lims.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,17 @@ def calc_performance_semantics_axis_lims(
x: str = "Simstep Period Inlet (ns)",
y: str = "Latency Simsteps Inlet",
hue: typing.Optional[str] = None,
outlier_percentile_x: float = 99.0,
) -> typing.Tuple[float, float, float, float]:

dummy_fig = plt.figure()
dummy_ax = dummy_fig.add_subplot(111)

percentile99x = np.percentile(data[x], outlier_percentile_x)
outlier_thresh = percentile99x * 2

sns.kdeplot(
ax=dummy_ax,
data=data,
data=data[data[x] < outlier_thresh],
x=x,
y=y,
alpha=0.0,
Expand Down
136 changes: 136 additions & 0 deletions conduitpylib/viz/_draw_edge_markers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import operator
import typing

from frozendict import frozendict

from matplotlib.axes import Axes as mpl_Axes
from matplotlib.collections import PathCollection as mpl_PathCollection


def draw_edge_marker(
ax: mpl_Axes,
x: float,
y: float,
annotation_kwargs: typing.Dict,
marker_kwargs: typing.Dict,
offset: float = 0.10,
) -> mpl_Axes:
"""
Draw an edge marker for a single point if it's out of bounds.
Parameters
----------
ax : matplotlib.axes.Axes
The axes object to draw markers on.
x, y : float
The x and y coordinates of the point.
"""
xlim, ylim = ax.get_xlim(), ax.get_ylim()
edge_x, edge_y, marker, annotation = x, y, None, ""
ha, va, xytext = "left", "center", (5, 0) # default alignment and offset

xwidth = -operator.__sub__(*xlim)
ywidth = -operator.__sub__(*ylim)
xoff = xwidth * offset
yoff = ywidth * offset
if x < xlim[0]:
amount = int((xlim[0] - x) / xwidth)
backslash = "\\"
edge_x, marker, annotation, ha = (
xlim[0] - xoff,
f"$◅{backslash * amount}$",
f"{x:.0f}",
"right",
)
elif x > xlim[1]:
amount = int((x - xlim[1]) / xwidth)
edge_x, marker, annotation, ha = (
xlim[1] + xoff,
rf"$| \!\! \leftrightarrow \!\!|{{\times}}{amount}\rangle\!\rangle\!\rangle$",
f"{x:.0f}",
"left",
)

if y < ylim[0]:
edge_y, marker, annotation, va, xytext = (
ylim[0] - yoff,
"^",
f"{y:.2f}" if not annotation else f"({annotation}, {y:.2f})",
"bottom",
(0, 5),
)
elif y > ylim[1]:
edge_y, marker, annotation, va, xytext = (
ylim[1] + yoff,
"v",
f"{y:.2f}" if not annotation else f"({annotation}, {y:.2f})",
"top",
(0, -5),
)

if marker:
ax.plot(
edge_x,
edge_y,
**{
"color": "none",
"clip_on": False,
"linewidth": 1,
"color": "red",
"marker": marker,
"markersize": 20,
"markeredgewidth": 0.5,
"zorder": 10000,
**marker_kwargs,
},
)
ax.annotate(
annotation,
(edge_x, edge_y),
**{
"ha": ha,
"va": va,
"textcoords": "offset points",
"xytext": xytext,
**annotation_kwargs,
},
)

return edge_x, edge_y


def draw_edge_markers(
ax: mpl_Axes,
annotation_kwargs: typing.Dict = frozendict(),
marker_kwargs: typing.Dict = frozendict(),
offset: float = 0.10,
) -> mpl_Axes:
"""
Draw edge markers for points lying outside the plot's bounds.
Parameters
----------
ax : matplotlib.axes.Axes
The axes object to draw markers on.
"""
xlim, ylim = ax.get_xlim(), ax.get_ylim()

for collection in ax.collections:
if isinstance(collection, mpl_PathCollection):
# Extract points from the line
points = list(collection.get_offsets())

new_offsets = []
for x, y in points:
# Draw edge marker if the point is out of bounds
if x < xlim[0] or x > xlim[1] or y < ylim[0] or y > ylim[1]:
x_, y_ = draw_edge_marker(
ax,
x,
y,
annotation_kwargs=annotation_kwargs,
marker_kwargs=marker_kwargs,
offset=offset,
)
else:
new_offsets.append((x, y))
7 changes: 7 additions & 0 deletions conduitpylib/viz/_performance_semantics_scatterplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
seaborn_monkeypatched_kdecache,
)
from ._compact_xaxis_units import compact_xaxis_units
from ._draw_edge_markers import draw_edge_markers
from ._DrawBatched import DrawBatched
from ._get_defaults import get_default_linestyles, get_default_palette
from ._set_performance_semantics_axis_lims import (
Expand All @@ -50,11 +51,13 @@ def performance_semantics_scatterplot(
background_color: typing.Optional[str] = None,
bunching_smear_alpha: float = 0.2,
bunching_smear_color: str = "green",
edge_marker_kwargs: typing.Dict = frozendict(),
legend: typing.Literal["hide", "only", "show"] = "show",
legend_contents_pad: bool = False,
legend_font_name: typing.Optional[str] = None,
legend_prop: typing.Dict = frozendict(),
linestyles: typing.Optional[typing.List[str]] = None,
outlier_percentile_x: float = 99.0,
palette: typing.Optional[typing.List[str]] = None,
scatter_kwargs: typing.Dict = frozendict(),
show_bunching_smear: bool = True,
Expand Down Expand Up @@ -403,19 +406,23 @@ def remove_alpha(handle, orig) -> None:
for ax in jointgrid.ax_marg_x, jointgrid.ax_marg_y:
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax.patch.set_alpha(0.0)

set_performance_semantics_axis_lims(
ax=jointgrid.ax_joint,
data=data,
x=x,
y=y,
hue=hue,
outlier_percentile_x=outlier_percentile_x,
)
if xlim is not None:
jointgrid.ax_joint.set_xlim(xlim)
if ylim is not None:
jointgrid.ax_joint.set_ylim(ylim)

draw_edge_markers(jointgrid.ax_joint, **edge_marker_kwargs)

if xlabel is not None:
jointgrid.ax_joint.set_xlabel(xlabel)
if ylabel is not None:
Expand Down
3 changes: 2 additions & 1 deletion conduitpylib/viz/_set_performance_semantics_axis_lims.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ def set_performance_semantics_axis_lims(
x: str = "Simstep Period Inlet (ns)",
y: str = "Latency Simsteps Inlet",
hue: typing.Optional[str] = None,
**kwargs,
) -> None:

xmin, xmax, ymin, ymax = calc_performance_semantics_axis_lims(
data=data,
x=x,
y=y,
hue=hue,
**kwargs,
)
ax.set_xlim(left=xmin, right=xmax)
ax.set_ylim(bottom=ymin, top=ymax)

0 comments on commit 54ae9d9

Please sign in to comment.