The purpose of this notebook is to introduce a few useful Python and Markdown topics:
- assert statements
- list comprehension
- plt.bar()
- including a figure in a Markdown document
Topic 1: Assert Statements¶
Assert statements are like small little "fact-checkers" that you can insert into your code to make sure it is doing what you think it should do. For example, if a matrix should be 4x6 you can use an assert statement to check this; if the matrix is not the right size, and error will occur. Although your first thought may be "why would I want to make my code break with an error?!?!" it turns out this is a very useful debugging and testing tool (something we will cover in a later PA as well). The reason it is useful is that it causes your code to break for a very specific reason, so that you can identify problems and fix them efficiently and confidently. Sounds useful, right?! Luckily assert statements are also quite simple:
There are two cases of this statement that we will use:
Case 1:
assert <logical_argument>
Case 2:
assert <logical_argument>, 'my error message'
The best way to illustrate the use of this tool is by example. But first, some important information about the <logical_argument>
.
Background Information: Logical Statements¶
Another key aspect for using this tool effectively is to be aware of what a logical statement is and how they can be specified in Python.
A logical statement is simply a statement that can be evaluated as true or false. This is sometimes referred to as a binary, or, in computing in particular, a Boolean. Here are some examples of logical statements, formulated as questions, that have a binary/Boolean results:
- Is it raining?
- Is my computer on?
- Were you in class yesterday?
Here are some examples of questions that cannot be evaluated as a logical statement:
- How long will it rain for?
- When will my computer battery reach 0%?
- How many lectures have you skipped this week?
Each of these examples
Python expression that checks whether or not something is
Name of Logical Statement | Python Syntax |
---|---|
Equals | a == b |
Not Equals | a != b |
Less than | a < b |
Less than or equal to | a <= b |
Greater than | a > b |
Greater than or equal to | a >= b |
Task 1.1:
Execute the cell below and observe the error. Note that it very specifically is an AssertionError
.
See if you can fix the code to prevent the error from occurring.
# x = 0
# assert x == 1
# SOLUTION
x = 1
assert x == 1
Task 1.2: Now try fixing the cell below by 1) adding your own error message (see Case 2 above), and 2) forcing the assert statement to fail. Confirm that you can see error message in the error report.
# y = 0
# assert y != 1, YOUR_MESSAGE_HERE
# SOLUTION
y = 0
assert y != 1, "y should not be 1 but it is"
Task 1.3: Explore assert statements by writing your own. Experiment with different data types (e.g., strings, floats, arrays, lists) and use different logical statements from the list above.
# YOUR_CODE_HERE
Summary of Asserts¶
You are now an expert on assert
statements. Remember these key points:
- an assert statement answers a true/false question; it will will cause an error if false and do nothing if true
- the syntax is
assert <logical_statement>
- you can easily customize the error message with the syntax
assert <logical_argument>, 'my error message'
- the
<logical_statement>
must be a Boolean result
Part 2: List Comprehension¶
List and dictionary comprehensions are elegant constructs in Python that allow you to manipulate Python objects in a very compact and efficient way. You can think of them as writing a for
loop in a single line. Here is the syntax:
[<DO_SOMETHING> for <ITEM> in <ITERABLE>]
Note the following key elements:
- the list comprehension is enclosed in list brackets:
[ ... ]
<DO_SOMETHING>
is any Python expression (for example,print()
orx**2
)<ITEM>
is generally a (temporary) variable that is used in the expression<DO_SOMETHING>
and represents all of the "items" in<ITERABLE>
<ITERABLE>
is an iterable object. Don't worry about what this is, exactly (we will study it more later).
For our purposes, it is enough to consider the following iterables:
- lists (e.g.,
[1, 2, 3]
) - ndarrays (Numpy)
range()
- dictionaries
As with assert statements, the best way to illustrate this is by example.
Task 2.1: Read the cell below then execute it to see an example of a "normal" for loop that creates a list of squares from 0 to 9.
squares = []
for i in range(10):
squares.append(i**2)
print(squares)
Task 2.2: Read the cell below then execute it to see an example of a list comprehension that does the same thing. It's much more compact, right?!
squares = [i ** 2 for i in range(10)]
print(squares)
Task 2.3: Read the cell below then execute it to see an example of a "normal" for loop that creates a dictionary that maps numbers to their squares.
squares_dict = {}
for i in range(10):
squares_dict[i] = i ** 2
print(squares_dict)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
Task 2.4: Read the cell below then execute it to see an example of a list comprehension that does the same thing. It's much more compact, right?!
squares_dict = {i: i ** 2 for i in range(10)}
print(squares_dict)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
Task 2.5: Now try it yourself! Create a new list from from the one defined below that has values that are half that of the original.
Note the use of asserts to make sure your answer is correct!
# my_list = [1, 2, 3, 4, 5]
# new_list = []
# print(new_list)
# assert new_list == [0.5, 1.0, 1.5, 2.0, 2.5], "new_list values are not half of my_list!"
# SOLUTION
my_list = [1, 2, 3, 4, 5]
new_list = [x/2 for x in my_list]
assert new_list == [0.5, 1.0, 1.5, 2.0, 2.5], "new_list values are not half of my_list!"
Summary¶
There are several reasons why you should use list comprehension, hopefully you can recognize them from the examples and tasks above:
- Readability: Comprehensions often turn multiple lines of code into a single, readable line, making the code easier to understand at a glance.
- Efficiency: They are generally faster because they are optimized in the Python interpreter.
- Simplicity: Reduces the need for loop control variables and indexing, making the code simpler.
Sometimes the hardest thing to remember is the order and syntax. The following list comprehension uses obvious variable names to illustrate it (assuming you have an object with "stuff" in it, for example, objects = [1, 2, 3]
); if you can remember this, you can remember list comprehensions!
[print(object) for object in objects]
The plt.bar()
Method¶
At this point we have created many figures in MUDE assignments using a method from the Matplotlib plotting library: plt.plot()
. This is our "bread and butter" plot because it is so easy to plot lines, data, scatter plots, etc. However, there are many more types of plots available in Matplotlib. Today we will try bar()
, which, as you can imagine, creates bar plots.
First take a look at the documentation and see if you can figure out how it works.
Task 3.1: Run the cell below and read the docstring for the method. Can you determine the minimum type of inputs required, and what they will do?
import numpy as np
import matplotlib.pyplot as plt
help(plt.bar)
Help on function bar in module matplotlib.pyplot: bar(x: 'float | ArrayLike', height: 'float | ArrayLike', width: 'float | ArrayLike' = 0.8, bottom: 'float | ArrayLike | None' = None, *, align: "Literal['center', 'edge']" = 'center', data=None, **kwargs) -> 'BarContainer' Make a bar plot. The bars are positioned at *x* with the given *align*\ment. Their dimensions are given by *height* and *width*. The vertical baseline is *bottom* (default 0). Many parameters can take either a single value applying to all bars or a sequence of values, one for each bar. Parameters ---------- x : float or array-like The x coordinates of the bars. See also *align* for the alignment of the bars to the coordinates. height : float or array-like The height(s) of the bars. Note that if *bottom* has units (e.g. datetime), *height* should be in units that are a difference from the value of *bottom* (e.g. timedelta). width : float or array-like, default: 0.8 The width(s) of the bars. Note that if *x* has units (e.g. datetime), then *width* should be in units that are a difference (e.g. timedelta) around the *x* values. bottom : float or array-like, default: 0 The y coordinate(s) of the bottom side(s) of the bars. Note that if *bottom* has units, then the y-axis will get a Locator and Formatter appropriate for the units (e.g. dates, or categorical). align : {'center', 'edge'}, default: 'center' Alignment of the bars to the *x* coordinates: - 'center': Center the base on the *x* positions. - 'edge': Align the left edges of the bars with the *x* positions. To align the bars on the right edge pass a negative *width* and ``align='edge'``. Returns ------- `.BarContainer` Container with all the bars and optionally errorbars. Other Parameters ---------------- color : :mpltype:`color` or list of :mpltype:`color`, optional The colors of the bar faces. edgecolor : :mpltype:`color` or list of :mpltype:`color`, optional The colors of the bar edges. linewidth : float or array-like, optional Width of the bar edge(s). If 0, don't draw edges. tick_label : str or list of str, optional The tick labels of the bars. Default: None (Use default numeric labels.) label : str or list of str, optional A single label is attached to the resulting `.BarContainer` as a label for the whole dataset. If a list is provided, it must be the same length as *x* and labels the individual bars. Repeated labels are not de-duplicated and will cause repeated label entries, so this is best used when bars also differ in style (e.g., by passing a list to *color*.) xerr, yerr : float or array-like of shape(N,) or shape(2, N), optional If not *None*, add horizontal / vertical errorbars to the bar tips. The values are +/- sizes relative to the data: - scalar: symmetric +/- values for all bars - shape(N,): symmetric +/- values for each bar - shape(2, N): Separate - and + values for each bar. First row contains the lower errors, the second row contains the upper errors. - *None*: No errorbar. (Default) See :doc:`/gallery/statistics/errorbar_features` for an example on the usage of *xerr* and *yerr*. ecolor : :mpltype:`color` or list of :mpltype:`color`, default: 'black' The line color of the errorbars. capsize : float, default: :rc:`errorbar.capsize` The length of the error bar caps in points. error_kw : dict, optional Dictionary of keyword arguments to be passed to the `~.Axes.errorbar` method. Values of *ecolor* or *capsize* defined here take precedence over the independent keyword arguments. log : bool, default: False If *True*, set the y-axis to be log scale. data : indexable object, optional If given, all parameters also accept a string ``s``, which is interpreted as ``data[s]`` (unless this raises an exception). **kwargs : `.Rectangle` properties Properties: agg_filter: a filter function, which takes a (m, n, 3) float array and a dpi value, and returns a (m, n, 3) array and two offsets from the bottom left corner of the image alpha: scalar or None angle: unknown animated: bool antialiased or aa: bool or None bounds: (left, bottom, width, height) capstyle: `.CapStyle` or {'butt', 'projecting', 'round'} clip_box: `~matplotlib.transforms.BboxBase` or None clip_on: bool clip_path: Patch or (Path, Transform) or None color: :mpltype:`color` edgecolor or ec: :mpltype:`color` or None facecolor or fc: :mpltype:`color` or None figure: `~matplotlib.figure.Figure` fill: bool gid: str hatch: {'/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'} height: unknown in_layout: bool joinstyle: `.JoinStyle` or {'miter', 'round', 'bevel'} label: object linestyle or ls: {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} linewidth or lw: float or None mouseover: bool path_effects: list of `.AbstractPathEffect` picker: None or bool or float or callable rasterized: bool sketch_params: (scale: float, length: float, randomness: float) snap: bool or None transform: `~matplotlib.transforms.Transform` url: str visible: bool width: unknown x: unknown xy: (float, float) y: unknown zorder: float See Also -------- barh : Plot a horizontal bar plot. Notes ----- .. note:: This is the :ref:`pyplot wrapper <pyplot_interface>` for `.axes.Axes.bar`. Stacked bars can be achieved by passing individual *bottom* values per bar. See :doc:`/gallery/lines_bars_and_markers/bar_stacked`.
Task 3.2: That's right, it plots a bar chart where the first argument is the x coordinate of the bar and the second argument is the height. Fill in the empty lists below to create a bar plot with any values you like.
# plt.bar([], [])
# SOLUTION
plt.bar([1, 2, 3, 4],[0.2, 0.5, 0.1, 0.6])
<BarContainer object of 4 artists>
Pretty easy, right? Let's try to do one more thing with this - suppose we don't like that the center of the bar is over the value we enter. It's easy to change this using a keyword argument; these are the input arguments to the function that have the equals sign (e.g., function(keyword_arg=<xxx>)
). These are optional arguments; they are generally not needed, but can be specified, along with a value, to change the default behavior of the function. For our purposes this week, we will want to change two keyword arguments:
width
align
Fortunately the help
function printed out the docstring for bar()
, which contains all the information you need to figure out what these keyword arguments do and how to use them.
Task 3.3: Set the keyword arguments below to make the bars fill up the entire space between each bar (no white space) and to force the left side of the bar to align with the value specified.
Note the addition of keyword argument edgecolor
to make it easier to see the edges of the bar.
# plt.bar([1, 2, 3, 4],[0.2, 0.5, 0.1, 0.6],
# width=YOUR_CODE_HERE,
# align=YOUR_CODE_HERE,
# edgecolor='black')
# SOLUTION
plt.bar([1, 2, 3, 4],[0.2, 0.5, 0.1, 0.6],
width=1,
align='edge',
edgecolor='black')
<BarContainer object of 4 artists>
Task 3.4: Now set the keyword arguments below to make the bars fill up the entire space between each bar (no white space) and to force the right side of the bar to align with the value specified.
# plt.bar([1, 2, 3, 4],[0.2, 0.5, 0.1, 0.6],
# width=YOUR_CODE_HERE,
# align=YOUR_CODE_HERE,
# edgecolor='black')
# SOLUTION
plt.bar([1, 2, 3, 4],[0.2, 0.5, 0.1, 0.6],
width=-1,
align='edge',
edgecolor='black')
<BarContainer object of 4 artists>
Topic 4: Exporting a Figure and Including it in a Markdown Notebook¶
Now that we can create a wider variety of figures, we should be able to include them in our Reports for communicating the results of our analyses. Here we show you a very simple way to save a figure generated in your notebook, then use Markdown to visualize the figure. Once a figure is made it is easy, use this syntax:

