Skip to content

Commit b71c387

Browse files
committed
Change signature to grouped_bar(heights, *, positions=None,
tick_labels=None, labels=None, ...)
1 parent 07effe5 commit b71c387

File tree

3 files changed

+86
-67
lines changed

3 files changed

+86
-67
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+
group_labels = ['group A', 'group 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=group_labels,
12+
labels=['dataset 0', 'dataset 1', 'dataset 2'],
13+
colors=['#1f77b4', '#58a1cf', '#abd0e6'],
14+
)
15+
ax.legend()

galleries/examples/lines_bars_and_markers/grouped_bar_chart.py

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,27 @@
1717
import matplotlib.pyplot as plt
1818
import numpy as np
1919

20-
x = ['A', 'B']
20+
group_labels = ['A', 'B']
2121
data1 = [1, 1.2]
2222
data2 = [2, 2.4]
2323
data3 = [3, 3.6]
2424

2525

2626
fig, axs = plt.subplots(1, 2)
2727

28-
# current solution: manual positioning with multiple bar)= calls
28+
# current solution: manual positioning with multiple bar() calls
2929
label_pos = np.array([0, 1])
3030
bar_width = 0.8 / 3
3131
data_shift = -1*bar_width + np.array([0, bar_width, 2*bar_width])
3232
axs[0].bar(label_pos + data_shift[0], data1, width=bar_width, label="data1")
3333
axs[0].bar(label_pos + data_shift[1], data2, width=bar_width, label="data2")
3434
axs[0].bar(label_pos + data_shift[2], data3, width=bar_width, label="data3")
35-
axs[0].set_xticks(label_pos, x)
35+
axs[0].set_xticks(label_pos, group_labels)
3636
axs[0].legend()
3737

3838
# grouped_bar() with list of datasets
39-
# note also that this is a straight-forward generalization of the single-dataset case:
40-
# bar(x, data1, label="data1")
41-
axs[1].grouped_bar(x, [data1, data2, data3], labels=["data1", "data2", "data3"])
39+
axs[1].grouped_bar([data1, data2, data3],
40+
tick_labels=group_labels, labels=["data1", "data2", "data3"])
4241
axs[1].legend()
4342

4443

@@ -61,10 +60,10 @@
6160
fig, axs = plt.subplots(1, 2)
6261

6362
# explicitly extract values and labels from a dict and feed to grouped_bar():
64-
axs[0].grouped_bar(x, datasets.values(), labels=datasets.keys())
63+
axs[0].grouped_bar(datasets.values(), tick_labels=group_labels, labels=datasets.keys())
6564
axs[0].legend()
6665
# accepting a dict as input
67-
axs[1].grouped_bar(x, datasets)
66+
axs[1].grouped_bar(datasets, tick_labels=group_labels)
6867
axs[1].legend()
6968

7069
# %%
@@ -86,57 +85,56 @@
8685
# i.e. hen turning a list into a numpy array, you have to transpose that array to get
8786
# the correct representation. Those two behave the same::
8887
#
89-
# grouped_bar(x, [data1, data2])
90-
# grouped_bar(x, np.array([data1, data2]).T)
88+
# grouped_bar([data1, data2])
89+
# grouped_bar(np.array([data1, data2]).T)
9190
#
9291
# This is a conscious decision, because the commonly understood dimension ordering
9392
# semantics of "list of datasets" and 2D array of datasets is different.
9493

95-
x = ['A', 'B']
94+
group_labels = ['A', 'B']
9695
data = np.array([
9796
[1, 2, 3],
9897
[1.2, 2.4, 3.6],
9998
])
10099
columns = ["data1", "data2", "data3"]
101100

102101
fig, ax = plt.subplots()
103-
ax.grouped_bar(x, data, labels=columns)
102+
ax.grouped_bar(data, tick_labels=group_labels, labels=columns)
104103

105104
# %%
106105
# This creates the same plot as pandas (code cannot be executed because pandas
107-
# os not a doc dependency)::
106+
# is not a doc dependency)::
108107
#
109-
# df = pd.DataFrame(data, index=x, columns=columns)
108+
# df = pd.DataFrame(data, index=group_labels, columns=columns)
110109
# df.plot.bar()
111110

112111
# %%
113-
# Numeric x values
114-
# ----------------
115-
# In the most common case, one will want to pass categorical labels as *x*.
116-
# Additionally, we allow numeric values for *x*, as with `~.Axes.bar()`.
117-
# But for simplicity and clarity, we require that these are equidistant.
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
118117

119-
x = [0, 2, 4]
118+
positions = [0, 2, 4]
120119
data = {
121120
'data1': [1, 2, 3],
122121
'data2': [1.2, 2.2, 3.2],
123122
}
124123

