Skip to content

Commit 7cbdc40

Browse files
committed
add ax support
1 parent 4df4333 commit 7cbdc40

3 files changed

Lines changed: 77 additions & 33 deletions

File tree

src/klib/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Current version of klib."""
22

3-
__version__ = "1.4.0"
3+
__version__ = "1.4.1"

src/klib/describe.py

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ def corr_plot(
293293
*,
294294
annot: bool = True,
295295
dev: bool = False,
296+
ax: plt.Axes | None = None,
296297
**kwargs, # noqa: ANN003
297298
) -> plt.Axes:
298299
"""2D visualization of the correlation between feature-columns excluding NA values.
@@ -340,6 +341,8 @@ def corr_plot(
340341
dev : bool, optional
341342
Display figure settings in the plot by setting dev = True. If False, the \
342343
settings are not displayed, by default False
344+
ax : matplotlib Axes, optional
345+
Existing Axes object to draw into, by default None.
343346
344347
kwargs : optional
345348
Additional elements to control the visualization of the plot, e.g.:
@@ -395,7 +398,10 @@ def corr_plot(
395398
vmax = np.round(np.nanmax(corr.where(~mask)) - 0.05, 2)
396399
vmin = np.round(np.nanmin(corr.where(~mask)) + 0.05, 2)
397400

398-
fig, ax = plt.subplots(figsize=figsize)
401+
if ax is None:
402+
fig, ax = plt.subplots(figsize=figsize)
403+
else:
404+
fig = ax.figure
399405

400406
# Specify kwargs for the heatmap
401407
kwargs = {
@@ -407,6 +413,7 @@ def corr_plot(
407413
"linewidths": 0.5,
408414
"annot_kws": {"size": 10},
409415
"cbar_kws": {"shrink": 0.95, "aspect": 30},
416+
"ax": ax,
410417
**kwargs,
411418
}
412419

@@ -679,6 +686,7 @@ def dist_plot(
679686
rug_kws: dict[str, Any] | None = None,
680687
fill_kws: dict[str, Any] | None = None,
681688
font_kws: dict[str, Any] | None = None,
689+
ax: plt.Axes | None = None,
682690
) -> None | Any: # noqa: ANN401
683691
"""2D visualization of the distribution of non binary numerical features.
684692
@@ -708,6 +716,8 @@ def dist_plot(
708716
font_kws : dict[str, Any], optional
709717
Keyword arguments to control the font, by default {"color": "#111111", \
710718
"weight": "normal", "size": 11}
719+
ax : matplotlib Axes, optional
720+
Existing Axes object to draw a single numeric column into, by default None.
711721
712722
Returns
713723
-------
@@ -763,25 +773,34 @@ def dist_plot(
763773
if not cols:
764774
print("No columns with numeric data were detected.")
765775
return None
776+
if ax is not None and len(cols) != 1:
777+
msg = "When ax is provided, dist_plot requires exactly one numeric non-binary column."
778+
raise ValueError(msg)
766779

767780
for col in cols:
768781
col_data = data[col].dropna(axis=0)
769782
col_df = df[col].dropna(axis=0)
770783

771-
g = sns.displot(
772-
col_data,
773-
kind="kde",
774-
rug=True,
775-
height=size,
776-
aspect=5,
777-
legend=False,
778-
rug_kws=rug_kws,
779-
**kde_kws,
780-
)
784+
if ax is None:
785+
g = sns.displot(
786+
col_data,
787+
kind="kde",
788+
rug=True,
789+
height=size,
790+
aspect=5,
791+
legend=False,
792+
rug_kws=rug_kws,
793+
**kde_kws,
794+
)
795+
plot_ax = g.axes[0, 0]
796+
else:
797+
sns.kdeplot(col_data, ax=ax, **kde_kws)
798+
sns.rugplot(col_data, ax=ax, **rug_kws)
799+
plot_ax = ax
781800

782801
# Vertical lines and fill
783-
x, y = g.axes[0, 0].lines[0].get_xydata().T
784-
g.axes[0, 0].fill_between(
802+
x, y = plot_ax.lines[0].get_xydata().T
803+
plot_ax.fill_between(
785804
x,
786805
y,
787806
where=(
@@ -794,7 +813,7 @@ def dist_plot(
794813

795814
mean = np.mean(col_df)
796815
std = scipy.stats.tstd(col_df)
797-
g.axes[0, 0].vlines(
816+
plot_ax.vlines(
798817
x=mean,
799818
ymin=0,
800819
ymax=np.interp(mean, x, y),
@@ -803,15 +822,15 @@ def dist_plot(
803822
lw=2,
804823
label="mean",
805824
)
806-
g.axes[0, 0].vlines(
825+
plot_ax.vlines(
807826
x=np.median(col_df),
808827
ymin=0,
809828
ymax=np.interp(np.median(col_df), x, y),
810829
ls=":",
811830
color=".3",
812831
label="median",
813832
)
814-
g.axes[0, 0].vlines(
833+
plot_ax.vlines(
815834
x=[mean - std, mean + std],
816835
ymin=0,
817836
ymax=[np.interp(mean - std, x, y), np.interp(mean + std, x, y)],
@@ -820,51 +839,51 @@ def dist_plot(
820839
label="\u03bc \u00b1 \u03c3",
821840
)
822841

823-
g.axes[0, 0].set_ylim(0)
824-
g.axes[0, 0].set_xlim(
825-
g.axes[0, 0].get_xlim()[0] - g.axes[0, 0].get_xlim()[1] * 0.05,
826-
g.axes[0, 0].get_xlim()[1] * 1.03,
842+
plot_ax.set_ylim(0)
843+
plot_ax.set_xlim(
844+
plot_ax.get_xlim()[0] - plot_ax.get_xlim()[1] * 0.05,
845+
plot_ax.get_xlim()[1] * 1.03,
827846
)
828847

829848
# Annotations and legend
830-
g.axes[0, 0].text(
849+
plot_ax.text(
831850
0.005,
832851
0.9,
833852
f"Mean: {mean:.2f}",
834853
fontdict=font_kws,
835-
transform=g.axes[0, 0].transAxes,
854+
transform=plot_ax.transAxes,
836855
)
837-
g.axes[0, 0].text(
856+
plot_ax.text(
838857
0.005,
839858
0.7,
840859
f"Std. dev: {std:.2f}",
841860
fontdict=font_kws,
842-
transform=g.axes[0, 0].transAxes,
861+
transform=plot_ax.transAxes,
843862
)
844-
g.axes[0, 0].text(
863+
plot_ax.text(
845864
0.005,
846865
0.5,
847866
f"Skew: {scipy.stats.skew(col_df):.2f}",
848867
fontdict=font_kws,
849-
transform=g.axes[0, 0].transAxes,
868+
transform=plot_ax.transAxes,
850869
)
851-
g.axes[0, 0].text(
870+
plot_ax.text(
852871
0.005,
853872
0.3,
854873
f"Kurtosis: {scipy.stats.kurtosis(col_df):.2f}", # Excess Kurtosis
855874
fontdict=font_kws,
856-
transform=g.axes[0, 0].transAxes,
875+
transform=plot_ax.transAxes,
857876
)
858-
g.axes[0, 0].text(
877+
plot_ax.text(
859878
0.005,
860879
0.1,
861880
f"Count: {len(col_df)}",
862881
fontdict=font_kws,
863-
transform=g.axes[0, 0].transAxes,
882+
transform=plot_ax.transAxes,
864883
)
865-
g.axes[0, 0].legend(loc="upper right")
884+
plot_ax.legend(loc="upper right")
866885

867-
return g.axes[0, 0]
886+
return plot_ax
868887

869888

870889
def missingval_plot( # noqa: PLR0915

tests/test_describe.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,18 @@ def test_corr_plot_smoke(self):
144144

145145
assert corr_plot(data, figsize=(4, 4)) is not None
146146

147+
def test_corr_plot_uses_existing_ax(self):
148+
data = pd.DataFrame(
149+
{
150+
"a": [1, 2, 3, 4, 5],
151+
"b": [2, 4, 6, 8, 10],
152+
"c": [5, 4, 3, 2, 1],
153+
},
154+
)
155+
_, ax = plt.subplots()
156+
157+
assert corr_plot(data, ax=ax) is ax
158+
147159
def test_corr_interactive_plot_smoke(self):
148160
data = pd.DataFrame(
149161
{
@@ -160,6 +172,19 @@ def test_dist_plot_smoke(self):
160172

161173
assert dist_plot(data, size=2) is not None
162174

175+
def test_dist_plot_uses_existing_ax(self):
176+
data = pd.Series(np.arange(30), name="a")
177+
_, ax = plt.subplots()
178+
179+
assert dist_plot(data, ax=ax) is ax
180+
181+
def test_dist_plot_existing_ax_requires_single_column(self):
182+
data = pd.DataFrame({"a": np.arange(30), "b": np.arange(30) ** 2})
183+
_, ax = plt.subplots()
184+
185+
with self.assertRaises(ValueError):
186+
dist_plot(data, ax=ax)
187+
163188
def test_missingval_plot_smoke(self):
164189
data = pd.DataFrame({"a": [1, np.nan, 3], "b": [np.nan, 2, 3]})
165190

0 commit comments

Comments
 (0)