Skip to content

Commit 1f329ef

Browse files
Plotting seaborn.FacetGrid compatibility (#314)
* remove all fig and dpi arguments, instead generate default axis if no ax passed * set equal aspect manually in docstring demos for qqplot and circmean * rm qqplot title * move scatter_kws keyword to kwargs and color LOA span to match scatter * add qqplot kwargs for scatter * remove all fig and dpi arguments, instead generate default axis if no ax passed * set equal aspect manually in docstring demos for qqplot and circmean * rm qqplot title * move scatter_kws keyword to kwargs and color LOA span to match scatter * add qqplot kwargs for scatter * Added square keyword argument to qqplot and plot_circmean * updated changelog * Updated plotting tests * update docstring for lint passing * update kwargs using .update instead of | for prior Python compatibility * Conform kwargs updating to match other pinguoin functions * add scatter kwargs to unittests * Minor documentation update :py:method: not working + add ptests to API Co-authored-by: Raphael Vallat <raphaelvallat9@gmail.com>
1 parent 6816d02 commit 1f329ef

File tree

5 files changed

+49
-50
lines changed

5 files changed

+49
-50
lines changed

docs/api.rst

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ ANOVA and T-test
2424
welch_anova
2525
tost
2626
ttest
27+
ptests
2728

2829
Bayesian
2930
--------
@@ -125,6 +126,7 @@ Multiple comparisons and post-hoc tests
125126
pairwise_tests
126127
pairwise_tukey
127128
pairwise_gameshowell
129+
ptests
128130
multicomp
129131

130132
Multivariate tests

docs/changelog.rst

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ We have added the :py:func:`pingouin.ptests` function to calculate a T-test (T-
2828
**Improvements**
2929

3030
- Added customization options to :py:func:`pingouin.plot_rm_corr`, which now takes optional keyword arguments to pass through to :py:func:`seaborn.regplot` and :py:func:`seaborn.scatterplot`. `PR 312 <https://github.com/raphaelvallat/pingouin/pull/312>`_.
31+
- Changed some plotting functions to increase compatibility with :py:class:`seaborn.FacetGrid`. As explained in `issue 306 <https://github.com/raphaelvallat/pingouin/issues/306>`_, the major change is to generate :py:class:`matplotlib.axes` using default parameters instead of accepting ``fig`` and ``dpi`` keyword arguments. This change applies to :py:func:`pingouin.plot_blandaltman`, :py:func:`pingouin.plot_paired`, :py:func:`pingouin.plot_circmean`, and :py:func:`pingouin.qqplot`. In the future, open a :py:class:`matplotlib.axes` and pass it through using the ``ax`` parameter to use custom figure settings with these functions. Other minor changes include the addition of the ``square`` keyword argument to :py:func`pingouin.plot_circmean` and :py:func`pingouin.qqplot` to ensure equal aspect ratios, and the removal of ``scatter_kws`` as a keyword argument in :py:func:`pingouin.plot_blandaltmann` (now alter the scatter parameters using general ``**kwargs``). `PR 314 <https://github.com/raphaelvallat/pingouin/pull/314>`_.
3132

3233
*************
3334

docs/index.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ Plot the curve of achieved power given the effect size (Cohen d) and the sample
470470
import pingouin as pg
471471
import numpy as np
472472
df = pg.read_dataset('mixed_anova').query("Group == 'Meditation' and Time != 'January'")
473-
ax = pg.plot_paired(data=df, dv='Scores', within='Time', subject='Subject', dpi=150)
473+
ax = pg.plot_paired(data=df, dv='Scores', within='Time', subject='Subject')
474474
ax.set_title("Effect of meditation on school performance")
475475

476476
Integration with Pandas

pingouin/plotting.py

+41-45
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,7 @@
2626

2727

2828
def plot_blandaltman(
29-
x,
30-
y,
31-
agreement=1.96,
32-
xaxis="mean",
33-
confidence=0.95,
34-
annotate=True,
35-
scatter_kws=dict(color="tab:blue", alpha=0.8),
36-
figsize=(4.5, 4.5),
37-
dpi=100,
38-
ax=None,
29+
x, y, agreement=1.96, xaxis="mean", confidence=0.95, annotate=True, ax=None, **kwargs
3930
):
4031
"""
4132
Generate a Bland-Altman plot to compare two sets of measurements.
@@ -61,15 +52,10 @@ def plot_blandaltman(
6152
annotate : bool
6253
If True (default), annotate the values for the mean difference
6354
and agreement limits.
64-
scatter_kws : dict
65-
Additional keyword arguments passed to
66-
:py:func:`matplotlib.pyplot.scatter`.
67-
figsize : tuple
68-
Figsize in inches
69-
dpi : int
70-
Resolution of the figure in dots per inches.
7155
ax : matplotlib axes
7256
Axis on which to draw the plot.
57+
**kwargs : optional
58+
Optional argument(s) passed to :py:func:`matplotlib.pyplot.scatter`.
7359
7460
Returns
7561
-------
@@ -137,6 +123,10 @@ def plot_blandaltman(
137123
assert not np.isnan(x).any(), "Missing values in x or y are not supported."
138124
assert not np.isnan(y).any(), "Missing values in x or y are not supported."
139125

126+
# Update default kwargs with specified inputs
127+
_scatter_kwargs = {"color": "tab:blue", "alpha": 0.8}
128+
_scatter_kwargs.update(kwargs)
129+
140130
# Calculate mean, STD and SEM of x - y
141131
n = x.size
142132
dof = n - 1
@@ -162,10 +152,10 @@ def plot_blandaltman(
162152

163153
# Start the plot
164154
if ax is None:
165-
fig, ax = plt.subplots(1, 1, figsize=figsize, dpi=dpi)
155+
ax = plt.gca()
166156

167157
# Plot the mean diff, limits of agreement and scatter
168-
ax.scatter(xval, diff, **scatter_kws)
158+
ax.scatter(xval, diff, **_scatter_kwargs)
169159
ax.axhline(mean_diff, color="k", linestyle="-", lw=2)
170160
ax.axhline(high, color="k", linestyle=":", lw=1.5)
171161
ax.axhline(low, color="k", linestyle=":", lw=1.5)
@@ -193,10 +183,10 @@ def plot_blandaltman(
193183
ci["high"] = stats.t.interval(confidence, dof, loc=high, scale=high_low_se)
194184
ci["low"] = stats.t.interval(confidence, dof, loc=low, scale=high_low_se)
195185
ax.axhspan(ci["mean"][0], ci["mean"][1], facecolor="tab:grey", alpha=0.2)
196-
ax.axhspan(ci["high"][0], ci["high"][1], facecolor="tab:blue", alpha=0.2)
197-
ax.axhspan(ci["low"][0], ci["low"][1], facecolor="tab:blue", alpha=0.2)
186+
ax.axhspan(ci["high"][0], ci["high"][1], facecolor=_scatter_kwargs["color"], alpha=0.2)
187+
ax.axhspan(ci["low"][0], ci["low"][1], facecolor=_scatter_kwargs["color"], alpha=0.2)
198188

199-
# Labels and title
189+
# Labels
200190
ax.set_ylabel(f"{xname} - {yname}")
201191
ax.set_xlabel(xlabel)
202192
sns.despine(ax=ax)
@@ -226,7 +216,7 @@ def _ppoints(n, a=0.5):
226216
return (np.arange(n) + 1 - a) / (n + 1 - 2 * a)
227217

228218

229-
def qqplot(x, dist="norm", sparams=(), confidence=0.95, figsize=(5, 4), ax=None):
219+
def qqplot(x, dist="norm", sparams=(), confidence=0.95, square=True, ax=None, **kwargs):
230220
"""Quantile-Quantile plot.
231221
232222
Parameters
@@ -242,10 +232,12 @@ def qqplot(x, dist="norm", sparams=(), confidence=0.95, figsize=(5, 4), ax=None)
242232
confidence : float
243233
Confidence level (.95 = 95%) for point-wise confidence envelope.
244234
Can be disabled by passing False.
245-
figsize : tuple
246-
Figsize in inches
235+
square: bool
236+
If True (default), ensure equal aspect ratio between X and Y axes.
247237
ax : matplotlib axes
248238
Axis on which to draw the plot
239+
**kwargs : optional
240+
Optional argument(s) passed to :py:func:`matplotlib.pyplot.scatter`.
249241
250242
Returns
251243
-------
@@ -336,6 +328,10 @@ def qqplot(x, dist="norm", sparams=(), confidence=0.95, figsize=(5, 4), ax=None)
336328
>>> sns.set_style('darkgrid')
337329
>>> ax = pg.qqplot(x, dist='norm', sparams=(mean, std))
338330
"""
331+
# Update default kwargs with specified inputs
332+
_scatter_kwargs = {"marker": "o", "color": "blue"}
333+
_scatter_kwargs.update(kwargs)
334+
339335
if isinstance(dist, str):
340336
dist = getattr(stats, dist)
341337

@@ -370,13 +366,12 @@ def qqplot(x, dist="norm", sparams=(), confidence=0.95, figsize=(5, 4), ax=None)
370366

371367
# Start the plot
372368
if ax is None:
373-
fig, ax = plt.subplots(1, 1, figsize=figsize)
369+
ax = plt.gca()
374370

375-
ax.plot(theor, observed, "bo")
371+
ax.scatter(theor, observed, **_scatter_kwargs)
376372

377373
ax.set_xlabel("Theoretical quantiles")
378374
ax.set_ylabel("Ordered quantiles")
379-
ax.set_title("Q-Q Plot")
380375

381376
# Add diagonal line
382377
end_pts = [ax.get_xlim(), ax.get_ylim()]
@@ -405,6 +400,10 @@ def qqplot(x, dist="norm", sparams=(), confidence=0.95, figsize=(5, 4), ax=None)
405400
ax.plot(theor, upper, "r--", lw=1.25)
406401
ax.plot(theor, lower, "r--", lw=1.25)
407402

403+
# Make square
404+
if square:
405+
ax.set_aspect("equal")
406+
408407
return ax
409408

410409

@@ -417,8 +416,6 @@ def plot_paired(
417416
boxplot=True,
418417
boxplot_in_front=False,
419418
orient="v",
420-
figsize=(4, 4),
421-
dpi=100,
422419
ax=None,
423420
colors=["green", "grey", "indianred"],
424421
pointplot_kwargs={"scale": 0.6, "marker": "."},
@@ -455,10 +452,6 @@ def plot_paired(
455452
by 90 degrees.
456453
457454
.. versionadded:: 0.3.9
458-
figsize : tuple
459-
Figsize in inches
460-
dpi : int
461-
Resolution of the figure in dots per inches.
462455
ax : matplotlib axes
463456
Axis on which to draw the plot.
464457
colors : list of str
@@ -491,8 +484,7 @@ def plot_paired(
491484
>>> import pingouin as pg
492485
>>> df = pg.read_dataset('mixed_anova').query("Time != 'January'")
493486
>>> df = df.query("Group == 'Meditation' and Subject > 40")
494-
>>> ax = pg.plot_paired(data=df, dv='Scores', within='Time',
495-
... subject='Subject', dpi=150)
487+
>>> ax = pg.plot_paired(data=df, dv='Scores', within='Time', subject='Subject')
496488
497489
Paired plot on an existing axis (no boxplot and uniform color):
498490
@@ -583,7 +575,7 @@ def plot_paired(
583575

584576
# Start the plot
585577
if ax is None:
586-
fig, ax = plt.subplots(1, 1, figsize=figsize, dpi=dpi)
578+
ax = plt.gca()
587579

588580
# Set x and y depending on orientation using the num. replacement within
589581
_x = "wthn" if orient == "v" else dv
@@ -1028,8 +1020,7 @@ def plot_rm_corr(
10281020

10291021
def plot_circmean(
10301022
angles,
1031-
figsize=(4, 4),
1032-
dpi=None,
1023+
square=True,
10331024
ax=None,
10341025
kwargs_markers=dict(color="tab:blue", marker="o", mfc="none", ms=10),
10351026
kwargs_arrow=dict(width=0.01, head_width=0.1, head_length=0.1, fc="tab:red", ec="tab:red"),
@@ -1043,10 +1034,8 @@ def plot_circmean(
10431034
----------
10441035
angles : array or list
10451036
Angles (expressed in radians). Only 1D array are supported here.
1046-
figsize : tuple
1047-
Figsize in inches. Default is (4, 4).
1048-
dpi : int
1049-
Resolution of the figure in dots per inches.
1037+
square: bool
1038+
If True (default), ensure equal aspect ratio between X and Y axes.
10501039
ax : matplotlib axes
10511040
Axis on which to draw the plot.
10521041
kwargs_markers : dict
@@ -1077,9 +1066,11 @@ def plot_circmean(
10771066
.. plot::
10781067
10791068
>>> import pingouin as pg
1069+
>>> import matplotlib.pyplot as plt
1070+
>>> _, ax = plt.subplots(1, 1, figsize=(3, 3))
10801071
>>> ax = pg.plot_circmean([0.05, -0.8, 1.2, 0.8, 0.5, -0.3, 0.3, 0.7],
10811072
... kwargs_markers=dict(color='k', mfc='k'),
1082-
... kwargs_arrow=dict(ec='k', fc='k'))
1073+
... kwargs_arrow=dict(ec='k', fc='k'), ax=ax)
10831074
10841075
.. plot::
10851076
@@ -1128,7 +1119,8 @@ def plot_circmean(
11281119
zm = r * np.exp(1j * phi)
11291120

11301121
# Plot unit circle
1131-
fig, ax = plt.subplots(figsize=figsize, dpi=dpi)
1122+
if ax is None:
1123+
ax = plt.gca()
11321124
circle = Circle((0, 0), 1, edgecolor="k", facecolor="none", linewidth=2)
11331125
ax.add_patch(circle)
11341126
ax.axvline(0, lw=1, ls=":", color="slategrey")
@@ -1149,4 +1141,8 @@ def plot_circmean(
11491141
ax.text(-1.3, 0, r"$\pi$", verticalalignment="center")
11501142
ax.text(0, 1.2, r"$+\pi/2$", horizontalalignment="center")
11511143
ax.text(0, -1.3, r"$-\pi/2$", horizontalalignment="center")
1144+
1145+
# Make square
1146+
if square:
1147+
ax.set_aspect("equal")
11521148
return ax

pingouin/tests/test_plotting.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ def test_plot_blandaltman(self):
3434
assert isinstance(ax, matplotlib.axes.Axes)
3535
_, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 4))
3636
plot_blandaltman(x, y, agreement=2, confidence=None, ax=ax1)
37-
plot_blandaltman(x, y, agreement=2, confidence=0.68, dpi=200, ax=ax2)
37+
plot_blandaltman(x, y, agreement=2, confidence=0.68, ax=ax2)
3838
plt.close("all")
3939
# With Pingouin's dataset
4040
df_ba = read_dataset("blandaltman")
4141
x, y = df_ba["A"], df_ba["B"]
4242
plot_blandaltman(x, y)
4343
plot_blandaltman(x, y, annotate=False)
4444
plot_blandaltman(x, y, xaxis="x", confidence=None)
45-
plot_blandaltman(x, y, xaxis="y")
45+
plot_blandaltman(x, y, xaxis="y", color="green", s=10)
4646
plt.close("all")
4747

4848
def test_ppoints(self):
@@ -78,7 +78,7 @@ def test_qqplot(self):
7878
ax = qqplot(x, dist="norm")
7979
assert isinstance(ax, matplotlib.axes.Axes)
8080
_, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 4))
81-
qqplot(x_exp, dist="expon", ax=ax2)
81+
qqplot(x_exp, dist="expon", ax=ax2, color="black", marker="+")
8282
mean, std = 0, 0.8
8383
qqplot(x, dist=stats.norm, sparams=(mean, std), confidence=False)
8484
# For lognormal distribution, the shape parameter must be specified
@@ -164,6 +164,6 @@ def test_plot_circmean(self):
164164
angles = np.array([0.02, 0.07, -0.12, 0.14, 1.2, -1.3])
165165
ax = plot_circmean(angles)
166166
assert isinstance(ax, matplotlib.axes.Axes)
167-
ax = plot_circmean(angles, figsize=(5, 5), dpi=100, kwargs_markers={}, kwargs_arrow={})
167+
ax = plot_circmean(angles, kwargs_markers={}, kwargs_arrow={})
168168
assert isinstance(ax, matplotlib.axes.Axes)
169169
plt.close("all")

0 commit comments

Comments
 (0)