The label is simply a name that will appear in case the figure fails to load. It can also be read by a website-reading app (for example, then a blind person could understand what the content of the figure may be). Here is an example for what this could look like in practice:

This is very easy, so once again we lead by example! However, first a couple notes about filepaths.
File Paths¶
A file path is like an address to a file. There are generally two types, absolute and relative. Most of our activities focus on working in a working directory, so we will focus almost entirely on relative paths. The general format is like this:
./subdirectory_1/subdir_2/filename.ext
where:
- the dot
.
indicates one should use the current directory of the file (or the CLI) as the current location (the.
is like saying " start here") - forward slashes
/
separate subdirectories - the last two words are the file name and extension. For example, common image extensions are
.jpg
,.png
or.svg
- in this example, the image file is stored inside a folder called
subdir_2
which is inside a folder calledsubdirectory_1
which is in our working directory.
As a general rule, always use forward slashes whenever possible. Although backward slashes are the default and must be used at times on Windows, they don't work on Mac or Linux systems. This causes problems when sharing code with others running these systems (or when we check your assignments on GitHub!!!). Remember that we try to do things in a way that allows easy collaboration: using approaches that are agnostic of the operating system (i.e., works on all platforms). This is hard to guarantee in practice, but consistently using forward slashes will get us close!
Try it out yourself¶
We will try this out, but first we need to create a figure! The code below is a quick way of saving a Matplotlib figure as an svg file.
Task 4.1: Run the cell below to create the svg file. Confirm that it is created successfully by examining your working directory.
fig, ax = plt.subplots(1,1,figsize = (8,6))
plt.bar([1, 2, 3, 4],[0.2, 0.5, 0.1, 0.6])
fig.savefig('my_figure.svg')
Task 4.2: Now that the image is created, use the Markdown cell below to display it in this notebook.
Use this Markdown cell to try visualizing the figure we just saved using Markdown!
SOLUTION

Task 4.3:
Test your understanding of relative file paths by moving the csv file to a subdirectory with name figure
and getting the following Markdown cell to display the figure.

** MAKE SURE A FIGURE APPEARS HERE BY MODIFYING THIS MARKDOWN CELL**
Task 4.4:
Now add the figure to an actual markdown while, specifically myfigure.md
. Then visualize to confirm you did it properly (remember to use `CTRL+SHIFT+V` in VSC, the [Markdown All-in-one extension](https://mude.citg.tudelft.nl/2024/book/external/learn-programming/book/install/ide/vsc/extensions.html#markdown-all-in-one))