.
675 |
--------------------------------------------------------------------------------
/MatplotlibTutorial.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "5252158e-95ec-4f72-b943-f6b668e58f53",
6 | "metadata": {},
7 | "source": [
8 | "\n",
9 | " MATPLOTLIB TUTORIAL FOR NEWCOMERS
\n",
10 | "
"
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "id": "c6728cce",
16 | "metadata": {},
17 | "source": [
18 | ">by Dr Juan H Klopper\n",
19 | "\n",
20 | "- Research Fellow\n",
21 | "- School for Data Science and Computational Thinking\n",
22 | "- Division of Biostatistics and Epidemiology"
23 | ]
24 | },
25 | {
26 | "cell_type": "markdown",
27 | "id": "4a72eeb0",
28 | "metadata": {},
29 | "source": [
30 | "
"
31 | ]
32 | },
33 | {
34 | "cell_type": "markdown",
35 | "id": "70447879",
36 | "metadata": {},
37 | "source": [
38 | "## INTRODUCTION"
39 | ]
40 | },
41 | {
42 | "cell_type": "markdown",
43 | "id": "ae7a8b4d",
44 | "metadata": {},
45 | "source": [
46 | "The matplotlib package is arguably the best known and the largest plotting package in the Python ecosystem. In this notebook I touch on some of the useful aspects when using matplotlib. It is an enormous package and I will concentrate on topics pertaining to its use in science in general."
47 | ]
48 | },
49 | {
50 | "cell_type": "markdown",
51 | "id": "ac17682f",
52 | "metadata": {},
53 | "source": [
54 | "The [matplotlib homepage](https://matplotlib.org/stable/gallery/index.html#examples-index) lists many examples. [Full documentation](https://matplotlib.org/stable/Matplotlib.pdf) is available as a PDF file for download."
55 | ]
56 | },
57 | {
58 | "cell_type": "markdown",
59 | "id": "f914d396",
60 | "metadata": {},
61 | "source": [
62 | "We can set values for almost all of the aspects of a plot when creating the plot. Some of these can also be set on a global scale using the `rcParams` class. A full list of these parameters can be viewed [here](https://matplotlib.org/stable/api/matplotlib_configuration_api.html). All the settings are actually saved in a file called `matplotlibrc`. To customise this file, read [here](https://matplotlib.org/stable/tutorials/introductory/customizing.html). You can find this file by running the code cell below."
63 | ]
64 | },
65 | {
66 | "cell_type": "code",
67 | "execution_count": null,
68 | "id": "1f835a2d",
69 | "metadata": {},
70 | "outputs": [],
71 | "source": [
72 | "import matplotlib\n",
73 | "matplotlib.matplotlib_fname()"
74 | ]
75 | },
76 | {
77 | "cell_type": "markdown",
78 | "id": "781a62ee",
79 | "metadata": {},
80 | "source": [
81 | "There are many other plotting packages available in Python. A few are listed below:\n",
82 | "\n",
83 | "- [Plotly](https://plotly.com/python/)\n",
84 | "- [Seaborn](https://seaborn.pydata.org)\n",
85 | "- [Bokeh](https://docs.bokeh.org/en/latest/)\n",
86 | "- [Altair](https://altair-viz.github.io)"
87 | ]
88 | },
89 | {
90 | "cell_type": "markdown",
91 | "id": "1540cb53",
92 | "metadata": {},
93 | "source": [
94 | "## TABLE OF CONTENTS"
95 | ]
96 | },
97 | {
98 | "cell_type": "markdown",
99 | "id": "c53a4250",
100 | "metadata": {},
101 | "source": [
102 | "A set of links to the sections and subsections in this notebook."
103 | ]
104 | },
105 | {
106 | "cell_type": "markdown",
107 | "id": "e6815586",
108 | "metadata": {},
109 | "source": [
110 | "- [PACKAGES USED IN THIS NOTEBOOK](#PACKAGES-USED-IN-THIS-NOTEBOOK)\n",
111 | "- [QUICK PLOTS WITH THE PYPLOT INTERFACE](#QUICK-PLOTS-WITH-THE-PYPLOT-INTERFACE)\n",
112 | " - [LINE AND SCATTER PLOTS](#LINE-AND-SCATTER-PLOTS)\n",
113 | " - [FREQUENCY PLOTS](#FREQUENCY-PLOTS)\n",
114 | " - [BAR PLOTS FOR STATISTICS](#BAR-PLOTS-FOR-STATISTICS)\n",
115 | " - [BOX AND WHISKER PLOTS](#BOX-AND-WHISKER-PLOTS)\n",
116 | "- [OBJECT ORIENTED INTERFACE](#OBJECT-ORIENTED-INTERFACE)\n",
117 | "- [PLOTS FOR FUNCTIONS AND VECTORS](#PLOTS-FOR-FUNCTIONS-AND-VECTORS)\n",
118 | " - [CONTOUR PLOTS](#CONTOUR-PLOTS)\n",
119 | " - [QUIVER PLOTS](#QUIVER-PLOTS)\n",
120 | " - [STREAM PLOTS](#STREAM-PLOTS)"
121 | ]
122 | },
123 | {
124 | "cell_type": "markdown",
125 | "id": "02a1b59b-ff70-4f90-9c65-cd307381b2b4",
126 | "metadata": {},
127 | "source": [
128 | "## PACKAGES USED IN THIS NOTEBOOK"
129 | ]
130 | },
131 | {
132 | "cell_type": "code",
133 | "execution_count": null,
134 | "id": "2e1d639b-c6a0-43d1-96d6-48ee06075022",
135 | "metadata": {},
136 | "outputs": [],
137 | "source": [
138 | "import os # Interacting with the operating system and directory structure"
139 | ]
140 | },
141 | {
142 | "cell_type": "code",
143 | "execution_count": null,
144 | "id": "ec68c92f",
145 | "metadata": {},
146 | "outputs": [],
147 | "source": [
148 | "import sympy as sym # Symbolic python (computer algebra system)"
149 | ]
150 | },
151 | {
152 | "cell_type": "code",
153 | "execution_count": null,
154 | "id": "4921ba8d-4c96-46eb-bc94-ae0c13f723f3",
155 | "metadata": {},
156 | "outputs": [],
157 | "source": [
158 | "import numpy as np # Numerical computation"
159 | ]
160 | },
161 | {
162 | "cell_type": "code",
163 | "execution_count": null,
164 | "id": "b585b567-94b6-4400-8e6c-2848f415058f",
165 | "metadata": {},
166 | "outputs": [],
167 | "source": [
168 | "import matplotlib.pyplot as plt # Plotting package"
169 | ]
170 | },
171 | {
172 | "cell_type": "code",
173 | "execution_count": null,
174 | "id": "70586fab",
175 | "metadata": {},
176 | "outputs": [],
177 | "source": [
178 | "import matplotlib.gridspec as gridspec"
179 | ]
180 | },
181 | {
182 | "cell_type": "code",
183 | "execution_count": null,
184 | "id": "52002687-3140-430d-8ff1-e719a0a374c6",
185 | "metadata": {},
186 | "outputs": [],
187 | "source": [
188 | "%config InlineBackend.figure_format = 'retina'"
189 | ]
190 | },
191 | {
192 | "cell_type": "markdown",
193 | "id": "abfc7563-f906-40e8-9051-39dd46b6c9f6",
194 | "metadata": {},
195 | "source": [
196 | "The `style.available` attribute from the pyplot module shows a list of all the available plot styles. Note that this Python environment has the scienceplots package installed using pip. A Tex installation is required for many of these themes."
197 | ]
198 | },
199 | {
200 | "cell_type": "code",
201 | "execution_count": null,
202 | "id": "186ad881-be32-469b-8d4e-c7d1636fa5bb",
203 | "metadata": {},
204 | "outputs": [],
205 | "source": [
206 | "plt.style.available"
207 | ]
208 | },
209 | {
210 | "cell_type": "markdown",
211 | "id": "893ce786",
212 | "metadata": {},
213 | "source": [
214 | "I will use the classic plotting style."
215 | ]
216 | },
217 | {
218 | "cell_type": "code",
219 | "execution_count": null,
220 | "id": "89fb6bd7-fbe8-4b3c-b833-7e2196c31eff",
221 | "metadata": {},
222 | "outputs": [],
223 | "source": [
224 | "plt.style.use(['classic'])"
225 | ]
226 | },
227 | {
228 | "cell_type": "markdown",
229 | "id": "752161bf",
230 | "metadata": {},
231 | "source": [
232 | "A backend can also be set for matplotlib. The current backend is set to generate plots inline with the notebook cells."
233 | ]
234 | },
235 | {
236 | "cell_type": "markdown",
237 | "id": "1b43da4d",
238 | "metadata": {},
239 | "source": [
240 | "The default is to have plot generated in line with the cells that generate them. The code below is the default behavior and need to be executed."
241 | ]
242 | },
243 | {
244 | "cell_type": "code",
245 | "execution_count": null,
246 | "id": "7addfa67",
247 | "metadata": {},
248 | "outputs": [],
249 | "source": [
250 | "%matplotlib inline"
251 | ]
252 | },
253 | {
254 | "cell_type": "code",
255 | "execution_count": null,
256 | "id": "4737bed3",
257 | "metadata": {},
258 | "outputs": [],
259 | "source": [
260 | "import matplotlib\n",
261 | "matplotlib.get_backend()"
262 | ]
263 | },
264 | {
265 | "cell_type": "markdown",
266 | "id": "268d40ed",
267 | "metadata": {},
268 | "source": [
269 | "We can set `%matplotlib notebook`. The `get_backend` function then returns `nbAgg`. Each plot will now be interactive and has to be shut down (button on the top right corner), before a new plot can be created."
270 | ]
271 | },
272 | {
273 | "cell_type": "markdown",
274 | "id": "5890aa87-5222-42f6-824f-eac3a142cc47",
275 | "metadata": {},
276 | "source": [
277 | "## QUICK PLOTS WITH THE PYPLOT INTERFACE"
278 | ]
279 | },
280 | {
281 | "cell_type": "markdown",
282 | "id": "b34e9dce",
283 | "metadata": {},
284 | "source": [
285 | "There are many ways to use matplotlib. We start by exploring the __pyplot interface__. We will take a look ar tge __object oriented interface__ later. It allows for more control, but requires more code."
286 | ]
287 | },
288 | {
289 | "cell_type": "markdown",
290 | "id": "ef82a34d",
291 | "metadata": {},
292 | "source": [
293 | "A list of links to pyplot plots and settings is available [here](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.html)."
294 | ]
295 | },
296 | {
297 | "cell_type": "markdown",
298 | "id": "110db090-695c-4939-b0ef-9151e6a2b89b",
299 | "metadata": {},
300 | "source": [
301 | "### LINE AND SCATTER PLOTS"
302 | ]
303 | },
304 | {
305 | "cell_type": "markdown",
306 | "id": "f99b0253-f6b2-4b61-b628-04232076c1ca",
307 | "metadata": {},
308 | "source": [
309 | "We generate points that serve as _x_ axis values for the expression $\\sin{\\left( x \\right)}$ with some random noise taken from the standard normal distribution."
310 | ]
311 | },
312 | {
313 | "cell_type": "code",
314 | "execution_count": null,
315 | "id": "fa7b0d16-46d6-4100-ae56-c452ee8f4d84",
316 | "metadata": {},
317 | "outputs": [],
318 | "source": [
319 | "xvals = np.linspace(0, 4 * np.pi, 50) # Start, stop, number of points\n",
320 | "yvals_1 = np.sin(xvals) + np.random.randn(len(xvals)) * 0.1 # Experiment 1\n",
321 | "yvals_2 = np.sin(xvals) + np.random.randn(len(xvals)) * 0.1 # Experiment 2"
322 | ]
323 | },
324 | {
325 | "cell_type": "markdown",
326 | "id": "8e548cb0-6a2c-4674-80fc-f329928387cc",
327 | "metadata": {},
328 | "source": [
329 | "We start with a bare bones line chart. While we onlu have $50$ data points, the `plot` function will interpolate values to create a continuous line."
330 | ]
331 | },
332 | {
333 | "cell_type": "code",
334 | "execution_count": null,
335 | "id": "fafecd2f-d81a-48a7-911d-fba0eb34b797",
336 | "metadata": {},
337 | "outputs": [],
338 | "source": [
339 | "plt.plot(xvals, yvals_1)"
340 | ]
341 | },
342 | {
343 | "cell_type": "markdown",
344 | "id": "abef83f5",
345 | "metadata": {},
346 | "source": [
347 | "Placing a semicolon after the lat plot command suprresses the extra information printed to the screen."
348 | ]
349 | },
350 | {
351 | "cell_type": "code",
352 | "execution_count": null,
353 | "id": "8e1c613d",
354 | "metadata": {},
355 | "outputs": [],
356 | "source": [
357 | "plt.plot(xvals, yvals_1); # Semicolon supresses other output to the screen"
358 | ]
359 | },
360 | {
361 | "cell_type": "markdown",
362 | "id": "782a668d-b7fa-4814-8890-fe2e523fa1fa",
363 | "metadata": {},
364 | "source": [
365 | "We can add a variety of arguments to the `plot` function. We start by changing the line style. Below, the `linestyle` argument is set to `solid`."
366 | ]
367 | },
368 | {
369 | "cell_type": "code",
370 | "execution_count": null,
371 | "id": "9ee6e8d3",
372 | "metadata": {},
373 | "outputs": [],
374 | "source": [
375 | "plt.plot(xvals, yvals_1, linestyle='solid');"
376 | ]
377 | },
378 | {
379 | "cell_type": "markdown",
380 | "id": "e9cc143e",
381 | "metadata": {},
382 | "source": [
383 | "We can use the shorthand `-` notation instead of the string `solid`."
384 | ]
385 | },
386 | {
387 | "cell_type": "code",
388 | "execution_count": null,
389 | "id": "b94c7685",
390 | "metadata": {},
391 | "outputs": [],
392 | "source": [
393 | "plt.plot(xvals, yvals_1, linestyle='-');"
394 | ]
395 | },
396 | {
397 | "cell_type": "markdown",
398 | "id": "28ddb43e",
399 | "metadata": {},
400 | "source": [
401 | "Setting the argument value to `dashed` or using `--` produces an evenly spaced dashed line."
402 | ]
403 | },
404 | {
405 | "cell_type": "code",
406 | "execution_count": null,
407 | "id": "a86615a1",
408 | "metadata": {},
409 | "outputs": [],
410 | "source": [
411 | "plt.plot(xvals, yvals_1, linestyle='dashed');"
412 | ]
413 | },
414 | {
415 | "cell_type": "code",
416 | "execution_count": null,
417 | "id": "5a78e647",
418 | "metadata": {},
419 | "outputs": [],
420 | "source": [
421 | "plt.plot(xvals, yvals_1, linestyle='--');"
422 | ]
423 | },
424 | {
425 | "cell_type": "markdown",
426 | "id": "4aeaf8e9",
427 | "metadata": {},
428 | "source": [
429 | "We can also use `dotted` and `dashdot`."
430 | ]
431 | },
432 | {
433 | "cell_type": "code",
434 | "execution_count": null,
435 | "id": "b0c580f8",
436 | "metadata": {},
437 | "outputs": [],
438 | "source": [
439 | "plt.plot(xvals, yvals_1, linestyle='dotted');"
440 | ]
441 | },
442 | {
443 | "cell_type": "code",
444 | "execution_count": null,
445 | "id": "434eb474",
446 | "metadata": {},
447 | "outputs": [],
448 | "source": [
449 | "plt.plot(xvals, yvals_1, linestyle=':');"
450 | ]
451 | },
452 | {
453 | "cell_type": "code",
454 | "execution_count": null,
455 | "id": "f6844186",
456 | "metadata": {},
457 | "outputs": [],
458 | "source": [
459 | "plt.plot(xvals, yvals_1, linestyle='dashdot');"
460 | ]
461 | },
462 | {
463 | "cell_type": "code",
464 | "execution_count": null,
465 | "id": "2016de53",
466 | "metadata": {},
467 | "outputs": [],
468 | "source": [
469 | "plt.plot(xvals, yvals_1, linestyle='-.');"
470 | ]
471 | },
472 | {
473 | "cell_type": "markdown",
474 | "id": "33ffebec",
475 | "metadata": {},
476 | "source": [
477 | "In fact, we can use a tuple to parameterize the length of dashes and spaces, as well as an offset. Below, we have $0$ offset, then a dash of $3$ points, a space of $5$ points, a dash of 1 point, and the space $5$ points. We also introduce the `linewidth` argument and set it to `2`."
478 | ]
479 | },
480 | {
481 | "cell_type": "code",
482 | "execution_count": null,
483 | "id": "08b8b352",
484 | "metadata": {},
485 | "outputs": [],
486 | "source": [
487 | "plt.plot(xvals, yvals_1, linestyle=(0, (0, 3, 5, 1, 5)));"
488 | ]
489 | },
490 | {
491 | "cell_type": "markdown",
492 | "id": "b76ac530",
493 | "metadata": {},
494 | "source": [
495 | "We have used the plot function to generate a continuous line (even with dashes and dots) to interpolate values between our $50$ data points. We can place markers on the actual data points. There are numerous markers. A list of all the markers are available [here](https://matplotlib.org/stable/api/markers_api.html). Below, we commence with a point, setting the `marker` argument to `.`"
496 | ]
497 | },
498 | {
499 | "cell_type": "code",
500 | "execution_count": null,
501 | "id": "242c4334",
502 | "metadata": {},
503 | "outputs": [],
504 | "source": [
505 | "plt.plot(xvals, yvals_1, linestyle='-', marker='.');"
506 | ]
507 | },
508 | {
509 | "cell_type": "markdown",
510 | "id": "81df9c58",
511 | "metadata": {},
512 | "source": [
513 | "We can increase the marker size by setting the `markersize` argument."
514 | ]
515 | },
516 | {
517 | "cell_type": "code",
518 | "execution_count": null,
519 | "id": "c9f1901a",
520 | "metadata": {},
521 | "outputs": [],
522 | "source": [
523 | "plt.plot(xvals, yvals_1, linestyle='-', marker='.', markersize=10); # Using 10pt"
524 | ]
525 | },
526 | {
527 | "cell_type": "markdown",
528 | "id": "17513938",
529 | "metadata": {},
530 | "source": [
531 | "Instead of a point, we can use a circle, setting the `marker` argument to `o`."
532 | ]
533 | },
534 | {
535 | "cell_type": "code",
536 | "execution_count": null,
537 | "id": "3a5a4f61",
538 | "metadata": {},
539 | "outputs": [],
540 | "source": [
541 | "plt.plot(xvals, yvals_1, linestyle='-', marker='o', markersize=5);"
542 | ]
543 | },
544 | {
545 | "cell_type": "markdown",
546 | "id": "5be17ee5",
547 | "metadata": {},
548 | "source": [
549 | "There are shorthand keyword arguments. Below we use `ls` for `linestyle`, `ms` for `marker`, and `lw` for `linewidth`. We also set the marker to `x`."
550 | ]
551 | },
552 | {
553 | "cell_type": "code",
554 | "execution_count": null,
555 | "id": "8815e4ab",
556 | "metadata": {},
557 | "outputs": [],
558 | "source": [
559 | "plt.plot(xvals, yvals_1, ls='-', marker='x', ms=8, lw=1);"
560 | ]
561 | },
562 | {
563 | "cell_type": "markdown",
564 | "id": "cedeb624",
565 | "metadata": {},
566 | "source": [
567 | "The markers module in the matplotlib package provides direct use of marker names."
568 | ]
569 | },
570 | {
571 | "cell_type": "code",
572 | "execution_count": null,
573 | "id": "5a393b90",
574 | "metadata": {},
575 | "outputs": [],
576 | "source": [
577 | "plt.plot(xvals, yvals_1, ls='-', marker=matplotlib.markers.CARETDOWNBASE, ms=8, lw=1);"
578 | ]
579 | },
580 | {
581 | "cell_type": "markdown",
582 | "id": "565b148b",
583 | "metadata": {},
584 | "source": [
585 | "It is common to see shorthand positional argument notation for combinating the interpolated lines and markers."
586 | ]
587 | },
588 | {
589 | "cell_type": "code",
590 | "execution_count": null,
591 | "id": "3d84075e",
592 | "metadata": {},
593 | "outputs": [],
594 | "source": [
595 | "plt.plot(xvals, yvals_1, 'x-', ms=8, lw=1);"
596 | ]
597 | },
598 | {
599 | "cell_type": "markdown",
600 | "id": "44e8c805",
601 | "metadata": {},
602 | "source": [
603 | "A title and axes labels can be added using the `title`, `xlabel` and `ylabel` function in the pyplot module. These functions allow for the use of TeX notation. TeX is enclosed in a pair of `$` symbols. A full list of the subset of TeX markup that can be used in matplotlib is available [here](https://matplotlib.org/stable/tutorials/text/mathtext.html)."
604 | ]
605 | },
606 | {
607 | "cell_type": "code",
608 | "execution_count": null,
609 | "id": "3ff8a8ee",
610 | "metadata": {},
611 | "outputs": [],
612 | "source": [
613 | "plt.plot(xvals, yvals_1, 'x-', ms=8, lw=1)\n",
614 | "plt.title(r'Position at time $t$')\n",
615 | "plt.xlabel(r'Time $\\left[ \\mu s \\right]$')\n",
616 | "plt.ylabel(r'Position $\\left[ m \\right]$');"
617 | ]
618 | },
619 | {
620 | "cell_type": "markdown",
621 | "id": "18832c2f",
622 | "metadata": {},
623 | "source": [
624 | "The title and axes labels are too small. We can use the `fontdict` argument to change font parameters. Note that we could also simply use the `size` argument."
625 | ]
626 | },
627 | {
628 | "cell_type": "code",
629 | "execution_count": null,
630 | "id": "2b455cf7",
631 | "metadata": {},
632 | "outputs": [],
633 | "source": [
634 | "title_font_size = 16 # Setting a default title font size\n",
635 | "plt.plot(xvals, yvals_1, 'x-', ms=8, lw=1)\n",
636 | "plt.title(r'Position at time $t$', fontdict={'size':title_font_size})\n",
637 | "plt.xlabel(r'Time $\\left[ \\mu s \\right]$', fontdict={'size':title_font_size - 4})\n",
638 | "plt.ylabel(r'Position $\\left[ m \\right]$', size=title_font_size - 4);"
639 | ]
640 | },
641 | {
642 | "cell_type": "markdown",
643 | "id": "7b886ff3",
644 | "metadata": {},
645 | "source": [
646 | "The `legend` function generates a legend. For this, we need to set a label name in the `plot` function. Below, we set to legend position and font size."
647 | ]
648 | },
649 | {
650 | "cell_type": "code",
651 | "execution_count": null,
652 | "id": "8a037494",
653 | "metadata": {},
654 | "outputs": [],
655 | "source": [
656 | "title_font_size = 16 # Setting a default title font size\n",
657 | "plt.plot(xvals, yvals_1, 'x-', ms=8, lw=1, label='Experiment 1')\n",
658 | "plt.title(r'Position at time $t$', fontdict={'size':title_font_size})\n",
659 | "plt.xlabel(r'Time $\\left[ \\mu s \\right]$', fontdict={'size':title_font_size - 4})\n",
660 | "plt.ylabel(r'Position $\\left[ m \\right]$', fontdict={'size':title_font_size - 4})\n",
661 | "plt.legend(loc='upper right', fontsize=10);"
662 | ]
663 | },
664 | {
665 | "cell_type": "markdown",
666 | "id": "fc0959ea",
667 | "metadata": {},
668 | "source": [
669 | "To add a second data set, we simple use another `plot` function."
670 | ]
671 | },
672 | {
673 | "cell_type": "markdown",
674 | "id": "28f0072f",
675 | "metadata": {},
676 | "source": [
677 | "Below, we combine all of our knowledge and add a grid and change the axes ticks to face outwards. We also use keywords for the color of each data set and we use the `figure` function to set the `figsize` argument. This argument takes a tuple, indicating the width and height of the figure."
678 | ]
679 | },
680 | {
681 | "cell_type": "code",
682 | "execution_count": null,
683 | "id": "d06984e8-71bc-48b2-b831-e77367b164cf",
684 | "metadata": {},
685 | "outputs": [],
686 | "source": [
687 | "marker_type = 'o--' # Dashes with dots at actual values\n",
688 | "color_1 = 'dodgerblue' # Color\n",
689 | "color_2 = 'orangered' # Color\n",
690 | "lw = 1 # Line width\n",
691 | "ms = 5 # Marker size\n",
692 | "label_1 = 'Experiment 1' # Label for values\n",
693 | "label_2 = 'Experiment 2' # lable for values"
694 | ]
695 | },
696 | {
697 | "cell_type": "code",
698 | "execution_count": null,
699 | "id": "73bdb1d9-123d-4976-8028-45a450397a6a",
700 | "metadata": {},
701 | "outputs": [],
702 | "source": [
703 | "plt.figure(figsize=(10, 6)) # Size of the plot\n",
704 | "plt.plot(xvals, yvals_1, marker_type, color=color_1, lw=lw, ms=ms, label=label_1)\n",
705 | "plt.plot(xvals, yvals_2, marker_type, color=color_2, lw=lw, ms=ms, label=label_2)\n",
706 | "plt.legend(loc='upper right', fontsize=10) # Legend with position and font size\n",
707 | "plt.xlabel(r'Time $\\left[ \\mu s \\right]$', fontdict={'size':12}) # x axis title with TeX\n",
708 | "plt.ylabel(r'Position [cm]', fontdict={'size':12})# y axis title\n",
709 | "plt.title(r'Position over time $\\left[ \\mu s \\right]$', fontdict={'size':16}) # Plot title\n",
710 | "plt.grid() # Add x and y grid lines\n",
711 | "plt.tick_params(which='both', direction='out'); # Ticks face into plot area"
712 | ]
713 | },
714 | {
715 | "cell_type": "markdown",
716 | "id": "bce905f1-1bf4-4c27-aa59-b5eaabea9a53",
717 | "metadata": {},
718 | "source": [
719 | "There are eight _shortcut_ color keywords: `k` for black, `w` for white, `r` for red, `g` for green, `b` for blue, `c` for cyan, `m` for magenta, and `y` for yellow. By using a fraction such as `color='0.5` we set a shade of grey. We can also specify HEX colors such as `color='#aabbcc'`. Finally there are a list of named colors. The list is available [here](https://en.wikipedia.org/wiki/Web_colors)."
720 | ]
721 | },
722 | {
723 | "cell_type": "markdown",
724 | "id": "a8cfd977-c287-4ac2-afae-804d2753c5c9",
725 | "metadata": {},
726 | "source": [
727 | "Below, we add a model, $\\sin{\\left( x \\right)}$, indicated by more points and a solid line. We also remove the dashed lines of the data interpolation."
728 | ]
729 | },
730 | {
731 | "cell_type": "code",
732 | "execution_count": null,
733 | "id": "263b4060-4472-4608-8071-7c017d8dac10",
734 | "metadata": {},
735 | "outputs": [],
736 | "source": [
737 | "xvals_model = np.linspace(0, 4 * np.pi, 200)\n",
738 | "yvals_model = np.sin(xvals_model)"
739 | ]
740 | },
741 | {
742 | "cell_type": "code",
743 | "execution_count": null,
744 | "id": "2621acba-2058-44d3-9b2c-4a95c5b40de4",
745 | "metadata": {},
746 | "outputs": [],
747 | "source": [
748 | "plt.figure(figsize=(10, 6)) # Size of the plot\n",
749 | "plt.plot(xvals, yvals_1, 'o', color=color_1, ms=ms, label=label_1)\n",
750 | "plt.plot(xvals, yvals_2, 'o', color=color_2, ms=ms, label=label_2)\n",
751 | "plt.plot(xvals_model, yvals_model, '-', color='#555555', lw=2, label='Model')\n",
752 | "plt.legend(loc='upper right', fontsize=10, ncol=3) # Legend with position and font size and columns\n",
753 | "plt.xlabel(r'Time $\\left[ \\mu s \\right]$', fontdict={'size':12}) # x axis title with TeX\n",
754 | "plt.ylabel('Position [cm]', fontdict={'size':12})# y axis title\n",
755 | "plt.ylim(top=1.5) # Adding space for the legend\n",
756 | "plt.title('Position over time and model', fontdict={'size':16}) # Plot title\n",
757 | "plt.grid()\n",
758 | "plt.tick_params(which='both', direction='in');"
759 | ]
760 | },
761 | {
762 | "cell_type": "markdown",
763 | "id": "b1254327-a412-4b77-b58c-a532bcf51be5",
764 | "metadata": {},
765 | "source": [
766 | "Without the interpolated lines, these plots are actually scatter plots. Below, we generate data for an independent and a dependent variable."
767 | ]
768 | },
769 | {
770 | "cell_type": "code",
771 | "execution_count": null,
772 | "id": "bdacd463-db25-4c3e-a029-6aa9c507882d",
773 | "metadata": {},
774 | "outputs": [],
775 | "source": [
776 | "indep = np.random.randint(100, 300, 50) / 2\n",
777 | "dep = indep * 0.8 + np.random.randint(-10, 10, 50)"
778 | ]
779 | },
780 | {
781 | "cell_type": "code",
782 | "execution_count": null,
783 | "id": "5adb556e-0c11-4760-a4b5-a53046ba669f",
784 | "metadata": {},
785 | "outputs": [],
786 | "source": [
787 | "plt.figure(figsize=(5, 5))\n",
788 | "plt.plot(indep, dep, 'o')\n",
789 | "plt.title('Scatter plot')\n",
790 | "plt.xlabel('Independent variable')\n",
791 | "plt.ylabel('Dependent variable')\n",
792 | "plt.grid(True, color='0.5', dashes=(5, 2, 1, 2)) # For dashes we set a tuple of line and break lengths\n",
793 | "plt.tick_params(which='both', direction='out');"
794 | ]
795 | },
796 | {
797 | "cell_type": "markdown",
798 | "id": "6cc5a716",
799 | "metadata": {},
800 | "source": [
801 | "The `scatter` function also generates scatter plots. We can visualise another numerical variable by setting the size of the markers. A fourth numerical variable can be included by coloring the markers."
802 | ]
803 | },
804 | {
805 | "cell_type": "code",
806 | "execution_count": null,
807 | "id": "28fd35cb",
808 | "metadata": {},
809 | "outputs": [],
810 | "source": [
811 | "indep2 = np.random.randint(50, 200, 50)\n",
812 | "indep3 = np.random.randint(1000, 2000, 50)"
813 | ]
814 | },
815 | {
816 | "cell_type": "code",
817 | "execution_count": null,
818 | "id": "55f70320",
819 | "metadata": {},
820 | "outputs": [],
821 | "source": [
822 | "plt.figure(figsize=(5, 5))\n",
823 | "plt.scatter(indep, dep, s=indep2, c=indep3, alpha=0.5)\n",
824 | "plt.title('Scatter plot')\n",
825 | "plt.xlabel('Independent variable')\n",
826 | "plt.ylabel('Dependent variable')\n",
827 | "plt.grid()\n",
828 | "plt.tick_params(which='both', direction='out');"
829 | ]
830 | },
831 | {
832 | "cell_type": "markdown",
833 | "id": "5b7ea858",
834 | "metadata": {},
835 | "source": [
836 | "The `colorbar` function adds a color bar for the _fourth_ dimension used here for the `indep3` variable."
837 | ]
838 | },
839 | {
840 | "cell_type": "code",
841 | "execution_count": null,
842 | "id": "a00fcf25",
843 | "metadata": {},
844 | "outputs": [],
845 | "source": [
846 | "plt.figure(figsize=(5, 5))\n",
847 | "plt.scatter(indep, dep, s=indep2, c=indep3, alpha=0.5)\n",
848 | "plt.title('Scatter plot')\n",
849 | "plt.xlabel('Independent variable')\n",
850 | "plt.ylabel('Dependent variable')\n",
851 | "plt.grid()\n",
852 | "plt.tick_params(which='both', direction='out')\n",
853 | "plt.colorbar();"
854 | ]
855 | },
856 | {
857 | "cell_type": "markdown",
858 | "id": "f6d2b082",
859 | "metadata": {},
860 | "source": [
861 | "Both the _x_ and _y_ axes are linear. We can change this to log scales. We start by generating values for plots and then plot with the _y_ axis scale set to $\\log_{10}$."
862 | ]
863 | },
864 | {
865 | "cell_type": "code",
866 | "execution_count": null,
867 | "id": "5dc82836",
868 | "metadata": {},
869 | "outputs": [],
870 | "source": [
871 | "range(10)"
872 | ]
873 | },
874 | {
875 | "cell_type": "code",
876 | "execution_count": null,
877 | "id": "5552ef1a",
878 | "metadata": {},
879 | "outputs": [],
880 | "source": [
881 | "list(range(10))"
882 | ]
883 | },
884 | {
885 | "cell_type": "code",
886 | "execution_count": null,
887 | "id": "a1a1a303",
888 | "metadata": {},
889 | "outputs": [],
890 | "source": [
891 | "vals_for_log = [10**i for i in range(10)]\n",
892 | "\n",
893 | "plt.plot(vals_for_log, 'o--') # Single list defaults to y axis values\n",
894 | "plt.yscale('log')\n",
895 | "plt.title(r'Log scale for $y$ axis', size=16)\n",
896 | "plt.ylabel(r'$\\log_{10}$ scale', size=12)\n",
897 | "plt.grid()\n",
898 | "plt.tick_params(which='both', direction='out');"
899 | ]
900 | },
901 | {
902 | "cell_type": "markdown",
903 | "id": "58e4fc48",
904 | "metadata": {},
905 | "source": [
906 | "We do the same for the _x_ axis."
907 | ]
908 | },
909 | {
910 | "cell_type": "code",
911 | "execution_count": null,
912 | "id": "8dc379ce",
913 | "metadata": {},
914 | "outputs": [],
915 | "source": [
916 | "plt.plot(vals_for_log, range(10), 'o--')\n",
917 | "plt.xscale('log')\n",
918 | "plt.title('Log scale for $x$ axis', size=16)\n",
919 | "plt.xlabel(r'$\\log_{10}$ scale', size=12)\n",
920 | "plt.grid()\n",
921 | "plt.tick_params(which='both', direction='out');"
922 | ]
923 | },
924 | {
925 | "cell_type": "markdown",
926 | "id": "eed3c10d",
927 | "metadata": {},
928 | "source": [
929 | "The axes can use different log scales."
930 | ]
931 | },
932 | {
933 | "cell_type": "code",
934 | "execution_count": null,
935 | "id": "0daa8715",
936 | "metadata": {},
937 | "outputs": [],
938 | "source": [
939 | "vals_for_x = [10**i for i in range(1, 6)]\n",
940 | "vals_for_y = [2**i for i in range(1, 6)]"
941 | ]
942 | },
943 | {
944 | "cell_type": "code",
945 | "execution_count": null,
946 | "id": "6bb3b79e",
947 | "metadata": {},
948 | "outputs": [],
949 | "source": [
950 | "plt.plot(vals_for_x, vals_for_y, 'o--')\n",
951 | "plt.semilogx() # Default is base=10\n",
952 | "plt.semilogy(base=2)\n",
953 | "plt.title('Log base 10 and log base 2', size=16)\n",
954 | "plt.xlabel(r'$\\log_{10}$ scale', size=12)\n",
955 | "plt.ylabel(r'$\\log_{2}$ scale', size=12)\n",
956 | "plt.grid()\n",
957 | "plt.tick_params(which='both', direction='out');"
958 | ]
959 | },
960 | {
961 | "cell_type": "markdown",
962 | "id": "9bfca2ec",
963 | "metadata": {},
964 | "source": [
965 | "If both axes share the same log scale, we can use the `loglog` function."
966 | ]
967 | },
968 | {
969 | "cell_type": "code",
970 | "execution_count": null,
971 | "id": "32baaa7b",
972 | "metadata": {},
973 | "outputs": [],
974 | "source": [
975 | "vals_for_x = [10**i for i in range(1, 6)]\n",
976 | "vals_for_y = [10**i for i in range(6, 11)]"
977 | ]
978 | },
979 | {
980 | "cell_type": "code",
981 | "execution_count": null,
982 | "id": "96784277",
983 | "metadata": {},
984 | "outputs": [],
985 | "source": [
986 | "plt.plot(vals_for_x, vals_for_y, 'o--')\n",
987 | "plt.loglog(base=10)\n",
988 | "plt.title('Log base 10 on both axes', size=16)\n",
989 | "plt.xlabel(r'$\\log_{10}$ scale', size=12)\n",
990 | "plt.ylabel(r'$\\log_{10}$ scale', size=12)\n",
991 | "plt.grid()\n",
992 | "plt.tick_params(which='both', direction='out');"
993 | ]
994 | },
995 | {
996 | "cell_type": "markdown",
997 | "id": "20e62c10-8d89-4adf-b942-c509d0a810d5",
998 | "metadata": {},
999 | "source": [
1000 | "### FREQUENCY PLOTS"
1001 | ]
1002 | },
1003 | {
1004 | "cell_type": "markdown",
1005 | "id": "2a89c811",
1006 | "metadata": {},
1007 | "source": [
1008 | "Frequency plots indicate the count of binned numerical variables (intervals) or the classes (sample space elements) of categorical variables. The former is called a __histogram__ and the latter a __bar plot__. In a relative frequency plot, we divide by the sample size."
1009 | ]
1010 | },
1011 | {
1012 | "cell_type": "markdown",
1013 | "id": "abf0cd17",
1014 | "metadata": {},
1015 | "source": [
1016 | "Below, we generate random variables for two groups taken from normal distributions."
1017 | ]
1018 | },
1019 | {
1020 | "cell_type": "code",
1021 | "execution_count": null,
1022 | "id": "cf508a3b-0423-466c-b78d-06329e59ebcb",
1023 | "metadata": {},
1024 | "outputs": [],
1025 | "source": [
1026 | "var_group_1 = np.random.normal(loc=100, scale=10, size=1000)\n",
1027 | "var_group_2 = np.random.normal(loc=98, scale=15, size=1000)"
1028 | ]
1029 | },
1030 | {
1031 | "cell_type": "markdown",
1032 | "id": "cb45c553-2683-4e7e-afe1-12f9eea4dce7",
1033 | "metadata": {},
1034 | "source": [
1035 | "We start once again with a bare bones histogram."
1036 | ]
1037 | },
1038 | {
1039 | "cell_type": "code",
1040 | "execution_count": null,
1041 | "id": "0a047224-bb00-4680-aa01-bcee2e8bee50",
1042 | "metadata": {},
1043 | "outputs": [],
1044 | "source": [
1045 | "plt.hist(var_group_1)"
1046 | ]
1047 | },
1048 | {
1049 | "cell_type": "markdown",
1050 | "id": "0006bc44",
1051 | "metadata": {},
1052 | "source": [
1053 | "Two array and a plot are returned. The first array indicates the frequency (count) of values in each bin. The second array indicates the intervals created by matplotlib. We can overwrite the intervals. Below, we create eight intervals (requiring nine values)."
1054 | ]
1055 | },
1056 | {
1057 | "cell_type": "code",
1058 | "execution_count": null,
1059 | "id": "b918c3a8-650e-47a5-ad01-827490fcc5ff",
1060 | "metadata": {},
1061 | "outputs": [],
1062 | "source": [
1063 | "bin_intv = [10 * i for i in range(6, 15)]\n",
1064 | "bin_intv"
1065 | ]
1066 | },
1067 | {
1068 | "cell_type": "markdown",
1069 | "id": "7fe14322",
1070 | "metadata": {},
1071 | "source": [
1072 | "The `bins` argument can now be set to these intervals."
1073 | ]
1074 | },
1075 | {
1076 | "cell_type": "code",
1077 | "execution_count": null,
1078 | "id": "e3198de8-8e89-4a53-95ee-4faebe9272c2",
1079 | "metadata": {},
1080 | "outputs": [],
1081 | "source": [
1082 | "plt.hist(var_group_1, bins=bin_intv, color='gray');"
1083 | ]
1084 | },
1085 | {
1086 | "cell_type": "markdown",
1087 | "id": "f7fb8f29",
1088 | "metadata": {},
1089 | "source": [
1090 | "We can also simply specify the number of bins. Below, we create a histogram for both groups. The first sets the number of bins to $10$ and the second specifies the intervals. The histogram type, `histtype`, is set to `step`. This shows the outlines of the bars only. We also add a legend, specifying a single column setup, with the set legend outside of the plot using the `bbox_to_anchor` argument. The assigned tuple uses relative positioning where the left lower corner of the figure is `(0, 0)` and the top right is `(1, 1)`. We also specify the `loc` argument to fit the top left corner of the legend to the specified spot of the `bbox_to_anchor` argument. Finally, we remove the border from the legend and add a title."
1091 | ]
1092 | },
1093 | {
1094 | "cell_type": "code",
1095 | "execution_count": null,
1096 | "id": "53e42bba-1301-4d91-9f9f-7c0cc2ea6272",
1097 | "metadata": {},
1098 | "outputs": [],
1099 | "source": [
1100 | "plt.figure(figsize=(10, 5))\n",
1101 | "plt.hist(var_group_1, bins=10, histtype='step', label='Group 1')\n",
1102 | "plt.hist(var_group_2, bins=bin_intv, histtype='step', label='Group 2')\n",
1103 | "plt.legend(fontsize=8, bbox_to_anchor=(1.05, 1), loc='upper left', frameon=False, title='Groups')\n",
1104 | "plt.title('Frequency plot')\n",
1105 | "plt.xlabel('Variable value')\n",
1106 | "plt.ylabel('Frequency')\n",
1107 | "plt.tick_params(which='both', direction='out');"
1108 | ]
1109 | },
1110 | {
1111 | "cell_type": "markdown",
1112 | "id": "3c26ac65",
1113 | "metadata": {},
1114 | "source": [
1115 | "The `stepfilled` value for the `histtype` argument fills the histogram with color. We also add some transparency using the `alpha` argument."
1116 | ]
1117 | },
1118 | {
1119 | "cell_type": "code",
1120 | "execution_count": null,
1121 | "id": "3597b708",
1122 | "metadata": {},
1123 | "outputs": [],
1124 | "source": [
1125 | "plt.figure(figsize=(10, 5))\n",
1126 | "plt.hist(var_group_1, bins=10, histtype='stepfilled', alpha=0.5, label='Group 1')\n",
1127 | "plt.hist(var_group_2, bins=bin_intv, histtype='stepfilled', alpha=0.5, label='Group 2')\n",
1128 | "plt.legend(fontsize=8, bbox_to_anchor=(1.05, 1), loc='upper left', frameon=False, title='Groups')\n",
1129 | "plt.title('Frequency plot')\n",
1130 | "plt.xlabel('Variable value')\n",
1131 | "plt.ylabel('Frequency')\n",
1132 | "plt.tick_params(which='both', direction='out');"
1133 | ]
1134 | },
1135 | {
1136 | "cell_type": "markdown",
1137 | "id": "bd69fd0d",
1138 | "metadata": {},
1139 | "source": [
1140 | "The `hist2d` function creates a two-dimensional histogram (heat map)."
1141 | ]
1142 | },
1143 | {
1144 | "cell_type": "code",
1145 | "execution_count": null,
1146 | "id": "ad74ac08",
1147 | "metadata": {},
1148 | "outputs": [],
1149 | "source": [
1150 | "x = np.random.normal(loc=0, scale=1, size=10000)\n",
1151 | "y = np.random.normal(loc=0, scale=1, size=10000)\n",
1152 | "\n",
1153 | "plt.hist2d(x, y, bins=30)\n",
1154 | "plt.colorbar().set_label('Frequency in bin');"
1155 | ]
1156 | },
1157 | {
1158 | "cell_type": "markdown",
1159 | "id": "6991b720",
1160 | "metadata": {},
1161 | "source": [
1162 | "The `hexbin` function generates regular six-sided polygons."
1163 | ]
1164 | },
1165 | {
1166 | "cell_type": "code",
1167 | "execution_count": null,
1168 | "id": "7f81235a",
1169 | "metadata": {},
1170 | "outputs": [],
1171 | "source": [
1172 | "plt.hexbin(x, y, gridsize=30, cmap='Reds')\n",
1173 | "plt.colorbar().set_label('Frequency in bin');"
1174 | ]
1175 | },
1176 | {
1177 | "cell_type": "markdown",
1178 | "id": "46de3d59-aa0f-4071-a412-42d8c8fd97a0",
1179 | "metadata": {},
1180 | "source": [
1181 | "The classes or sample space elements of a categorical variable can also be visualised as a frequency chart. In this case, we use a bar chart. There are spaces between the bars to indicate that the variable is not continuous numerical as in a histogram with no spaces between the bars."
1182 | ]
1183 | },
1184 | {
1185 | "cell_type": "code",
1186 | "execution_count": null,
1187 | "id": "cc5b0efb-e423-498b-bf40-f6f8d90c898a",
1188 | "metadata": {},
1189 | "outputs": [],
1190 | "source": [
1191 | "classes = ['A', 'B', 'C', 'D']"
1192 | ]
1193 | },
1194 | {
1195 | "cell_type": "code",
1196 | "execution_count": null,
1197 | "id": "fe01fbec-6a2a-428c-9ff6-49afbe626cca",
1198 | "metadata": {},
1199 | "outputs": [],
1200 | "source": [
1201 | "cat_group_1 = np.random.choice(classes, 500)\n",
1202 | "cat_group_2 = np.random.choice(classes, 500)"
1203 | ]
1204 | },
1205 | {
1206 | "cell_type": "code",
1207 | "execution_count": null,
1208 | "id": "22b91c93-a89a-4810-80f4-c2f9e1b866ec",
1209 | "metadata": {},
1210 | "outputs": [],
1211 | "source": [
1212 | "np.unique(cat_group_1, return_counts=True)"
1213 | ]
1214 | },
1215 | {
1216 | "cell_type": "code",
1217 | "execution_count": null,
1218 | "id": "efe7cc8b-e0d4-4e21-9baa-b9f9c699613a",
1219 | "metadata": {},
1220 | "outputs": [],
1221 | "source": [
1222 | "cnts_1 = np.unique(cat_group_1, return_counts=True)[1]\n",
1223 | "cnts_2 = np.unique(cat_group_2, return_counts=True)[1]"
1224 | ]
1225 | },
1226 | {
1227 | "cell_type": "markdown",
1228 | "id": "aecbef4d-1b63-4c61-b770-48788176c715",
1229 | "metadata": {},
1230 | "source": [
1231 | "We have to subtract and add to the _x_ axis value using a numpy array, and then set the width to generate grouped bar plots. Below, we subtract $0.2$ and add $0.2$ and set a width of $0.4$. The four numerical _x_ axis values are then changed using the `xticks` function."
1232 | ]
1233 | },
1234 | {
1235 | "cell_type": "code",
1236 | "execution_count": null,
1237 | "id": "2e27e7aa-6bf6-47fb-9414-c8da9feed172",
1238 | "metadata": {},
1239 | "outputs": [],
1240 | "source": [
1241 | "plt.figure(figsize=(10, 5))\n",
1242 | "plt.bar(np.arange(4)-0.2, cnts_1, 0.4, label='Group 1', color='gray')\n",
1243 | "plt.bar(np.arange(4)+0.2, cnts_2, 0.4, label='Group 2', color='lightgray')\n",
1244 | "plt.xticks(range(4), classes) # Replace 1, 2, 3, 4 with A, B, C, D\n",
1245 | "plt.legend(loc='upper right', fontsize=10, ncol=2)\n",
1246 | "plt.ylim(top=160); # Adding a margin for legend"
1247 | ]
1248 | },
1249 | {
1250 | "cell_type": "markdown",
1251 | "id": "247c6950",
1252 | "metadata": {},
1253 | "source": [
1254 | "### BAR PLOTS FOR STATISTICS"
1255 | ]
1256 | },
1257 | {
1258 | "cell_type": "markdown",
1259 | "id": "9ca28345",
1260 | "metadata": {},
1261 | "source": [
1262 | "Bar plots can also be used to indicate statistics such as mean and standard deviation. Below, we generate some data for the coefficient of thermal expansion of two metals. The height of the bars indicate the means and the error bars visualise the standard deviation."
1263 | ]
1264 | },
1265 | {
1266 | "cell_type": "code",
1267 | "execution_count": null,
1268 | "id": "c846332d",
1269 | "metadata": {},
1270 | "outputs": [],
1271 | "source": [
1272 | "aluminium = np.random.normal(loc=0.00004, scale=0.000015, size=100)\n",
1273 | "copper = np.random.normal(loc=0.000025, scale=0.00001, size=100)"
1274 | ]
1275 | },
1276 | {
1277 | "cell_type": "code",
1278 | "execution_count": null,
1279 | "id": "1a7f3378",
1280 | "metadata": {},
1281 | "outputs": [],
1282 | "source": [
1283 | "al_mean = np.mean(aluminium)\n",
1284 | "al_std = np.std(aluminium, ddof=1)\n",
1285 | "\n",
1286 | "cu_mean = np.mean(copper)\n",
1287 | "cu_std = np.std(copper, ddof=1)"
1288 | ]
1289 | },
1290 | {
1291 | "cell_type": "markdown",
1292 | "id": "6a106f19",
1293 | "metadata": {},
1294 | "source": [
1295 | "The `bar` function generates a bar plot. On the $x$ axis we place the values $0$ and $1$ by using the `range` function. We overwrite the numerical $x$ axis values with the `xticks` function."
1296 | ]
1297 | },
1298 | {
1299 | "cell_type": "code",
1300 | "execution_count": null,
1301 | "id": "f9161bb4",
1302 | "metadata": {},
1303 | "outputs": [],
1304 | "source": [
1305 | "plt.figure(figsize=(5, 5))\n",
1306 | "plt.bar(range(2),\n",
1307 | " [al_mean, cu_mean],\n",
1308 | " 0.8, # Width\n",
1309 | " yerr=[al_std, cu_std],\n",
1310 | " align='center',\n",
1311 | " capsize=8,\n",
1312 | " color='gray',\n",
1313 | " alpha=0.5)\n",
1314 | "plt.title('Coefficient of thermal expansion')\n",
1315 | "plt.xticks(range(2), ['Aluminium', 'Copper'])\n",
1316 | "plt.ylabel('Coefficient of thermal expansion $[{}^{o}C^{-1}]$');"
1317 | ]
1318 | },
1319 | {
1320 | "cell_type": "markdown",
1321 | "id": "e17357ff",
1322 | "metadata": {},
1323 | "source": [
1324 | "Since the bar can appear at the left and right limits of the plot we can add limits to the $x$ axis. Below we use the `xlim` function and set the arguments `-0.5, 1.5`."
1325 | ]
1326 | },
1327 | {
1328 | "cell_type": "code",
1329 | "execution_count": null,
1330 | "id": "ebc85f27",
1331 | "metadata": {},
1332 | "outputs": [],
1333 | "source": [
1334 | "plt.figure(figsize=(5, 5))\n",
1335 | "plt.bar(range(2),\n",
1336 | " [al_mean, cu_mean],\n",
1337 | " 0.4,\n",
1338 | " yerr=[al_std, cu_std],\n",
1339 | " align='center',\n",
1340 | " capsize=8,\n",
1341 | " color='gray',\n",
1342 | " alpha=0.5)\n",
1343 | "plt.title('Coefficient of thermal expansion')\n",
1344 | "plt.xticks(range(2), ['Aluminium', 'Copper'])\n",
1345 | "plt.ylabel('Coefficient of thermal expansion $[{}^{o}C^{-1}]$')\n",
1346 | "plt.xlim(-0.5, 1.5);"
1347 | ]
1348 | },
1349 | {
1350 | "cell_type": "markdown",
1351 | "id": "b405e1f5-f920-442a-ae3a-1bd584de8872",
1352 | "metadata": {},
1353 | "source": [
1354 | "The `errorbar` function can be used separately."
1355 | ]
1356 | },
1357 | {
1358 | "cell_type": "code",
1359 | "execution_count": null,
1360 | "id": "30529018-77c0-4ac5-ade3-a8d4c2727064",
1361 | "metadata": {},
1362 | "outputs": [],
1363 | "source": [
1364 | "plt.figure(figsize=(5, 5))\n",
1365 | "plt.bar(range(2),\n",
1366 | " [al_mean, cu_mean],\n",
1367 | " 0.4,\n",
1368 | " color='lightgray')\n",
1369 | "plt.errorbar(range(2),\n",
1370 | " [al_mean, cu_mean],\n",
1371 | " yerr=[al_std, cu_std],\n",
1372 | " fmt=\"o\", # Data marker\n",
1373 | " color=\"r\")\n",
1374 | "plt.title('Coefficient of thermal expansion', size=16)\n",
1375 | "plt.xticks(range(2), ['Aluminium', 'Copper'])\n",
1376 | "plt.ylabel('Coefficient of thermal expansion $[{}^{o}C^{-1}]$', size=12)\n",
1377 | "plt.xlim(-0.5, 1.5);"
1378 | ]
1379 | },
1380 | {
1381 | "cell_type": "markdown",
1382 | "id": "dfdab09c-64fe-4029-abf0-2f0cc6535bb2",
1383 | "metadata": {},
1384 | "source": [
1385 | "The error bars can be used on their own."
1386 | ]
1387 | },
1388 | {
1389 | "cell_type": "code",
1390 | "execution_count": null,
1391 | "id": "477e7fef-6f3f-4c27-b73f-0189e86a97dd",
1392 | "metadata": {},
1393 | "outputs": [],
1394 | "source": [
1395 | "plt.errorbar(range(2),\n",
1396 | " [al_mean, cu_mean],\n",
1397 | " yerr=[al_std, cu_std],\n",
1398 | " fmt=\"d\", # Diamond\n",
1399 | " color=\"r\")\n",
1400 | "plt.title(r'Coefficient of thermal expansion ($\\mu , \\; \\sigma$)')\n",
1401 | "plt.xticks(range(2), ['Aluminium', 'Copper'])\n",
1402 | "plt.ylabel('Coefficient of thermal expansion $[{}^{o}C^{-1}]$')\n",
1403 | "plt.ylim(0, 0.00006) # Including zero\n",
1404 | "plt.grid()\n",
1405 | "plt.xlim(-0.5, 1.5);"
1406 | ]
1407 | },
1408 | {
1409 | "cell_type": "markdown",
1410 | "id": "7f7332e4-3af2-48f2-82f3-a410977a29e6",
1411 | "metadata": {},
1412 | "source": [
1413 | "### BOX AND WHISKER PLOTS"
1414 | ]
1415 | },
1416 | {
1417 | "cell_type": "markdown",
1418 | "id": "14b7544f",
1419 | "metadata": {},
1420 | "source": [
1421 | "Box-and-whisker plots also give an indication of the distribution of values for a numerical variable. Below, we use the `boxplot` function to visualise the same information as in the histograms above."
1422 | ]
1423 | },
1424 | {
1425 | "cell_type": "code",
1426 | "execution_count": null,
1427 | "id": "c17db943-52d5-4ed7-94cf-b4f328746cee",
1428 | "metadata": {},
1429 | "outputs": [],
1430 | "source": [
1431 | "plt.figure(figsize=(10, 5))\n",
1432 | "plt.boxplot([var_group_1, var_group_2],\n",
1433 | " flierprops={'marker':'D'}, # Suspected outliers are diamonds\n",
1434 | " meanline=True, # Calculate mean\n",
1435 | " showmeans=True, # Show mean\n",
1436 | " notch=True, # To indicate CI\n",
1437 | " bootstrap=5000) # 95% CI around the median\n",
1438 | "plt.title('Box-and-whisker plot for variable values in two groups')\n",
1439 | "plt.xlabel('Group')\n",
1440 | "plt.ylabel('Variable value')\n",
1441 | "plt.xticks([1, 2],['Group 1', 'Group 2']) # Change 1 and 2 to Group 1 and Group 2\n",
1442 | "plt.tick_params(which='both', direction='in')\n",
1443 | "plt.grid()\n",
1444 | "ax = plt.gca() # Note on these two last lines below\n",
1445 | "ax.set_frame_on(False);"
1446 | ]
1447 | },
1448 | {
1449 | "cell_type": "markdown",
1450 | "id": "4caea185-d965-489e-92bd-9740af6ab1c2",
1451 | "metadata": {},
1452 | "source": [
1453 | "## OBJECT ORIENTED INTERFACE"
1454 | ]
1455 | },
1456 | {
1457 | "cell_type": "markdown",
1458 | "id": "e514fc89-9af3-4c4e-8fb0-096510e19aa5",
1459 | "metadata": {},
1460 | "source": [
1461 | "More options become available when we use the object oriented matplotlib interface. You can read more about subplots [here](https://matplotlib.org/stable/gallery/subplots_axes_and_figures/subplots_demo.html)."
1462 | ]
1463 | },
1464 | {
1465 | "cell_type": "markdown",
1466 | "id": "1849673e",
1467 | "metadata": {},
1468 | "source": [
1469 | "When using the pyplot module, a single plot is created."
1470 | ]
1471 | },
1472 | {
1473 | "cell_type": "code",
1474 | "execution_count": null,
1475 | "id": "c98e19e3",
1476 | "metadata": {},
1477 | "outputs": [],
1478 | "source": [
1479 | "plt.plot(xvals, yvals_1, 'o')\n",
1480 | "plt.title('Simple scatter plot');"
1481 | ]
1482 | },
1483 | {
1484 | "cell_type": "markdown",
1485 | "id": "6dc828c4",
1486 | "metadata": {},
1487 | "source": [
1488 | "This is actually a subplot within a figure. In matplotlib these two entities are usually assigned to the variables `fig` and `ax`. We can create a figure with a single subplot and then return more information about the current subplot using the `gca` function."
1489 | ]
1490 | },
1491 | {
1492 | "cell_type": "code",
1493 | "execution_count": null,
1494 | "id": "08a9dcf3",
1495 | "metadata": {},
1496 | "outputs": [],
1497 | "source": [
1498 | "plt.plot(xvals, yvals_1, 'o'); # Don't add anything else to the plot\n",
1499 | "ax = plt.gca()"
1500 | ]
1501 | },
1502 | {
1503 | "cell_type": "markdown",
1504 | "id": "5f70d5a8",
1505 | "metadata": {},
1506 | "source": [
1507 | "The `type` function shows that `ax` is a `matplotlib.axes._subplots.AxesSubplot` object."
1508 | ]
1509 | },
1510 | {
1511 | "cell_type": "code",
1512 | "execution_count": null,
1513 | "id": "d5fd2b71",
1514 | "metadata": {},
1515 | "outputs": [],
1516 | "source": [
1517 | "type(ax)"
1518 | ]
1519 | },
1520 | {
1521 | "cell_type": "markdown",
1522 | "id": "a20019c1",
1523 | "metadata": {},
1524 | "source": [
1525 | "We us the object oriented interface to separate the two entities."
1526 | ]
1527 | },
1528 | {
1529 | "cell_type": "code",
1530 | "execution_count": null,
1531 | "id": "adc77ff8-fa89-4af1-a27b-c38c1575f16c",
1532 | "metadata": {},
1533 | "outputs": [],
1534 | "source": [
1535 | "fig, ax = plt.subplots() # Single plot"
1536 | ]
1537 | },
1538 | {
1539 | "cell_type": "markdown",
1540 | "id": "b3c31050",
1541 | "metadata": {},
1542 | "source": [
1543 | "Below, we create three rows and two columns of subplots in a figure. Since this is an arry along two dimensions, we using indexing to reference each axes in the figure."
1544 | ]
1545 | },
1546 | {
1547 | "cell_type": "code",
1548 | "execution_count": null,
1549 | "id": "56e4840f-53ef-4787-b771-05623258d9a2",
1550 | "metadata": {},
1551 | "outputs": [],
1552 | "source": [
1553 | "# Subplot [row] [column] indexing from single ax \n",
1554 | "fig, ax = plt.subplots(3, 2, figsize=(16, 9)) # Three rows and two columns of plots\n",
1555 | "\n",
1556 | "ax1 = ax[0][0] # Indices are [row][column]\n",
1557 | "ax1.set_title('Top left plot')\n",
1558 | "\n",
1559 | "ax6 = ax[2][1]\n",
1560 | "ax6.set_title('Bottom right')\n",
1561 | "\n",
1562 | "plt.tight_layout() # Can fix overlapping by using padding"
1563 | ]
1564 | },
1565 | {
1566 | "cell_type": "markdown",
1567 | "id": "c8e05d0f-1e7d-4d5d-bcfb-ca6c8817ff94",
1568 | "metadata": {},
1569 | "source": [
1570 | "There are numerous ways to add axes to a figure. We will see some in the rest of the notebook. Below, we generate a figure first and then add two axes, using the `add_axes` method. It takes a list as argument where we specify the fraction (left bottom is `(0, 0)` and top right is `(1, 1)`) from the left edge and the bottom edge that the axes must take up in the fidure. The last two fractions are the width and height. We then use the same technique to generate a second axes inside the first."
1571 | ]
1572 | },
1573 | {
1574 | "cell_type": "code",
1575 | "execution_count": null,
1576 | "id": "a5826f0a-e352-4f00-9c06-3170362cb9b2",
1577 | "metadata": {},
1578 | "outputs": [],
1579 | "source": [
1580 | "# Add subplot from plt\n",
1581 | "fig_1 = plt.figure(figsize=(6, 4))\n",
1582 | "axes_1 = fig_1.add_axes([0.05, 0.05, 0.95, 0.95]) # [left, bottom, width, height]\n",
1583 | "axes_1.set_title('Main plot')\n",
1584 | "\n",
1585 | "axes_2 = fig_1.add_axes([0.6, 0.6, 0.3, 0.3])\n",
1586 | "axes_2.set_title('Inside');"
1587 | ]
1588 | },
1589 | {
1590 | "cell_type": "markdown",
1591 | "id": "d6dee0cb",
1592 | "metadata": {},
1593 | "source": [
1594 | "The frames (borders) called spines around a plot can be removed. To remove all splines, the `set_frame_on` method can be set to `False`."
1595 | ]
1596 | },
1597 | {
1598 | "cell_type": "code",
1599 | "execution_count": null,
1600 | "id": "649d7339",
1601 | "metadata": {},
1602 | "outputs": [],
1603 | "source": [
1604 | "fig, ax = plt.subplots(1, 1)\n",
1605 | "ax.set_frame_on(False) # Remove all spines"
1606 | ]
1607 | },
1608 | {
1609 | "cell_type": "markdown",
1610 | "id": "dddb4878",
1611 | "metadata": {},
1612 | "source": [
1613 | "Below, we remove the top and bottom spines individually using the `set_visible` method. There are also bottom and left spines."
1614 | ]
1615 | },
1616 | {
1617 | "cell_type": "code",
1618 | "execution_count": null,
1619 | "id": "cee21478",
1620 | "metadata": {},
1621 | "outputs": [],
1622 | "source": [
1623 | "fig, ax = plt.subplots(1, 1)\n",
1624 | "ax.spines['top'].set_visible(False)\n",
1625 | "ax.spines['right'].set_visible(False)"
1626 | ]
1627 | },
1628 | {
1629 | "cell_type": "markdown",
1630 | "id": "7430b964",
1631 | "metadata": {},
1632 | "source": [
1633 | "As an example of using subplots, we create a single row of plots, with two columns. We also add a text box to the histogram."
1634 | ]
1635 | },
1636 | {
1637 | "cell_type": "code",
1638 | "execution_count": null,
1639 | "id": "36695fbd",
1640 | "metadata": {},
1641 | "outputs": [],
1642 | "source": [
1643 | "txt = '\\n'.join((r'$\\sigma_{1}=%.2f$'%(np.std(var_group_1)), r'$\\sigma_{2}=%.2f$'%(np.std(var_group_2))))"
1644 | ]
1645 | },
1646 | {
1647 | "cell_type": "code",
1648 | "execution_count": null,
1649 | "id": "991247b6-e1c0-4baf-904f-06ca78eb3bdb",
1650 | "metadata": {},
1651 | "outputs": [],
1652 | "source": [
1653 | "# Separate axes as a tuple\n",
1654 | "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 8), frameon=False)\n",
1655 | "\n",
1656 | "ax1.hist(var_group_1, bins=bin_intv, histtype='step', label='Group 1', lw=3, color='deepskyblue')\n",
1657 | "ax1.hist(var_group_2, bins=bin_intv, histtype='step', label='Group 2', lw=3, color='orangered')\n",
1658 | "ax1.legend(loc='upper right', fontsize=8, ncol=2, edgecolor='gray', facecolor='lightgrey')\n",
1659 | "ax1.set_title('Frequency plot')\n",
1660 | "ax1.set_xlabel('Variable value')\n",
1661 | "ax1.set_ylabel('Frequency')\n",
1662 | "ax1.text(65, 200, txt, bbox={'facecolor':'white', 'edgecolor':'gray'})\n",
1663 | "ax1.tick_params(which='both', direction='out')\n",
1664 | "ax1.grid()\n",
1665 | "\n",
1666 | "ax2.boxplot([var_group_1, var_group_2], flierprops={'marker':'D'})\n",
1667 | "ax2.set_title('Box-and-whisker plot for variable values in two groups')\n",
1668 | "ax2.set_xlabel('Group')\n",
1669 | "ax2.set_ylabel('Variable value')\n",
1670 | "ax2.yaxis.grid(True)\n",
1671 | "ax2.set_xticklabels(['Group 1', 'Group 2'])\n",
1672 | "ax2.tick_params(which='both', direction='out')\n",
1673 | "ax2.set_frame_on(False)\n",
1674 | "\n",
1675 | "fig.text(0.5, -0.05, 'Experiment 1', ha='center', fontsize=12);"
1676 | ]
1677 | },
1678 | {
1679 | "cell_type": "markdown",
1680 | "id": "aeb6c410",
1681 | "metadata": {},
1682 | "source": [
1683 | "In line $9$ above where we have `ax1.text` we set the _x_ and _y_ coordinates according to the axes tick values of the plot. If we use the keyword argument and value `transform=ax1.transAxes` the coordinates will be detached from the tick values. Now $0,0$ will be the left bottom corner and $1,1$ will be the top right corner."
1684 | ]
1685 | },
1686 | {
1687 | "cell_type": "markdown",
1688 | "id": "46667bf4",
1689 | "metadata": {},
1690 | "source": [
1691 | "The gridspec module allows even more flexibility when it comes to plots. Below, we incorporate three of our previous plots in two rows. The top row spans a single plot and the bottom row has plots in two columns."
1692 | ]
1693 | },
1694 | {
1695 | "cell_type": "code",
1696 | "execution_count": null,
1697 | "id": "95a2f3c5",
1698 | "metadata": {},
1699 | "outputs": [],
1700 | "source": [
1701 | "fig = plt.figure(tight_layout=True, figsize=(16, 9))\n",
1702 | "gs = gridspec.GridSpec(2, 2)\n",
1703 | "\n",
1704 | "ax1 = fig.add_subplot(gs[0, :])\n",
1705 | "ax1.plot(xvals, yvals_1)\n",
1706 | "ax1.set_title('Basic plot')\n",
1707 | "\n",
1708 | "ax2 = fig.add_subplot(gs[1, 0])\n",
1709 | "ax2.plot(xvals, yvals_1, marker_type, color=color_1, lw=lw, ms=ms, label=label_1)\n",
1710 | "ax2.plot(xvals, yvals_2, marker_type, color=color_2, lw=lw, ms=ms, label=label_2)\n",
1711 | "ax2.legend(loc='upper right', fontsize=8)\n",
1712 | "ax2.set_xlabel(r'Time $\\left[ \\mu s \\right]$')\n",
1713 | "ax2.set_ylabel('Position [cm]')\n",
1714 | "ax2.set_title('Position over time')\n",
1715 | "ax2.tick_params(which='both', direction='in')\n",
1716 | "ax2.grid(True)\n",
1717 | "\n",
1718 | "ax3 = fig.add_subplot(gs[1, 1])\n",
1719 | "ax3.plot(xvals, yvals_1, 'o', color=color_1, ms=ms, label=label_1)\n",
1720 | "ax3.plot(xvals, yvals_2, 'o', color=color_2, ms=ms, label=label_2)\n",
1721 | "ax3.plot(xvals_model, yvals_model, '-', label='Model')\n",
1722 | "ax3.legend(loc='upper right', fontsize=8, ncol=3)\n",
1723 | "ax3.set_xlabel(r'Time $\\left[ \\mu s \\right]$')\n",
1724 | "ax3.set_ylim(top=1.5)\n",
1725 | "ax3.set_title('Position over time and model')\n",
1726 | "ax3.tick_params(which='both', direction='in')\n",
1727 | "ax3.grid(True);"
1728 | ]
1729 | },
1730 | {
1731 | "cell_type": "markdown",
1732 | "id": "b9f026f1",
1733 | "metadata": {},
1734 | "source": [
1735 | "You will have noticed that not all functions have the same names when comparing the plotting and the object-oriented formats. Below is a list of some of the differences.\n",
1736 | "\n",
1737 | "- `xlabel` $\\to$ `set_xlabel`\n",
1738 | "- `ylabel` $\\to$ `set_ylabel`\n",
1739 | "- `xlim` $\\to$ `set_xlim`\n",
1740 | "- `ylim` $\\to$ `set_ylim`\n",
1741 | "- `title` $\\to$ `set_title`"
1742 | ]
1743 | },
1744 | {
1745 | "cell_type": "markdown",
1746 | "id": "cc3d76bb",
1747 | "metadata": {},
1748 | "source": [
1749 | "There is a simple `set` function that can take many settings arguments."
1750 | ]
1751 | },
1752 | {
1753 | "cell_type": "code",
1754 | "execution_count": null,
1755 | "id": "4159fc5d",
1756 | "metadata": {},
1757 | "outputs": [],
1758 | "source": [
1759 | "fig, ax = plt.subplots()\n",
1760 | "ax.plot(xvals, yvals_1)\n",
1761 | "ax.set(xlim=(0, 13), xlabel=r'Time $\\left[ \\mu s \\right]$', ylabel=r'Position $\\left[ cm \\right]$',\n",
1762 | " title='Position over time');"
1763 | ]
1764 | },
1765 | {
1766 | "cell_type": "markdown",
1767 | "id": "fbf82d7c",
1768 | "metadata": {},
1769 | "source": [
1770 | "## PLOTS FOR FUNCTIONS AND VECTORS"
1771 | ]
1772 | },
1773 | {
1774 | "cell_type": "markdown",
1775 | "id": "b8574a07",
1776 | "metadata": {},
1777 | "source": [
1778 | "### CONTOUR PLOTS"
1779 | ]
1780 | },
1781 | {
1782 | "cell_type": "markdown",
1783 | "id": "efc2b331",
1784 | "metadata": {},
1785 | "source": [
1786 | "There are two types of contour plots. The `contourf` function creates filled contour plots and the `contour` function creates contour lines only (without the colour fill)."
1787 | ]
1788 | },
1789 | {
1790 | "cell_type": "markdown",
1791 | "id": "a5c6173d",
1792 | "metadata": {},
1793 | "source": [
1794 | "Below, we consider the function $f \\left( x,y \\right) = x^{2} + y^{2}$."
1795 | ]
1796 | },
1797 | {
1798 | "cell_type": "markdown",
1799 | "id": "42473c20",
1800 | "metadata": {},
1801 | "source": [
1802 | "We have to use numpy to set up a grid of _x_ and _y_ coordinates. The `linspace` function can generate an array of values that we can use for both axes. We calculate coordinate values for each point on the grid and assign the function to it."
1803 | ]
1804 | },
1805 | {
1806 | "cell_type": "code",
1807 | "execution_count": null,
1808 | "id": "9938aaaa",
1809 | "metadata": {},
1810 | "outputs": [],
1811 | "source": [
1812 | "_ = np.linspace(-1, 1, 200)\n",
1813 | "X, Y = np.meshgrid(_, _)\n",
1814 | "f = X**2 + Y**2"
1815 | ]
1816 | },
1817 | {
1818 | "cell_type": "markdown",
1819 | "id": "feebd3e7",
1820 | "metadata": {},
1821 | "source": [
1822 | "Now, we create a filled contour plot."
1823 | ]
1824 | },
1825 | {
1826 | "cell_type": "code",
1827 | "execution_count": null,
1828 | "id": "90c402ce",
1829 | "metadata": {},
1830 | "outputs": [],
1831 | "source": [
1832 | "plt.contourf(X, Y, f, levels=60, cmap='inferno')\n",
1833 | "plt.colorbar(label='Value of $f(x,y)$')\n",
1834 | "plt.title('Filled contour plot');"
1835 | ]
1836 | },
1837 | {
1838 | "cell_type": "markdown",
1839 | "id": "6d741d73",
1840 | "metadata": {},
1841 | "source": [
1842 | "We can add the `vmin` or the `vmax` arguments to the `contourf` function. Either of these can be used to set the limit beyond which the colour is constant. This is helpful for infinities."
1843 | ]
1844 | },
1845 | {
1846 | "cell_type": "markdown",
1847 | "id": "59bfd416",
1848 | "metadata": {},
1849 | "source": [
1850 | "Instead of a filled contour plot, we an view only the contour lines. Below, we add values to the contours themselves instead of adding a color bar."
1851 | ]
1852 | },
1853 | {
1854 | "cell_type": "code",
1855 | "execution_count": null,
1856 | "id": "a3260423",
1857 | "metadata": {},
1858 | "outputs": [],
1859 | "source": [
1860 | "C = plt.contour(X, Y, f, levels=10, cmap='plasma')\n",
1861 | "plt.clabel(C, fontsize=12)\n",
1862 | "plt.title('Contour plot with values');"
1863 | ]
1864 | },
1865 | {
1866 | "cell_type": "markdown",
1867 | "id": "2dd76225",
1868 | "metadata": {},
1869 | "source": [
1870 | "### QUIVER PLOTS"
1871 | ]
1872 | },
1873 | {
1874 | "cell_type": "markdown",
1875 | "id": "3e2ebfd0",
1876 | "metadata": {},
1877 | "source": [
1878 | "Quiver plots generate vectors. A vector takes an initial _x_ and _y_ coordinate and then a magnitude for each of the two coordinate directions."
1879 | ]
1880 | },
1881 | {
1882 | "cell_type": "code",
1883 | "execution_count": null,
1884 | "id": "84b807fc",
1885 | "metadata": {},
1886 | "outputs": [],
1887 | "source": [
1888 | "# Coordinate positions for tail of vector\n",
1889 | "x_pos = 0\n",
1890 | "y_pos = 0\n",
1891 | "\n",
1892 | "# Magnitude for x and y directions (not to scale)\n",
1893 | "x_direct = 2\n",
1894 | "y_direct = 1"
1895 | ]
1896 | },
1897 | {
1898 | "cell_type": "markdown",
1899 | "id": "8286707d",
1900 | "metadata": {},
1901 | "source": [
1902 | "We now plot the vector and with the values above and set the _x_ axis and _y_ axis limits."
1903 | ]
1904 | },
1905 | {
1906 | "cell_type": "code",
1907 | "execution_count": null,
1908 | "id": "7f44e249",
1909 | "metadata": {},
1910 | "outputs": [],
1911 | "source": [
1912 | "fig, ax = plt.subplots(figsize=(5, 5))\n",
1913 | "ax.quiver(x_pos, y_pos, x_direct, y_direct, scale=5) # Scale the magnitude\n",
1914 | "ax.axis([-0.01,0.03, -0.01, 0.03]) # Axes limits\n",
1915 | "ax.set_aspect('equal'); # Set equal aspect ratio for the axes"
1916 | ]
1917 | },
1918 | {
1919 | "cell_type": "markdown",
1920 | "id": "e3b6d280",
1921 | "metadata": {},
1922 | "source": [
1923 | "We do not create individual vectors for a vector field. Below we see a function, $\\mathbf{F}$, representing a vector field."
1924 | ]
1925 | },
1926 | {
1927 | "cell_type": "markdown",
1928 | "id": "a9507b07",
1929 | "metadata": {},
1930 | "source": [
1931 | "$$\\mathbf{F} = \\frac{x}{5} \\hat{i} - \\frac{y}{5} \\hat{j} \\\\ \\mathbf{F}\\left( x,y \\right) = \\left( \\frac{x}{5}, -\\frac{y}{5} \\right)$$"
1932 | ]
1933 | },
1934 | {
1935 | "cell_type": "markdown",
1936 | "id": "927120d1",
1937 | "metadata": {},
1938 | "source": [
1939 | "We use the `meshgrid` function again. The variables `u` and `v` hold the components of $\\mathbf{F}$."
1940 | ]
1941 | },
1942 | {
1943 | "cell_type": "code",
1944 | "execution_count": null,
1945 | "id": "7e4131a1",
1946 | "metadata": {},
1947 | "outputs": [],
1948 | "source": [
1949 | "_ = np.arange(0, 2.2, 0.2) # Starting points"
1950 | ]
1951 | },
1952 | {
1953 | "cell_type": "code",
1954 | "execution_count": null,
1955 | "id": "195dc170",
1956 | "metadata": {},
1957 | "outputs": [],
1958 | "source": [
1959 | "X, Y = np.meshgrid(_, _) # Mesh of x and y coordinates\n",
1960 | "u = X/5 # x direction\n",
1961 | "v = -Y/5 # y direction"
1962 | ]
1963 | },
1964 | {
1965 | "cell_type": "markdown",
1966 | "id": "50cdec75",
1967 | "metadata": {},
1968 | "source": [
1969 | "The `quiver` function creates multiple vectors demonstrating the vector field."
1970 | ]
1971 | },
1972 | {
1973 | "cell_type": "code",
1974 | "execution_count": null,
1975 | "id": "e3689e0e",
1976 | "metadata": {},
1977 | "outputs": [],
1978 | "source": [
1979 | "fig, ax = plt.subplots(figsize=(7,7))\n",
1980 | "ax.quiver(X, Y, u, v)\n",
1981 | "ax.axis([-0.2, 2.3, -0.2, 2.1])\n",
1982 | "ax.set_aspect('equal')\n",
1983 | "ax.set_title('Vector field $\\mathbf{F}$')\n",
1984 | "ax.text(0, 0,\n",
1985 | " r'$\\vec{F} = \\frac{x}{5} \\hat{i} - \\frac{y}{5} \\hat{j}$',\n",
1986 | " fontdict={'fontsize':14},\n",
1987 | " bbox={'facecolor':'white', 'edgecolor':'gray'});"
1988 | ]
1989 | },
1990 | {
1991 | "cell_type": "markdown",
1992 | "id": "cb9c422e",
1993 | "metadata": {},
1994 | "source": [
1995 | "We can also use the `quiver` function for a gradient field. We start with a multivariable function shown below."
1996 | ]
1997 | },
1998 | {
1999 | "cell_type": "markdown",
2000 | "id": "588d3d40",
2001 | "metadata": {},
2002 | "source": [
2003 | "$$f \\left( x,y \\right) = x^{2} - y^{2}$$"
2004 | ]
2005 | },
2006 | {
2007 | "cell_type": "markdown",
2008 | "id": "57d5c014",
2009 | "metadata": {},
2010 | "source": [
2011 | "We are interested in the gradient."
2012 | ]
2013 | },
2014 | {
2015 | "cell_type": "markdown",
2016 | "id": "308b7b25",
2017 | "metadata": {},
2018 | "source": [
2019 | "$$\\nabla f = \\left( \\frac{\\partial{f}}{\\partial{x}} , \\frac{\\partial{f}}{\\partial{y}} \\right) = \\left( f_{x} , f_{y} \\right)$$"
2020 | ]
2021 | },
2022 | {
2023 | "cell_type": "markdown",
2024 | "id": "a3eca388",
2025 | "metadata": {},
2026 | "source": [
2027 | "Sympy can be used to calculate the partial derivatives analytically. We set the variables `x` and `y` to be mathematical symbols."
2028 | ]
2029 | },
2030 | {
2031 | "cell_type": "code",
2032 | "execution_count": null,
2033 | "id": "73eebac6",
2034 | "metadata": {},
2035 | "outputs": [],
2036 | "source": [
2037 | "x, y = sym.symbols('x y')"
2038 | ]
2039 | },
2040 | {
2041 | "cell_type": "markdown",
2042 | "id": "46c9e41c",
2043 | "metadata": {},
2044 | "source": [
2045 | "The variable `f` holds our symbolic function."
2046 | ]
2047 | },
2048 | {
2049 | "cell_type": "code",
2050 | "execution_count": null,
2051 | "id": "0807a4bd",
2052 | "metadata": {},
2053 | "outputs": [],
2054 | "source": [
2055 | "f = x**2 - y**2\n",
2056 | "f"
2057 | ]
2058 | },
2059 | {
2060 | "cell_type": "markdown",
2061 | "id": "f3958f32",
2062 | "metadata": {},
2063 | "source": [
2064 | "We consider the partial derivative of $f$ with respect to $x$, written as $f_{x}$ using the `diff` method."
2065 | ]
2066 | },
2067 | {
2068 | "cell_type": "code",
2069 | "execution_count": null,
2070 | "id": "f96af182",
2071 | "metadata": {},
2072 | "outputs": [],
2073 | "source": [
2074 | "f.diff(x) # First derivative of f with respect to x"
2075 | ]
2076 | },
2077 | {
2078 | "cell_type": "markdown",
2079 | "id": "0940e034",
2080 | "metadata": {},
2081 | "source": [
2082 | "The partial derivative of $f$ with respect to $y$ is written as $f_{y}$."
2083 | ]
2084 | },
2085 | {
2086 | "cell_type": "code",
2087 | "execution_count": null,
2088 | "id": "adf1fccc",
2089 | "metadata": {},
2090 | "outputs": [],
2091 | "source": [
2092 | "f.diff(y) # First derivative of f with respect to x"
2093 | ]
2094 | },
2095 | {
2096 | "cell_type": "markdown",
2097 | "id": "0873e217",
2098 | "metadata": {},
2099 | "source": [
2100 | "We consider the point $p \\left( 2,2 \\right)$. Below, we calculate $f_{x} \\left( p \\right)$. The `subs` method allows us to substitute values for our variables."
2101 | ]
2102 | },
2103 | {
2104 | "cell_type": "code",
2105 | "execution_count": null,
2106 | "id": "5b4530a0",
2107 | "metadata": {},
2108 | "outputs": [],
2109 | "source": [
2110 | "f.diff(x).subs(x, 2).subs(y, 2)"
2111 | ]
2112 | },
2113 | {
2114 | "cell_type": "markdown",
2115 | "id": "b876d20b",
2116 | "metadata": {},
2117 | "source": [
2118 | "We also calculate $f_{y} \\left( p \\right)$."
2119 | ]
2120 | },
2121 | {
2122 | "cell_type": "code",
2123 | "execution_count": null,
2124 | "id": "5061a4bf",
2125 | "metadata": {},
2126 | "outputs": [],
2127 | "source": [
2128 | "f.diff(y).subs(x, 2).subs(y, 2)"
2129 | ]
2130 | },
2131 | {
2132 | "cell_type": "markdown",
2133 | "id": "d3c6252b",
2134 | "metadata": {},
2135 | "source": [
2136 | "We now have that $\\nabla f \\left( p \\right) = \\left( 4,-4 \\right)$. We can view this point on our gradient plot to make sure that our plot is accurate."
2137 | ]
2138 | },
2139 | {
2140 | "cell_type": "code",
2141 | "execution_count": null,
2142 | "id": "feabf955",
2143 | "metadata": {},
2144 | "outputs": [],
2145 | "source": [
2146 | "_ = np.linspace(-2, 2, 20)\n",
2147 | "X, Y = np.meshgrid(_, _)"
2148 | ]
2149 | },
2150 | {
2151 | "cell_type": "code",
2152 | "execution_count": null,
2153 | "id": "6e96d356",
2154 | "metadata": {},
2155 | "outputs": [],
2156 | "source": [
2157 | "u = 2 * X # Partial drivative of f with respect to x\n",
2158 | "v = -2 * Y # Partial drivative of f with respect to y"
2159 | ]
2160 | },
2161 | {
2162 | "cell_type": "markdown",
2163 | "id": "3c0f0bd9",
2164 | "metadata": {},
2165 | "source": [
2166 | "We now create the gradient plot. This time we assign the plot to a variable and then pass that to the `quiverkey` function. The positions are relative to the plot, with `(0, 0)` in the bottom left. We state a length and add a title to the position `E` for east of the key."
2167 | ]
2168 | },
2169 | {
2170 | "cell_type": "code",
2171 | "execution_count": null,
2172 | "id": "478f2847",
2173 | "metadata": {},
2174 | "outputs": [],
2175 | "source": [
2176 | "fig, ax = plt.subplots(figsize=(7, 7))\n",
2177 | "Q = ax.quiver(X, Y, u, v)\n",
2178 | "ax.quiverkey(Q, 0.85, 1.02, 10, '[10 units]', labelpos='E')\n",
2179 | "ax.set_aspect('equal')\n",
2180 | "ax.set_title('Gradient plot')\n",
2181 | "ax.set_xlabel('$x$ axis')\n",
2182 | "ax.set_ylabel('$y$ axis');"
2183 | ]
2184 | },
2185 | {
2186 | "cell_type": "markdown",
2187 | "id": "c44ef118",
2188 | "metadata": {},
2189 | "source": [
2190 | "The point $p$ does indeed show a slope of $\\left( 4, -4 \\right)$."
2191 | ]
2192 | },
2193 | {
2194 | "cell_type": "markdown",
2195 | "id": "9256bc53-207d-4203-9b14-7db3c3add4d1",
2196 | "metadata": {},
2197 | "source": [
2198 | "Instead of using sympy for analytical partial differentiation, we can use the `gradient` function from numpy. We set the step size the same same as when we generated the meshgrid object. Since both are the same, we only set a single value. Note that the function returns the derivative with respect to $y$ first, hence `v, u`."
2199 | ]
2200 | },
2201 | {
2202 | "cell_type": "code",
2203 | "execution_count": null,
2204 | "id": "ee98606f-6080-4e4a-85fb-83fd1b77403d",
2205 | "metadata": {},
2206 | "outputs": [],
2207 | "source": [
2208 | "my_f = X**2 - Y**2\n",
2209 | "v, u = np.gradient(my_f, 0.1)"
2210 | ]
2211 | },
2212 | {
2213 | "cell_type": "code",
2214 | "execution_count": null,
2215 | "id": "fffca5a8-dcfb-4532-94eb-f797da20b67d",
2216 | "metadata": {},
2217 | "outputs": [],
2218 | "source": [
2219 | "fig, ax = plt.subplots(figsize=(7, 7))\n",
2220 | "Q = ax.quiver(X, Y, u, v)\n",
2221 | "ax.quiverkey(Q, 0.85, 1.02, 10, '[10 units]', labelpos='E')\n",
2222 | "ax.set_aspect('equal')\n",
2223 | "ax.set_title('Gradient plot')\n",
2224 | "ax.set_xlabel('$x$ axis')\n",
2225 | "ax.set_ylabel('$y$ axis');"
2226 | ]
2227 | },
2228 | {
2229 | "cell_type": "markdown",
2230 | "id": "8e608549-e72f-46a3-bfc2-6416f9ac1000",
2231 | "metadata": {},
2232 | "source": [
2233 | "Now we add the gradient vectors to a contour plot."
2234 | ]
2235 | },
2236 | {
2237 | "cell_type": "code",
2238 | "execution_count": null,
2239 | "id": "ed28284f-679a-4b45-a47f-babc8e0129c4",
2240 | "metadata": {},
2241 | "outputs": [],
2242 | "source": [
2243 | "fig, ax = plt.subplots(figsize=(7, 7))\n",
2244 | "Q = ax.quiver(X, Y, u, v)\n",
2245 | "ax.quiverkey(Q, 0.85, 1.02, 10, '[10 units]', labelpos='E')\n",
2246 | "C = ax.contour(X, Y, my_f, levels=10, cmap='plasma')\n",
2247 | "ax.clabel(C, inline=True, fontsize=10)\n",
2248 | "ax.set_aspect('equal')\n",
2249 | "ax.set_title('Gradient plot')\n",
2250 | "ax.set_xlabel('$x$ axis')\n",
2251 | "ax.set_ylabel('$y$ axis');"
2252 | ]
2253 | },
2254 | {
2255 | "cell_type": "markdown",
2256 | "id": "a8502977-7059-4500-b08f-33ad2fd365b6",
2257 | "metadata": {},
2258 | "source": [
2259 | "The gradient is pointing uphill and the vectors are perpendicular to the contours."
2260 | ]
2261 | },
2262 | {
2263 | "cell_type": "markdown",
2264 | "id": "7c4a5df9",
2265 | "metadata": {},
2266 | "source": [
2267 | "### STREAM PLOTS"
2268 | ]
2269 | },
2270 | {
2271 | "cell_type": "markdown",
2272 | "id": "4e7215b2",
2273 | "metadata": {},
2274 | "source": [
2275 | "Stream plots can likewise help us visualise a vector field. Below, we use sub plots to create three plots. The first is a basic stream plot of our previous function $f$. The magnitude of the vector at each coordinate point is demonstrated using colour in the second plot. The last plot uses variable line width to demonstrate the magnitude of the vector as each coordinate. We calculate the magnitude in the usual way for $\\mathbb{R}^{2}$ using $\\sqrt{x^{2} + y^{2}}$."
2276 | ]
2277 | },
2278 | {
2279 | "cell_type": "code",
2280 | "execution_count": null,
2281 | "id": "a389ced7",
2282 | "metadata": {},
2283 | "outputs": [],
2284 | "source": [
2285 | "fig, axes = plt.subplots(1, 3, figsize=(12, 4), sharey=True) # Set single y axis label\n",
2286 | "\n",
2287 | "ax = axes[0]\n",
2288 | "ax.streamplot(X, Y, u, v)\n",
2289 | "ax.set_title('Basic stream plot')\n",
2290 | "\n",
2291 | "ax = axes[1]\n",
2292 | "magn = np.sqrt(u**2 + v**2) # Magnitude of each vector\n",
2293 | "ax.streamplot(X, Y, u, v, color=magn, cmap='magma')\n",
2294 | "ax.set_title('Colour stream plot')\n",
2295 | "\n",
2296 | "ax = axes[2]\n",
2297 | "lw = 4 * magn / np.max(magn)\n",
2298 | "ax.streamplot(X, Y, u, v, linewidth=lw)\n",
2299 | "ax.set_title('Velocity stream plot');"
2300 | ]
2301 | },
2302 | {
2303 | "cell_type": "markdown",
2304 | "id": "e29fa5a6-3020-4736-a581-2a54c46746f4",
2305 | "metadata": {},
2306 | "source": [
2307 | "## SAVING PLOTS"
2308 | ]
2309 | },
2310 | {
2311 | "cell_type": "markdown",
2312 | "id": "8141b187-24d3-409c-ad31-4edda6167113",
2313 | "metadata": {},
2314 | "source": [
2315 | "A plot can be saved in may format using the `savefig` function. The os package allows us to interact with the foldder structure on our computer. We can get the working directory in which our notebook is saved using the `getcwd` function in case we need to use it when saving the plot."
2316 | ]
2317 | },
2318 | {
2319 | "cell_type": "code",
2320 | "execution_count": null,
2321 | "id": "7043c3b2-3303-4352-96e4-8fecf2ac06de",
2322 | "metadata": {},
2323 | "outputs": [],
2324 | "source": [
2325 | "os.getcwd()"
2326 | ]
2327 | },
2328 | {
2329 | "cell_type": "markdown",
2330 | "id": "0aaf5e8b-decd-4292-86bc-6603b5188c24",
2331 | "metadata": {},
2332 | "source": [
2333 | "In this case, I have a subfolder named `images` in the current working folder. This is where the plot will be saved."
2334 | ]
2335 | },
2336 | {
2337 | "cell_type": "code",
2338 | "execution_count": null,
2339 | "id": "3d4e0cef-dac8-43fe-8123-71507d2e4d0d",
2340 | "metadata": {},
2341 | "outputs": [],
2342 | "source": [
2343 | "fig.savefig('images/streamplots.png')"
2344 | ]
2345 | },
2346 | {
2347 | "cell_type": "markdown",
2348 | "id": "1b29c06a-b11f-4483-856b-a1f58a878d7b",
2349 | "metadata": {},
2350 | "source": [
2351 | "We can also specify the folder structure directly."
2352 | ]
2353 | },
2354 | {
2355 | "cell_type": "code",
2356 | "execution_count": null,
2357 | "id": "9c216f3e-f310-4479-a584-4c2517900959",
2358 | "metadata": {},
2359 | "outputs": [],
2360 | "source": [
2361 | "fig.savefig('/Users/juan/Desktop/streamplots.png')"
2362 | ]
2363 | },
2364 | {
2365 | "cell_type": "markdown",
2366 | "id": "7b5b7c89",
2367 | "metadata": {},
2368 | "source": [
2369 | "## ONLINE TEXTBOOK"
2370 | ]
2371 | },
2372 | {
2373 | "cell_type": "markdown",
2374 | "id": "0b6f3688",
2375 | "metadata": {},
2376 | "source": [
2377 | "https://jakevdp.github.io/PythonDataScienceHandbook/04.00-introduction-to-matplotlib.html"
2378 | ]
2379 | },
2380 | {
2381 | "cell_type": "code",
2382 | "execution_count": null,
2383 | "id": "42a50732",
2384 | "metadata": {},
2385 | "outputs": [],
2386 | "source": []
2387 | }
2388 | ],
2389 | "metadata": {
2390 | "@webio": {
2391 | "lastCommId": null,
2392 | "lastKernelId": null
2393 | },
2394 | "kernelspec": {
2395 | "display_name": "Python 3 (ipykernel)",
2396 | "language": "python",
2397 | "name": "python3"
2398 | },
2399 | "language_info": {
2400 | "codemirror_mode": {
2401 | "name": "ipython",
2402 | "version": 3
2403 | },
2404 | "file_extension": ".py",
2405 | "mimetype": "text/x-python",
2406 | "name": "python",
2407 | "nbconvert_exporter": "python",
2408 | "pygments_lexer": "ipython3",
2409 | "version": "3.9.7"
2410 | },
2411 | "toc-autonumbering": false,
2412 | "toc-showcode": false,
2413 | "toc-showmarkdowntxt": false,
2414 | "toc-showtags": false
2415 | },
2416 | "nbformat": 4,
2417 | "nbformat_minor": 5
2418 | }
2419 |
--------------------------------------------------------------------------------