Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 3bc2cbd

Browse filesBrowse files
authored
Merge pull request #20648 from timhoffm/doc-barchart-demp
Simplify barchart_demo
2 parents 579475b + 69805d2 commit 3bc2cbd
Copy full SHA for 3bc2cbd

File tree

Expand file treeCollapse file tree

1 file changed

+44
-101
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+44
-101
lines changed

‎examples/statistics/barchart_demo.py

Copy file name to clipboardExpand all lines: examples/statistics/barchart_demo.py
+44-101Lines changed: 44 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,16 @@
1515
just make up some data for little Johnny Doe.
1616
"""
1717

18+
from collections import namedtuple
1819
import numpy as np
1920
import matplotlib.pyplot as plt
20-
from matplotlib.ticker import MaxNLocator
21-
from collections import namedtuple
2221

23-
np.random.seed(42)
2422

2523
Student = namedtuple('Student', ['name', 'grade', 'gender'])
26-
Score = namedtuple('Score', ['score', 'percentile'])
24+
Score = namedtuple('Score', ['value', 'unit', 'percentile'])
2725

28-
# GLOBAL CONSTANTS
29-
test_names = ['Pacer Test', 'Flexed Arm\n Hang', 'Mile Run', 'Agility',
30-
'Push Ups']
31-
test_units = dict(zip(test_names, ['laps', 'sec', 'min:sec', 'sec', '']))
3226

33-
34-
def attach_ordinal(num):
27+
def to_ordinal(num):
3528
"""Convert an integer to an ordinal string, e.g. 2 -> '2nd'."""
3629
suffixes = {str(i): v
3730
for i, v in enumerate(['th', 'st', 'nd', 'rd', 'th',
@@ -43,118 +36,68 @@ def attach_ordinal(num):
4336
return v + suffixes[v[-1]]
4437

4538

46-
def format_score(score, test):
39+
def format_score(score):
4740
"""
4841
Create score labels for the right y-axis as the test name followed by the
4942
measurement unit (if any), split over two lines.
5043
"""
51-
unit = test_units[test]
52-
if unit:
53-
return f'{score}\n{unit}'
54-
else: # If no unit, don't include a newline, so that label stays centered.
55-
return score
56-
44+
return f'{score.value}\n{score.unit}' if score.unit else str(score.value)
5745

58-
def format_ycursor(y):
59-
y = int(y)
60-
if y < 0 or y >= len(test_names):
61-
return ''
62-
else:
63-
return test_names[y]
6446

65-
66-
def plot_student_results(student, scores, cohort_size):
67-
fig, ax1 = plt.subplots(figsize=(9, 7)) # Create the figure
68-
fig.subplots_adjust(left=0.115, right=0.88)
47+
def plot_student_results(student, scores_by_test, cohort_size):
48+
fig, ax1 = plt.subplots(figsize=(9, 7), constrained_layout=True)
6949
fig.canvas.manager.set_window_title('Eldorado K-8 Fitness Chart')
7050

71-
pos = np.arange(len(test_names))
72-
73-
rects = ax1.barh(pos, [scores[k].percentile for k in test_names],
74-
align='center',
75-
height=0.5,
76-
tick_label=test_names)
77-
7851
ax1.set_title(student.name)
52+
ax1.set_xlabel(
53+
'Percentile Ranking Across {grade} Grade {gender}s\n'
54+
'Cohort Size: {cohort_size}'.format(
55+
grade=to_ordinal(student.grade),
56+
gender=student.gender.title(),
57+
cohort_size=cohort_size))
58+
59+
test_names = list(scores_by_test.keys())
60+
percentiles = [score.percentile for score in scores_by_test.values()]
61+
62+
rects = ax1.barh(test_names, percentiles, align='center', height=0.5)
63+
# Partition the percentile values to be able to draw large numbers in
64+
# white within the bar, and small numbers in black outside the bar.
65+
large_percentiles = [to_ordinal(p) if p > 40 else '' for p in percentiles]
66+
small_percentiles = [to_ordinal(p) if p <= 40 else '' for p in percentiles]
67+
ax1.bar_label(rects, small_percentiles,
68+
padding=5, color='black', fontweight='bold')
69+
ax1.bar_label(rects, large_percentiles,
70+
padding=-32, color='white', fontweight='bold')
7971

8072
ax1.set_xlim([0, 100])
81-
ax1.xaxis.set_major_locator(MaxNLocator(11))
73+
ax1.set_xticks([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
8274
ax1.xaxis.grid(True, linestyle='--', which='major',
8375
color='grey', alpha=.25)
84-
85-
# Plot a solid vertical gridline to highlight the median position
86-
ax1.axvline(50, color='grey', alpha=0.25)
76+
ax1.axvline(50, color='grey', alpha=0.25) # median position
8777

8878
# Set the right-hand Y-axis ticks and labels
8979
ax2 = ax1.twinx()
90-
91-
# Set the tick locations and labels
92-
ax2.set_yticks(
93-
pos, labels=[format_score(scores[k].score, k) for k in test_names])
9480
# Set equal limits on both yaxis so that the ticks line up
9581
ax2.set_ylim(ax1.get_ylim())
82+
# Set the tick locations and labels
83+
ax2.set_yticks(
84+
np.arange(len(scores_by_test)),
85+
labels=[format_score(score) for score in scores_by_test.values()])
9686

9787
ax2.set_ylabel('Test Scores')
9888

99-
xlabel = ('Percentile Ranking Across {grade} Grade {gender}s\n'
100-
'Cohort Size: {cohort_size}')
101-
ax1.set_xlabel(xlabel.format(grade=attach_ordinal(student.grade),
102-
gender=student.gender.title(),
103-
cohort_size=cohort_size))
104-
105-
rect_labels = []
106-
# Lastly, write in the ranking inside each bar to aid in interpretation
107-
for rect in rects:
108-
# Rectangle widths are already integer-valued but are floating
109-
# type, so it helps to remove the trailing decimal point and 0 by
110-
# converting width to int type
111-
width = int(rect.get_width())
112-
113-
rank_str = attach_ordinal(width)
114-
# The bars aren't wide enough to print the ranking inside
115-
if width < 40:
116-
# Shift the text to the right side of the right edge
117-
xloc = 5
118-
# Black against white background
119-
clr = 'black'
120-
align = 'left'
121-
else:
122-
# Shift the text to the left side of the right edge
123-
xloc = -5
124-
# White on magenta
125-
clr = 'white'
126-
align = 'right'
127-
128-
# Center the text vertically in the bar
129-
yloc = rect.get_y() + rect.get_height() / 2
130-
label = ax1.annotate(
131-
rank_str, xy=(width, yloc), xytext=(xloc, 0),
132-
textcoords="offset points",
133-
horizontalalignment=align, verticalalignment='center',
134-
color=clr, weight='bold', clip_on=True)
135-
rect_labels.append(label)
136-
137-
# Make the interactive mouse over give the bar title
138-
ax2.fmt_ydata = format_ycursor
139-
# Return all of the artists created
140-
return {'fig': fig,
141-
'ax': ax1,
142-
'ax_right': ax2,
143-
'bars': rects,
144-
'perc_labels': rect_labels}
145-
146-
147-
student = Student('Johnny Doe', 2, 'boy')
148-
scores = dict(zip(
149-
test_names,
150-
(Score(v, p) for v, p in
151-
zip(['7', '48', '12:52', '17', '14'],
152-
np.round(np.random.uniform(0, 100, len(test_names)), 0)))))
153-
cohort_size = 62 # The number of other 2nd grade boys
154-
155-
arts = plot_student_results(student, scores, cohort_size)
156-
plt.show()
15789

90+
student = Student(name='Johnny Doe', grade=2, gender='Boy')
91+
scores_by_test = {
92+
'Pacer Test': Score(7, 'laps', percentile=37),
93+
'Flexed Arm\n Hang': Score(48, 'sec', percentile=95),
94+
'Mile Run': Score('12:52', 'min:sec', percentile=73),
95+
'Agility': Score(17, 'sec', percentile=60),
96+
'Push Ups': Score(14, '', percentile=16),
97+
}
98+
99+
plot_student_results(student, scores_by_test, cohort_size=62)
100+
plt.show()
158101

159102
#############################################################################
160103
#
@@ -164,5 +107,5 @@ def plot_student_results(student, scores, cohort_size):
164107
# in this example:
165108
#
166109
# - `matplotlib.axes.Axes.bar` / `matplotlib.pyplot.bar`
167-
# - `matplotlib.axes.Axes.annotate` / `matplotlib.pyplot.annotate`
110+
# - `matplotlib.axes.Axes.bar_label` / `matplotlib.pyplot.bar_label`
168111
# - `matplotlib.axes.Axes.twinx` / `matplotlib.pyplot.twinx`

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.