125124
fig, ax = plt.subplots()
126-
ax.grouped_bar(x, data)
125+
ax.grouped_bar(data, positions=positions)
127126

128127

129128
# %%
130129
# Bar width and spacing
131130
# =====================
132-
# The center positions of the bar groups are given by x. We can still choose
131+
# The center positions of the bar groups are equidistantly spaced. We can still choose
133132
# two of the following properties: bar width, spacing between groups, and
134133
# spacing between bars.
135134
#
136135
# We believe the most convenient approach is defining spacing between groups
137136
# and spacing between bars as fraction of the bar width.
138137

139-
x = ['A', 'B', 'C']
140138
data = {
141139
'data1': [1, 2, 3],
142140
'data2': [1.2, 2.2, 3.2],
@@ -145,10 +143,10 @@
145143
}
146144

147145
fig, axs = plt.subplots(2, 2)
148-
axs[0, 0].grouped_bar(x, data)
149-
axs[0, 1].grouped_bar(x, data, group_spacing=0.5)
150-
axs[1, 0].grouped_bar(x, data, bar_spacing=0.2)
151-
axs[1, 1].grouped_bar(x, data, group_spacing=0.5, bar_spacing=0.1)
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)
152150

153151

154152
# %%
@@ -167,7 +165,7 @@
167165
}
168166

169167
fig, ax = plt.subplots()
170-
ax.grouped_bar(x, data, colors=["r", "g", "b", "m"], edgecolor="black")
168+
ax.grouped_bar(data, tick_labels=x, colors=["r", "g", "b", "m"], edgecolor="black")
171169

172170

173171
# %%
@@ -182,4 +180,4 @@
182180
}
183181

184182
fig, ax = plt.subplots()
185-
ax.grouped_bar(x, data, orientation="horizontal")
183+
ax.grouped_bar(data, tick_labels=x, orientation="horizontal")

lib/matplotlib/axes/_axes.py

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3066,8 +3066,8 @@ def broken_barh(self, xranges, yrange, **kwargs):
30663066
return col
30673067

