PA 1.5: A Few Useful Tricks¶

No description has been provided for this image No description has been provided for this image

CEGM1000 MUDE: Week 1.5. Due: before Friday, Oct 4, 2024.

The purpose of this notebook is to introduce a few useful Python and Markdown topics:

  1. assert statements
  2. list comprehension
  3. plt.bar()
  4. 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.

In [7]:
# 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.

In [4]:
# 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.

In [ ]:
# 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() or x**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.

In [ ]:
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?!

In [ ]:
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.

In [13]:
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?!

In [14]:
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!

In [24]:
# 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?

In [25]:
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.

In [26]:
# plt.bar([], [])

# SOLUTION
plt.bar([1, 2, 3, 4],[0.2, 0.5, 0.1, 0.6])
Out[26]:
<BarContainer object of 4 artists>
No description has been provided for this image

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:

  1. width
  2. 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.

In [31]:
# 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')
Out[31]:
<BarContainer object of 4 artists>
No description has been provided for this image

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.

In [30]:
# 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')
Out[30]:
<BarContainer object of 4 artists>
No description has been provided for this image

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:

![<an arbitrary label for my figure>](<relative path to my figure>)

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:

![bar chart of dummy data](./my_bar_chart.svg)

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 called subdirectory_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.

In [32]:
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')
No description has been provided for this image

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!

a figure

SOLUTION

![a figure](./my_figure.svg)

a figure

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.


![a figure](./figures/my_figure.svg)

** MAKE SURE A FIGURE APPEARS HERE BY MODIFYING THIS MARKDOWN CELL**

a figure

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))

End of notebook.

Creative Commons License TU Delft MUDE

By MUDE Team © 2024 TU Delft. CC BY 4.0. doi: 10.5281/zenodo.16782515.