Skip to content

Commit 6ffec53

Browse files
committed
ENH: Add grouped_bar() method
1 parent dc63ec7 commit 6ffec53

File tree

10 files changed

+600
-1
lines changed

10 files changed

+600
-1
lines changed

doc/_embedded_plots/grouped_bar.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import matplotlib.pyplot as plt
2+
3+
categories = ['A', 'B']
4+
data0 = [1.0, 3.0]
5+
data1 = [1.4, 3.4]
6+
data2 = [1.8, 3.8]
7+
8+
fig, ax = plt.subplots(figsize=(4, 2.2))
9+
ax.grouped_bar(
10+
[data0, data1, data2],
11+
tick_labels=categories,
12+
labels=['dataset 0', 'dataset 1', 'dataset 2'],
13+
colors=['#1f77b4', '#58a1cf', '#abd0e6'],
14+
)
15+
ax.legend()

doc/api/axes_api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Basic
6767
Axes.bar
6868
Axes.barh
6969
Axes.bar_label
70+
Axes.grouped_bar
7071

7172
Axes.stem
7273
Axes.eventplot

doc/api/pyplot_summary.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Basic
6161
bar
6262
barh
6363
bar_label
64+
grouped_bar
6465
stem
6566
eventplot
6667
pie
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Grouped bar charts
2+
------------------
3+
4+
The new method `~.Axes.grouped_bar()` simplifies the creation of grouped bar charts
5+
significantly. It supports different input data types (lists of datasets, dicts of
6+
datasets, data in 2D arrays, pandas DataFrames), and allows for easy customization
7+
of placement via controllable distances between bars and between bar groups.
8+
9+
Example:
10+
11+
.. plot::
12+
:include-source: true
13+
14+
import matplotlib.pyplot as plt
15+
16+
categories = ['A', 'B']
17+
datasets = {
18+
'dataset 0': [1.0, 3.0],
19+
'dataset 1': [1.4, 3.4],
20+
'dataset 2': [1.8, 3.8],
21+
}
22+
23+
fig, ax = plt.subplots(figsize=(4, 2.2))
24+
ax.grouped_bar(datasets, tick_labels=categories)
25+
ax.legend()
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
"""
2+
=================
3+
Grouped bar chart
4+
=================
5+
6+
This example serves to showcase and discuss the API. It's geared towards illustating
7+
API usage and design decisions only through the development phase. It's not intended
8+
to go into the final PR in this form.
9+
10+
Input data formats
11+
==================
12+
13+
Case 1: multiple separate datasets
14+
----------------------------------
15+
16+
"""
17+
import matplotlib.pyplot as plt
18+
import numpy as np
19+
20+
group_labels = ['A', 'B']
21+
data1 = [1, 1.2]
22+
data2 = [2, 2.4]
23+
data3 = [3, 3.6]
24+
25+
26+
fig, axs = plt.subplots(1, 2)
27+
28+
# current solution: manual positioning with multiple bar() calls
29+
label_pos = np.array([0, 1])
30+
bar_width = 0.8 / 3
31+
data_shift = -1*bar_width + np.array([0, bar_width, 2*bar_width])
32+
axs[0].bar(label_pos + data_shift[0], data1, width=bar_width, label="data1")
33+
axs[0].bar(label_pos + data_shift[1], data2, width=bar_width, label="data2")
34+
axs[0].bar(label_pos + data_shift[2], data3, width=bar_width, label="data3")
35+
axs[0].set_xticks(label_pos, group_labels)
36+
axs[0].legend()
37+
38+
# grouped_bar() with list of datasets
39+
axs[1].grouped_bar([data1, data2, data3],
40+
tick_labels=group_labels, labels=["data1", "data2", "data3"])
41+
axs[1].legend()
42+
43+
44+
# %%
45+
# Case 1b: multiple datasets as dict
46+
# ----------------------------------
47+
# instead of carrying a list of datasets and a list of dataset labels, users may
48+
# want to organize their datasets in a dict.
49+
50+
datasets = {
51+
'data1': data1,
52+
'data2': data2,
53+
'data3': data3,
54+
}
55+
56+
# %%
57+
# While you can feed keys and values into the above API, it may be convenient to pass
58+
# the whole dict as "data" and automatically extract the labels from the keys:
59+
60+
fig, axs = plt.subplots(1, 2)
61+
62+
# explicitly extract values and labels from a dict and feed to grouped_bar():
63+
axs[0].grouped_bar(datasets.values(), tick_labels=group_labels, labels=datasets.keys())
64+
axs[0].legend()
65+
# accepting a dict as input
66+
axs[1].grouped_bar(datasets, tick_labels=group_labels)
67+
axs[1].legend()
68+
69+
# %%
70+
# Case 2: 2D array data
71+
# ---------------------
72+
# When receiving a 2D array, we interpret the data as
73+
#
74+
# .. code-block:: none
75+
#
76+
# dataset_0 dataset_1 dataset_2
77+
# x[0]='A' ds0_a ds1_a ds2_a
78+
# x[1]='B' ds0_b ds1_b ds2_b
79+
#
80+
# This is consistent with the standard data science interpretation of instances
81+
# on the vertical and features on the horizontal. And also matches how pandas is
82+
# interpreting rows and columns.
83+
#
84+
# Note that a list of individual datasets and a 2D array behave structurally different,
85+
# i.e. hen turning a list into a numpy array, you have to transpose that array to get
86+
# the correct representation. Those two behave the same::
87+
#
88+
# grouped_bar([data1, data2])
89+
# grouped_bar(np.array([data1, data2]).T)
90+
#
91+
# This is a conscious decision, because the commonly understood dimension ordering
92+
# semantics of "list of datasets" and 2D array of datasets is different.
93+
94+
group_labels = ['A', 'B']
95+
data = np.array([
96+
[1, 2, 3],
97+
[1.2, 2.4, 3.6],
98+
])
99+
columns = ["data1", "data2", "data3"]
100+
101+
fig, ax = plt.subplots()
102+
ax.grouped_bar(data, tick_labels=group_labels, labels=columns)
103+
104+
# %%
105+
# This creates the same plot as pandas (code cannot be executed because pandas
106+
# is not a doc dependency)::
107+
#
108+
# df = pd.DataFrame(data, index=group_labels, columns=columns)
109+
# df.plot.bar()
110+
111+
# %%
112+
# Controlling bar group center positions
113+
# ======================================
114+
# By default, bars groups are centered on integer positions 0, 1, 2, ...
115+
# This can be overridden using the *positions* parameter, but for simplicity
116+
# and clarity, we require that the positions are equidistant
117+
118+
positions = [0, 2, 4]
119+
data = {
120+
'data1': [1, 2, 3],
121+
'data2': [1.2, 2.2, 3.2],
122+
}
123+
124+
fig, ax = plt.subplots()
125+
ax.grouped_bar(data, positions=positions)
126+
127+
128+
# %%
129+
# Bar width and spacing
130+
# =====================
131+
# The center positions of the bar groups are equidistantly spaced. We can still choose
132+
# two of the following properties: bar width, spacing between groups, and
133+
# spacing between bars.
134+
#
135+
# We believe the most convenient approach is defining spacing between groups
136+
# and spacing between bars as fraction of the bar width.
137+
138+
data = {
139+
'data1': [1, 2, 3],
140+
'data2': [1.2, 2.2, 3.2],
141+
'data3': [1.4, 2.4, 3.4],
142+
'data4': [1.6, 2.6, 3.6],
143+
}
144+
145+
fig, axs = plt.subplots(2, 2)
146+
axs[0, 0].grouped_bar(data)
147+
axs[0, 1].grouped_bar(data, group_spacing=0.5)
148+
axs[1, 0].grouped_bar(data, bar_spacing=0.2)
149+
axs[1, 1].grouped_bar(data, group_spacing=0.5, bar_spacing=0.1)
150+
151+
152+
# %%
153+
# Styling
154+
# =======
155+
# The bars can be styled through additional keyword arguments. Currently,
156+
# the only per-dataset setting is ``colors``. Additionally, all
157+
# `.Rectangle` parameters are passed through and applied to all datasets.
158+
159+
x = ['A', 'B', 'C']
160+
data = {
161+
'data1': [1, 2, 3],
162+
'data2': [1.2, 2.2, 3.2],
163+
'data3': [1.4, 2.4, 3.4],
164+
'data4': [1.6, 2.6, 3.6],
165+
}
166+
167+
fig, ax = plt.subplots()
168+
ax.grouped_bar(data, tick_labels=x, colors=["r", "g", "b", "m"], edgecolor="black")
169+
170+
171+
# %%
172+
# Horizontal grouped bars
173+
# =======================
174+
# Use ``orientation="horizontal"`` to create horizontal grouped bar charts.
175+
176+
x = ['A', 'B', 'C']
177+
data = {
178+
'data1': [1, 2, 3],
179+
'data2': [1.2, 2.2, 3.2],
180+
}
181+
182+
fig, ax = plt.subplots()
183+
ax.grouped_bar(data, tick_labels=x, orientation="horizontal")

0 commit comments

Comments
 (0)