Skip to content

Commit cc6ae3d

Browse files
committed
Merge pull request #2643 from phobson/refactor-boxplots
ENH/REF: Overhauled boxplots
2 parents 129c738 + 64b1196 commit cc6ae3d

37 files changed

+1776
-383
lines changed

CHANGELOG

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
argument. A 30x30 grid is now used for both density=1 and
33
density=(1, 1).
44

5+
2013-12-03 Added a pure boxplot-drawing method that allow a more complete
6+
customization of boxplots. It takes a list of dicts contains stats.
7+
Also created a function (`cbook.boxplot_stats`) that generates the
8+
stats needed.
9+
510
2013-11-28 Added qhull extension module to perform Delaunay triangulation more
611
robustly than before. It is used by tri.Triangulation (and hence
712
all pyplot.tri* methods) and mlab.griddata. Deprecated

doc/users/whats_new.rst

+25
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,31 @@ Phil Elson rewrote of the documentation and userguide for both Legend and PathEf
3232
New plotting features
3333
---------------------
3434

35+
Fully customizable boxplots
36+
````````````````````````````
37+
Paul Hobson overhauled the :func:`~matplotlib.pyplot.boxplot` method such
38+
that it is now completely customizable in terms of the styles and positions
39+
of the individual artists. Under the hood, :func:`~matplotlib.pyplot.boxplot`
40+
relies on a new function (:func:`~matplotlib.cbook.boxplot_stats`), which
41+
accepts any data structure currently compatible with
42+
:func:`~matplotlib.pyplot.boxplot`, and returns a list of dictionaries
43+
containing the positions for each element of the boxplots. Then
44+
a second method, :func:`~matplotlib.Axes.bxp` is called to draw the boxplots
45+
based on the stats.
46+
47+
The :func:~matplotlib.pyplot.boxplot function can be used as before to
48+
generate boxplots from data in one step. But now the user has the
49+
flexibility to generate the statistics independently, or to modify the
50+
output of :func:~matplotlib.cbook.boxplot_stats prior to plotting
51+
with :func:~matplotlib.Axes.bxp.
52+
53+
Lastly, each artist (e.g., the box, outliers, cap, notches) can now be
54+
toggled on or off and their styles can be passed in through individual
55+
kwargs. See the examples:
56+
:ref:`~examples/statistics/boxplot_demo.py` and
57+
:ref:`~examples/statistics/bxp_demo.py`
58+
59+
3560
Support for datetime axes in 2d plots
3661
`````````````````````````````````````
3762
Andrew Dawson added support for datetime axes to

examples/statistics/boxplot_demo.py

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""
2+
Demo of the new boxplot functionality
3+
"""
4+
5+
import numpy as np
6+
import matplotlib.pyplot as plt
7+
8+
# fake data
9+
np.random.seed(937)
10+
data = np.random.lognormal(size=(37, 4), mean=1.5, sigma=1.75)
11+
labels = list('ABCD')
12+
fs = 10 # fontsize
13+
14+
# demonstrate how to toggle the display of different elements:
15+
fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(6,6))
16+
axes[0, 0].boxplot(data, labels=labels)
17+
axes[0, 0].set_title('Default', fontsize=fs)
18+
19+
axes[0, 1].boxplot(data, labels=labels, showmeans=True)
20+
axes[0, 1].set_title('showmeans=True', fontsize=fs)
21+
22+
axes[0, 2].boxplot(data, labels=labels, showmeans=True, meanline=True)
23+
axes[0, 2].set_title('showmeans=True,\nmeanline=True', fontsize=fs)
24+
25+
axes[1, 0].boxplot(data, labels=labels, showbox=False, showcaps=False)
26+
axes[1, 0].set_title('Tufte Style \n(showbox=False,\nshowcaps=False)', fontsize=fs)
27+
28+
axes[1, 1].boxplot(data, labels=labels, notch=True, bootstrap=10000)
29+
axes[1, 1].set_title('notch=True,\nbootstrap=10000', fontsize=fs)
30+
31+
axes[1, 2].boxplot(data, labels=labels, showfliers=False)
32+
axes[1, 2].set_title('showfliers=False', fontsize=fs)
33+
34+
for ax in axes.flatten():
35+
ax.set_yscale('log')
36+
ax.set_yticklabels([])
37+
38+
fig.subplots_adjust(hspace=0.4)
39+
plt.show()
40+
41+
42+
# demonstrate how to customize the display different elements:
43+
boxprops = dict(linestyle='--', linewidth=3, color='darkgoldenrod')
44+
flierprops = dict(marker='o', markerfacecolor='green', markersize=12,
45+
linestyle='none')
46+
medianprops = dict(linestyle='-.', linewidth=2.5, color='firebrick')
47+
meanpointprops = dict(marker='D', markeredgecolor='black',
48+
markerfacecolor='firebrick')
49+
meanlineprops = dict(linestyle='--', linewidth=2.5, color='purple')
50+
51+
fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(6,6))
52+
axes[0, 0].boxplot(data, boxprops=boxprops)
53+
axes[0, 0].set_title('Custom boxprops', fontsize=fs)
54+
55+
axes[0, 1].boxplot(data, flierprops=flierprops, medianprops=medianprops)
56+
axes[0, 1].set_title('Custom medianprops\nand flierprops', fontsize=fs)
57+
58+
axes[0, 2].boxplot(data, whis='range')
59+
axes[0, 2].set_title('whis="range"', fontsize=fs)
60+
61+
axes[1, 0].boxplot(data, meanprops=meanpointprops, meanline=False,
62+
showmeans=True)
63+
axes[1, 0].set_title('Custom mean\nas point', fontsize=fs)
64+
65+
axes[1, 1].boxplot(data, meanprops=meanlineprops, meanline=True, showmeans=True)
66+
axes[1, 1].set_title('Custom mean\nas line', fontsize=fs)
67+
68+
axes[1, 2].boxplot(data, whis=[15, 85])
69+
axes[1, 2].set_title('whis=[15, 85]\n#percentiles', fontsize=fs)
70+
71+
for ax in axes.flatten():
72+
ax.set_yscale('log')
73+
ax.set_yticklabels([])
74+
75+
fig.suptitle("I never said they'd be pretty")
76+
fig.subplots_adjust(hspace=0.4)
77+
plt.show()

examples/statistics/bxp_demo.py

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""
2+
Demo of the new boxplot drawer function
3+
"""
4+
5+
import numpy as np
6+
import matplotlib.pyplot as plt
7+
import matplotlib.cbook as cbook
8+
9+
# fake data
10+
np.random.seed(937)
11+
data = np.random.lognormal(size=(37, 4), mean=1.5, sigma=1.75)
12+
labels = list('ABCD')
13+
14+
# compute the boxplot stats
15+
stats = cbook.boxplot_stats(data, labels=labels, bootstrap=10000)
16+
# After we've computed the stats, we can go through and change anything.
17+
# Just to prove it, I'll set the median of each set to the median of all
18+
# the data, and double the means
19+
for n in range(len(stats)):
20+
stats[n]['med'] = np.median(data)
21+
stats[n]['mean'] *= 2
22+
23+
print(stats[0].keys())
24+
fs = 10 # fontsize
25+
26+
# demonstrate how to toggle the display of different elements:
27+
fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(6,6))
28+
axes[0, 0].bxp(stats)
29+
axes[0, 0].set_title('Default', fontsize=fs)
30+
31+
axes[0, 1].bxp(stats, showmeans=True)
32+
axes[0, 1].set_title('showmeans=True', fontsize=fs)
33+
34+
axes[0, 2].bxp(stats, showmeans=True, meanline=True)
35+
axes[0, 2].set_title('showmeans=True,\nmeanline=True', fontsize=fs)
36+
37+
axes[1, 0].bxp(stats, showbox=False, showcaps=False)
38+
axes[1, 0].set_title('Tufte Style\n(showbox=False,\nshowcaps=False)', fontsize=fs)
39+
40+
axes[1, 1].bxp(stats, shownotches=True)
41+
axes[1, 1].set_title('notch=True', fontsize=fs)
42+
43+
axes[1, 2].bxp(stats, showfliers=False)
44+
axes[1, 2].set_title('showfliers=False', fontsize=fs)
45+
46+
for ax in axes.flatten():
47+
ax.set_yscale('log')
48+
ax.set_yticklabels([])
49+
50+
fig.subplots_adjust(hspace=0.4)
51+
plt.show()
52+
53+
54+
# demonstrate how to customize the display different elements:
55+
boxprops = dict(linestyle='--', linewidth=3, color='darkgoldenrod')
56+
flierprops = dict(marker='o', markerfacecolor='green', markersize=12,
57+
linestyle='none')
58+
medianprops = dict(linestyle='-.', linewidth=2.5, color='firebrick')
59+
meanpointprops = dict(marker='D', markeredgecolor='black',
60+
markerfacecolor='firebrick')
61+
meanlineprops = dict(linestyle='--', linewidth=2.5, color='purple')
62+
63+
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(6,6))
64+
axes[0, 0].bxp(stats, boxprops=boxprops)
65+
axes[0, 0].set_title('Custom boxprops', fontsize=fs)
66+
67+
axes[0, 1].bxp(stats, flierprops=flierprops, medianprops=medianprops)
68+
axes[0, 1].set_title('Custom medianprops\nand flierprops', fontsize=fs)
69+
70+
axes[1, 0].bxp(stats, meanprops=meanpointprops, meanline=False,
71+
showmeans=True)
72+
axes[1, 0].set_title('Custom mean\nas point', fontsize=fs)
73+
74+
axes[1, 1].bxp(stats, meanprops=meanlineprops, meanline=True, showmeans=True)
75+
axes[1, 1].set_title('Custom mean\nas line', fontsize=fs)
76+
77+
for ax in axes.flatten():
78+
ax.set_yscale('log')
79+
ax.set_yticklabels([])
80+
81+
fig.suptitle("I never said they'd be pretty")
82+
fig.subplots_adjust(hspace=0.4)
83+
plt.show()

0 commit comments

Comments
 (0)