30683068
@_docstring.interpd
3069-
def grouped_bar(self, x, heights, *, group_spacing=1.5, bar_spacing=0,
3070-
labels=None, orientation="vertical", colors=None,
3069+
def grouped_bar(self, heights, *, positions=None, group_spacing=1.5, bar_spacing=0,
3070+
tick_labels=None, labels=None, orientation="vertical", colors=None,
30713071
**kwargs):
30723072
"""
30733073
Make a grouped bar plot.
@@ -3080,25 +3080,24 @@ def grouped_bar(self, x, heights, *, group_spacing=1.5, bar_spacing=0,
30803080
into one Axes. In particular, it simplifies positioning of the bars
30813081
compared to individual `~.Axes.bar` plots.
30823082
3083+
Terminology: A bar *group* is a set of bars drawn next to each other. They
3084+
can be associated with a group name, which is visualized as the tick label
3085+
below that group. A *dataset* is a set of values, one for each bar group.
3086+
This means *dataset_0* will be rendered as the first bar in each bar group.
3087+
3088+
.. plot:: _embedded_plots/grouped_bar.py
3089+
30833090
Parameters
30843091
----------
3085-
x : array-like or list of str
3086-
The center positions of the bar groups. If these are numeric values,
3087-
they have to be equidistant. As with `~.Axes.bar`, you can provide
3088-
categorical labels, which will be used at integer numeric positions
3089-
``range(x)``.
3090-
30913092
heights : list of array-like or dict of array-like or 2D array
30923093
The heights for all x and groups. One of:
30933094
30943095
- list of array-like: A list of datasets, each dataset must have
3095-
``len(x)`` elements.
3096+
the same number of elements.
30963097
30973098
.. code-block:: none
30983099
3099-
x = ["a", "b"]
3100-
3101-
# x[0] x[1]
3100+
# group_A group_B
31023101
dataset_0 = [ds0_a, ds0_b]
31033102
dataset_1 = [ds1_a, ds1_b]
31043103
dataset_2 = [ds2_a, ds2_b]
@@ -3107,24 +3106,24 @@ def grouped_bar(self, x, heights, *, group_spacing=1.5, bar_spacing=0,
31073106
31083107
Example call::
31093108
3110-
grouped_bar(x, [dataset_0, dataset_1, dataset_2])
3109+
grouped_bar([dataset_0, dataset_1, dataset_2])
31113110
31123111
- dict of array-like: A mapping names to datasets. Each dataset
3113-
(dict value) must have ``len(x)`` elements.
3112+
(dict value) must have the same number of elements elements.
31143113
31153114
This is similar to passing a list of array-like, with the addition that
31163115
each dataset gets a name.
31173116
31183117
Example call::
31193118
3120-
grouped_bar(x, {'ds0': dataset_0, 'ds1': dataset_1, 'ds2': dataset_2]})
3119+
grouped_bar({'ds0': dataset_0, 'ds1': dataset_1, 'ds2': dataset_2]})
31213120
31223121
The names are used as *labels*, i.e. the following two calls are
31233122
equivalent::
31243123
31253124
data_dict = {'ds0': dataset_0, 'ds1': dataset_1, 'ds2': dataset_2]}
3126-
grouped_bar(x, data_dict)
3127-
grouped_bar(x, data_dict.values(), labels=data_dict.keys())
3125+
grouped_bar(data_dict)
3126+
grouped_bar(data_dict.values(), labels=data_dict.keys())
31283127
31293128
When using a dict-like input, you must not pass *labels* explicitly.
31303129
@@ -3133,33 +3132,41 @@ def grouped_bar(self, x, heights, *, group_spacing=1.5, bar_spacing=0,
31333132
.. code-block:: none
31343133
31353134
dataset_0 dataset_1 dataset_2
3136-
x[0]="a" ds0_a ds1_a ds2_a
3137-
x[1]="b" ds0_b ds1_b ds2_b
3135+
group_A ds0_a ds1_a ds2_a
3136+
group_B ds0_b ds1_b ds2_b
31383137
31393138
.. code-block::
31403139
3141-
x = ["a", "b"]
3140+
group_labels = ["group_A", "group_B"]
31423141
dataset_labels = ["dataset_0", "dataset_1", "dataset_2"]
31433142
array = np.random.random((2, 3))
31443143
31453144
Note that this is consistent with pandas. These two calls produce
31463145
the same bar plot structure::
31473146
3148-
grouped_bar(x, array, labels=dataset_labels)
3149-
pd.DataFrame(array, index=x, columns=dataset_labels).plot.bar()
3147+
grouped_bar(array, tick_labels=group_labels, labels=dataset_labels)
3148+
df = pd.DataFrame(array, index=group_labels, columns=dataset_labels)
3149+
df.plot.bar()
31503150
3151-
group_spacing : float
3152-
The space between two bar groups in units of bar width.
3151+
positions : array-like, optional
3152+
The center positions of the bar groups. The values have to be equidistant.
3153+
If not given, a sequence of integer positions 0, 1, 2, ... is used.
31533154
3154-
bar_spacing : float
3155-
The space between bars in units of bar width.
3155+
tick_labels: list of str, optional
3156+
The group labels, which are placed on ticks at the center *positions*
3157+
of the bar groups.
31563158
3157-
labels : array-like of str, optional
3159+
If not set, the axis ticks (positions and labels) are left unchanged.
3160+
3161+
labels : list of str, optional
31583162
The labels of the datasets, i.e. the bars within one group.
31593163
These will show up in the legend.
31603164
3161-
Note: The "other" label dimension are the group labels, which
3162-
can be set via *x*.
3165+
group_spacing : float
3166+
The space between two bar groups in units of bar width.
3167+
3168+
bar_spacing : float
3169+
The space between bars in units of bar width.
31633170
31643171
orientation : {"vertical", "horizontal"}, default: "vertical"
31653172
The direction of the bars.
@@ -3196,27 +3203,26 @@ def grouped_bar(self, x, heights, *, group_spacing=1.5, bar_spacing=0,
31963203
raise ValueError(
31973204
"'labels' cannot be used if 'heights' are a mapping")
31983205
labels = heights.keys()
3199-
heights = heights.values()
3206+
heights = list(heights.values())
32003207
elif hasattr(heights, 'shape'):
32013208
heights = heights.T
32023209

3203-
num_groups = len(x)
32043210
num_datasets = len(heights)
3211+
dataset_0 = next(iter(heights))
3212+
num_groups = len(dataset_0)
32053213

3206-
if isinstance(x[0], str):
3207-
tick_labels = x
3214+
if positions is None:
32083215
group_centers = np.arange(num_groups)
32093216
group_distance = 1
32103217
else:
3211-
if num_groups > 1:
3212-
d = np.diff(x)
3218+
group_centers = np.asanyarray(positions)
3219+
if len(group_centers) > 1:
3220+
d = np.diff(group_centers)
32133221
if not np.allclose(d, d.mean()):
3214-
raise ValueError("'x' must be equidistant")
3222+
raise ValueError("'positions' must be equidistant")
32153223
group_distance = d[0]
32163224
else:
32173225
group_distance = 1
3218-
group_centers = np.asarray(x)
3219-
tick_labels = None
32203226

32213227
for i, dataset in enumerate(heights):
32223228
if len(dataset) != num_groups:

0 commit comments

Comments
 (0)