diff --git a/Course Notes/Python Course Notes.html b/Course Notes/Python Course Notes.html deleted file mode 100644 index 39ee5f6..0000000 --- a/Course Notes/Python Course Notes.html +++ /dev/null @@ -1,14325 +0,0 @@ - - - -Python Course Notes - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
-
-

Table of Contents

-
-
-
-
-
-
-
-
-
-

DISCLAIMER

THIS DOCUMENT IS WORK-IN-PROGRESS

-

General Python Concepts

Indentation

The syntax used in Python relies heavily on indentation. If the code is not indented 100% correctly, running it will result in an error.

-

Every block of code that follows a : needs to be indented. The standard indent is equal four spaces, which is default in most editors. -A code block can be e.g. a conditional statement:

-
if a > 0:       # <- Remember colon after the condition
-    print(a)    # <- Indent by four spaces inside if-block
-    print(2*a)  # <- All code in the block must be indented
-
-b = 2           # <- This code is outside the if-block
-
-

If this indentation is not followed, an error will be thrown. IF for instance the variable b outside the if statement was indented only a single space, running the code would result in an IndentationError.

-

After having typed the colon, hitting enter in the editor should automatically indent the code correctly.

-

Getting used to this indentation can be a little hard at first, but quickly becomes second nature. Such kind of strictness in the syntax is common for all programming languages. Many other languages use parantheses or brackets of some kind or begin/end statements to keep blocks of code separated, which can become a little messier to look at. Some might argue that Python keeps the strictness to a minimum and that following the forced indentation scheme only helps keeping the code neat and tidy. Something that one should also strive to do in other languages.

-

The same concept of : followed by indentation goes for for and while loops, functions, classes etc.

-

Common Nomenclature

Iterable

An iterable is an object that can be iterated/looped over. This could be strings, lists, tuples, keys of a dictionary etc. It can occur in a for loop like this:

-
for item in iterable:
-    # Some code
-
-

In more detail

An iterable is an object that has an __iter__() method attached to it. Understanding the constuct of Python classes is paramount to fully understand iterables.

-

Sequence

A construct that supports indexing and slicing. Most important ones are strings, lists, tuples.

-

Function

A function is a block of code that is first defined, and thereafter can be called to run as many times as needed. A function might have arguments, some of which can be optional if a default value is specified. -A function is called by parantheses: function_name(). Arguments are placed inside the paranthes and comma separated if there are more than one. -Similar to f(x, y) from mathematics.

-

A function can return one or more values to the caller. The values to return are put in the return statement, which ends the function. If no return statement is given, the function will return None.

-

The general syntax of a function is:

-
def function_name(arg1, arg2, optional_arg1=0, optional_arg2=None):
-    '''
-    This is the so-called 'docstring', which documents concisely what the function does.
-    It is basically a multiline comment.
-    '''
-
-    # Function code goes here
-
-    # Possible 'return' statement which terminates the function. 
-    # If 'return' is not specified, function returns None.
-    return value_to_return
-
-

If multiple values are to be returned, they can be separeted by commas. The returned entity will by default be a tuple.

-

Note: When using default arguments, it is good practice to only use immutable types as defaults. Using mutable types can have unexpected and hard-to-detect side effects.

- -
-
-
-
-
-
-
-
-

Class

A class is a convenient way to create customized objects in Python. When classes are being used in code the programming style is referred to as Object Oriented Programming (OOP).

-

Some benenits of OOP:

-
    -
  • Code that is naturally tied can bunched together. If one has multiple functions that can perform related but still different opertions, they can be grouped inside a class and turned to methods. It makes the code more modular.

    -
  • -
  • It avoids a lot of code repetition.

    -
  • -
  • It can make it easier for users of the code to directly create objects and work with them.

    -
  • -
-

Some drawbacks of OOP:

-
    -
  • The concept is harder to grasp compared to working with standard functions.

    -
  • -
  • Will often create many lines of code

    -
  • -
-

OOP is mostly used for actual software programs and perhaps less so for standard single-file scripting.

-

Regardless whether ones has to use it directly or not, knowing the concept of OOP is still very integral to understanding how Python works. Many built-in features and third party libraries use this concept.

-

Consider the example below where a class for concrete sections is created:

- -
-
-
-
-
-
In [1]:
-
-
-
class ConcreteSection():
-    
-    def __init__(self, b, h):
-        self.b = b
-        self.h = h
-       
-    
-# Print to see what it is
-print(ConcreteSection)
-
- -
-
-
- -
-
- - -
-
- -
-
<class '__main__.ConcreteSection'>
-
-
-
- -
-
- -
-
-
-
-
-
-

__init__() is a special method that initiates the the class instance. See explanations below for these concepts.

- -
-
-
-
-
-
-
-
-

Class instance

-
-
-
-
-
-
-
-
-

Defining a class like ConcreteSection above allows for creating many different concrete sections and storing them in variables:

- -
-
-
-
-
-
In [2]:
-
-
-
# Create a concrete section with width 100 and height 200
-section1 = ConcreteSection(100, 200)
-
-print(section1)
-
- -
-
-
- -
-
- - -
-
- -
-
<__main__.ConcreteSection object at 0x0000021B3FE197B8>
-
-
-
- -
-
- -
-
-
-
-
-
-

The variable section1 is called an instance of the ConcreteSection class.

-

The height of the cross section stored in section1 can be accesssed as

- -
-
-
-
-
-
In [3]:
-
-
-
print(section1.h)
-
- -
-
-
- -
-
- - -
-
- -
-
200
-
-
-
- -
-
- -
-
-
-
-
-
-

Method

A method is similar to a function, except it resides inside a class.

-

A method is called by 'dot'-notation: class_instance.method_name()

-

An example of a class is the predefined list in Python. -The list class has many methods, for example list.append() where append() is a 'function' written and contained inside the list-class, therefore called a method instead of a function. -The fact that a list is a class can be seen by

- -
-
-
-
-
-
In [4]:
-
-
-
# Define a list
-w = [1, 2, 3]
-
-# Print the type of the variable 'w' 
-print(type(w))
-
- -
-
-
- -
-
- - -
-
- -
-
<class 'list'>
-
-
-
- -
-
- -
-
-
-
In [5]:
-
-
-
# Use the 'append' method
-w.append(4)
-print(w)
-
- -
-
-
- -
-
- - -
-
- -
-
[1, 2, 3, 4]
-
-
-
- -
-
- -
-
-
-
-
-
-

Continuing from the ConcreteSection class above, we could write a method that calculates and returns the area of the section:

- -
-
-
-
-
-
In [6]:
-
-
-
class ConcreteSection():
-    
-    def __init__(self, b, h):
-        self.b = b
-        self.h = h
-    
-    def area(self):
-        '''
-        This method returns the area of the concrete section.
-        '''
-        return self.b * self.h
-
- -
-
-
- -
-
-
-
-
-
-

Use of self

Notice the use of self as the first argument to both the special __init__ method and the standard area method. Think of self is the instance itself.

-

So after an instance (section) is created, computation of the area for that section does not need to have the width and the height as input. These values are already stored inside the instance, i.e. inside self. They can then be referred to as self.b and self.h.

-

This is a showcase of one of the advantages of OOP. It does require writing a lot of boiler-plate code to set it up, but afterwards it is easier to use since one can work directly with objects.

-

The 'ease' of use is demonstrated below:

- -
-
-
-
-
-
In [7]:
-
-
-
# Define an instance of the ConcreteSection class 
-sect = ConcreteSection(b=500, h=1000)
-
-# Use the 'area' method to calculate the area of the instance
-print(sect.area())
-
- -
-
-
- -
-
- - -
-
- -
-
500000
-
-
-
- -
-
- -
-
-
-
-
-
-

Print some info for the section:

- -
-
-
-
-
-
In [8]:
-
-
-
print(f'The section has dimensions (b, h)=({sect.b}, {sect.h}) and an area of {sect.area()}')
-
- -
-
-
- -
-
- - -
-
- -
-
The section has dimensions (b, h)=(500, 1000) and an area of 500000
-
-
-
- -
-
- -
-
-
-
-
-
-

A more advanced scenario

In the same manner, many addtional methods could be created and tied to the instance sect. Ones all desired methods are written it is easy to calculate everything by simply typing sect.method_name(...).

-

Consider a more advanced scenario of the ConcreteSection class like:

-
# Create section as an instance of class ConcreteSection
-sect = ConcreteSection(b, h, some_other_needed_parameters)
-
-# Perform some desired operations by calling appropriate methods of the instance (section)
-sect.plot_section()
-sect.area()
-sect.reinforcement_area()
-sect.reinforcement_ratio()
-sect.plot_mn_diagram()
-sect.plot_capacity_surface()
-sect.perform_sls_analysis(...)
-sect.largest_crack_width(...)
-sect.load_combination_with_largest_utilisation_ratio(...)
-sect.spit_out_the_entire_damn_documentation_report(...)
-
- -
-
-
-
-
-
-
-
-

Special method

__init__()

- -
-
-
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
-
-
-
-

Generator

...

- -
-
-
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
-
-
-
-

Iterator

... __next__() method.

- -
-
-
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
-
-
-
-

Common error messages

Some common error messages and their meaning:

-

IndentationError

Occurs when atempting to run code that is uncorrectly indented. Python uses indentation to structure blocks of code instead of parantheses, brackets and end statements. Therefore, it is very strict about correct indentation. A code editor will assist so maintaining correct indentation becomes very easy. -Make sure the editor will treat a tab as four spaces, otherwise indentation errors can be hard to spot. This configuration should be default in many editors though.

-

SyntaxError

Occurs when syntax is incorrect. The following example will throw a SyntaxError since the parathesis in function call to len() is not closed:

-
len([1, 2, 3]   # <--- Will throw SyntaxError
-
-

IndexError

Occurs when atempting to access an index that is not present in the sequence.

- -
-
-
-
-
-
In [9]:
-
-
-
L = [1, 2, 3]
-L[10]           # <--- IndexError, since index 10 is not present in L
-
- -
-
-
- -
-
- - -
-
- -
-
----------------------------------------------------------------------------
-IndexError                                Traceback (most recent call last)
-<ipython-input-9-0328e124d272> in <module>()
-      1 L = [1, 2, 3]
-----> 2 L[10]           # <--- IndexError, since index 10 is not present in L
-
-IndexError: list index out of range
-
-
- -
-
- -
-
-
-
-
-
-

ValueError

Occurs when ..

- -
-
-
-
-
-
-
-
-

TypeError

Occurs when atempting to do an operation that is not supported by the given type. For example a function call of an integer:

- -
-
-
-
-
-
In [10]:
-
-
-
4()
-
- -
-
-
- -
-
- - -
-
- -
-
----------------------------------------------------------------------------
-TypeError                                 Traceback (most recent call last)
-<ipython-input-10-2388798513fb> in <module>()
-----> 1 4()
-
-TypeError: 'int' object is not callable
-
-
- -
-
- -
-
-
-
-
-
-

Collection of Python Code Snippets

Th chapters belwo contains small chunks of code that can serve as parts of bigger scripts and programs.

- -
-
-
-
-
-
-
-
-

Iteration

-
-
-
-
-
-
-
-
-

Simplest forms of iteration

-
-
-
-
-
-
In [11]:
-
-
-
# Iterate over lists
-for i in [1, 2, 3]:
-    print(i)
-
- -
-
-
- -
-
- - -
-
- -
-
1
-2
-3
-
-
-
- -
-
- -
-
-
-
In [12]:
-
-
-
# Iterate over strings
-for letter in 'abc':
-    print(letter)
-
- -
-
-
- -
-
- - -
-
- -
-
a
-b
-c
-
-
-
- -
-
- -
-
-
-
-
-
-

Iteration while keeping track of index

-
-
-
-
-
-
In [13]:
-
-
-
names = ['Alpha', 'Bravo', 'Charlie']
-
-idx = 0
-for name in names:
-    print(idx, name)
-    idx += 1
-
- -
-
-
- -
-
- - -
-
- -
-
0 Alpha
-1 Bravo
-2 Charlie
-
-
-
- -
-
- -
-
-
-
-
-
-

A better way using the enumerate function

- -
-
-
-
-
-
In [14]:
-
-
-
# Use enumerate to access both index and value during iteration
-for idx, name in enumerate(names):  
-    print(idx, name)
-
- -
-
-
- -
-
- - -
-
- -
-
0 Alpha
-1 Bravo
-2 Charlie
-
-
-
- -
-
- -
-
-
-
In [15]:
-
-
-
# Index starting from 1 instead of default 0
-for index, name in enumerate(names, start=1):  
-    print(index, name)
-
- -
-
-
- -
-
- - -
-
- -
-
1 Alpha
-2 Bravo
-3 Charlie
-
-
-
- -
-
- -
-
-
-
-
-
-

Thus, enumerate keeps track of both the value and the index that is being iterated over.

- -
-
-
-
-
-
-
-
-

Iterating over multiple iterables at once

Iterating over multiple iterables at once accessing the first element of all, then the second element of all etc. can be done in multiple ways. The cleanest way is using the zip function.

-

An iterable is something that can be iterated over, e.g. lists, stings, tuples, sets etc.

-

An example with two lists:

- -
-
-
-
-
-
In [16]:
-
-
-
x_values = [1, 2, 3]
-y_values = [3, 6, 4]
-
-for x, y in zip(x_values, y_values):
-    print(f'x = {x} and y = {y}')
-
- -
-
-
- -
-
- - -
-
- -
-
x = 1 and y = 3
-x = 2 and y = 6
-x = 3 and y = 4
-
-
-
- -
-
- -
-
-
-
-
-
-

An example with three lists:

- -
-
-
-
-
-
In [17]:
-
-
-
x_values = [1, 2, 3]
-y_values = [3, 6, 4]
-z_values = [10, 23, 26]
-
-for x, y, z in zip(x_values, y_values, z_values):
-    print(f'x = {x} and y = {y} and z = {z}')
-
- -
-
-
- -
-
- - -
-
- -
-
x = 1 and y = 3 and z = 10
-x = 2 and y = 6 and z = 23
-x = 3 and y = 4 and z = 26
-
-
-
- -
-
- -
-
-
-
In [18]:
-
-
-
english = ['one', 'two', 'three']
-german = ['eins', 'zwei', 'drei']
-
-for e, g in zip(english, german):
-    print(f'{g} means {e} in german')
-
- -
-
-
- -
-
- - -
-
- -
-
eins means one in german
-zwei means two in german
-drei means three in german
-
-
-
- -
-
- -
-
-
-
-
-
-

Iterating over multiple iterables while keeping track of index

By using enumerate and zip together, multiple iteratbles can be iterated over while keeping track of the index (loop counter).

-

In case of multiple iteratbles, enumerate returns the index and all values in a tuple, which then need to be unpacked. See below:

- -
-
-
-
-
-
In [19]:
-
-
-
for idx, val in enumerate(zip([12,3,4], [5, 3, 46], [52, 53, 2])):
-    print(f'idx = {idx}   ,   val = {val}')
-
- -
-
-
- -
-
- - -
-
- -
-
idx = 0   ,   val = (12, 5, 52)
-idx = 1   ,   val = (3, 3, 53)
-idx = 2   ,   val = (4, 46, 2)
-
-
-
- -
-
- -
-
-
-
-
-
-

Another example:

- -
-
-
-
-
-
In [20]:
-
-
-
english = ['one', 'two', 'three']
-german = ['eins', 'zwei', 'drei']
-
-for idx, val in enumerate( zip(english, german) ):
-    print(f'Index {idx}: {val[1]} means {val[0]} in german')
-
- -
-
-
- -
-
- - -
-
- -
-
Index 0: eins means one in german
-Index 1: zwei means two in german
-Index 2: drei means three in german
-
-
-
- -
-
- -
-
-
-
-
-
-

Printing and formatting

Printing strings containing variables

There is quite often a need for printing a combination of static text and variables. This could e.g. be to output the result of a computation. Often the best way is to use the so-called f-strings. See examples below.

- -
-
-
-
-
-
In [21]:
-
-
-
# Basic usage of f-strings
-a = 2
-b = 27
-print(f'Multiplication: a * b = {a} * {b} = {a*b}')
-print(f'Division: a / b = {a} / {b} = {a/b}')
-
- -
-
-
- -
-
- - -
-
- -
-
Multiplication: a * b = 2 * 27 = 54
-Division: a / b = 2 / 27 = 0.07407407407407407
-
-
-
- -
-
- -
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
-
-
-
-

Rounding anf formating numeric string values

When printing variables it can be useful to format them. Some common ways of formatting are shown below:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NumberSyntaxOutputDescription
3.1415926{3.1415926:.2f}3.142 decimal places
3.1415926{3.1415926:+.2f}+3.142 decimal places with sign
-1{-1:+.2f}-1.002 decimal places with sign
2.71828{2.71828:.0f}3No decimal places
5{5:0>2d}05Pad number with zeros (left padding, width 2)
5{5:x<4d}5xxxPad number with x’s (right padding, width 4)
10{10:x<4d}10xxPad number with x’s (right padding, width 4)
1000000{1000000:,}1,000,000Number format with comma separator
0.25{0.25:.2%}25.00%Format percentage
1000000000{1000000000:.2e}1.00e+09Exponent notation
-

The part of the syntax that actually formats the value is shown in bold.

-

Source: https://mkaz.blog/code/python-string-format-cookbook/

-

Some examples are shown below. Note the location where the formatting must be specified, i.e. directly after the variable to be formatted.

- -
-
-
-
-
-
In [22]:
-
-
-
c = 50
-d = 200
-# f-string with formatting for number of decimal places
-print(f'Division: c / d = {c} / {d} = {c/d:.3f}')
-
- -
-
-
- -
-
- - -
-
- -
-
Division: c / d = 50 / 200 = 0.250
-
-
-
- -
-
- -
-
-
-
In [23]:
-
-
-
# f-string with formatting as percent
-print(f'{c} is {c/d:.1%} of {d}')
-
- -
-
-
- -
-
- - -
-
- -
-
50 is 25.0% of 200
-
-
-
- -
-
- -
-
-
-
-
-
-

List comprehensions

List comprehensions are often shorter and easier to read than their traditional for loop counterparts. They are also faster since the entire list is built in one go instead of by appending element by element.

-

Consider the following to equivalent loops: -TODO INPUT

-

Simple form

The general form of the simplest list comprehension is

-
result_list = [expression for item in iterable]
-
-
    -
  • iterable is a sequence that can be iterated over, this could be a list, a string, a tuple etc.
  • -
  • item is the counter for the iterable, think of this as the i'th element
  • -
  • expression can be anything, but will often include the item
  • -
-
-
# This list comprehension
-x = [r**2 for r in [5, 6, 8, 10]]
-
-# Will be computed at this list
-x = [5**2, 6**2, 8**2, 10**2]
-
-# To be equal to
-x = [25, 36, 64, 100]
-
- -
-
-
-
-
-
In [24]:
-
-
-
x = [1, 2, 3, 4, 5]             # Define a list (iterable)
-
-# Double numbers from old list and put in new list
-result_list = [2*i for i in x]  
-result_list                 
-
- -
-
-
- -
-
- - -
-
Out[24]:
- - - -
-
[2, 4, 6, 8, 10]
-
- -
- -
-
- -
-
-
-
In [25]:
-
-
-
diameters = [10, 12, 16, 20]  # Define list of rebar diameters (iterable)
-
-# Compute rebar areas for each diameter
-rebar_areas = [3.14 * dia**2 / 4 for dia in diameters]  
-rebar_areas
-
- -
-
-
- -
-
- - -
-
Out[25]:
- - - -
-
[78.5, 113.04, 200.96, 314.0]
-
- -
- -
-
- -
-
-
-
-
-
-

Form with conditional if statement

The general form of a list comprehension with a conditional is

-
result_list = [expression for item in iterable if condition]
-
-
    -
  • iterable is a sequence that can be iterated over, this could be a list, a string, a tuple etc.
  • -
  • condition is a logical condition, e.g. "item > 3", which returns a boolean (True/False). This can act as as filter.
  • -
  • result_list is a new list containing expression for each item that fulfilled the condition
  • -
-
- -
-
-
-
-
-
In [26]:
-
-
-
x = [1, 2, 3, 4, 5]   # Define a list (iterable)
-
-# Filter list with list comprehension by a condition
-result_list = [i for i in x if i > 3]   
-result_list
-
- -
-
-
- -
-
- - -
-
Out[26]:
- - - -
-
[4, 5]
-
- -
- -
-
- -
-
-
-
-
-
-

A litlle more complex example that includes slicing of strings and type conversion from strings to integers within the conditional statement:

- -
-
-
-
-
-
In [27]:
-
-
-
# Define list of steel profiles as a list of strings
-profiles = ['HE170A', 'HE180A', 'HE190A', 'HE200A', 'HE210A', 'HE210A']
-
-# Filter all profiles that are HE200A or smaller
-filtered_profiles = [p for p in profiles if int(p[2:5]) <= 200 ]
-filtered_profiles
-
- -
-
-
- -
-
- - -
-
Out[27]:
- - - -
-
['HE170A', 'HE180A', 'HE190A', 'HE200A']
-
- -
- -
-
- -
-
-
-
-
-
-

Here int(p[2:5]) extracts the number part of the steel profile (e.g. '190' from 'HE190A') and converts it from a string to an integer. This is necessary because it is to be compared with an integer afterwards.

-

A statement like '190' <= 200 would throw a TypeError: '<=' not supported between instances of 'int' and 'str'

- -
-
-
-
-
-
-
-
-

Form with conditional if / else statement

The general form of a list comprehension with a conditional if and else is:

-
result_list = [expression1 if condition else expression2 for item in iterable]
-
-
    -
  • iterable is a sequence that can be iterated over, this could be a list, a string, a tuple etc.
  • -
  • condition is a logical condition, e.g. "item > 3", which returns a boolean (True/False). This can act as as filter.
  • -
  • result_list is a new list containing elements where each item depends on condition. If it is True, expression1 is put in. Else expression2 is put in.
  • -
-
- -
-
-
-
-
-
In [28]:
-
-
-
V = [3, 62, 182, 26, 151, 174]
-
-# Set all elements of V that are less than 100 equal to 0
-W = [0 if i < 100 else i for i in V]
-W
-
- -
-
-
- -
-
- - -
-
Out[28]:
- - - -
-
[0, 0, 182, 0, 151, 174]
-
- -
- -
-
- -
-
-
-
-
-
-

List comprehensions with multiple if / else statements

-
-
-
-
-
-
In [29]:
-
-
-
Q1 = (-18, -27, 2, -21, -15, 5)
-[0 if val > 0 else val if 0 > val > -25 else -25 for val in Q1]
-
- -
-
-
- -
-
- - -
-
Out[29]:
- - - -
-
[-18, -25, 0, -21, -15, 0]
-
- -
- -
-
- -
-
-
-
-
-
-

Nested list comprehensions

It is possible to make nested list comprehensions, i.e. with for loops inside eachother.

- -
-
-
-
-
-
In [30]:
-
-
-
[[j for j in range(25, 30)] for i in range(5)]
-
- -
-
-
- -
-
- - -
-
Out[30]:
- - - -
-
[[25, 26, 27, 28, 29],
- [25, 26, 27, 28, 29],
- [25, 26, 27, 28, 29],
- [25, 26, 27, 28, 29],
- [25, 26, 27, 28, 29]]
-
- -
- -
-
- -
-
-
-
-
-
-

Complex one-liners can be quite hard to read, so for nested if statements or loops it is sometimes better to create a traditional multi line for loop.

- -
-
-
-
-
-
-
-
-

Dictionaries

-
-
-
-
-
-
-
-
-

Create a dictionary from two lists

Making first list the keys and second the values

- -
-
-
-
-
-
In [31]:
-
-
-
names = ['Pelé', 'Maradona', 'Zidane']
-countries = ['Brazil', 'Argentina', 'France']
-
-d = dict(zip(names, countries))
-d
-
- -
-
-
- -
-
- - -
-
Out[31]:
- - - -
-
{'Maradona': 'Argentina', 'Pelé': 'Brazil', 'Zidane': 'France'}
-
- -
- -
-
- -
-
-
-
-
-
-

Functions

Default arguments

A little side note

A good practice is to use only immutable types as default arguments. Python will allow mutable arguments to be given as defaults, but the behavior can be confusing if the default argument is mutated within the function.

-

Consider this example:

-

TODO

-

A list (which is mutable) is given as default value and the list is mutated within the function. -This is because it will mutate the deafult argument so the next function call has the mutated value as the default.

- -
-
-
-
-
-
-
-
-

Examples

-
-
-
-
-
-
In [32]:
-
-
-
def abbreviate_day(day):    
-    '''Return the specified day in abrreviated three character form'''
-    
-    # Define dictionary for mapping days to their abbreviations
-    day_to_abbreviation = {'monday': 'Mon', 'tuesday': 'Tue', 
-                           'wednesday': 'Wed', 'thursday': 
-                           'Thu', 'friday': 'Fri', 
-                           'saturday': 'Sat', 'sunsay': 'Sun'}
-
-    # Convert input string to all lowercase
-    day = day.lower()    
-    
-    # Return abbreviation if day is a valid day, raise error otherwise
-    try:
-        return day_to_abbreviation[day]
-    except KeyError:
-        raise ValueError(f"Expected input 'day' to be a day name, but received '{day}'")
-        
-        
-# Test function
-d = 'Monday'
-print(abbreviate_day(d))
-
- -
-
-
- -
-
- - -
-
- -
-
Mon
-
-
-
- -
-
- -
-
-
-
-
-
-
    -
  • Note: The input parameter when the function was defined was a string called day, but the when the function was called, the input string was called d. Thus, the input parameters passed into a function do not need to have the same name as when the function is defined.
  • -
- -
-
-
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
-
-
-
-

Tables as Pandas dataframes

-
-
-
-
-
-
In [33]:
-
-
-
import pandas as pd
-
-# Allow for dataframes to be displayed as HTML-tables
-from IPython.display import display, HTML   
-
- -
-
-
- -
-
-
-
-
-
-

merge, join and concat

The best explanation is arguably from the documentation itself: https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html

-

Roughly speaking, merge is used to combine two or more dataframes, while concat is used to 'glue' dataframes together, i.e. append them to eachother or put them side by side. concat is not content aware in that it will might include duplicate columns, whereas merge will remove/merge duplicates. concat requires the dataframe dimensions to be equal along the axis of concatenation, where merge can treat any size.

-

All join operations could also be achieved by using merge, but join commands have easier/shorter code for some common use cases.

- -
-
-
-
-
-
-
-
-

Functionality similar to VLOOKUP in Excel

-
-
-
-
-
-
In [34]:
-
-
-
# Read pile data from csv-file
-df_piles = pd.read_csv('piles.csv')
-
-# Read steel profile data from csv-file
-df_profiles = pd.read_csv('steel_profiles.csv')
-
-# Merge dataframes on the "Profile" column (similar to Excel VLOOKUP)
-df_merged = df_piles.merge(df_profiles, on='Profile', how='left')
-
-display(df_piles, df_profiles, df_merged)
-
- -
-
-
- -
-
- - -
-
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Pile_typeProfile
0P01HE200A
1P20HE220A
2P05HE240B
3P23NaN
4P04HE200A
5P01HE300B
-
-
- -
- -
-
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Profileh[mm]b[mm]Iy[mm4]Wel_y[mm3]g[kg/m]
0HE100A96100349000072.816.7
1HE120A1141206060000106.019.9
2HE140A13314010300000155.024.7
3HE160A15216016700000220.030.4
4HE180A17118025100000294.035.5
5HE200A19020036900000389.042.3
6HE220A21022054100000515.050.5
7HE240A23024077600000675.060.3
8HE260A250260104500000836.068.2
9HE280A2702801367000001010.076.4
10HE300A2903001826000001260.088.3
11HE100B100100450000089.920.4
12HE120B1201208640000144.026.7
13HE140B14014015100000216.033.7
14HE160B16016024900000311.042.6
15HE180B18018038300000426.051.2
16HE200B20020057000000570.061.3
17HE220B22022080900000736.071.5
18HE240B240240112600000938.083.2
19HE260B2602601492000001150.093.0
20HE280B2802801927000001380.0103.0
21HE300B3003002517000001680.0117.0
-
-
- -
- -
-
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Pile_typeProfileh[mm]b[mm]Iy[mm4]Wel_y[mm3]g[kg/m]
0P01HE200A190.0200.036900000.0389.042.3
1P20HE220A210.0220.054100000.0515.050.5
2P05HE240B240.0240.0112600000.0938.083.2
3P23NaNNaNNaNNaNNaNNaN
4P04HE200A190.0200.036900000.0389.042.3
5P01HE300B300.0300.0251700000.01680.0117.0
-
-
- -
- -
-
- -
-
-
-
-
-
-

The first dataframe containing pile types and profile has been populated by the data of the steel profiles. The resulting dataframe has all profile data, but it could have been specified to include only certain columns of the profile properties.

-

Compared to e.g. Excel, this has the advantage of being scalable in each table direction. Meaning that if more rows or columns were to be added to either dataframe, the code need no change at al, it just adapts. No manual adjustment of ranges/cells.

- -
-
-
-
-
-
-
-
-

Plotting with Matplotlib

-
-
-
-
-
-
In [35]:
-
-
-
import matplotlib.pyplot as plt
-
-# Enable for plots to be shown inside notebook cells
-%matplotlib inline      
-
- -
-
-
- -
-
-
-
-
-
-

Simplest forms of graphs

-
-
-
-
-
-
In [36]:
-
-
-
# Create x- and y-coordinates for f(x) = x^2 
-x = [i for i in range(-100, 105, 5)]
-y = [i**2 for i in x]
-
-# Create basic plot
-plt.plot(x, y)
-
-# Show plot
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Note: When using an editor, one has to call plt.show() to show the figure, which then opens a separate window.

- -
-
-
-
-
-
-
-
-

Including title, labels and marker styling:

- -
-
-
-
-
-
In [37]:
-
-
-
plt.title('Graph for $f(x) = x^2$')
-plt.xlabel('x')
-plt.ylabel('f(x)')
-plt.plot(x, y, 'x', color='limegreen', markersize=8)
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Multiple plots in same figure

-
-
-
-
-
-
In [38]:
-
-
-
plt.plot(x, y, label='$f(x)=x^2$')                   # x and y defined above
-plt.plot(x, [2*i for i in y], label='$f(x)=2x^2$')
-plt.legend()
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Subplots

In order for subplots to be used, it is necessary to access the underlying objects that control ....

- -
-
-
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
-
-
-
-

Heatmaps/colormaps

-
-
-
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
-
-
-
-

Integration with Pandas

-
-
-
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
-
-
-
-

Numerical computations

A large portion of the computations are carried out as optimized C-code under the hood, which is pretty much as fast as it gets.

- -
-
-
-
-
-
-
-
-

Numpy

TODO: np.linspace()

-

TODO: np.append()

-

TODO: np.where()

-

TODO: Fancy indexing

- -
-
-
-
-
-
-
-
-

Scipy

TODO: from scipy.signal import find_peaks

- -
-
-
-
-
- - - - - - diff --git a/Session 1 - Data Types/Session 1 - Data Types.html b/Session 1 - Data Types/Session 1 - Data Types.html deleted file mode 100644 index cc1dbf8..0000000 --- a/Session 1 - Data Types/Session 1 - Data Types.html +++ /dev/null @@ -1,13188 +0,0 @@ - - - -Session 1 - Explanation and Exercises - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
-
-
-

Session 1

Use a locally installed editor like Spyder or Visual Studio Code. If you want to try out coding without installing anything, try this online editor: https://www.onlinegdb.com/online_python_compiler

-

Code comments

In order to explain the code, comments in Python can be made by preceding a line with a hashtag #. Everything on that particular line will then not be interpreted as part of the code:

-
# This is a comment and will not be interpreted as code
-
-

Data Types

The basic data types in Python are illustrated below.

- -
-
-
-
-
-
-
-
-

Integers (int)

-
-
-
-
-
-
In [1]:
-
-
-
# Integers
-a = 2
-b = 239
-
- -
-
-
- -
-
-
-
-
-
-

Floating point numbers (float)

-
-
-
-
-
-
In [2]:
-
-
-
# Floats
-c = 2.1
-d = 239.0
-
- -
-
-
- -
-
-
-
-
-
-

Strings (str)

-
-
-
-
-
-
In [3]:
-
-
-
e = 'Hello world!'
-my_text = 'This is my text'
-
- -
-
-
- -
-
-
-
-
-
-

Both " and ' can be used to denote strings. If the apostrophe character should be part of the string, use " as outer boundaries:

-
"Barrack's last name is Obama"
-
-

Alternatively, \ can be used as an escape character:

- -
-
-
-
-
-
In [4]:
-
-
-
'Barrack\'s last name is Obama'
-
- -
-
-
- -
-
- - -
-
Out[4]:
- - - -
-
"Barrack's last name is Obama"
-
- -
- -
-
- -
-
-
-
-
-
-

Note that strings are immutable, which means that they can't be changed after creation. They can be copied or manipulated and saved into a new variable though.

- -
-
-
-
-
-
-
-
-

Boolean (bool)

-
-
-
-
-
-
In [5]:
-
-
-
x = True
-y = False
-
- -
-
-
- -
-
-
-
-
-
-

Extra Info: Python is dynamically typed language

As you might have noticed, Python is a dynamically typed language. That means that one does not have to declare the type of a variable before creating it.

-

To create a variable a in a statically typed language, it would go something like (C++):

-
int a
-a = 5
-
-
-

You might also have seen this when using VBA with Option Explicit enabled, where it would be Dim a As Integer and then a = 5. If the variable type is not declared beforehand in such languages, an error wiil be thrown. -In Python, this is all abstracted away. It automatically figures out which type to assign to each variable in the background.

-

This also means that variables can change types throughout the program, so somthing like this is valid:

-
a = 5
-a = 'Hi'
-
-
-

Which can be both a blessing and a curse. The flexibility is nice, but it can lead to unexpected code behavior if variables are not tracked.

- -
-
-
-
-
-
-
-
-

Calculations with data types

-
-
-
-
-
-
-
-
-

Standard calculator-like operations

Most basic operations on integers and floats such as addition, subtraction, multiplication work as one would expect:

- -
-
-
-
-
-
In [6]:
-
-
-
2 * 4
-
- -
-
-
- -
-
- - -
-
Out[6]:
- - - -
-
8
-
- -
- -
-
- -
-
-
-
In [7]:
-
-
-
2 / 5
-
- -
-
-
- -
-
- - -
-
Out[7]:
- - - -
-
0.4
-
- -
- -
-
- -
-
-
-
In [8]:
-
-
-
3.1 + 7.4
-
- -
-
-
- -
-
- - -
-
Out[8]:
- - - -
-
10.5
-
- -
- -
-
- -
-
-
-
-
-
-

Exponents

Exponents are denoted by ** and not ^:

- -
-
-
-
-
-
In [9]:
-
-
-
2**3
-
- -
-
-
- -
-
- - -
-
Out[9]:
- - - -
-
8
-
- -
- -
-
- -
-
-
-
-
-
-

Floor division

Returns integer part of division result (removes decimals after division)

- -
-
-
-
-
-
In [10]:
-
-
-
10 // 3
-
- -
-
-
- -
-
- - -
-
Out[10]:
- - - -
-
3
-
- -
- -
-
- -
-
-
-
-
-
-

Modulo

Returns remainder after divions

- -
-
-
-
-
-
In [11]:
-
-
-
10 % 3
-
- -
-
-
- -
-
- - -
-
Out[11]:
- - - -
-
1
-
- -
- -
-
- -
-
-
-
-
-
-

Operations on strings

Strings can be added (concatenated)

- -
-
-
-
-
-
In [12]:
-
-
-
'Bruce' + ' ' + 'Wayne'
-
- -
-
-
- -
-
- - -
-
Out[12]:
- - - -
-
'Bruce Wayne'
-
- -
- -
-
- -
-
-
-
-
-
-

Multiplication is also allowed:

- -
-
-
-
-
-
In [13]:
-
-
-
'a' * 3
-
- -
-
-
- -
-
- - -
-
Out[13]:
- - - -
-
'aaa'
-
- -
- -
-
- -
-
-
-
-
-
-

Subtraction and division are not allowed:

- -
-
-
-
-
-
In [14]:
-
-
-
'a' / 3    # Division results in error
-
- -
-
-
- -
-
- - -
-
- -
-
----------------------------------------------------------------------------
-TypeError                                 Traceback (most recent call last)
-<ipython-input-14-18a27a45574d> in <module>
-----> 1 'a' / 3    # Division results in error
-
-TypeError: unsupported operand type(s) for /: 'str' and 'int'
-
-
- -
-
- -
-
-
-
In [33]:
-
-
-
'a' - 'b'    # Subtraction results in error
-
- -
-
-
- -
-
- - -
-
- -
-
----------------------------------------------------------------------------
-TypeError                                 Traceback (most recent call last)
-<ipython-input-33-f52d61c1bb56> in <module>
-----> 1 'a' - 'b'    # Subtraction results in error
-
-TypeError: unsupported operand type(s) for -: 'str' and 'str'
-
-
- -
-
- -
-
-
-
-
-
-

Printing strings with variables

-
-
-
-
-
-
-
-
-

There is quite often a need for printing a combination of static text and variables. This could e.g. be to output the result of a computation. Often the best way is to use the so-called f-strings. See examples below.

- -
-
-
-
-
-
In [15]:
-
-
-
# Basic usage of f-strings
-a = 2
-b = 27
-print(f'Multiplication: a * b = {a} * {b} = {a*b}')
-print(f'Division: a / b = {a} / {b} = {a/b}')
-
- -
-
-
- -
-
- - -
-
- -
-
Multiplication: a * b = 2 * 27 = 54
-Division: a / b = 2 / 27 = 0.07407407407407407
-
-
-
- -
-
- -
-
-
-
In [16]:
-
-
-
# f-strings with formatting for number of decimal places
-print(f'Division: a / b = {a} / {b} = {a/b:.3f}')   # The part ':.xf' specfifies 'x' decimals to be printed 
-
- -
-
-
- -
-
- - -
-
- -
-
Division: a / b = 2 / 27 = 0.074
-
-
-
- -
-
- -
-
-
-
-
-
-

Both variables and complex computations can be inserted inside the curly brackets to be printed.

- -
-
-
-
-
-
In [17]:
-
-
-
print(f'Computation insde curly bracket: {122**2 / 47}')
-
- -
-
-
- -
-
- - -
-
- -
-
Computation insde curly bracket: 316.6808510638298
-
-
-
- -
-
- -
-
-
-
-
-
-

Function: len()

The len() function returns the length of a sequence, e.g. a string:

- -
-
-
-
-
-
In [18]:
-
-
-
len('aaa')
-
- -
-
-
- -
-
- - -
-
Out[18]:
- - - -
-
3
-
- -
- -
-
- -
-
-
-
In [19]:
-
-
-
len('a and b')   # Spaces are also counted
-
- -
-
-
- -
-
- - -
-
Out[19]:
- - - -
-
7
-
- -
- -
-
- -
-
-
-
-
-
-

Some string methods

A string object can be interacted with in many ways by so-called methods. Some useful methods are shown below:

- -
-
-
-
-
-
In [20]:
-
-
-
name = 'Edward Snowden'
-
- -
-
-
- -
-
-
-
-
-
-

string.replace()

Replaces characters inside a string:

-
string.replace('old_substring', 'new_substring')
-
- -
-
-
-
-
-
In [21]:
-
-
-
name.replace('Edward', 'Ed')
-
- -
-
-
- -
-
- - -
-
Out[21]:
- - - -
-
'Ed Snowden'
-
- -
- -
-
- -
-
-
-
-
-
-

Recal that strings are immutable. They can be copied or manipulated and saved to a new variable, but they can't be changed per se. This concept transfers to other more complex concstructs as well. -Thus, the original string name is still the same:

- -
-
-
-
-
-
In [22]:
-
-
-
name
-
- -
-
-
- -
-
- - -
-
Out[22]:
- - - -
-
'Edward Snowden'
-
- -
- -
-
- -
-
-
-
-
-
-

In order to save the replacement and retain the name of the variable, we could just reassign it to the same name:

- -
-
-
-
-
-
In [23]:
-
-
-
name = name.replace('Edward', 'Ed') 
-
- -
-
-
- -
-
-
-
-
-
-

Internally, the computer gives this new name variable a new id due to the immutability, but for us this does not really matter.

- -
-
-
-
-
-
-
-
-

string.endswith()

This method might be self explanatory, but returns a boolean (True of False) depending on whether or not the strings ends with the specified substring.

-
string.endswith('substring_to_test_for')
-
- -
-
-
-
-
-
In [24]:
-
-
-
name.endswith('g')
-
- -
-
-
- -
-
- - -
-
Out[24]:
- - - -
-
False
-
- -
- -
-
- -
-
-
-
In [25]:
-
-
-
name.endswith('n')
-
- -
-
-
- -
-
- - -
-
Out[25]:
- - - -
-
True
-
- -
- -
-
- -
-
-
-
In [26]:
-
-
-
name.endswith('den')
-
- -
-
-
- -
-
- - -
-
Out[26]:
- - - -
-
True
-
- -
- -
-
- -
-
-
-
-
-
-

string.count()

Counts the number of occurences of a substring inside a string:

-
string.count('substring_to_count')
-
- -
-
-
-
-
-
In [27]:
-
-
-
text = 'This is how it is done'
-text.count('i')
-
- -
-
-
- -
-
- - -
-
Out[27]:
- - - -
-
4
-
- -
- -
-
- -
-
-
-
In [28]:
-
-
-
text.count('is')
-
- -
-
-
- -
-
- - -
-
Out[28]:
- - - -
-
3
-
- -
- -
-
- -
-
-
-
-
-
-

The match is case sensistive:

- -
-
-
-
-
-
In [29]:
-
-
-
text.count('t')
-
- -
-
-
- -
-
- - -
-
Out[29]:
- - - -
-
1
-
- -
- -
-
- -
-
-
-
-
-
-

Code indendation

In Python, code blocks are separated by use of indentation. See e.g. ine the defintion of an if-statement below:

-
if some_condition:
-    # Code here must be indented
-    # Otherwise, IndentationError will be thrown
-
-# Code placed here is outside of the if-statement
-
-

Note the : as the last character of the if condition. This : must be present and requires an indentation in the line immediately after. This is how Python interprets the code as a block. -the if-statement is exited by reverting the indentation as shown above.

-

All editors will automatically make the indentation upon hitting enter after the :, so it doesn't take long to get used to this.

-

Comparison to other languages

In many other programming languages indentation is not required. It is however still used as good practice to increase code readability. Instead of indentation, code blocks are denoted by encapsulating code in characters like (), {} etc.

- -
-
-
-
-
-
-
-
-

Conditional statements

if-statements

An if-statement has the following syntax

- -
-
-
-
-
-
In [1]:
-
-
-
x = 2
-if x > 1:
-    print('x is larger than 1')
-
- -
-
-
- -
-
- - -
-
- -
-
x is larger than 1
-
-
-
- -
-
- -
-
-
-
-
-
-

if / else-statements

-
-
-
-
-
-
In [31]:
-
-
-
y = 1
-if y > 1:
-    print('y is larger than 1')
-else:
-    print('y is less than or equal to 1')
-
- -
-
-
- -
-
- - -
-
- -
-
y is less than or equal to 1
-
-
-
- -
-
- -
-
-
-
-
-
-

if / elif / else

-
-
-
-
-
-
In [32]:
-
-
-
z = 0
-if z > 1:
-    print('z is larger than 1')
-elif z < 1:
-    print('z is less than 1')
-else:
-    print('z is equal to 1')
-
- -
-
-
- -
-
- - -
-
- -
-
z is less than 1
-
-
-
- -
-
- -
-
-
-
-
-
-

An umlimited number of elif blocks can be used in between if and else.

- -
-
-
-
-
-
-
-
-

Exercises

-
-
-
-
-
-
-
-
-

Exercise

-
-
-
-
-
-
-
-
-

Find the length of the following string:

-
s = "Batman's real name is Bruce Wayne"
-
-
- -
-
-
-
-
-
-
-
-

Exercise

Test if s from above has "Wayne" as last characters (should return True or course)

- -
-
-
-
-
-
-
-
-

Exercise

Print the following sentence using an f-string:

-

The string s has a length of <insert_length_of_s>

-
-

Use s from above.

- -
-
-
-
-
-
-
-
-

Exercise

Use the count() method to print the number of e's in the string s form above.

- -
-
-
-
-
-
-
-
-

Exercise

Use the replace() method to replace Ø with Y in the following string:

-
string1 = '33Ø12'
-
-
-

Save the new string in a variable string2 and print the following sentence:

-

The string <insert_string1> was replaced by <insert_string2>

-
- -
-
-
-
-
-
-
-
-

Exercise

If the string below has more 100 characters, print "String has more than 100 characters", otherwise print "String has less than 100 characters".

-
dummy_string = 'Lorem ipsum is placeholder text commonly used in the graphic, print, and publishing industries for previewing layouts and visual mockups.'
-
- -
-
-
-
-
-
-
-
-

Exercise

Print the number of space characters in dummy_string from above.

- -
-
-
-
-
- - - - - - diff --git a/Session 2 - Data Structures/Session 2 - Data Structures.html b/Session 2 - Data Structures/Session 2 - Data Structures.html deleted file mode 100644 index 9a9055c..0000000 --- a/Session 2 - Data Structures/Session 2 - Data Structures.html +++ /dev/null @@ -1,13369 +0,0 @@ - - - -Session 2 - Explanation and Exercises - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
-
-

Session 2

Use a locally installed editor like Visual Studio Code or this online editor: https://www.onlinegdb.com/online_python_compiler

-

Data Structures

Data structures are constructs that can contain one or more variables.

-

There are four basic data structures in Python:

-
    -
  • Lists
  • -
  • Dictionaries
  • -
  • Tuples
  • -
  • Sets
  • -
-

Lists

Lists are defined by sqaure brackets [] with elements separated by commas. They can have elements of any data type.

-

Lists are probably the most used data structure.

-

Mutability

Lists are mutable. They can be changed after creation.

-

List examples

Some examples of lists:

- -
-
-
-
-
-
In [1]:
-
-
-
a = [10, 20, 30, 40]
-b = [1, True, 'Hi!', 4.3]                       # Multiple data types in the same list
-c = [['Nested', 'lists'], ['are', 'possible']]  # List of lists
-
- -
-
-
- -
-
-
-
-
-
-

Dictionaries

Dictionaries have key/value pairs. This can for some problems be easier than having two lists that relate to eachother by index.

- -
Syntax : {key1: value1, key2: value2, ..., key_n, value_n}
-
-
-

Note that values can be of any datatype like floats, strings etc., but they can also be lists or other data structures.

-

Keys must be unique within the dictionary. Otherwise it would be hard to extract the value by calling out a certain key, see the section about indexing and slicing below.

-

Keys also must be of an immutable type.

-

Mutability

Dictionaries are mutable. They can be changed after creation.

-

Dictionary examples

Some examples of dictionaries:

- -
-
-
-
-
-
In [2]:
-
-
-
my_dict1 = {'axial_force': 319.2, 'moment': 74, 'shear': 23}         # Strings as keys and numbers as values
-my_dict2 = {1: True, 'hej': 23}                                      # Keys are different types (ints and strings)
-my_dict3 = {'Point1': [1.3, 51, 10.6], 'Point2': [7.1, 11, 6.7]}     # Strings as keys and lists as values
-
- -
-
-
- -
-
-
-
-
-
-

As with most stuff in Python the flexibility is very nice, but it can also be confusing to have many different types mixed in the same data structure. To make code more readable, it is often preferred to keep the same trend throughout the dictionary. I.e. all keys are of same type and all values are of the same type.

-

The keys and values can be extracted separately by the methods dict.keys() and dict.values():

- -
-
-
-
-
-
In [3]:
-
-
-
my_dict1.keys()
-
- -
-
-
- -
-
- - -
-
Out[3]:
- - - -
-
dict_keys(['axial_force', 'moment', 'shear'])
-
- -
- -
-
- -
-
-
-
In [4]:
-
-
-
my_dict1.values()
-
- -
-
-
- -
-
- - -
-
Out[4]:
- - - -
-
dict_values([319.2, 74, 23])
-
- -
- -
-
- -
-
-
-
-
-
-

Tuples

Tuples are very comparable to lists, but they are defined by parantheses (). Most notable difference from lists is that tuples are immutable.

-

Mutability

Tuples are immutable. They cannot be changed after creation.

-

Tuple examples

-
-
-
-
-
-
In [5]:
-
-
-
t1 = (1, 24, 56)                      # Simple tuple of integers
-t2 = (1, 1.62, '12', [1, 2 , 3])      # Multiple types as tuple elements
-points = ((4, 5), (12, 6), (14, 9))   # Tuple of tuples
-
- -
-
-
- -
-
-
-
-
-
-

Sets

Sets are defined with curly brackets {}. They are unordered and don't have an index. See description of indexing further down. Sets also have unique items.

-

The primary idea about sets is the ability to perform set operations. These are known from mathematics and can determine the union, intersection, difference etc. of two given sets.

-

See for example these links for explanations on set operations: https://en.wikipedia.org/wiki/Set_(mathematics)#Basic_operations or https://snakify.org/en/lessons/sets/.

-

A list, string or tuple can be converted to a set by set(sequence_to_convert). Since sets only have unique items, the set resulting from the operation has same values as the input sequence, but with duplicates removed. This can be a way to find all unique elements.

-

For example:

-
list_uniques = list(set(list_duplicates))  # Convert list to set and back to list again with now only unique elements
-
-

Mutability

Sets are mutable. They can be changed after creation.

-

Set examples

-
-
-
-
-
-
In [6]:
-
-
-
s1 = {32, 3, 1, 86, 6, 8}
-s2 = {8, 6, 21, 7, 26}
-s1.union(s2)               # Find the union of the two sets
-
- -
-
-
- -
-
- - -
-
Out[6]:
- - - -
-
{1, 3, 6, 7, 8, 21, 26, 32, 86}
-
- -
- -
-
- -
-
-
-
In [7]:
-
-
-
s1.intersection(s2)       # Find the intersection of the two sets
-
- -
-
-
- -
-
- - -
-
Out[7]:
- - - -
-
{6, 8}
-
- -
- -
-
- -
-
-
-
In [8]:
-
-
-
list_with_duplicates = [1, 2, 3, 4, 5, 2, 2, 3, 1]
-s3 = set(list_with_duplicates)   # Create a set of the list (which removed duplicates)
-s3
-
- -
-
-
- -
-
- - -
-
Out[8]:
- - - -
-
{1, 2, 3, 4, 5}
-
- -
- -
-
- -
-
-
-
-
-
-

If a list is wanted again:

- -
-
-
-
-
-
In [9]:
-
-
-
list(s3)
-
- -
-
-
- -
-
- - -
-
Out[9]:
- - - -
-
[1, 2, 3, 4, 5]
-
- -
- -
-
- -
-
-
-
-
-
-

The in operator

The in operator can be used to check whether a certain item is contained in a sequence. The result of the evaluation is a boolean (True or False):

- -
-
-
-
-
-
In [10]:
-
-
-
2 in [1, 2, 3]
-
- -
-
-
- -
-
- - -
-
Out[10]:
- - - -
-
True
-
- -
- -
-
- -
-
-
-
In [11]:
-
-
-
'ma' in 'Denmark'
-
- -
-
-
- -
-
- - -
-
Out[11]:
- - - -
-
True
-
- -
- -
-
- -
-
-
-
In [12]:
-
-
-
'er' in 'Denmark'  
-
- -
-
-
- -
-
- - -
-
Out[12]:
- - - -
-
False
-
- -
- -
-
- -
-
-
-
-
-
-

Indexing

When elements are put into a sequences (strings, lists, tuples etc.), the individual elements gets an index assgined to them. This enables each element to be extracted.
-Indexing in Python starts from 0, while the negative index starts from -1: -image.png

-

Use square brackets []

To extract an element by indexing, use sqaure brackets []. This is the general way and works for all sequences. Strings, lists and tuples are all sequences.

- -
-
-
-
-
-
In [13]:
-
-
-
example_list = ['a', 'b', 'c', 'd']
-example_list[0]
-
- -
-
-
- -
-
- - -
-
Out[13]:
- - - -
-
'a'
-
- -
- -
-
- -
-
-
-
In [14]:
-
-
-
example_list[-2]
-
- -
-
-
- -
-
- - -
-
Out[14]:
- - - -
-
'c'
-
- -
- -
-
- -
-
-
-
In [15]:
-
-
-
example_tuple = (10, 20, 30, 40)
-example_tuple[3]
-
- -
-
-
- -
-
- - -
-
Out[15]:
- - - -
-
40
-
- -
- -
-
- -
-
-
-
-
-
-

IndexError

When trying to refer to an index that is not in the data structure, an IndexError is raised:

- -
-
-
-
-
-
In [16]:
-
-
-
example_tuple[10]
-
- -
-
-
- -
-
- - -
-
- -
-
----------------------------------------------------------------------------
-IndexError                                Traceback (most recent call last)
-<ipython-input-16-e749b52b197d> in <module>()
-----> 1 example_tuple[10]
-
-IndexError: tuple index out of range
-
-
- -
-
- -
-
-
-
-
-
-

Extracting values from dictionaries

Dictionaries differ from data structures like strings, lists and tuples since they do not have an index. Instead, a value are extracted by indexing the corresponding key:

- -
-
-
-
-
-
In [17]:
-
-
-
d = {'N': 83, 'My': 154, 'Mz': 317}
-d['My']
-
- -
-
-
- -
-
- - -
-
Out[17]:
- - - -
-
154
-
- -
- -
-
- -
-
-
-
-
-
-

Note: This means that keys in a dictionary must be unique!

-

See demonstation below, where the key 'a' is defined twice. The second defintion overwrites the first one.

- -
-
-
-
-
-
In [18]:
-
-
-
{'a': 1, 'b': 2, 'c':3, 'a': 4}
-
- -
-
-
- -
-
- - -
-
Out[18]:
- - - -
-
{'a': 4, 'b': 2, 'c': 3}
-
- -
- -
-
- -
-
-
-
-
-
-

Slicing

Slicing is the operation of extracting multiple elements at once by calling out the index range. An example for slicing of a list is shown below: -image.png

-

Note that the stop point of the slice is not included.

-

These examples are shown for lists, but the same concept works for strings and tuples. Set objects do not support indexing/slicing since they are unordered, and dictionaries cannot be sliced either as they have the key functionality instead.

-

The most common slicing operations are:

-
n[start:stop]  # elements from start to stop-1
-    n[start:]      # elements from start to the end of the list 
-    n[:stop]       # elements from the beginning to stop-1
-    n[:]           # a copy of the whole list (alternative: list.copy())
-
-

As seen, the last one creates a copy. This can be useful when working with mutable objects. For example for copying a list to mutate the copy while keeping the orginal list unchanged.

-

There is also setp mechanism that can be used:

-
a[start:stop:step] # start through not past stop, by step
-
-

Steps could be combined with the slicing operations shown above.

- -
-
-
-
-
-
-
-
-

List methods

Lists have many methods for common manipulations. Methods are recognized by the 'dot'-notation, so if a method was called method_name(), it would be used like list.method_name().

-

Some of the most common list methods are:

-
list.append(val)  # Insert val as the last element of the list, and make list one element longer
-list.pop([i])     # Remove i'th element from list and return it (if i is not provided, it defaults to last element)
-list.reverse()    # Reverse all elements in list
-
-

Note that these all mutate list.

-

The len() function which was used for strings in Session 1 also works for lists, dictionaries, tuples and sets. This is a function since it does not use 'dot'-notation.

-

Many websites have explanations about string manipulations. This is form the Python documentation itself: https://docs.python.org/3/tutorial/datastructures.html#data-structures

-

Note: Methods for common operations on dictionaires, tuples and sets also exist, but are not shown here.

- -
-
-
-
-
-
-
-
-

Copying mutable objects

When copying objects that are mutable like, lists or dictionaries, there are some things to be aware of. This is demonstrated by a list example below.

- -
-
-
-
-
-
In [19]:
-
-
-
x = [1, 2, 3]     
-y = x          # <-- This does not make y a copy of x 
-y              # It make y a pointer to the same underlying object (or id) as x has inside the computer
-
- -
-
-
- -
-
- - -
-
Out[19]:
- - - -
-
[1, 2, 3]
-
- -
- -
-
- -
-
-
-
In [20]:
-
-
-
id(x)  # Behind the scenes, the variable x gets assigned a unique object id
-
- -
-
-
- -
-
- - -
-
Out[20]:
- - - -
-
2235132766792
-
- -
- -
-
- -
-
-
-
In [21]:
-
-
-
id(y)  # y is seen to have the same underlying object id
-
- -
-
-
- -
-
- - -
-
Out[21]:
- - - -
-
2235132766792
-
- -
- -
-
- -
-
-
-
-
-
-

This means that when we mutate (or modify) y, the orignal list x gets changed as well, which is often not desired. This is because it's a pointer to the same object as y.

- -
-
-
-
-
-
In [22]:
-
-
-
y.append(89)
-y
-
- -
-
-
- -
-
- - -
-
Out[22]:
- - - -
-
[1, 2, 3, 89]
-
- -
- -
-
- -
-
-
-
In [23]:
-
-
-
x  # x also got 89 appended to it
-
- -
-
-
- -
-
- - -
-
Out[23]:
- - - -
-
[1, 2, 3, 89]
-
- -
- -
-
- -
-
-
-
-
-
-

Instead, the list x should be copied either by a the list.copy() method or by the slicing operation shown earlier.

-

An example is shown below by using the list.copy()method:

- -
-
-
-
-
-
In [24]:
-
-
-
x_new = [1, 2, 3]   # Redefining x since it was mutated above
-y_new = x_new.copy()  # Copy by a method that is built-in for lists
-y_new
-
- -
-
-
- -
-
- - -
-
Out[24]:
- - - -
-
[1, 2, 3]
-
- -
- -
-
- -
-
-
-
In [25]:
-
-
-
# Append a value to y_new 
-y_new.append(327)
-y_new
-
- -
-
-
- -
-
- - -
-
Out[25]:
- - - -
-
[1, 2, 3, 327]
-
- -
- -
-
- -
-
-
-
In [26]:
-
-
-
# x has not changed
-x_new
-
- -
-
-
- -
-
- - -
-
Out[26]:
- - - -
-
[1, 2, 3]
-
- -
- -
-
- -
-
-
-
In [27]:
-
-
-
# Print object id's as f-string 
-print(f'x_new has object id: {id(x_new)} \ny_new has object id: {id(y_new)}')
-
- -
-
-
- -
-
- - -
-
- -
-
x_new has object id: 2235134073480 
-y_new has object id: 2235134300424
-
-
-
- -
-
- -
-
-
-
-
-
-

for loops

The general syntax in a for loop is

-
for item in iterable:
-    # code goes here
-
-

Recall that an iterable is a fancy word for something that can be iterated over like a string, a list, a tuple etc.

-

So, printing numbers from 0-5 can be done like this:

- -
-
-
-
-
-
In [28]:
-
-
-
# Printing numbers from 0-5
-for i in [0, 1, 2, 3, 4, 5]:
-    print(i)
-
- -
-
-
- -
-
- - -
-
- -
-
0
-1
-2
-3
-4
-5
-
-
-
- -
-
- -
-
-
-
-
-
-

A common way of quickly generating the numbers from 0-5 instead of typing the list [0, 1, 2, 3, 4, 5] is by the range() function, which has two forms:

-
range(stop)                 # Generates numbers from 0 to stop-1
-
-
range(start, stop[, step])  # Generates numbers from start to stop-1 (step is optional)
-
- -
-
-
-
-
-
In [29]:
-
-
-
# Printing numbers from 0-5
-for i in range(6):
-    print(i**2)
-
- -
-
-
- -
-
- - -
-
- -
-
0
-1
-4
-9
-16
-25
-
-
-
- -
-
- -
-
-
-
-
-
-

As an alternatibe to iterating over a number sequence with a counter, it is also possible to just access the each value in turn.

-

Here is an example where each element of a list of strings is accessed and names string.

- -
-
-
-
-
-
In [30]:
-
-
-
g = ['batman', 'superman', 'spiderman', 'ironman', 'green lantern']
-h = []
-for string in g:          # This would be like saying: for each string in the list g
-    if len(string) > 7:   # If the current string has more than seven characters 
-        print(string)     # Print it
-
- -
-
-
- -
-
- - -
-
- -
-
superman
-spiderman
-green lantern
-
-
-
- -
-
- -
-
-
-
-
-
-

while loops

A while loop is a loop that continues until some condition is no longer satisfied.

-

Generally, a while will look like this:

-
while condition: 
-    # code goes here
-
-

Where evaluation of condition must return a boolean (True or False).

-

There must be some kind of change in condition while looping. Otherwise the loop becomes an infinite loop and runs forever (or until you stop it). An example of an infinite loop is

-
counter = 0
-while counter < 3: 
-    print(counter)     # The variable counter is never updated, so this prints forever
-
-

The counter should be updated wihtin the loop, e.g. like this:

- -
-
-
-
-
-
In [31]:
-
-
-
counter = 1
-while counter < 5: 
-    print(f'The count is {counter}')
-    counter += 1    # Update counter (this is equivalent to: counter = counter + 1)
-
- -
-
-
- -
-
- - -
-
- -
-
The count is 1
-The count is 2
-The count is 3
-The count is 4
-
-
-
- -
-
- -
-
-
-
-
-
-

A while loop can be good when the number of iterations are not known beforehand. This could be when serching for a root for an equation or finding the neutral axis of a reinforced concrete section.

-

When iterating for in such cases, convergence is not always ensured. A common way of exiting the while loop is to define a max number of iterations and then check in each loop whether this number has been reached. If it has, then the loop should break.

-

A similar logic to whileloops could be done with by for loops, but a while loop is cleaner for some purposes and can help to clarify the intetn of the code.

- -
-
-
-
-
-
-
-
-

List comprehensions

List comprehensions are another way of writing a for loop. It can be done in one line and is often cleaner and more readable for simple iteration.

-

General form

The general form of the simplest list comprehension is

-
result_list = [expression for item in iterable]
-
-
    -
  • iterable is a sequence that can be iterated over, this could be a list, a string, a tuple etc.
  • -
  • item is the counter for the iterable, think of this as the i'th element
  • -
  • expression can be anything, but will often include the item
  • -
-
- -
-
-
-
-
-
In [32]:
-
-
-
L1 = [12, 215, 31, 437, 51]
-L2 = [2*i for i in L1]       # List comprehension to multiply each element of L1 by 2
-L2
-
- -
-
-
- -
-
- - -
-
Out[32]:
- - - -
-
[24, 430, 62, 874, 102]
-
- -
- -
-
- -
-
-
-
-
-
-

Note that 2 * L1 will not create the same output, but instead repeat the list as seen below. To get a vectorized behavior like that, we could have use numpy, which is a third party library for numerical compuations similar to Matlab.

- -
-
-
-
-
-
In [33]:
-
-
-
2 * L1
-
- -
-
-
- -
-
- - -
-
Out[33]:
- - - -
-
[12, 215, 31, 437, 51, 12, 215, 31, 437, 51]
-
- -
- -
-
- -
-
-
-
-
-
-

List comprehension with if statement

The general form of a list comprehension with a conditional is

-
result_list = [expression for item in iterable if condition]
-
-
    -
  • iterable is a sequence that can be iterated over, this could be a list, a string, a tuple etc.
  • -
  • condition is a logical condition, e.g. "item > 3", which returns a boolean (True/False). This can act as as filter.
  • -
  • result_list is a new list containing expression for each item that fulfilled the condition
  • -
-
- -
-
-
-
-
-
In [34]:
-
-
-
x = [1, 2, 3, 4, 5]                      # Define a list (iterable)
-result_list = [i for i in x if i > 3]    # Filter list with list comprehension by use of condition
-result_list
-
- -
-
-
- -
-
- - -
-
Out[34]:
- - - -
-
[4, 5]
-
- -
- -
-
- -
-
-
-
-
-
-

Form with conditional if and else statement

The general form of a list comprehension with a conditional if and else is:

-
result_list = [expression1 if condition else expression2 for item in iterable]
-
-
    -
  • iterable is a sequence that can be iterated over, this could be a list, a string, a tuple etc.
  • -
  • condition is a logical condition, e.g. "item > 3", which returns a boolean (True/False). This can act as as filter.
  • -
  • result_list is a new list containing elements where each item depends on condition. If it is True, expression1 is put in. Else expression2 is put in.
  • -
-
- -
-
-
-
-
-
In [35]:
-
-
-
V = [3, 62, 182, 26, 151, 174]
-
-# Set all elements of V that are less than 100 equal to 0
-W = [0 if i < 100 else i for i in V]
-W
-
- -
-
-
- -
-
- - -
-
Out[35]:
- - - -
-
[0, 0, 182, 0, 151, 174]
-
- -
- -
-
- -
-
-
-
-
-
-

List comprehensions are generally computationally faster than regular for loops and also faster to type.

- -
-
-
-
-
-
-
-
-

Exercise 1

Extract the last and first element from the list

-
L1 = [10, 20, 30, 40, 50, 60]
-
- -
-
-
-
-
-
-
-
-

Exercise 2

Extract the following list from L1 defined above by use of slicing.

-
[20, 30, 40]
-
- -
-
-
-
-
-
-
-
-

Exercise 3

Create a list of the unique values from the following list:

-
L2 = ['Hi', 'Hello', 'Hi!', 'Hey', 'Hi', 'hey', 'Hey']
-
- -
-
-
-
-
-
-
-
-

Exercise 4

Given the dictionary

-
d = {2: 122, 3: 535, 't': 'T', 'rom': 'cola'}
-
-

Play around with extracting the values by calling out the keys for all key/value pairs.

- -
-
-
-
-
-
-
-
-

Exercise 5

Given the list

-
n = [23, 73, 12, 84]
-
-

Create a for loop that prints:

-
'23 sqaured is 529'
-'73 sqaured is 5329'
-'12 sqaured is 144'
-'84 sqaured is 7056'
-
- -
-
-
-
-
-
-
-
-

Exercise 6

Use a list comprehension to create a new list with areas of the circles that have diameters defined by

-
diameters = [10, 12, 16, 20, 25, 32]
-
- -
-
-
-
-
-
-
-
-

Exercise 7

From the following list, create a new list containing only the elements that have exactly five characters.

-
phonetic_alphabet = ['Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot']
-
- -
-
-
-
-
-
-
-
-

Exercise 8

Find the intersection of the two sets (elements that occur in both sets)

-
s1 = {'HE170B', 'HE210B', 'HE190A', 'HE200A', 'HE210A', 'HE210A'}
-
-s2 = {'HE200A', 'HE210A', 'HE240A', 'HE200A', 'HE210B', 'HE340A'}
-
- -
-
-
-
-
-
-
-
-

Exercise 9

Create a variable fy and set it equal to 435.

-

Given the tuple below, create a list where each value is:

-
    -
  • The value itself if the value is below fy
  • -
  • fy if the value is larger than fy
  • -
-
rebar_stresses = (125, 501, 362, 156, 80, 475, 489)
-
- -
-
-
-
-
-
-
-
-

Exercise 10

Given the tuple below, create a list where each value is:

-
    -
  • 0 if the value is positive
  • -
  • The value itself is the value is negative and larger than -25
  • -
  • -25 if the value is lower than -25
  • -
-
T1 = (-18, -27, 2, -21, -15, 5)
-
- -
-
-
-
-
- - - - - - diff --git a/Session 2 - Data Structures/Session 2 - Exercise solutions.html b/Session 2 - Data Structures/Session 2 - Exercise solutions.html deleted file mode 100644 index 200cee1..0000000 --- a/Session 2 - Data Structures/Session 2 - Exercise solutions.html +++ /dev/null @@ -1,12331 +0,0 @@ - - - -Session 2 - Exercise solutions - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
-
-

Session 2 - Exercise solutions

There are often many ways to do the same thing, so these are just one way of solving the problems.

-

Exercise 1

-
-
-
-
-
-
In [1]:
-
-
-
L1 = [10, 20, 30, 40, 50, 60]
-first_elem = L1[0]
-last_elem = L1[-1]
-print(f'First element of L1 is {first_elem} and last elements of L1 is {last_elem}')
-
- -
-
-
- -
-
- - -
-
- -
-
First element of L1 is 10 and last elements of L1 is 60
-
-
-
- -
-
- -
-
-
-
-
-
-

Exercise 2

-
-
-
-
-
-
In [2]:
-
-
-
L1_sliced = L1[1:4]
-print(L1_sliced)
-
- -
-
-
- -
-
- - -
-
- -
-
[20, 30, 40]
-
-
-
- -
-
- -
-
-
-
-
-
-

Exercise 3

-
-
-
-
-
-
In [3]:
-
-
-
L2 = ['Hi', 'Hello', 'Hi!', 'Hey', 'Hi', 'hey', 'Hey']
-L2_unique = list(set(L2))
-print(L2_unique)
-
- -
-
-
- -
-
- - -
-
- -
-
['Hey', 'Hello', 'Hi', 'Hi!', 'hey']
-
-
-
- -
-
- -
-
-
-
-
-
-

Exercise 4

-
-
-
-
-
-
In [4]:
-
-
-
d = {2: 122, 3: 535, 't': 'T', 'rom': 'cola'}
-print(d[2])       # Print value corresponding to key 2
-
- -
-
-
- -
-
- - -
-
- -
-
122
-
-
-
- -
-
- -
-
-
-
In [5]:
-
-
-
print(d['t'])       # Print value corresponding to key 't'
-
- -
-
-
- -
-
- - -
-
- -
-
T
-
-
-
- -
-
- -
-
-
-
In [6]:
-
-
-
print(f"I like rom and {d['rom']}, but mostly the rom")
-
- -
-
-
- -
-
- - -
-
- -
-
I like rom and cola, but mostly the rom
-
-
-
- -
-
- -
-
-
-
-
-
-

Exercise 5

-
-
-
-
-
-
In [7]:
-
-
-
n = [23, 73, 12, 84]
-
-# By using conventional for loop
-for elem in n:
-    print(f'{elem} squared is {elem**2}')
-
- -
-
-
- -
-
- - -
-
- -
-
23 squared is 529
-73 squared is 5329
-12 squared is 144
-84 squared is 7056
-
-
-
- -
-
- -
-
-
-
-
-
-

Exercise 6

-
-
-
-
-
-
In [8]:
-
-
-
diameters = [10, 12, 16, 20, 25, 32]
-areas = [3.1459 * dia**2 / 4 for dia in diameters]   # To use pi the math module would need to be imported
-print(areas)
-
- -
-
-
- -
-
- - -
-
- -
-
[78.64750000000001, 113.25240000000001, 201.3376, 314.59000000000003, 491.546875, 805.3504]
-
-
-
- -
-
- -
-
-
-
In [9]:
-
-
-
# Convert elements to strings and round down
-print([f'{area:.0f}' for area in areas])
-
- -
-
-
- -
-
- - -
-
- -
-
['79', '113', '201', '315', '492', '805']
-
-
-
- -
-
- -
-
-
-
-
-
-

Exercise 7

-
-
-
-
-
-
In [10]:
-
-
-
phonetic_alphabet = ['Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot']
-words_of_5_chars = [word for word in phonetic_alphabet if len(word) == 5]
-print(words_of_5_chars)
-
- -
-
-
- -
-
- - -
-
- -
-
['Alpha', 'Bravo', 'Delta']
-
-
-
- -
-
- -
-
-
-
-
-
-

Exercise 8

-
-
-
-
-
-
In [11]:
-
-
-
s1 = {'HE170B', 'HE210B', 'HE190A', 'HE200A', 'HE210A', 'HE210A'}
-s2 = {'HE200A', 'HE210A', 'HE240A', 'HE200A', 'HE210B', 'HE340A'}
-s_intersection = s1.intersection(s2)
-print(s_intersection)
-
- -
-
-
- -
-
- - -
-
- -
-
{'HE200A', 'HE210B', 'HE210A'}
-
-
-
- -
-
- -
-
-
-
-
-
-

Exercise 9

-
-
-
-
-
-
In [12]:
-
-
-
rebar_stresses = (125, 501, 362, 156, 80, 475, 489)
-fy = 435
-sigma_s = [stress if stress <= 435 else fy for stress in rebar_stresses]
-print(sigma_s)
-
- -
-
-
- -
-
- - -
-
- -
-
[125, 435, 362, 156, 80, 435, 435]
-
-
-
- -
-
- -
-
-
-
-
-
-

Exercise 10

-
-
-
-
-
-
In [13]:
-
-
-
T1 = (-18, -27, 2, -21, -15, 5)
-
-T2 = []
-for val in T1:
-    if val > 0:
-        T2.append(0)
-    elif 0 > val > -25:
-        T2.append(val)
-    else:
-        T2.append(-25)
-
-print(T2)
-
- -
-
-
- -
-
- - -
-
- -
-
[-18, -25, 0, -21, -15, 0]
-
-
-
- -
-
- -
-
-
-
In [14]:
-
-
-
# Alternative by list comprehension with chained if's
-T3 = [0 if val > 0 else val if 0 > val > -25 else -25 for val in T1]
-print(T3)
-
- -
-
-
- -
-
- - -
-
- -
-
[-18, -25, 0, -21, -15, 0]
-
-
-
- -
-
- -
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
- - - - - - diff --git a/Session 3 - Functions/Session 3 - Exercise Solutions.html b/Session 3 - Functions/Session 3 - Exercise Solutions.html deleted file mode 100644 index 4679429..0000000 --- a/Session 3 - Functions/Session 3 - Exercise Solutions.html +++ /dev/null @@ -1,13817 +0,0 @@ - - - - -Session 3 - Solutions - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-

Session 3 - Solutions

-
-
-
-
-
-
-

Exercise 1

-
-
-
-
-
-
In [4]:
-
-
-
def circle_area(r):
-    '''Return circle area'''
-    return pi * r**2 / 4 
-
-
-# Import pi from the math library
-from math import pi
-
-# Test function with input with r=20, save returned value
-A20 = circle_area(r=20)
-
-# Print the result
-print(A20)
-
- -
-
-
- -
-
- - -
- -
- - -
-
314.1592653589793
-
-
-
- -
-
- -
-
-
-
-
    -
  • Note: Calling the funtion as circle_area(20) and circle_area(r=20) is the same.
  • -
- -
-
-
-
-
-
-

Exercise 2

-
-
-
-
-
-
In [5]:
-
-
-
def circle_areas(radii):
-    # Use list comprehension to return a list of radii
-    return [circle_area(r) for r in radii]
-
-
-# Define list of radii
-list_of_radii = [10, 12, 16, 20, 25, 32]
-
-# Call function with input list
-print(circle_areas(list_of_radii))
-
- -
-
-
- -
-
- - -
- -
- - -
-
[78.53981633974483, 113.09733552923255, 201.06192982974676, 314.1592653589793, 490.8738521234052, 804.247719318987]
-
-
-
- -
-
- -
-
-
-
-
    -
  • Note 1: Call to function circle_area defined in Exercise instead of defining the expression pi * r**2 / 4 again. The concept of functions calling other functions can be used to make modular programs that are easy to follow and maintain. Each function only needs to do a small thing in itself.

    -
  • -
  • Note 2: The input parameter when the function was defined was a list called radii, but the when the function was called, the input list was called list_of_radii. Thus, the input parameters passed into a function do not need to have the same name as when the function is defined.

    -
  • -
- -
-
-
-
-
-
-

Exercise 3

-
-
-
-
-
-
In [6]:
-
-
-
def is_pile_long(pile_lengths):
-    
-    # Create True or False value by list comprehension with if/else
-    return [True if length >= 5 else False for length in pile_lengths]
-
-
-# Define a list of some pile lengths to test
-piles = [4.51, 6.12, 4.15, 7.31, 5.01, 4.99, 5.00]
-
-# Call function 
-print(is_pile_long(piles))
-
- -
-
-
- -
-
- - -
- -
- - -
-
[False, True, False, True, True, False, True]
-
-
-
- -
-
- -
-
-
-
-
    -
  • Note: The built-in boolean values True and False must be capitalized to be recognized by Python. If for example true is used, Python will assume that it is a variable that you have named true and give an error if it is not defined. All editors will highlight the special words recognized by Python, so if the editor does not highlight, it's a sign that something is wrong.
  • -
- -
-
-
-
-
-
-

Exercise 4

-
-
-
-
-
-
In [7]:
-
-
-
# Import sqrt from the math library
-from math import sqrt
-
-
-def dist_point_to_line(x, y, x1, y1, x2, y2):
-    '''Return distance between a point and a line defined by two points.
-
-    Args:
-        x  : x-coordinate of point 
-        y  : y-coordinate of point
-        x1 : x-coordinate of point 1 defining the line
-        y1 : y-coordinate of point 1 defining the line
-        x2 : x-coordinate of point 2 defining the line
-        y2 : y-coordinate of point 2 defining the line
-
-    Returns:
-           The distance between the point and the line        
-    '''
-    return abs( (y2 - y1) * x - (x2 - x1) * y + x2 * y1 - x1 * y2) / sqrt((x2 - x1)**2 + (y2 - y1)**2)
-
-
-# Call the function with the two test cases
-print(dist_point_to_line(2, 1, 5, 5, 1, 6))
-print(dist_point_to_line(1.4, 5.2, 10.1, 2.24, 34.142, 13.51))
-
- -
-
-
- -
-
- - -
- -
- - -
-
4.608176875690327
-6.3728037317960675
-
-
-
- -
-
- -
-
-
-
-
    -
  • Note: abs() used to get the numerical value.
  • -
- -
-
-
-
-
-
-

Exercise 5

-
-
-
-
-
-
In [5]:
-
-
-
# Two points defining the line
-x1, y1, x2, y2 = 2, 3, 8, 7
-
-# Define points for distance to line calculation
-x_coords = [4.1, 22.2, 7.7, 62.2, 7.8, 1.1]
-y_coords = [0.3, 51.2, 3.5, 12.6, 2.7, 9.8]
-
-# Call function dist_point_to_line for all (x, y) points
-distances = [dist_point_to_line(x_coords[i], y_coords[i], x1, y1, x2, y2) for i in range(len(x_coords))]
-
-# Print new list
-print(distances)
-
- -
-
-
- -
-
- - -
- -
- - -
-
[3.4114062067851583, 28.899880223334442, 2.745765971314884, 25.405268987115498, 3.466876226407682, 6.157172178100044]
-
-
-
- -
-
- -
-
-
-
-

A way that I like better than the above is using the zip function, which takes the two coordinate lists and puts -them side by side, almost like a zipper. It is often more clean and expressive than using a loop counter i over the length of one of the lists.

- -
-
-
-
-
-
In [6]:
-
-
-
# Solution using zip
-distances_zip = [dist_point_to_line(x, y, x1, y1, x2, y2) for x, y in zip(x_coords, y_coords)]
-print(distances_zip)
-
- -
-
-
- -
-
- - -
- -
- - -
-
[3.4114062067851583, 28.899880223334442, 2.745765971314884, 25.405268987115498, 3.466876226407682, 6.157172178100044]
-
-
-
- -
-
- -
-
-
-
In [7]:
-
-
-
# Results rounded to two decimals using the round() function
-print([round(dist, 2) for dist in distances])
-
- -
-
-
- -
-
- - -
- -
- - -
-
[3.41, 28.9, 2.75, 25.41, 3.47, 6.16]
-
-
-
- -
-
- -
-
-
-
-

Exercise 6

-
-
-
-
-
-
In [8]:
-
-
-
def polygon_area(xv, yv, signed=False):
-    ''' Return the area of a non-self-intersecting polygon given the coordinates of its vertices'''
-
-    # Perform shoelace multiplication
-    a1 = [xv[i] * yv[i+1] for i in range(len(xv)-1)]
-    a2 = [yv[i] * xv[i+1] for i in range(len(yv)-1)]
-
-    # Check if area should be signed and return area
-    if signed:          # <--- Same as "if signed == True:"
-        return 1/2 * ( sum(a1) - sum(a2) )
-    else:
-        return 1/2 * abs( sum(a1) - sum(a2) )
-
-
-# Define the polygon vertices to test
-x = [3, 4, 7, 8, 8.5, 3]
-y = [5, 3, 0, 1, 3, 5]
-
-# Calculate area by calling the function
-A = polygon_area(x, y)
-
-# Print the area
-print(A)
-
- -
-
-
- -
-
- - -
- -
- - -
-
12.0
-
-
-
- -
-
- -
-
-
-
-

Exercise 7

-
-
-
-
-
-
In [9]:
-
-
-
def polygon_centroid(x, y):
-
-    # Initialize empty lists for holding summation terms
-    cx, cy = [], []
-    
-    # Loop over vertices and put the summation terms in the lists
-    for i in range(len(x)-1):
-        
-        # Compute and append summation terms to each list
-        cx.append((x[i] + x[i+1]) * (x[i] * y[i+1] - x[i+1] * y[i]))
-        cy.append((y[i] + y[i+1]) * (x[i] * y[i+1] - x[i+1] * y[i]))
-
-    # Calculate the signed polygon area by calling already defined function
-    A = polygon_area(x, y, signed=True)    
-        
-    # Sum summation terms and divide by 6A to get coordinates
-    Cx = sum(cx) / (6*A)
-    Cy = sum(cy) / (6*A)
-    
-    return Cx, Cy    
-
-
-# Define lists of vertex coordinates for testing
-x = [3, 4, 7, 8, 8.5, 3]
-y = [5, 3, 0, 1, 3, 5]
-
-# Compute centroid by calling function, store in two variables
-cx, cy = polygon_centroid(x, y)
-
-# Print result
-print(cx, cy)
-
- -
-
-
- -
-
- - -
- -
- - -
-
6.083333333333333 2.5833333333333335
-
-
-
- -
-
- -
-
-
-
In [10]:
-
-
-
# Print result as text with formatted decimals
-print(f'Polygon centroid is at (Cx, Cy) = ({cx:.1f}, {cy:.1f})')
-
- -
-
-
- -
-
- - -
- -
- - -
-
Polygon centroid is at (Cx, Cy) = (6.1, 2.6)
-
-
-
- -
-
- -
-
-
-
-

Appetizer for next time - Plotting

Plotting the solution for the polygon centroid exercise:

-
-
-
-
-
-
In [13]:
-
-
-
import matplotlib.pyplot as plt
-
-# Plot polygon from exercises with centroid and area
-plt.plot(x, y, '.-', label='Polygon')
-plt.plot(cx, cy, 'x', label='Centroid')
-
-# Plot coordinates of centroid as text
-plt.annotate(f'({cx:.1f}, {cy:.1f})', xy=(cx, cy),
-             xytext=(cx, cy), textcoords='offset points')
-
-# Set labels, titles and legend
-plt.xlabel('x')
-plt.ylabel('y')
-plt.title(f'Polygon with A={A}')
-plt.legend()
-plt.show()
-
- -
-
-
- -
-
- - -
- -
- - - - -
- -
- -
- -
-
- -
-
-
-
-

Function for plotting an arbitrary polygon

The plotting code above could be turned into a function to plot an arbitrary polygon together with its center of gravity and put its area in the title:

- -
-
-
-
-
-
In [12]:
-
-
-
def plot_polygon(xv, yv, plot_centroid=True):   
-    '''Plot the polygon with the specified vertex coordinates. 
-    
-    The plot is created with legend and the computed area of the 
-    polygon shown in the title. The plot shows the centroid of 
-    the polygon by default, but this can be turned off by setting 
-    plot_centroid=False.
-    
-    Args:
-        xv (list)            : x-coordinates of polygon vertices
-        yv (list)            : y-coordinates of polygon vertices
-        plot_centroid (bool) : Plot centroid of polygon (Cx, Cy).
-                               Defaults to plotting the centroid.  
-    '''
-    
-    # Compute area of polygon
-    A = polygon_area(xv, yv)
-    
-    # Compute polygon centroid
-    cx, cy = polygon_centroid(xv, yv)
-    
-    # Plot the polygon
-    plt.plot(xv, yv, '.-', label='Polygon')
-    
-    # Plot the centroid with coordinates if that was chosen
-    if plot_centroid:   # <- Eqiuvalent to: if plot_centroid == True:
-        plt.plot(cx, cy, 'x', label='Centroid')
-        plt.annotate(f'({cx:.1f}, {cy:.1f})', xy=(cx, cy),
-                     xytext=(cx, cy), textcoords='offset points')
-    
-    # Set labels, titles and legend
-    plt.xlabel('x')
-    plt.ylabel('y')
-    plt.title(f'Polygon with A={A}')
-    plt.legend()
-    plt.show()
-    
-    
-# Define vertices of some random polygon
-x_polygon = [-5, 2, 5, 7, 8, 5, 1, -5]
-y_polygon = [-2, -15, -13, -10, -6, 2, 5, -2]
-
-# Call function to plot polygon with area and centroid shown
-plot_polygon(x_polygon, y_polygon)
-
- -
-
-
- -
-
- - -
- -
- - - - -
- -
- -
- -
-
- -
-
-
-
-
    -
  • Note 1: Optional input parameter plot_centroid has True as default argument. True is immutable.
  • -
  • Note 2: The area of the polygon is actually calculated both in the polygon_area() function and in the polygon_centroid() function, which is maybe not so clean. A way to overcome this could be to have polygon_centroid() return the area. Thereby running the polygon_area()function alone would not be necessary.
  • -
- -
-
-
-
-
- - - - - - diff --git a/Session 3 - Functions/Session 3 - Functions.html b/Session 3 - Functions/Session 3 - Functions.html deleted file mode 100644 index 55908de..0000000 --- a/Session 3 - Functions/Session 3 - Functions.html +++ /dev/null @@ -1,13602 +0,0 @@ - - - - -Session 3 - Explanation and exercises - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-

Session 3

Functions

A function is a block of code that is first defined, and thereafter can be called to run as many times as needed. A function might have arguments, some of which can be optional if a default value is specified. -A function is called by parantheses: function_name(). Arguments are placed inside the paranthes and comma separated if there are more than one. -Similar to f(x, y) from mathematics.

-

A function can return one or more values to the caller. The values to return are put in the return statement, which ends the function. If no return statement is given, the function will return None.

-

The general syntax of a function is:

-
def function_name(arg1, arg2, default_arg1=0, default_arg2=None):
-    '''This is the docstring 
-
-    The docstring explains what the function does, so it is like a multi line comment. It does have to be here, 
-    but it is good practice to use them to document the code. They are especially useful for more complicated 
-    functions.
-    Arguments could be explained togehter with their types (e.g. strings, lists, dicts etc.).
-    '''
-
-    # Function code goes here
-
-    # Possible 'return' statement terminating the function. If 'return' is not specified, function returns None.
-    return return_val1, return_val2
-
-

If multiple values are to be returned, they can be separeted by commas. The returned entity will by default be a tuple.

-

Note that when using default arguments, it is good practice to only use immutable types as defaults. An example further below will demonstrate why this is recmommended.

- -
-
-
-
-
-
-

Basic functions

A simple function with one argument is defined below.

- -
-
-
-
-
-
In [1]:
-
-
-
def f(x):
-    return 6.25 + x + x**2
-
- -
-
-
- -
-
-
-
-

Note: No code has been executed yet. It has merely been defined and ready to run when called.

-

Calling the function with an argument returns:

- -
-
-
-
-
-
In [3]:
-
-
-
print(f(5))
-
- -
-
-
- -
-
- - -
- -
- - -
-
36.25
-
-
-
- -
-
- -
-
-
-
-

If we define a function without returning anything, it returns None:

- -
-
-
-
-
-
In [7]:
-
-
-
def first_char(word):
-    word[0]    # <--- No return statment, function returns None
-    
-
-a = first_char('hello')   # Variable a will be equal to None
-print(a)
-
- -
-
-
- -
-
- - -
- -
- - -
-
None
-
-
-
- -
-
- -
-
-
-
-

Often a return value is wanted from a function, but there could be scenarios where it is not wanted. E.g. if you want to mutate a list by the function. Consider this example:

- -
-
-
-
-
-
In [12]:
-
-
-
def say_hello_to(name):
-    ''' Say hello to the input name  '''
-    print(f'Hello {name}')
-    
-
-say_hello_to('Anders')           # <--- Calling the function prints 'Hello {name}'
-r = say_hello_to('Anders')
-print(r)                         # <--- Prints None, since function does not return anyhing 
-
- -
-
-
- -
-
- - -
- -
- - -
-
Hello Anders
-None
-
-
-
- -
-
- -
-
-
-
-

The function was still useful even though it did not return anything.

- -
-
-
-
-
-
-

Local vs. global variables

    -
  • Global variables: Variables defined outside a function
  • -
  • Local variables: Varaiables defined inside a function
  • -
-

Local variables cannot be accessed outside the function. By returning a local variable and saving it into a global variable we can use the result outside the function, in the global namespace.

- -
-
-
-
-
-
-

Default arguments

A little side note

A good practice is to use only immutable types as default arguments. Python will allow mutable arguments to be given as defaults, but the behavior can be confusing if the default argument is mutated within the function.

-

The enumerate function

The enumerate function is useful whenever you want to loop over an iterable together with the index of each value:

- -
-
-
-
-
-
In [18]:
-
-
-
letters = ['a', 'b', 'c', 'd', 'c']
-for idx, letter in enumerate(letters):
-    print(idx, letter)
-
- -
-
-
- -
-
- - -
- -
- - -
-
0 a
-1 b
-2 c
-3 d
-4 c
-
-
-
- -
-
- -
-
-
-
In [23]:
-
-
-
for idx, letter in enumerate(letters, start=1):   # Starting at 1 (internally, enumerate has start=0 set as default)
-    print(idx, letter)
-
- -
-
-
- -
-
- - -
- -
- - -
-
1 a
-2 b
-3 c
-4 d
-5 c
-
-
-
- -
-
- -
-
-
-
-

The zip function

The zipfunction is useful when you want to put two lists up beside eachother and loop over them.

- -
-
-
-
-
-
In [22]:
-
-
-
diameters = [10, 12, 16, 20, 25]                    
-areas = [3.14 * (d/2)**2 for d in diameters]
-
-for d, A in zip(diameters, areas):
-    print(d, A)
-
- -
-
-
- -
-
- - -
- -
- - -
-
10 78.5
-12 113.04
-16 200.96
-20 314.0
-25 490.625
-
-
-
- -
-
- -
-
-
-
-

Imports

Libraries

A quick overview of imports of libraries in Python, here shown for the math library:

-
import math            # Let's you access everything in the math library by dot-notation (e.g math.pi)  
-from math import pi    # Let's you use pi directly
-from math import *     # Let's you use everything in the math library directly
-
-

The last one is not considered good practice, since varaibles will be untracable. It can be good for making quick tests though.

-

Your own modules

You can also import your own .py files this way and access the functions inside them. It is easiest if the file to import is located in the same folder as the .py file you want to import to. Note that Python files .py are normally called modules.

-

An example:

-
import my_module      # my_module could be your own python file located in same directory
-
-

If you have a function inside my_module called my_func, you can now call it as my_module.my_func().

- -
-
-
-
-
-
-

Exercise 1

Finish the function below that takes a radius as input and make it return the circle area.

-
def circle_area(r):
-    '''Return circle area'''
-    # Your code goes here
-
-

Try to call it to see if it works. If you wan to access pi to avoid typing it out yourself, put the line from math import pi at some point before calling the function.

- -
-
-
-
-
-
-

Exercise 2

Write a function that takes a list radii as input and returns a list of the corresponding circle areas. Try to set it up from scrath and test it.

-

You can use the function from the previous exercise if you want.

- -
-
-
-
-
-
-

Exercise 3

Write the function described in the docstring below.

-
def is_pile_long(pile_lengths):
-   ''' Return a list with elements `True` for all piles longer than or equal to 5m, `False` otherwise.
-
-   Args:
-       pile_length : A list containing pile lengths   
-
-   Example: 
-       is_pile_long([[4.51, 6.12, 4.15, 7.31, 5.01, 4.99, 5.00]])
-           ---> [False, True, False, True, True, False, True] 
-   '''
-   # Your code goes here
-
- -
-
-
-
-
-
-

Exercise 4

Finish the function below so it does as described in the docstring. Remember to import the sqrt function from the math module.

-
-
def dist_point_to_line(x, y, x1, y1, x2, y2):
-    '''Return distance between a point and a line defined by two points.
-
-    Args:
-        x  : x-coordinate of point 
-        y  : y-coordinate of point
-        x1 : x-coordinate of point 1 defining the line
-        y1 : y-coordinate of point 1 defining the line
-        x2 : x-coordinate of point 2 defining the line
-        y2 : y-coordinate of point 2 defining the line
-
-    Returns:
-           The distance between the point and the line        
-    '''
-    # Your code goes here
-
-
-

The distance between a point $(x, y)$ and a line passing through points $(x_1, y_1)$ and $(x_2, y_2)$ can be found as

-\begin{equation*} -\textrm{ distance}(P_1, P_2, (x, y)) = \frac{|(y_2-y_1)x - (x_2-x_1)y + x_2 y_1 - y_2 x_1|}{\sqrt{ (x_2-x_1)^2 + (y_2-y_1)^2 }} -\end{equation*}

Call the function to test if it works. Some examples to test against:

-
dist_point_to_line(2, 1, 5, 5, 1, 6)                      -->   4.61 
-dist_point_to_line(1.4, 5.2, 10.1, 2.24, 34.142, 13.51)   -->   6.37
-
- -
-
-
-
-
-
-

Exercise 5

Given a line defined by points $(x_1, y_1)=(2, 3)$ and $(x_2, y_2)=(8, 7)$, compute the distance to the points with the coordinates below and put the results into a list

-
x_coords = [4.1, 22.2, 7.7, 62.2, 7.8, 1.1]
-y_coords = [0.3, 51.2, 3.5, 12.6, 2.7, 9.8]
-
-

You can either use a list comprehension or create a traditional for loop where results get appended to the list in every loop.

- -
-
-
-
-
-
-

Exercise 6

Create a function that calculates the area of a simple (non-self-intersecting) polygon by using the so-called Shoelace Formula

-$$ A_p = \frac{1}{2} \sum_{i=0}^{n-1} (x_i y_{i+1} - x_{i+1} y_i) $$

The area is signed depending on the ordering of the polygon being clockwise or counter-clockwise. The numerical value of the formula will always be equal to the actual area.

-

The function should take three input parameters:

-
    -
  • xv - list of x-coordinates of all vertices
  • -
  • yv - list of y-coordinates of all vertices
  • -
  • signed - boolean value that dictates whether the function returns the signed area or the actual area. Default should be actual area.
  • -
-

Assume that the polygon is closed, i.e. the first and last elements of xv are identical and the same is true for yv.

-

A function call with these input coordinates should return 12.0:

-
x = [3, 4, 7, 8, 8.5, 3]
-y = [5, 3, 0, 1, 3, 5]
-
-

Source: https://en.wikipedia.org/wiki/Polygon#Area

- -
-
-
-
-
-
-

Exercise 7

Write a function that calculates and returns the centroid $(C_x, C_y)$ of a polygon by using the formula:

-$$ C_x = \frac{1}{6A} \sum_{i=0}^{n-1} (x_i+x_{i+1}) (x_i y_{i+1} - x_{i+1} y_i) $$$$ C_y = \frac{1}{6A} \sum_{i=0}^{n-1} (y_i+y_{i+1}) (x_i y_{i+1} - x_{i+1} y_i) $$

x and y are lists of coordinates of a closed simple polygon.

-

Here, $A$ is the signed area. When you need $A$, call the function from the previous exercise to get it. Be sure to call it with the non-default signed=True.

-

A function call with the input coordinates below should return (6.083, 2.583):

-
x = [3, 4, 7, 8, 8.5, 3]
-y = [5, 3, 0, 1, 3, 5]
-
-

Source: https://en.wikipedia.org/wiki/Centroid#Of_a_polygon

- -
-
-
-
-
-
-

If you are up for more

Try to write a function that can calculate the elastic centroid of a section with materials of two different stiffness values. E.g. a reinforced polygon concrete section.

- -
-
-
-
-
- - - - - - diff --git a/Session 4 - Plotting/Session 4 - Exercise Solutions.html b/Session 4 - Plotting/Session 4 - Exercise Solutions.html deleted file mode 100644 index e604ceb..0000000 --- a/Session 4 - Plotting/Session 4 - Exercise Solutions.html +++ /dev/null @@ -1,12374 +0,0 @@ - - - -Session 4 - Solutions - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
In [1]:
-
-
-
import matplotlib.pyplot as plt
-
- -
-
-
- -
-
-
-
-
-
-

Exercise 1.1 - 1-5

-
-
-
-
-
-
In [2]:
-
-
-
# --- EXERCISE 1.1 ---
-x = [1, 3, 6, 9, 16]
-y = [7, 3, 7, 1, 5]
-
-plt.plot(x, y, 'k.-', label='Original curve')
-
-
-# --- EXERCISE 1.2 ---
-plt.title('Title with random equation for showcase $c = \sqrt{a^2+b^2}$')
-
-
-# --- EXERCISE 1.3 ---
-plt.xlabel('This is the xlabel $x$')
-plt.ylabel('This is the ylabel $y$')
-
-
-# --- EXERCISE 1.4 ---
-y2 = [9, 5, 5, 2, 6]
-y3 = [4, 6, 2, 6, 8]
-y4 = [1, 8, 1, 3, 2]
-
-plt.plot(x, y2, label='Second curve')
-plt.plot(x, y3, label='Third curve')
-plt.plot(x, y4, label='Fourth curve')
-
-# --- EXERCISE 1.5 ---
-# The labels in the plot commands above were
-# added as part of this exercise
-
-plt.legend()
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Some addtional info

Almost every aspect of the figure can be modified. For instance, the legend is located on top of the plot. It placed it in the location where there is least overlap, but still not ideal.

-

An explicit location can be specified as shown in the documentation: https://matplotlib.org/api/_as_gen/matplotlib.pyplot.legend.html

-

It basically says that you can do:

-
plt.legend(loc='inset_location_string_or_code')
-
-

The available location strings/codes from the documentaion are shown in the table below. If not specified it will be set to loc='best'.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Location StringLocation Code
'best'0
'upper right'1
'upper left'2
'lower left'3
'lower right'4
'right'5
'center left'6
'center right'7
'lower center'8
'upper center'9
'center'10
-

However, these locations are all inside the Axes (i.e. the actual plotting area). If we want the legend outside the plot, we could do it like this:

-

Note that I did not know how to do this before I made the exercise myself. I just googled for the solution.

- -
-
-
-
-
-
In [3]:
-
-
-
x = [1, 3, 6, 9, 16]
-y = [7, 3, 7, 1, 5]
-y2 = [9, 5, 5, 2, 6]
-y3 = [4, 6, 2, 6, 8]
-y4 = [1, 8, 1, 3, 2]
-plt.plot(x, y, 'k.-', label='Original curve')
-plt.plot(x, y2, label='Second curve')
-plt.plot(x, y3, label='Third curve')
-plt.plot(x, y4, label='Fourth curve')
-
-# Set legend location via 'bbox_to_anchor'
-plt.legend(bbox_to_anchor=(1.0, 0.5), loc='center left')
-# The numbers in paranthesis are in fractions of figure width and height
-
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Exercise 1.6

Since we did not save the plot from Exercise 1.1-1.5 to a figure object, we need to repeat the code to create a new plot. This is becuase the plt.show() command shows and 'exhausts' the plot so it is inaccessible afterwards, unless it is saved in a figure object.

-

So, we repeat the code, this time saving the plots to a figure object.

- -
-
-
-
-
-
In [4]:
-
-
-
# Create figure object to store plots inside
-fig = plt.figure()
-
-# ---REPEAT CODE FROM EXERCISE 1.1-1.5 ---
-x = [1, 3, 6, 9, 16]
-y = [7, 3, 7, 1, 5]
-y2 = [9, 5, 5, 2, 6]
-y3 = [4, 6, 2, 6, 8]
-y4 = [1, 8, 1, 3, 2]
-plt.plot(x, y, 'k.-', label='Original curve')
-plt.title('Title with random equation for showcase $c = \sqrt{a^2+b^2}$')
-plt.xlabel('This is the xlabel $x$')
-plt.ylabel('This is the ylabel $y$')
-plt.plot(x, y2, label='Second curve')
-plt.plot(x, y3, label='Third curve')
-plt.plot(x, y4, label='Fourth curve')
-plt.legend(bbox_to_anchor=(1.0, 0.5), loc='center left')
-
-# Save figure to a file 
-fig.savefig('myfig.png')
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

In the saved png file, the legend appears to have been cut off. I dont' know why it cannot handle this. This would not have been a problem if it had stayed inside the plotting area.

-

A way to get it to show correctly could be to create a new Axes object to the right of the Axes object that contains the plots. Recall the Axes is the "plotting area".

-

So:

- -
-
-
-
-
-
In [5]:
-
-
-
# Create figure object to store plots inside
-fig2 = plt.figure(figsize=(14, 6))
-
-# Create new axes object for putting legend
-ax_leg = fig2.add_axes([0.1, 0.1, 0.6, 0.75])
-
-# ---REPEAT CODE FROM EXERCISE 1.1-1.5 ---
-x = [1, 3, 6, 9, 16]
-y = [7, 3, 7, 1, 5]
-y2 = [9, 5, 5, 2, 6]
-y3 = [4, 6, 2, 6, 8]
-y4 = [1, 8, 1, 3, 2]
-plt.plot(x, y, 'k.-', label='Original curve')
-plt.title('Title with random equation for showcase $c = \sqrt{a^2+b^2}$')
-plt.xlabel('This is the xlabel $x$')
-plt.ylabel('This is the ylabel $y$')
-plt.plot(x, y2, label='Second curve')
-plt.plot(x, y3, label='Third curve')
-plt.plot(x, y4, label='Fourth curve')
-
-# Add legend to new axes object
-ax_leg.legend(bbox_to_anchor=(1.0, 0.5), loc='center left')
-
-# Save figure to a file 
-fig2.savefig('myfig2.png')
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Exercise 2.1

-
-
-
-
-
-
In [6]:
-
-
-
plt.figure(figsize=(18, 5))
-x = [1, 3, 6, 9, 16]
-y = [7, 3, 7, 1, 5]
-y2 = [9, 5, 5, 2, 6]
-y3 = [4, 6, 2, 6, 8]
-y4 = [1, 8, 1, 3, 2]
-
-plt.subplot(141)
-plt.plot(x, y)
-
-plt.subplot(142)
-plt.plot(x, y2)
-
-plt.subplot(143)
-plt.plot(x, y3)
-
-plt.subplot(144)
-plt.plot(x, y4)
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Exercise 2.2

Only difference from the code in Exercise 2.1 is to change plt.plot() to plt.bar()

- -
-
-
-
-
-
In [7]:
-
-
-
plt.figure(figsize=(18, 5))
-x = [1, 3, 6, 9, 16]
-y = [7, 3, 7, 1, 5]
-y2 = [9, 5, 5, 2, 6]
-y3 = [4, 6, 2, 6, 8]
-y4 = [1, 8, 1, 3, 2]
-
-plt.subplot(141)
-plt.bar(x, y)
-
-plt.subplot(142)
-plt.bar(x, y2)
-
-plt.subplot(143)
-plt.bar(x, y3)
-
-plt.subplot(144)
-plt.bar(x, y4)
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Exercise 3.1

-
-
-
-
-
-
In [8]:
-
-
-
import numpy as np
-xx = np.linspace(-100, 100, 100)
-yy = xx**2-3027
-plt.plot(xx, yy)
-plt.fill_between(xx, yy, where= yy<0, color='darkorchid',  alpha=.25)
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Exercise 4.1

-
-
-
-
-
-
In [26]:
-
-
-
%%capture 
-# %%capture prevent plots from showing after the cell
-# ------------------------------------------------------
-
-# Solution:
-
-# Define x-values
-x_arr = np.linspace(1, 10, 10)
-
-# Copied y-values from exercise
-y_arr1 = np.random.rand(10)
-y_arr2 = np.random.rand(10)
-y_arr3 = np.random.rand(10)
-y_arr4 = np.random.rand(10)
-y_arr5 = np.random.rand(10)
-
-# Create list of the y-arrays
-y_arrays = [y_arr1, y_arr2, y_arr3, y_arr4, y_arr5]
-
-# Define names for the png files for each plot
-names = ['plot1', 'plot2', 'plot3', 'plot4', 'plot5']
-
-# Loop over all graphs and save as png file with their names 
-for y_arr, name in zip(y_arrays, names):
-    
-    # Create a figure object
-    plt.figure()
-    
-    # Create plot for current y-array
-    plt.plot(x_arr, y_arr)
-    
-    # Save to figure and name file with current name
-    plt.savefig(f'{name}.png')
-    
-
- -
-
-
- -
-
-
-
-
-
-

Some notes to the solution

The zip() function puts two (or more) lists up beside eachother so it becomes easier to iterate over them:

-
for y_arr, name in zip(y_arrays, name):
-    # Inside the loop, the i'th elements of the lists
-    # can be referred to as 'y_arr' and 'name'
-
-

The same outcome could be obtained by creating a counter variable outside the loop and use that as index to refer to the i'th element of the lists:

-
i = 1  # Initial value for counter
-for i in range(len(y_array)):
-    # Inside the loop, the i'th elements of the lists 
-    # can be referred to as 'y_array[i]' and 'names[i]' 
-
-    i += 1 # Increase counter by 1
-
-

Choose whatever method you find the easiest.

-

If a new figure object is not created in each loop as plt.figure(), consecutive plots will be added to eachother. So, first png file will contain first plot, second png file will contain the first two and the last file will contain all the plots.

- -
-
-
-
-
- - - - - - diff --git a/Session 4 - Plotting/Session 4 - Plotting.html b/Session 4 - Plotting/Session 4 - Plotting.html deleted file mode 100644 index e15796f..0000000 --- a/Session 4 - Plotting/Session 4 - Plotting.html +++ /dev/null @@ -1,12731 +0,0 @@ - - - -Session 4 - Plotting - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
-
-
-

Session 4

Plotting with matplotlib

There are many plotting libraries in the Python ecosystem, and more being added all the time. The most widely known is called matplotlib, which is the one we are going to focus on.

-

matplotlib is a third party library, which means that it is developed and maintained by the Python Community and not the core developers of the Python language itself. This means that it doesn't ship with the Python installation and has to be installed separately before we can import it and use it in our programs. matplotlib is one of the oldest, most known and well documented third party libraries in Python.

-

To install the library, go to the Windows start menu and find Anaconda Prompt. When it opens, type pip install matplotlib and hit enter. This should install the most recent version on your system.

- -
-
-
-
-
-
In [1]:
-
-
-
# Enable for plots to be shown inside Jupyter Notebook cells 
-# (Note: This line is not needed when using an editor)   
-%matplotlib inline  
-
-# The lines below sets the figure size throughout the notebook
-import matplotlib as mpl
-mpl.rcParams['figure.figsize'] = 7, 5
-
- -
-
-
- -
-
-
-
In [2]:
-
-
-
# Import the plotting library and refer to it as plt later on
-import matplotlib.pyplot as plt
-
- -
-
-
- -
-
-
-
-
-
-

It is customary to import as plt so referencing can be done as

-
plt.do_something()                # plt replaces matplotlib.pyplot
-
-

Where do_something() is some function/method inside matplotlib. This is much shorter than typing

-
matplotlib.pyplot.do_something()  # Long and cumbersome to type
-
-

In fact, the plt part could be named differently, but it is widely accepted to use this naming, which makes it easier to read other people's code.

- -
-
-
-
-
-
-
-
-

Simple plotting API

The simple API for plotting in matplotlib uses commands which deliberately were chosen very similar to the Matlab syntax.

-

API stand for Application Programming Interface and is basically a simplified way of interacting with more complex underlying code. Here, we will type fairly simple plotting commands, which do a lot of low level work in the background.

-

Creating a simple line plot is extremely easy:

- -
-
-
-
-
-
In [3]:
-
-
-
# Create x- and y-coordinates for f(x) = x^2 
-x = [i for i in range(-100, 105, 5)]
-y = [i**2 for i in x]
-
-# Create basic plot
-plt.plot(x, y)
-
-# Show plot
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Given a list of data for x and y, a line graph can be produced in a single command plt.plot(x,y), while actually showing the plot has its own command.

-

It might seem wierd that the plt.show() is needed, but it is not always desired to have the plot shown. Sometimes it is desired to produce the graph and have it saved to a png-file or do something else with it instead of showing it.

- -
-
-
-
-
-
-
-
-

Customization of graphs

The plot of $f(x)=x^2$ from above can be made much nicer by a little customization. The command names should make the code self-explanatory.

- -
-
-
-
-
-
In [4]:
-
-
-
plt.title('Graph for $f(x) = x^2$', fontsize=16)
-plt.xlabel('$x$', fontsize=14)
-plt.ylabel('$f(x)$', fontsize=14)
-plt.plot(x, y, '.-', color='limegreen', markersize=6)
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Plotting multiple graphs in the same plot

It is possible to plot many graphs in the same plot and attach a legend:

- -
-
-
-
-
-
In [5]:
-
-
-
# Create x-coordinates for graphs 
-x = [i for i in range(-10, 10, 1)]
-
-# Produce y-values for different graphs
-y1 = [i**2 for i in x]     # f(x) = x^2
-y2 = [10*i**2 for i in x]  # f(x) = 10x^2
-y3 = [i**3 for i in x]     # f(x) = x^3
-y4 = [2*i**3 for i in x]   # f(x) = 2x^3
-
-# Create plots with legend labels for each graph
-plt.plot(x, y1, label='$f(x)=x^2$')
-plt.plot(x, y2, label='$f(x)=10x^2$')
-plt.plot(x, y3, label='$f(x)=x^3$')
-plt.plot(x, y4, label='$f(x)=2x^3$')
-
-# Set titles, grid and legend
-plt.title('Different graphs', fontsize=16)
-plt.xlabel('$x$', fontsize=14)
-plt.ylabel('$y$', fontsize=14)
-plt.grid(linestyle=':')
-plt.legend(fontsize=14)
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-
    -
  • Graphs are automatically colorized, but this can of course be customized.
  • -
  • Legend will try to position itself so it does not overlap with the graphs.
  • -
- -
-
-
-
-
-
-
-
-

Fill between

Plot areas can be filled based on conditions. Below is an example.

-

The code in the next cell serves only to create summy data for a graph.

- -
-
-
-
-
-
In [6]:
-
-
-
# The code in this cell is just for creating a dummy graph
-import numpy as np
-x1 = np.linspace(1, 10, 100)
-x2 = np.linspace(10, 15, 100)
-x3 = np.linspace(15, 20, 100)
-y1 = 2 * np.sin(1.98*x1)
-y2 = 3 * np.sin(-x2)
-y3 = 1 * np.sin(2.2 * x3)
-y = np.append(np.append(y1, y2), y3)
-x = np.append(np.append(x1, x2), x3)
-
- -
-
-
- -
-
-
-
-
-
-

Plotting this dummy graph and filling areas between the graph and $y=0$ with green color:

- -
-
-
-
-
-
In [7]:
-
-
-
# Plot line graph in black
-plt.plot(x, y, color='black', linewidth=1)
-
-# Put green/purple fill between the graph y=0
-plt.fill_between(x, y, color='limegreen',  alpha=.25)
-
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

With differentiated colors:

- -
-
-
-
-
-
In [8]:
-
-
-
# Plot line graph in black
-plt.plot(x, y, color='black', linewidth=1)
-
-# Put green/purple fill between the graph y=0
-plt.fill_between(x, y, where= y <= 0, color='limegreen',  alpha=.25)
-plt.fill_between(x, y, where= y > 0, color='darkorchid',  alpha=.25)
-
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-
    -
  • Note: The sequences to be plotted has to be numpy arrays in order to make element wise comparison like where= y > 0. Trying this with standard Python lists will throw a TypeError: '<' not supported between instances of 'list' and 'int'. This is one of the many benefits of numpy. See a little more info about numpy later in this text.
  • -
- -
-
-
-
-
-
-
-
-

Subplots

Creating subplots is also straight forward:

- -
-
-
-
-
-
In [9]:
-
-
-
# Create a figure for holding subplots and set size
-plt.figure(figsize=(14,3))
-
-# Create first plot as line plot
-plt.subplot(131)
-plt.plot([1, 2, 3, 4], [1, 2, 3, 4])
-
-# Create second plot as scatter plot
-plt.subplot(132)
-plt.plot([1, 2, 3, 4], [1, 2, 3, 4], '.', markersize=12)
-
-# Create third plot as bar plot
-plt.subplot(133)
-plt.bar([1, 2, 3, 4], [1, 2, 3, 4])
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-
    -
  • Note 1: The subplot argument conatins three digits, where the first one is the number of rows, the second the number of columns and the third the current plot to manipulate.
  • -
-
    -
  • Note 2: For making more complicated grid formations, shared axis tick marks etc. The Object Oriented API should be used instead.
  • -
- -
-
-
-
-
-
-
-
-

Object Oriented API

Almost every aspect of a visualization can be controlled. However, in order to access more complex controls, the way of interaction with the graph elements also becomes a little more complex.

-

In order to use the more powerful matplotlib API, we get into so-called Object Oriented Programming. We access each element of a figure as an object and manipulate that object.

-

The figure below gives an overview of the objects that can be controlled.

- -
-
-
-
-
-
In [10]:
-
-
-
# The purpose of the code below is to show the image 
-from IPython.display import Image
-Image(filename="matplotlib_objects.png", width=600, height=600)
-
- -
-
-
- -
-
- - -
-
Out[10]:
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Subplots

-
-
-
-
-
-
-
-
-
    -
  • Note especially that the Axes object is the actual content of the plot, and therefore does not refer to x- or y-axis themselves.
  • -
-

The Object Oriented API is recommended for more complex plotting like creation of larger grids of subplots where each plot needs independent adjustments. For example:

- -
-
-
-
-
-
In [11]:
-
-
-
# Create a 2 by 3 subplot grid with shared x- and y-axis
-fig, ax = plt.subplots(2, 3, sharex='col', sharey='row')
-
-# Put content on the plot at grid spot 1,1 (0 indexed)
-ax[1, 1].plot([1, 2, 3], [1 ,2, 3], 'g-.')
-ax[1, 1].plot([3, 2, 1], [1, 2, 3], 'm:')
-
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-
    -
  • In this manner, all objects on every subplot can manipulated.

    -
  • -
  • There are many other plot types where the Object Orientated API is preferred compared to the simple Matlab style one.

    -
  • -
- -
-
-
-
-
-
-
-
-

Online help and plotting galleries

Instead of creating a figure from scrath, it can be quicker to serch the web for the graph style of choice, fetch a piece of code and modify it.

-

Here are some useful links:

- - - - - -
-
-
-
-
-
-
-
-

Numerical computation library: numpy

Another very well known and broadly used third party library is numpy (short for Numerical Python), which contains many useful features for fast and efficient numerical calculations. This library has a lot of the same functionality as Matlab and utilizes vectorization to perform calculations.

-

It can be installed in the same way as every other third party library, namely by entering pip install numpy in the Anaconda Prompt.

-

Once installed, the numpy can be used as shown below. Like with the matplotlib import, numpyalso has a community accepted standard:

- -
-
-
-
-
-
In [12]:
-
-
-
import numpy as np
-
- -
-
-
- -
-
-
-
-
-
-

The basic data structure in numpy is called an array, which can be compared to a normal Python list, except it can only hold numerical values.

-

A numpy array can be created, for example from a list, like this:

- -
-
-
-
-
-
In [13]:
-
-
-
L = [1, 2, 3, 4, 5]   # A normal Python list
-arr = np.array(L)     # List converted to numpy array
-
- -
-
-
- -
-
-
-
-
-
-

Printing the array looks like a normal list:

- -
-
-
-
-
-
In [14]:
-
-
-
print(arr)  
-
- -
-
-
- -
-
- - -
-
- -
-
[1 2 3 4 5]
-
-
-
- -
-
- -
-
-
-
-
-
-

But it is in fact a numpy array, which can be seen by inspecting the type:

- -
-
-
-
-
-
In [15]:
-
-
-
print(type(arr))
-
- -
-
-
- -
-
- - -
-
- -
-
<class 'numpy.ndarray'>
-
-
-
- -
-
- -
-
-
-
-
-
-

The fact that numpy uses verctorization can be seen for example by performing mulitplication:

- -
-
-
-
-
-
In [16]:
-
-
-
# Multiply all array elements by 2 and print result
-arr_double = 2 * arr
-print(arr_double)
-
- -
-
-
- -
-
- - -
-
- -
-
[ 2  4  6  8 10]
-
-
-
- -
-
- -
-
-
-
-
-
-

Recall that doing the same operation with a normal Python list is a little more complicated. Here shown with a list comprehension, but a normal for loop could also be used.

- -
-
-
-
-
-
In [17]:
-
-
-
# Multiply all list elements by 2 and print result
-L_double = [2*i for i in L]
-print(L_double)
-
- -
-
-
- -
-
- - -
-
- -
-
[2, 4, 6, 8, 10]
-
-
-
- -
-
- -
-
-
-
-
-
-

As mentioned, numpy has many useful functions and methods. One of the most used functions is

-
numpy.linspace(start, stop, num=50)
-
-

which will generate an array of num numbers evenly spaced between start and end. As we day from Session 3 about functions, the num=50 means that num is an optional argument, the default value of which is 50. So, if num is not specified, the generated array will have 50 evenly spaced numbers between startand end.

-

Note that the numpy.linspace() function has more arguments, which are not shown here. See the documention for more info: https://docs.scipy.org/doc/numpy/reference/generated/numpy.linspace.html

-

An example:

- -
-
-
-
-
-
In [18]:
-
-
-
np.linspace(0, 1, 10)
-
- -
-
-
- -
-
- - -
-
Out[18]:
- - - -
-
array([0.        , 0.11111111, 0.22222222, 0.33333333, 0.44444444,
-       0.55555556, 0.66666667, 0.77777778, 0.88888889, 1.        ])
-
- -
- -
-
- -
-
-
-
-
-
-

The numpy.linspace() function can especially be useful when generating $x$-values for plotting purposes.

- -
-
-
-
-
-
-
-
-

Exercises

All exercises use the simple API described above.

-

Exercise 1.1

Plot a black line graph with dots at the points with these coordinates:

-
x = [1, 3, 6, 9, 16]
-y = [7, 3, 7, 1, 5]
-
-

Remember to import matplotlib.pyplot as plt and to call plt.show().

-

The color can be set by plt.plot(..., color='black') or by a shortcut plt.plot(..., 'k'), where 'k' is black becuase 'b' is blue.

-

Exercise 1.2

Set the plot title on the graph from Exercise 1.1. You choose what the title should be.

-

Exercise 1.3

Set the title of the x- and y-axis on the graph from Exercise 1.1. You choose what the title should be.

-

Exercise 1.4

Add the graphs with the following y-values to the plot from Exercise 1.1. Use the same x-values for all curves.

-
y2 = [9, 5, 5, 2, 6]
-y3 = [4, 6, 2, 6, 8]
-y4 = [1, 8, 1, 3, 2]
-
-

Exercise 1.5

Go back through the code from the previous exercises and add a label to the plots that were procuded. Choose a label text freely. -Afterwards, add a lenged to the plot.

-

Exercise 1.6

Save the figure to a png-file. This can be done by the command plt.savefig(desired_filename.png). This will save it in the same folder as you have your script.

-

If you are dissatisfied with the size of the saved figure, this can be adjusted by explicitly creating the figure object before any of the graphs are created. Creating the figure object and setting a size is done by plt.figure(figsize=(width, height)). Both width and height are in inches. Try with different values.

-

Note: When using the simple API it is not necessary to explicitly create the figure object before starting the plotting with plt.plot(), as the figure is automaticllay created in the background with default settings. When saving to a file where it is not possible to drag the plot after creation, it is often useful to set the figure size beforehand

-

Exercise 2.1

Create a new figure by the command plt.figure(). This will create a new figure object that subsequent commands will tie to. This is to avoid having the commands in this exercise be plotted in the plot from the previous exercises.

-

Redo the graphs from the previous exercises, this time split into four subplots instead. You choose how to structure the grid and how to style the graphs with colors, titles, line types etc.

-

Exercise 2.2

Create a new figure and replicate the same subplots as in Exercise 2.1. But this time, turn the plots into bar plots. The only difference is that the plotting call should now be plt.bar(...).

-

Exercise 3.1

Create plot that is filled with some color of your choice between $y=0$ and the curve defined by $xx$ and $yy$ below:

-
import numpy as np
-xx = np.linspace(-100, 100, 100)
-yy = xx**2-3027         # <- Elementwise multiplication (numpy)
-
-

Exercise 4.1

Use numpy.linspace() to create an array with 10 values from 1-10. Save the array in a a variable called x_arr.

-

Use the code below to create the y-values for five graphs.

-
y_arr1 = np.random.rand(10)
-y_arr2 = np.random.rand(10)
-y_arr3 = np.random.rand(10)
-y_arr4 = np.random.rand(10)
-y_arr5 = np.random.rand(10)
-
-

The numpy.random.rand() function creates random values between 0 and 1 (see documentation for more: https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.rand.html)

-

Create a loop that goes through the five graphs. Each loop should create a figure with your chosen size and settings for the current graph and save it to a png-file. -You should end up with five png-files each containing only a single graph/curve.

- -
-
-
-
-
-
-
-
-

If you are up for more

Take the polygon plotting function plot_polygon() from the exercise solutions from Session 3. Create different polygons, run them through a for loop and save them to png-files. Remember that the function calls the functions polygon_area() and polygon_centroid, which also have to be copied.

- -
-
-
-
-
- - - - - - diff --git a/Session 4 - Plotting/Session 4 - Plotting.ipynb b/Session 4 - Plotting/Session 4 - Plotting.ipynb index f0b09c4..e872567 100644 --- a/Session 4 - Plotting/Session 4 - Plotting.ipynb +++ b/Session 4 - Plotting/Session 4 - Plotting.ipynb @@ -410,7 +410,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -481,7 +481,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -499,7 +499,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -516,7 +516,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -540,7 +540,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -564,7 +564,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -590,7 +590,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -625,7 +625,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -635,7 +635,7 @@ " 0.55555556, 0.66666667, 0.77777778, 0.88888889, 1. ])" ] }, - "execution_count": 18, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } diff --git a/Session 4 - Plotting/plotting_test.py b/Session 4 - Plotting/plotting_test.py index c8f520b..83cb79b 100644 --- a/Session 4 - Plotting/plotting_test.py +++ b/Session 4 - Plotting/plotting_test.py @@ -71,4 +71,4 @@ plt.plot([1, 2, 3, 4], [1, 2, 3, 4], '.', markersize=12) # Create third plot as bar plot plt.subplot(133) plt.bar([1, 2, 3, 4], [1, 2, 3, 4]) -plt.show() \ No newline at end of file +plt.show() diff --git a/Session 5 - Dataframes/Session 5 - Dataframes.html b/Session 5 - Dataframes/Session 5 - Dataframes.html deleted file mode 100644 index 49e823a..0000000 --- a/Session 5 - Dataframes/Session 5 - Dataframes.html +++ /dev/null @@ -1,14801 +0,0 @@ - - - - -Session 5 - Dataframes - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
-

Pandas Dataframes

Python is very good for data analysis. Much of this is thanks to the pandas library, which contains a wealth of powerful functions to load data and manipulate it.

-

In the pandas environment what we normally refer to as a table is called a DataFrame. Is the data has only a single column, it is called a Series. These are the core objects in the library.

-

As with many libraries, there is a convection for renaming when importing. In pandas the convention is to import as pd:

- -
-
-
-
-
-
In [1]:
-
-
-
import pandas as pd
-
- -
-
-
- -
-
-
-
-

Creating a simple DataFrame

A simple DataFrame can be created as with pandas.DataFrame():

- -
-
-
-
-
-
In [2]:
-
-
-
# Create a simple DataFrame
-df = pd.DataFrame({'Column1': [11, 12, 13], 'Column2': [21, 22, 23], 'Column3': [31, 32, 33]})
-df
-
- -
-
-
- -
-
- - -
- -
Out[2]:
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Column1Column2Column3
0112131
1122232
2132333
-
-
- -
- -
-
- -
-
-
-
-
    -
  • Note 1: The input argument for creating the DataFrame is a dictionary. I.e. a data structure with keys-value pairs.
  • -
-
    -
  • Note 2: It automatically creates and index column as the leftmost column.
  • -
-
    -
  • Note 3: The displayed DataFrame looks nicer than the it would have in an editor. This is because it is styled with HTML. In an editor, the printed DataFrame would look like this:
  • -
- -
-
-
-
-
-
In [3]:
-
-
-
# DataFrame as it would look without HTML-styling
-print(df)
-
- -
-
-
- -
-
- - -
- -
- - -
-
   Column1  Column2  Column3
-0       11       21       31
-1       12       22       32
-2       13       23       33
-
-
-
- -
-
- -
-
-
-
-

Presenting results with DataFrames

If we have a dictionary from a previous calculation of some kind, we can quickly turn it into a DataFrame with the same pricinple as above:

- -
-
-
-
-
-
In [4]:
-
-
-
# Define normal force and cross sectional area
-normal_force = [85, 56, 120]
-area = [314, 314, 314]
-
-# Compute stress in cross section for all normal forces
-stress = [n/a for n, a in zip(normal_force, area)]
-
-# Gather calculation results in dictionary 
-results = {'N [kN]': normal_force, 'A [mm2]': area, 'sigma_n [MPa]': stress}
-
-# Create a DataFrame of the results form the dictionary
-df2 = pd.DataFrame(results)
-df2
-
- -
-
-
- -
-
- - -
- -
Out[4]:
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
N [kN]A [mm2]sigma_n [MPa]
0853140.270701
1563140.178344
21203140.382166
-
-
- -
- -
-
- -
-
-
-
-

Adjusting the index column

The default index (leftmost column) is not really suited for this particular scenario, so we could change it to be "Load Case" and have it start at 1 instead of 0

- -
-
-
-
-
-
In [5]:
-
-
-
# Set the name of the index to "Load Case"
-df2.index.name = 'Load Case'
-
-# Add 1 to all indices
-df2.index += 1
-
-df2
-
- -
-
-
- -
-
- - -
- -
Out[5]:
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
N [kN]A [mm2]sigma_n [MPa]
Load Case
1853140.270701
2563140.178344
31203140.382166
-
-
- -
- -
-
- -
-
-
-
-

Extracting a subset of data

We can extract specific columns from the DataFrame:

- -
-
-
-
-
-
In [6]:
-
-
-
# Extract only the stress column to new DataFrame
-df2[['sigma_n [MPa]']]
-
- -
-
-
- -
-
- - -
- -
Out[6]:
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
sigma_n [MPa]
Load Case
10.270701
20.178344
30.382166
-
-
- -
- -
-
- -
-
-
-
-
    -
  • Note: The use of two square bracket pairs [[]] turns the result into a new DataFrame, with just one column. If there had been only a single square bracket, the result would be a Series object. See below.
  • -
- -
-
-
-
-
-
In [7]:
-
-
-
# Extract stress column to Series object
-df2['sigma_n [MPa]']
-
- -
-
-
- -
-
- - -
- -
Out[7]:
- - - - -
-
Load Case
-1    0.270701
-2    0.178344
-3    0.382166
-Name: sigma_n [MPa], dtype: float64
-
- -
- -
-
- -
-
-
-
-

Most of the time, we want to keep working with DataFrames, so remember to put double square brackets.

-

Double square brakcets must be used if we want to extract more than one column. Otherwise, a KeyError will be raised.

- -
-
-
-
-
-
In [8]:
-
-
-
# Extract multiple columns to DataFrame
-df2[['N [kN]', 'sigma_n [MPa]']]
-
- -
-
-
- -
-
- - -
- -
Out[8]:
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
N [kN]sigma_n [MPa]
Load Case
1850.270701
2560.178344
31200.382166
-
-
- -
- -
-
- -
-
-
-
-

Importing data from file to DataFrame

Data can be imported from various file types. The most common ones are probably standard text files (.txt), comma separated value files (.csv) or Excel files (.xlsx)

-

Some common scenarios

-
# Import from .csv (comma separated values)
-pd.read_csv('<file_name>.csv')
-
-# Import from .txt  with values separated by white space
-pd.read_csv('<file_name>.txt', delim_whitespace=True)
-
-# Import from Excel
-pd.read_excel('<file_name>.xlsx', sheet_name='<sheet_name>')
-
-

The above assumes that the files to import are located in the same directory as the script. Placing it there makes it easier to do the imports.

-

The functions above have many optional arguments. When importing from an Excel workbook it will often be necessary to specify more parameters than when importing a plain text file, because the Excel fil is a lot more complex. -For example, by default the data starts at cell A1 as the top left and the default sheet is the first sheet occuring in the workbook, but this is not always what is wanted.

-

See docs for both functions here:

- - -
-
-
-
-
-
-

Import example

-
-
-
-
-
-
In [9]:
-
-
-
# Import data from 'HEA.txt', which has data separated
-# by white spaces
-df = pd.read_csv('HEA.txt', delim_whitespace=True)
-df
-
- -
-
-
- -
-
- - -
- -
Out[9]:
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Profileh[mm]b[mm]Iy[mm4]Wel,y[mm3]g[kg/m]
0HE100A96100349000072.816.7
1HE120A1141206060000106.019.9
2HE140A13314010300000155.024.7
3HE160A15216016700000220.030.4
4HE180A17118025100000294.035.5
5HE200A19020036900000389.042.3
6HE220A21022054100000515.050.5
7HE240A23024077600000675.060.3
8HE260A250260104500000836.068.2
9HE280A2702801367000001010.076.4
10HE300A2903001826000001260.088.3
-
-
- -
- -
-
- -
-
-
-
-

Filtering data

Data filtering is easy and intuitive. It is done by conditional expressions.

-

For example, if we want to filter the HEA-DataFrame for profiles with moment of inertia $I_y$ larger than some value:

- -
-
-
-
-
-
In [10]:
-
-
-
df[df['Iy[mm4]'] > 30000000]
-
- -
-
-
- -
-
- - -
- -
Out[10]:
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Profileh[mm]b[mm]Iy[mm4]Wel,y[mm3]g[kg/m]
5HE200A19020036900000389.042.3
6HE220A21022054100000515.050.5
7HE240A23024077600000675.060.3
8HE260A250260104500000836.068.2
9HE280A2702801367000001010.076.4
10HE300A2903001826000001260.088.3
-
-
- -
- -
-
- -
-
-
-
-

Understanding the filtering process

The inner expression of the filtering

-
df['Iy[mm4]'] > 30000000
-
-

returns the column Iy[mm4] from the DataFrame converted into a boolean Series. I.e. a Series with True/False in each row depending on the condition being fulfilled or not. See the printout below.

- -
-
-
-
-
-
In [11]:
-
-
-
# Inner expression returns a boolean Series of Iy[mm4]
-df['Iy[mm4]'] > 30000000
-
- -
-
-
- -
-
- - -
- -
Out[11]:
- - - - -
-
0     False
-1     False
-2     False
-3     False
-4     False
-5      True
-6      True
-7      True
-8      True
-9      True
-10     True
-Name: Iy[mm4], dtype: bool
-
- -
- -
-
- -
-
-
-
-

This boolean Series is used to filter the original DataFrame, which is done in the outer expression by df[boolean_series].

-

The outer expression picks only the rows from the orignal DataFrame where the boolean series is True.

-

Filtering by multiple conditions

Filtering based on multiple conditions can be quite powerful. The syntax is only slightly more complicated

- -
-
-
-
-
-
In [12]:
-
-
-
df[(df['Iy[mm4]'] > 30000000) & (df['h[mm]'] < 260 )]
-
- -
-
-
- -
-
- - -
- -
Out[12]:
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Profileh[mm]b[mm]Iy[mm4]Wel,y[mm3]g[kg/m]
5HE200A19020036900000389.042.3
6HE220A21022054100000515.050.5
7HE240A23024077600000675.060.3
8HE260A250260104500000836.068.2
-
-
- -
- -
-
- -
-
-
-
-

Filtering can also be based on lists of values:

- -
-
-
-
-
-
In [13]:
-
-
-
# Valid profiles to choose from
-valid_profiles = ['HE180A', 'HE220A', 'HE260A', 'HE280A']
-
-# Filter DataFrame based in Iy and valid profiles
-df[(df['Iy[mm4]'] > 30000000) & (df['Profile'].isin(valid_profiles) )]
-
- -
-
-
- -
-
- - -
- -
Out[13]:
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Profileh[mm]b[mm]Iy[mm4]Wel,y[mm3]g[kg/m]
6HE220A21022054100000515.050.5
8HE260A250260104500000836.068.2
9HE280A2702801367000001010.076.4
-
-
- -
- -
-
- -
-
-
-
-

If we want to rule out some profiles, we could put a ~ in front of the condition to specify that values must not be present in the list:

- -
-
-
-
-
-
In [14]:
-
-
-
# Invalid profiles
-invalid_profiles = ['HE180A', 'HE220A', 'HE260A', 'HE280A']
-
-# Filter DataFrame based in Iy and valid profiles
-df[(df['Iy[mm4]'] > 30000000) & (~df['Profile'].isin(invalid_profiles) )]
-
- -
-
-
- -
-
- - -
- -
Out[14]:
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Profileh[mm]b[mm]Iy[mm4]Wel,y[mm3]g[kg/m]
5HE200A19020036900000389.042.3
7HE240A23024077600000675.060.3
10HE300A2903001826000001260.088.3
-
-
- -
- -
-
- -
-
-
-
-

Exporting a DataFrame to a file

Exporting a DataFrame to a new text file could not be easier. Saving to a .txt:

-
-
# Save df to a .txt file in the same folder as the script
-df.to_csv('filename.txt')
-
-
- -
-
-
-
-
-
-

GroupBy

groupby provides a way to split a DataFrame into groups based on some condition, apply a function to those groups and combine the results into a new DataFrame that is returned.

-

An example

-
-
-
-
-
-
In [15]:
-
-
-
# Create a dataframe to work with
-dff = pd.DataFrame({'Fruit': ['Pear', 'Apple', 'Apple', 'Banana', 'Lemon', 'Banana', 'Banana', 'Pear'], 
-                    'Amount_sold':  [3, 6, 7, 2, 4, 7, 1, 6]})
-dff
-
- -
-
-
- -
-
- - -
- -
Out[15]:
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FruitAmount_sold
0Pear3
1Apple6
2Apple7
3Banana2
4Lemon4
5Banana7
6Banana1
7Pear6
-
-
- -
- -
-
- -
-
-
-
-

The DataFrame.groupby method itself returns a groupby object, not a DataFrame. So printing that on its own will just show you the object.

- -
-
-
-
-
-
In [71]:
-
-
-
# The gropuby will return a groupby object
-dff.groupby('Fruit')
-
- -
-
-
- -
-
- - -
- -
Out[71]:
- - - - -
-
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x000001B716413978>
-
- -
- -
-
- -
-
-
-
-

The object contains metadata about how the data is grouped. The powerful operations are visible only after we apply a certain function to the groupby object, like sum():

- -
-
-
-
-
-
In [17]:
-
-
-
dff.groupby('Fruit').sum()
-
- -
-
-
- -
-
- - -
- -
Out[17]:
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Amount_sold
Fruit
Apple13
Banana10
Lemon4
Pear9
-
-
- -
- -
-
- -
-
-
-
-

We could say that we first split the DataFrame in fruit groups, applied a function to those individual groups and combined and returned the results.

-

Note that by default the column that was grouped by becomes the new index, since these are now unique values.

- -
-
-
- -
-
-
-

Printing with df.head() and df.tail()

When DataFrames become very large, printing all the data to the screen becomes unwieldy. Printing is mostly done only to make sure that some operation worked as we expected it would. In that case, printing just a few rows will be sufficient, which the following methods will alloww for:

-
# Print the first 5 rows of df
-df.head()
-
-# Print the last 5 rows of df
-df.tail()
-
-# Print the first x rows of df
-df.head(x)
-
-# Print the last y rows of df
-df.tail(y)
-
- -
-
-
-
-
-
-

Something to be aware of

A potentially confusing thing about pandas methods is that it can be hard to know which mutates the DataFrame inplace and which needs to be saved to a new varaible. Consider the lines below:

-
-
# This line does not rename the column in df
-# but returns a a copy of df with the column renamed.
-df.rename(columns={'Current_name': 'New_name})  
-
-# Thus, it has to be saved to a new variable
-df = df.rename(columns={'Current_name': 'New_name})  
-
-# Or, use the argument inplace=True to modify df directly
-df.rename(columns={'Current_name': 'New_name}, inplace=True)
-
-
-

You will most likely stumble across this when working with pandas. -Note that there is no error when when executing the first line shown above, but when df is eventually printed it will just not be as intended.

- -
-
-
-
-
-
-

Much more functionality

There are numerous functions and methods available in pandas and the above mentioned barely scrathes the surface.

-

Practically anything that you would want to do to a dataset can be done. And quite possibly somebody has had the same problem as you before and found a solution or maybe even even contributed to the pandas library and put in that functionality for everyone to use. -However, some functionality can be much harder to understand and use than the above mentioned.

-

The pandas library integrates well with other big libraries like numpy and matplotlib and other functionality in the Python language in general. For example, many DataFrame methods can take as input a customly defined function def ...() and run it through certian content of the DataFrame.

-

Plotting with matplotlib is directly supported in pandas via shortcuts so you can do df.plot() and it will create a plot of the DataFrame of a specified kind even without having to import matplotlib.

-

When and why to use pandas

    -
  • The manipulations that can be done with pandas are quite powerful when datasets become much larger than ones shown above. It is especially helpful when the dataset reaches a size where all data can not be viewed and understood well by simply scrolling down and looking at the data. If the number of rows go beyond just a couple of thousands, it is hard to get the overall feel for the data and its trends just by inspection. This is were typing logic commands to do manipulations becomes a great help.
  • -
-
    -
  • Use it when a very specific solution for data manipulation is desired. Especially when the solution is not trivially done in for example Excel.
  • -
-
    -
  • It is a good tool for combining multiple datasets, e.g. from different files.
  • -
-
    -
  • Last but not least, it is good for reproducibility and handling changes in data size.
  • -
- -
-
-
-
-
-
-

Exercise 1.1

All exercises 1.x are working with the same DataFrame.

-
-

Create a DataFrame from the dictionary d below. Save it is a variable called df.

-
-
# Import built-in libraries string and random 
-import random
-import string
-
-# Get upper- and lowercase letters from string library
-lower = string.ascii_lowercase
-upper = string.ascii_uppercase
-
-# Create a dictionary with dummy data of integers and letters
-d = {'Integers': [random.randint(1, 100) for i in range(1, 100)],
-     'Lowercase': [random.choice(lower) for i in range(1, 100)],
-     'Uppercase': [random.choice(upper) for i in range(1, 100)]}
-
-
-

Print/display the entire DataFrame to see it if it comes out as you expect.

-

Remember to import pandas as pd.

-

Exercise 1.2

Print/display the only the first or last rows by using DataFrame.head() or DataFrame.tail(). You choose how many rows to print (default is 5).

-

Use these methods to test print the DataFrames from now on to avoid printing all rows.

-

Exercse 1.3

Filter df to only contain the rows where the uppercase letter is 'K'. Save it to a new variable called dfk.

-

Print/display it to make sure it it correct.

-

If you were unlucky and did not have a 'K' generated in the uppercase column, try re-running the code.

-

Exercise 1.4

When printing the filtered dfk, notice that the index from the original DataFrame is kept. This is often useful for back reference, but sometimes we want the index to be reset.

-

Reset the index of dfk to start from 0 by using DataFrame.reset_index(). -This method does not modify the DataFrame inplace by default, so remember to either save to a new variable or give the input argument inplace=True.

-

By default, the orignal index will be added as a new column to the DataFrame. If you don't want this, use the input argument drop=True.

- -
-
-
-
-
-
-

Exercise 2.1

All exercises 2.x are to be seen as the same problem. It has just been divided into smaller tasks.

-
-

Import the file shear_key_forces.csv to a DataFrame using pandas.read_csv(). The values in the file are comma separated, which the function also assumes as default. -The file is located in the Session 5 folder and has 104329 rows. Print the head or the tail to see the imported data.

-

The data has all spring element forces in a bunch of load cases from a Sofistik finite element calculation.

-

Exercise 2.2

The model has many spring elements. Some of them represent shear keys between tunnel parts at movement joints. These are the one we are going to extract.

-

The data has a column 'shear_key' which has the name of the shear key if the element in that row is part of a shear key. E.g. 'Shear_key1'. If the element is not part of a shear key, the name is 'Not_a_shear_key'

-

Filter out all rows which are not part of a shear key. The resulting DataFrame should have 2874 rows.

-

Exercise 2.3

Since we are not really using the 'Element_no' column. Go ahead and remove it from the DataFrame. This can be done by

-
-
# Remove column 'column_name' form 'df'
-df = df.drop('column_name', axis=1)
-
-
-

The argument axis=1 specifies that it is a column and not a row that should be removed.

-

Remember to save to a new variable or use argument inplace=True. If you save to a variable, you can use the same name to 'overwrite' the old one if it's not needed anymore.

-

Exercise 2.4

Each shear key consists of three spring elements. The total force that the shear key should be designed for is the sum of those three spring forces.

-

Create a DataFrame with the sum of the three values within each shear key for every load case. The resulting DataFrame should have 958 rows.

-

Hint: Use the methods DataFrame.groupby() and DataFrame.sum() like this:

-
df.groupby(['Shear_key', 'LC', 'LC-title'], as_index=False).sum()
-
-

Replace df with the name of your variable contating the DataFrame.

-

Here, a list of column labels is passed in the groupby() method instead of just a single column label. The first column 'Shear_key' is what is used to create the groups, while consecutive labels just follow. Any columns that are not passed in will not appear in the resulting DataFrame.

-

Exercise 2.5

Filter the DataFrame for a shear key, for example 'Shear_key1' and create a bar plot of it with the DataFrame.plot() method. The bar plot should have the load cases as $x$-values and the force $P$ [kN] as $y$-values.

-
-
# Plot dataframe contents
-df.plot(kind='bar', x='column_for_x_values', y='column_for_y_values')
-
-
-

The method has many optional arguments, see https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.html.

-

Try for example to change the figure size by figsize=(width, height), rotate the x-ticks by rot=angle_in_degrees and change the color of the bars by color='some_color'.

-

If you are up for more

Create a loop that goes through all shear keys, creates a plot like the one from the previous exercise and saves each plot to a png-file.

- -
-
-
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
- - - - - - diff --git a/Session 5 - Dataframes/Session 5 - Exercise Solutions.html b/Session 5 - Dataframes/Session 5 - Exercise Solutions.html deleted file mode 100644 index 5b8fdfb..0000000 --- a/Session 5 - Dataframes/Session 5 - Exercise Solutions.html +++ /dev/null @@ -1,12666 +0,0 @@ - - - -Session 5 - Solutions - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
-
-
-

Exercise 1.1

-
-
-
-
-
-
In [1]:
-
-
-
import pandas as pd
-
-# Import built-in libraries 'string' and 'random' 
-import random
-import string
-
-# Get upper- and lowercase letters from 'string' library
-lower = string.ascii_lowercase
-upper = string.ascii_uppercase
-
-# Create a dictionary with dummy data of integers and letters
-d = {'Integers': [random.randint(1, 100) for i in range(1, 100)],
-     'Lowercase': [random.choice(lower) for i in range(1, 100)],
-     'Uppercase': [random.choice(upper) for i in range(1, 100)]}
-
-# Create dataframe from dictionary
-df = pd.DataFrame(d)
-
-# df  # <- This would print all 100 rows
-
- -
-
-
- -
-
-
-
-
-
-

Exercise 1.2

-
-
-
-
-
-
In [2]:
-
-
-
df.head(3)
-
- -
-
-
- -
-
- - -
-
Out[2]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IntegersLowercaseUppercase
059zD
140xP
22tE
-
-
- -
- -
-
- -
-
-
-
In [3]:
-
-
-
df.tail(4)
-
- -
-
-
- -
-
- - -
-
Out[3]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IntegersLowercaseUppercase
9562nV
9643gG
9716nC
987vK
-
-
- -
- -
-
- -
-
-
-
-
-
-

Exercise 1.3

-
-
-
-
-
-
In [4]:
-
-
-
dfk = df[df['Uppercase'] == 'K']
-dfk.head()
-
- -
-
-
- -
-
- - -
-
Out[4]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - -
IntegersLowercaseUppercase
4175vK
987vK
-
-
- -
- -
-
- -
-
-
-
-
-
-

Exercise 1.4

-
-
-
-
-
-
In [5]:
-
-
-
dfk = dfk.reset_index(drop=True)
-dfk
-
- -
-
-
- -
-
- - -
-
Out[5]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - -
IntegersLowercaseUppercase
075vK
17vK
-
-
- -
- -
-
- -
-
-
-
-
-
-

Exercise 2.1

-
-
-
-
-
-
In [27]:
-
-
-
# Import csv file to dataframe
-dfr = pd.read_csv('shear_key_forces.csv')
-dfr.head()
-
- -
-
-
- -
-
- - -
-
Out[27]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LCLC-titleElement_noP[kN]Shear_key
04033Shear keys - W - 2553/2554101000010.1Shear_key20
14033Shear keys - W - 2553/2554101000020.1Shear_key20
24033Shear keys - W - 2553/2554101000030.0Shear_key20
34033Shear keys - W - 2553/255410100004-0.1Shear_key19
44033Shear keys - W - 2553/2554101000050.0Shear_key19
-
-
- -
- -
-
- -
-
-
-
-
-
-

Exercise 2.2

-
-
-
-
-
-
In [28]:
-
-
-
# Filer dataframe to contain only shear keys 
-dfr = dfr[dfr['Shear_key'] != 'Not_a_shear_key']
-dfr.head()
-
- -
-
-
- -
-
- - -
-
Out[28]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LCLC-titleElement_noP[kN]Shear_key
04033Shear keys - W - 2553/2554101000010.1Shear_key20
14033Shear keys - W - 2553/2554101000020.1Shear_key20
24033Shear keys - W - 2553/2554101000030.0Shear_key20
34033Shear keys - W - 2553/255410100004-0.1Shear_key19
44033Shear keys - W - 2553/2554101000050.0Shear_key19
-
-
- -
- -
-
- -
-
-
-
-
-
-

Exercise 2.3

-
-
-
-
-
-
In [29]:
-
-
-
dfr = dfr.drop('Element_no', axis=1)
-dfr.head()
-
- -
-
-
- -
-
- - -
-
Out[29]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LCLC-titleP[kN]Shear_key
04033Shear keys - W - 2553/25540.1Shear_key20
14033Shear keys - W - 2553/25540.1Shear_key20
24033Shear keys - W - 2553/25540.0Shear_key20
34033Shear keys - W - 2553/2554-0.1Shear_key19
44033Shear keys - W - 2553/25540.0Shear_key19
-
-
- -
- -
-
- -
-
-
-
-
-
-

Exercise 2.4

-
-
-
-
-
-
In [30]:
-
-
-
# Create groupby object and perform sum operation on it
-dfr_sum = dfr.groupby(['Shear_key', 'LC', 'LC-title'], as_index=False).sum()
-dfr_sum.head()
-
- -
-
-
- -
-
- - -
-
Out[30]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Shear_keyLCLC-titleP[kN]
0Shear_key14043Shear keys - W - remaining0.0
1Shear_key14044Top slab- beam shear keys activa-284.1
2Shear_key14045Protection concrete-300.6
3Shear_key14046Backfill-855.8
4Shear_key14047Ballast concrete of 16.8-900.3
-
-
- -
- -
-
- -
-
-
-
-
-
-

The list of column labels ['Shear_key', 'LC', 'LC-title'] are the columns that are to be present in the resulting dataframe. The parameter as_index is by default True, which would create a multiindex out of all the column labels listed. By setting as_index=False, we reset to 0-indexing.

- -
-
-
-
-
-
-
-
-

Exercise 2.5

-
-
-
-
-
-
In [35]:
-
-
-
# Filter for shear key 1
-df_key1 = dfr_sum[dfr_sum['Shear_key'] == 'Shear_key1']
-
-# Use built-in pandas plot
-df_key1.plot(kind='bar', x='LC', y='P[kN]', rot=45, figsize=(15, 5), color='orange', alpha=0.50)
-
- -
-
-
- -
-
- - -
-
Out[35]:
- - - -
-
<matplotlib.axes._subplots.AxesSubplot at 0x1aeecabdbe0>
-
- -
- -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Note: The same plot could be constructed by extracting arrays in columns 'LC' and 'P[kN]' to variables and using matplotlib itself as plt.bar(...). To do that, you would have to import matplotlib.pyplot as plt. -pandas just provides a shortcut to get a fast graph out.

- -
-
-
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
- - - - - - diff --git a/Session 6 - Exercise (shear key plots)/Session 6 - Exercise (shear key plots).html b/Session 6 - Exercise (shear key plots)/Session 6 - Exercise (shear key plots).html deleted file mode 100644 index 5ec34e4..0000000 --- a/Session 6 - Exercise (shear key plots)/Session 6 - Exercise (shear key plots).html +++ /dev/null @@ -1,11913 +0,0 @@ - - - -Session 6 - Exercise (shear key plots) - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
-
-

Exercise

Intro

This exercise is taken from a project example where shear forces in a shell element from a Sofistik Finite Element calcuatation are extracted and plotted into one figure per Construction Stage.

-

The purpose of this procedure to give a quick overview of the results after a calculation has finished, and to be able to flip through the Construction Stages to easily compare them.

-

There are in total 56 Construction Stages in the dataset used and three different shear keys, resulting in 168 plots.

-

Each plot will look something like this:

-

title

-

Some plots will be almost empty as loads are close to zero in some Stages.

-

The dataset is called shear_keys_base_slab_v20.txt and can be found in the Session 6 folder for the workshop.

-

Note: Understanding the structural context of the dataset is not important for solving the exercise. The same concepts could be used for all other types of datasets.

-

The exercise

The code comments and some parts of the actual code from the original script is given below.

-

Copy this direcly into the editor to use as a guide through the exercise.

-
-
# Import libraries 
-import pandas as pd
-import matplotlib.pyplot as plt
-import numpy as np
-
-# Set style for matplotlib plots
-plt.style.use('seaborn-whitegrid')    
-
-# Dictionary for mapping node numbers to user chosen shear key names
-shear_keys = {   
-              # Shear key in Base Slab 101
-              'BS101': range(10101, 10199),  
-
-              # Shear key in Base Slab 201
-              'BS201': range(20101, 20199),  
-
-              # Shear key in Base Slab 301 
-              'BS301': range(30101, 30214),  
-}   
-
-# Set file name of dataset
-file_name = 'shear_keys_base_slab_v20.txt'
-
-# Read dataset from text file into dataframe, save it as 'df'
-    #     <Code here!>
-
-# Extract version number from file name as 'vXX'
-# (assume the last 6 characters will always be '...vXX.txt')
-    #     <Code here!>
-
-# Print the head of the dataframe to check it
-    #     <Code here!>
-
-# Contruct a dictionary that maps load case numbers to titles (auto removes duplicates)
-lc_no_to_title_map = dict(zip(df['LC'], df['LC-title']))    
-
-# Loop over all shear key names and their corresponding node numbers 
-for shear_key, nodes in shear_keys.items():
-
-    # Loop over all load cases, create plots and save them to a png-file
-    for lc in df['LC'].unique():
-
-        # Get title of current load case from mapping dictionary
-            #    <Code here!>    (see hint 1 below)
-
-        # Filter dataframe based on load case and nodes in shear key
-            #    <Code here!>    (see hint 2 below)
-
-        # Create figure
-            #    <Code here!> 
-
-        # Create x-values for plot as numbers running from 1 to length of y-values
-            #    <Code here!> 
-
-        # Create y-values for plot as shear forces vx
-            #    <Code here!> 
-
-        # Extract indices where y-values are negative and positive, respectively
-        idx_neg = np.where(y<0)
-        idx_pos = np.where(y>=0)
-
-        # Extract x-values where y-values are negative and positive, respectively
-        x_neg, x_pos = np.take(x, idx_neg)[0], np.take(x, idx_pos)[0]
-
-        # Extract y-values where y-values are negative and positive, respectively
-        y_neg, y_pos = np.take(y, idx_neg)[0], np.take(y, idx_pos)[0]
-
-        # Plot points for negative and positve values as two separate data series
-            #    <Code here!> 
-
-        # Fill between y=0 and the lines where y-values are negative and positive, respectively 
-            #    <Code here!> 
-
-        # Set titles and x- and y-labels
-            #    <Code here!>          
-
-        # Save figure to png-file with meaningful name that varies in every loop
-            #    <Code here!>
-
-
-

The hints below refer to the comments in the code above.

    -
  • Hint 1: The dictionary 'lc_no_to_title_map' has load case numbers as keys and the corresponding titles as values. Use this to get the load case title from inside the loop.

    -
  • -
  • Hint 2: Be sure to save the filtered dataframe to a new variable. If it is saved to a variable of the same name it will be mutated in every loop and quickly end up emtpty.

    -
  • -
-

Some improvements

    -
  • Comparison between the plots could be improved by having the same limits for the y-axis on all plots. This can be set by ax.set_ylim()
  • -
-
    -
  • The function below can find the indices of the peak values, which can be used to annotate the key points to make the plot more readable.
  • -
-
-
def find_local_extrema(y_curve):
-    '''
-    Return indices of all local extrema for the given sequence of values. Indices are sorted in
-    ascending format with no distinction between local maximum and minimum.
-    '''
-    local_max, _ = find_peaks(y_curve, height=0)
-    local_min, _ = find_peaks(-y_curve, height=0)
-    return sorted( np.append(local_min, local_max) )
-
-
-

Prior to running the function, find_peaks from the scipy library must be imported: from scipy.signal import find_peaks

-

After having found the extrema values, they can be annotated like so:

-
-
for extr_val in extrema_values:
-    ax.annotate(f'{y[extr_val]:.0f}', xy=(x[extr_val], y[extr_val]), xytext=(x[extr_val], y[extr_val]))
-
-
- -
-
-
-
-
- - - - - - diff --git a/Session 6 - Exercise (shear key plots)/Session 6 - Exercise (shear key plots).ipynb b/Session 6 - Exercise (shear key plots)/Session 6 - Exercise (shear key plots).ipynb index df30c83..c30850b 100644 --- a/Session 6 - Exercise (shear key plots)/Session 6 - Exercise (shear key plots).ipynb +++ b/Session 6 - Exercise (shear key plots)/Session 6 - Exercise (shear key plots).ipynb @@ -153,7 +153,7 @@ "metadata": { "hide_input": false, "kernelspec": { - "display_name": "Python [default]", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -167,7 +167,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.6" + "version": "3.7.1" }, "latex_envs": { "LaTeX_envs_menu_present": true, diff --git a/Session 6 - Exercise (shear key plots)/Session 6 - Exercise Solutions.html b/Session 6 - Exercise (shear key plots)/Session 6 - Exercise Solutions.html deleted file mode 100644 index bd71756..0000000 --- a/Session 6 - Exercise (shear key plots)/Session 6 - Exercise Solutions.html +++ /dev/null @@ -1,12035 +0,0 @@ - - - -Session 6 - Exercise solution - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
-
-

Session 6 - Exercise solution

-
-
-
-
-
-
In [1]:
-
-
-
# Import libraries 
-import pandas as pd
-import matplotlib.pyplot as plt
-import numpy as np
-
-# Set style for matplotlib plots
-plt.style.use('seaborn-whitegrid')    
-
-# Dictionary for mapping node numbers to user chosen shear key names
-shear_keys = {   
-              # Shear key in Base Slab 101
-              'BS101': range(10101, 10199),  
-
-              # Shear key in Base Slab 201
-              'BS201': range(20101, 20199),  
-
-              # Shear key in Base Slab 301 
-              'BS301': range(30101, 30214),  
-}   
-
-# Set file name of dataset
-file_name = 'shear_keys_base_slab_v20.txt'
-
-# Read dataset from text file into dataframe, save it as 'df'
-df = pd.read_csv(file_name)
-
-# Extract version number from file name as 'vXX'
-# (assume the last 6 characters will always be '...vXX.txt')
-version_number = file_name[-7:-4]
-
-# Print the head of the dataframe to check it
-df.head()
-
- -
-
-
- -
-
- - -
-
Out[1]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LCLC-titleNRNGvx[kN/m]
04031Shear keys - BS - 2553/2554201012012.33
14031Shear keys - BS - 2553/2554201022011.29
24031Shear keys - BS - 2553/2554201032010.87
34031Shear keys - BS - 2553/2554201042010.74
44031Shear keys - BS - 2553/2554201052010.64
-
-
- -
- -
-
- -
-
-
-
In [2]:
-
-
-
%%capture
-# %%capture prevent plots from showing as cell output
-# ------------------------------------------------------
-
-# Contruct a dictionary that maps load case numbers to titles (auto removes duplicates)
-lc_no_to_title_map = dict(zip(df['LC'], df['LC-title']))    
-
-# Loop over all shear key names and their corresponding node numbers 
-for shear_key, nodes in shear_keys.items():
-
-    # Loop over all load cases, create plots and save them to a png-file
-    for lc in df['LC'].unique():
-
-        # Get title of current load case from mapping dictionary
-        lc_title = lc_no_to_title_map[lc]
-        
-        # Filter dataframe based on load case and elements in shear key
-        df_filtered = df[(df['LC'] == lc) & (df['NR'].isin(nodes))]
-        
-        # Create figure
-        plt.figure(figsize=(12, 5))
-        
-        # Create x-values for plot as numbers running from 1 to length of y-values
-        x = np.array(range(1, len(df_filtered['vx[kN/m]'])+1))
-        
-        # Create y-values for plot as shear forces vx
-        y = df_filtered['vx[kN/m]'].values
-        
-        # Extract indices where y-values are negative and positive, respectively
-        idx_neg = np.where(y<0)
-        idx_pos = np.where(y>=0)
-
-        # Extract x-values where y-values are negative and positive, respectively
-        x_neg, x_pos = np.take(x, idx_neg)[0], np.take(x, idx_pos)[0]
-
-        # Extract y-values where y-values are negative and positive, respectively
-        y_neg, y_pos = np.take(y, idx_neg)[0], np.take(y, idx_pos)[0]
-
-        # Plot lines for negative and positve values as two separate lines
-        plt.plot(x_neg, y_neg, '.', color='salmon')
-        plt.plot(x_pos, y_pos, '.', color='cornflowerblue')    
-        
-        # Fill between y=0 and the lines where y-values are negative and positive, respectively 
-        plt.fill_between(x, y, where=y<0, color='salmon', alpha=0.25, interpolate=True)
-        plt.fill_between(x, y, where=y>=0, color='cornflowerblue', alpha=0.25, interpolate=True)
-        
-        # Set titles and x- and y-labels
-        plt.title(f'Shear force $vx$ [kN/m] for base slab shear key ${shear_key}$' + '\n' +
-                     f'{lc_title} $(LC: {lc}) ({version_number})$', fontsize=18)
-        plt.xlabel('Points along shear key', fontsize=14)
-        plt.ylabel('Slab shear force $vx$ [kN/m]', fontsize=14)
-        
-        # Save figure to png-file with meaningful name that varies in every loop
-        plt.savefig(f'Plots/{version_number}/{shear_key}_{lc}.png')
-
- -
-
-
- -
-
-
-
-
-
-

Explanations to some of the code lines are given below

    -
  • Line with df_filtered = ...: The dataframe df_filtered is uniqie in every loop, since it is filtered based on the current load case and the nodes in the current shear key. The filtering is done based on the original large dataframe. Every operation from here on out inside the loop must use df_filtered and not the original dataframe. For filtering based on the nodes in the shear key .isin is used. This is a good way to filter based on values in a list. And the nodes for each shear key is is stored in the loop variable elements as a list.
  • -
-
    -
  • Line with x = ...: This generates the x-values, which will just be increasing numbers from 1 and up. Note that range(start, stop) goes from start to stop-1.
  • -
-
    -
  • Line with y = ...: Collects the y-values for the plot in the current loop. df['vx[kN/m']] extracts a Series, which is a single column, but with index numbers to its left. To get only the column values as an array, we do df['vx[kN/m']].values. Btw, this will also work for dataframes, but will instead return a 'matrix' array instead of a 'vector' array as for Series.
  • -
-
    -
  • Lines with plt.plot(): The negative and positive points are plotting as two separate data series so they can have different colors. Connecting the points by lines makes the plot look strange after it has been separated, so only points are plotted.
  • -
-
    -
  • Lines with plt.fillbetween(): Parameter alpha set the opacity. Parameter interpolate=True will make sure the fill is not "cut off" near a crossing of the y-axis.
  • -
-
    -
  • Lines with for plt.title(): When creating plots by loops the title for each plot should probably have a unique title that varies with the loop variable(s). A convenient way to put variables inside text is by using f-strings.
  • -
-
    -
  • Line with plt.savefig() Subfolder 'Plots' and subsubfolder '' has to be created before running this. It the folders are not present FileNoteFoundError will be raised. The png-files could also be saved directly in the same folder as the script. In that case only '<file_name>.png' would be necessary. By saving in a subfolder whose name depends on the version number given in the name original txt file, it is easier to keep track of versions and avoid overwriting files from previous versions.
  • -
-

Improvements mentioned in exercise text

y-limits of plot

Set the y-limits of the plot by plt.ylim([ymin, ymax]).

-

ymin and ymax could be determined as the largest occuring magnitude values among all plots.

-
# Put this line before the loop
-all_loads = df['vx[kN/m]']
-extr_magnitude = max(abs(min(all_loads)), abs(max(all_loads)))
-
-
# Put this line in each loop before saving the figure
-plt.ylim([-1.1*extr_magnitude, 1.1*extr_magnitude])
-
-

Annotations of local extrema

For annotating the local extremum points, define the function find_local_extrema(y_curve) from the exercise text somewhere in the script before the loop. Afterwards, include the line below within the for loop somewhere between the line where the figure is create and the line where the figure is saved.

-
# Find local extremum points of graph
-extrema_indices = find_local_extrema(y)
-
-for extr_idx in extreme_indices:
-    ax.annotate(f'{y[extr_idx]:.0f}', xy=(x[extr_idx], y[extr_idx]), xytext=(x[extr_idx], y[extr_idx]))
-
-

This annotates the local peaks which helps for readability of the graph. The annotations could be even better than this by ensuring that text does not overlap. and by always annotating the two end points of the graph.

-

Another improvement

Instead of having to manually create the subdirectories Plots and /{version_no}, we could check if they exist and create them if they don't. For this, we could use the built-in os module to manipulate the file system.

- -
-
-
-
-
- - - - - - diff --git a/Session 7 - Coordinate Transformation/Session 7 - Coordinate Transformation.html b/Session 7 - Coordinate Transformation/Session 7 - Coordinate Transformation.html deleted file mode 100644 index 9bd3b39..0000000 --- a/Session 7 - Coordinate Transformation/Session 7 - Coordinate Transformation.html +++ /dev/null @@ -1,13652 +0,0 @@ - - - - -Session 7 - Explanations and exercises - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
- -
-
-
-
-
-
-

Session 7 - Tranformation functions

Coordinate transformations can be performed by matrix operations. Some common ones are:

-\begin{equation} -\tag{Rotation about Origin} -\begin{bmatrix} -x_{\text{rotated}}\\y_{\text{rotated}}\\1 -\end{bmatrix} = -\begin{bmatrix} -\cos(\theta) & \sin(\theta) & 0 \\ --\sin(\theta) & \cos(\theta) & 0 \\ -0 & 0 & 1 -\end{bmatrix} -\begin{bmatrix} x \\ y \\ 1 \end{bmatrix} -\end{equation}\begin{equation} -\tag{Translation} -\begin{bmatrix} -x_{\text{translated}}\\y_{\text{translated}}\\1 -\end{bmatrix} = -\begin{bmatrix} -1 & 0 & \Delta x \\ -0 & 1 & \Delta y \\ -0 & 0 & 1 -\end{bmatrix} -\begin{bmatrix} x \\ y \\ 1 \end{bmatrix} -\end{equation}\begin{equation} -\tag{Scale about Origin} -\begin{bmatrix} -x_{\text{scaled}}\\y_{\text{scaled}}\\1 -\end{bmatrix} = -\begin{bmatrix} -C_x & 0 & 0 \\ -0 & C_y & 0 \\ -0 & 0 & 1 -\end{bmatrix} -\begin{bmatrix} x \\ y \\ 1 \end{bmatrix} -\end{equation}\begin{equation} -\tag{Mirror about $y$-axis} -\begin{bmatrix} -x_{\text{mirrored}}\\y_{\text{mirrored}}\\1 -\end{bmatrix} = -\begin{bmatrix} --1 & 0 & 0 \\ -0 & 1 & 0 \\ -0 & 0 & 1 -\end{bmatrix} -\begin{bmatrix} x \\ y \\ 1 \end{bmatrix} -\end{equation}
    -
  • In the rotation matrix it is assumed that the angle $\theta$ is a clockwise rotation.
  • -
-
    -
  • In the translation matrix $\Delta x$ and $\Delta y$ denote the absolute translation in the $x$- and $y$-direction, respectively.
  • -
-
    -
  • In the scaling matrix $C_x$ and $C_y$ denote the scaling in the $x$- and $y$-direction, respectively.
  • -
-
    -
  • The mirroring matrix has no inputs and can bee seen as a boolean operation. It can be done in exactly one way or not done at all.
  • -
-

See more here: https://upload.wikimedia.org/wikipedia/commons/2/2c/2D_affine_transformation_matrix.svg

- -
-
-
-
-
-
-

Vectorization in numpy

numpy can perform calculation in a vectorized manner meaning that vector and matrix operations can be done on entire arrays at a time as opposed to value by value.

-

Vectorization can eliminate the use of for loops in many scenarios, which makes the code easier to read and write. As an added bonus, vectorized calculations are also much faster than their looping counterparts.

-

For the equations above, we can utilize vectorization by using a arrays (or lists) of values for $x$ and $y$ instead of single values. That implies that $1$ also must be an array of ones with the same size.

-

Thus, the vector $[x, y, 1]^T$ on the right hand side of the is actually an array of arrays.

-

The resulting vector $[x_{\text{transformed}}, y_{\text{transformed}}, 1]^T$ is of course also an array or arrays.

-

Unpacking values

A small example that demontrates unpacking of variables from a function output:

- -
-
-
-
-
-
In [1]:
-
-
-
def f():
-    '''
-    Define function that takes no input and returns three values
-    '''
-    return 1, 2, 3
-
-# Call function and save result in variable
-result = f()
-result
-
- -
-
-
- -
-
- - -
- -
Out[1]:
- - - - -
-
(1, 2, 3)
-
- -
- -
-
- -
-
-
-
-
    -
  • Note: When returning multiple values from a function, they will by default be a tuple. This is basically the same as if the last line of the function would have been return (1, 2)
  • -
-

Unpacking the result to two variables:

- -
-
-
-
-
-
In [2]:
-
-
-
# Unpack function output to two variables
-a, b, c = f()
-a
-
- -
-
-
- -
-
- - -
- -
Out[2]:
- - - - -
-
1
-
- -
- -
-
- -
-
-
-
In [3]:
-
-
-
b
-
- -
-
-
- -
-
- - -
- -
Out[3]:
- - - - -
-
2
-
- -
- -
-
- -
-
-
-
In [4]:
-
-
-
c
-
- -
-
-
- -
-
- - -
- -
Out[4]:
- - - - -
-
3
-
- -
- -
-
- -
-
-
-
-

As we see, if we do not unpack, all values are saved in a tuple.

-

But if we decide to unpack, we have to account for all outputs. If we try to unpack only 2 values when the function returns 3 we get this error:

- -
-
-
-
-
-
In [5]:
-
-
-
# ValueError, function returns 3 values, but only 2 unpacked
-h, i = f()
-
- -
-
-
- -
-
- - -
- -
- - -
-
----------------------------------------------------------------------------
-ValueError                                Traceback (most recent call last)
-<ipython-input-5-393f2c6eb4f0> in <module>
-      1 # ValueError, function returns 3 values, but only 2 unpacked
-----> 2 h, i = f()
-
-ValueError: too many values to unpack (expected 2)
-
-
- -
-
- -
-
-
-
-

If we try to unpack too many:

- -
-
-
-
-
-
In [6]:
-
-
-
# ValueError, function returns 3 values, but 4 were unpacked
-h, i, j, k = f()
-
- -
-
-
- -
-
- - -
- -
- - -
-
----------------------------------------------------------------------------
-ValueError                                Traceback (most recent call last)
-<ipython-input-6-6809bb4f4bd8> in <module>
-      1 # ValueError, function returns 3 values, but 4 were unpacked
-----> 2 h, i, j, k = f()
-
-ValueError: not enough values to unpack (expected 4, got 3)
-
-
- -
-
- -
-
-
-
-

Note: When unpacking, the variables of the left side of the equal sign must exactly match the number of outputs, so all unpacked elements are accounted for.

-

There are fancier ways to unpack which can handle cases where it is not known how many elements are to be unpacked. See for example https://www.python.org/dev/peps/pep-3132/.

- -
-
-
-
-
-
-

Unpacking values for the tranformation examples

The resulting array of arrays will have the code structure

-
# Define an array of arrays
-array_of_arrays = [ [x_values], [y_values], [ones] ]
-
-

Unpacking x-values and y_values to their own arrays can be done like this:

-
# Unpack an array of arrays
-x_values, y_values, ones = array_of_arrays
-
-

In this case, we don't care about the array of ones. It is a quite common scenario that a function returns some values that are not needed. In Python, it is common comvention to unpack unused variables to an underscore _ like this:

-
# Convention for unpacking unused variables
-x_transformed, y_transformed, _ = array_of_arrays
-
-

By following this convention, it is clear to anyvody reading the code that this value is not going to be used throughout the program.

-

Some numpy functions

numpy.matmul

This function will find the matrix product of two arrays:

- -
-
-
-
-
-
In [7]:
-
-
-
import numpy as np
-
-# Define a vector
-a = np.array([1, 2, 3])
-
-# Define a matrix
-T = np.array([ [1, 1, 5], [3, 1, 1], [5, 0, 1] ])
-
-# Compute the matrix product {T}x{a}
-b = np.matmul(T, a) 
-b
-
- -
-
-
- -
-
- - -
- -
Out[7]:
- - - - -
-
array([18,  8,  8])
-
- -
- -
-
- -
-
-
-
-

np.ones

Creates an array of ones of a specifies shape

- -
-
-
-
-
-
In [8]:
-
-
-
# Create 1x5 vector of ones (1D array)
-np.ones(5)
-
- -
-
-
- -
-
- - -
- -
Out[8]:
- - - - -
-
array([1., 1., 1., 1., 1.])
-
- -
- -
-
- -
-
-
-
In [9]:
-
-
-
# Create 5x5 matrix of ones (2D array)
-np.ones([5, 5])
-
- -
-
-
- -
-
- - -
- -
Out[9]:
- - - - -
-
array([[1., 1., 1., 1., 1.],
-       [1., 1., 1., 1., 1.],
-       [1., 1., 1., 1., 1.],
-       [1., 1., 1., 1., 1.],
-       [1., 1., 1., 1., 1.]])
-
- -
- -
-
- -
-
-
-
-

Exercise 1

Write a function that implements rotation of a array of coordinates x and y. The angle of rotation in clockwise direction should be an input parameter with default value of 90 degress. The function should return the rotated coordinates xr and yr.

-

Test the function with these arrays.

-
# Test arrays to transform
-x = np.array[-5, 5, 5, 0.5, 0.5, 5, 5, -5, -5, -0.5, -0.5, -5, -5])
-y = np.array[-8, -8, -6, -6, 6, 6, 8, 8, 6, 6, -6, -6, -8])
-
-

Exercise 2

Write a function that takes a pair of arrays of transformed coordinates xt and yt as input and plots them as a line plot. The function should be able to plot the orignal coordinates as well, but this should be optional and not plotted as default.

-

Plot the original shape from Exercise 1 together with the rotated shape.

-

A good way of setting default values for parameters that should not be present as default is by setting them equal to None in the function definition. The code inside the function can then check if they actual values were input

-

Exercise 3

Write a function that implements translation of an array of coordinates x and y. The translation shall be defined by values (not arrays) x_translate and y_translate, which are the distance the points will move in te $x$- and $y$-direction, respectively.

-

Test the function with the arrays given in Exercise 1. Plot the translation with the function written in Exercise 2.

-

Exercise 4

Implement the scaling transformation as a function. Test it by plotting it.

-

Exercise 5

Implement mirroring tranformation about the $y$-axis as a funcion.

-

Since the given coordinates in Exercise 1 are symmetic about the $y$-axis, the mirrored coordinates will lie on top of the orignal ones. Try to test it by plotting.

-

You can quickly make a more visible test by moving all the $x$-coordinates, say 20 units to the right. Since we are using numpy this can be done by simply adding 20 to the array itself x+20. This is a simple example of vectorization.

-

Exercise 6

Write a function that combines all of the above tranformations in one. When performing multiple transformations at once, the tranformation matrices can be multiplied by each other prior to multiplication with the orignal coordinates.

-

It could have a structure like this:

-
def tranform(x, y, rotatation=0, scaling=(1, 1), translation=(0, 0), mirroring=False)):
-    '''
-    Perform a combined coordinate tranformation according to given inputs. If no inputs are given, returns the unchanged coordinates.
-
-    Args:
-        x (array)                   : x-values to transform.
-        y (array)                   : y-values to transform.
-        rotate (float, optional)    : Clockwise rotation angle in [deg]. Defaults to no rotation.
-        scale (float, optional)     : Scaling factor in axes directions (cx, cy). Defaults to no scaling.
-        translate (tuple, optional) : Translation in axes directions (dx, dy). Defaults to no translation.
-        mirror (bool, optional)     : Whether or not to mirror the coordinates, Defaults to no mirroring.
-    '''
-
-    # Code here
-
-
    -
  • Remember that rotation and scaling are performed about the origin. So the order of operations will matter.
  • -
  • You can call the previously defined functions to perform the individual transformations if you want.
  • -
  • Return the tranformed coordinates xt, yt.
  • -
-

Addtional Exercise

Write a alternative function for translation where the translation input can be given as a distance that the points should move and the corresponding angle from the $x$-axis. This can often be useful instead of the one defined earlier where the distances are given parallel to the $x$- and $y$-axes.

- -
-
-
-
-
- - - - - - diff --git a/Session 7 - Coordinate Transformation/Session 7 - Exercise solutions.html b/Session 7 - Coordinate Transformation/Session 7 - Exercise solutions.html deleted file mode 100644 index b96daa3..0000000 --- a/Session 7 - Coordinate Transformation/Session 7 - Exercise solutions.html +++ /dev/null @@ -1,12208 +0,0 @@ - - - -Session 7 - Exercise solutions - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
-
-

Session 7 - Exercise solutions

-
-
-
-
-
-
In [1]:
-
-
-
import numpy as np
-from math import cos, sin, atan, pi
-import matplotlib.pyplot as plt
-
-
-def rotate(x, y, theta=90):
-    '''
-    Rotate coordinate lists/arrays x and y rotated angle theta [deg] clockwise. 
-    Returns the rotated coordinates as arrays.
-    '''
-    
-    # Convert angle to radians
-    theta = pi * theta / 180
-    
-    # Define rotation matrix    
-    R = np.array([[cos(theta), sin(theta), 0],
-                  [-sin(theta), cos(theta), 0],
-                  [0, 0, 1]])
-
-    # Define array of original coordinates 
-    xy1 = np.array([x, y, np.ones(len(x))])
-    
-    # Compute rotated coordinates
-    xr, yr, _ = np.matmul(R, xy1)
-
-    return xr, yr
-
-
-
-# Test arrays to transform
-x = np.array([-5, 5, 5, 0.5, 0.5, 5, 5, -5, -5, -0.5, -0.5, -5, -5])
-y = np.array([-8, -8, -6, -6, 6, 6, 8, 8, 6, 6, -6, -6, -8])
-
-# Call the function with test arrays
-xr, yr = rotate(x, y, theta=45)
-
-# Display the rotated x-coordinates
-xr
-
- -
-
-
- -
-
- - -
-
Out[1]:
- - - -
-
array([-9.19238816, -2.12132034, -0.70710678, -3.8890873 ,  4.59619408,
-        7.77817459,  9.19238816,  2.12132034,  0.70710678,  3.8890873 ,
-       -4.59619408, -7.77817459, -9.19238816])
-
- -
- -
-
- -
-
-
-
In [2]:
-
-
-
# Display the rotated y-coordinates
-yr
-
- -
-
-
- -
-
- - -
-
Out[2]:
- - - -
-
array([-2.12132034, -9.19238816, -7.77817459, -4.59619408,  3.8890873 ,
-        0.70710678,  2.12132034,  9.19238816,  7.77817459,  4.59619408,
-       -3.8890873 , -0.70710678, -2.12132034])
-
- -
- -
-
- -
-
-
-
In [3]:
-
-
-
def plot_transform(xt, yt, x=None, y=None, title=None):
-    ''' 
-    Plot the transformed coordinates (xr, yr). Optionally plot the original coordinates (x, y).
-    All four inputs are of type list or array and should all have same length. 
-    Optionally give a title tp the plot as a string.
-    '''
-    
-    # Plot transformed coordintes
-    plt.plot(xt, yt, '.-', color='limegreen')
-    
-    # Plot original coordinates if they were input
-    if x is not None and y is not None:
-        plt.plot(x, y, '.-', color='black')
-        
-    # Set title if that was input
-    if title is not None:
-        plt.title(title, fontsize=15)
-        
-    # Set same scaling on x- and y-axis and show the plot
-    plt.axis('equal')    
-    plt.show()
-    
-    
-# Plot rotated coordinates from previosly
-rotate_title = 'Rotated coordinates with $\\theta = 45 ^{\circ} $'
-plot_transform(xr, yr, x, y, title=rotate_title)
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Note: An optional title parameter is also implemented here, even though it was not required by the exercise text.

- -
-
-
-
-
-
In [4]:
-
-
-
def translate(x, y, x_translate, y_translate):
-    ''' 
-    Translate coordinate lists/arrays x and y the distance x_translate in the x-direction 
-    and the distance y_translate in the y-direction. Returns the translated coordinates as arrays. 
-    '''
-    
-    # Define translation matrix
-    T = np.array([[1, 0, x_translate],
-                  [0, 1, y_translate],
-                  [0, 0, 1]])
-    
-    # Define array of original coordinates
-    xy1 = np.array([x, y, np.ones(len(x))])
-    
-    # Compute translated coordinates
-    xt, yt, _ = np.matmul(T, xy1)
-    
-    return xt, yt
-
-
-# Call the function with test arrays
-xt, yt = translate(x, y, 10, 8)
-
-# Plot the translated coordinates
-translate_title = 'Translated coordinates with $(\Delta x, \Delta y) = (10, 8)$'
-plot_transform(xt, yt, x, y, title=translate_title)
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
In [5]:
-
-
-
def scale(x, y, x_scale, y_scale):
-    ''' 
-    Scale coordinate lists/arrays x and y the by factors x_scale and y_scale in the x-direction 
-    and y_translate, respectively. Returns the scaled coordinates as arrays.  
-    '''
-    
-    # Define scaling matrix
-    S = np.array([[x_scale, 0, 0],
-                  [0, y_scale, 0],
-                  [0, 0, 1]])
-    
-    # Define array of original coordinates
-    xy1 = np.array([x, y, np.ones(len(x))])
-    
-    # Compute scaled coordinates
-    xs, ys, _ = np.matmul(S, xy1)
-    
-    return xs, ys
-
-
-# Call the function with test arrays
-xs, ys = scale(x, y, 2, 1.5)
-
-# Plot the scaled coordinates
-scale_title = 'Scaled coordinates with $(x_{scaled}, y_{scaled}) = (2, 1.5)$'
-plot_transform(xs, ys, x, y, title=scale_title)
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
In [6]:
-
-
-
def mirror(x, y):
-    ''' 
-    Mirror coordinate lists/arrays x and y about the y-axis. Returns the mirrored 
-    coordinates as arrays. 
-    '''
-    
-    # Define translation matrix
-    M = np.array([[-1, 0, 0],
-                  [0, 1, 0],
-                  [0, 0, 1]])
-    
-    # Define array of original coordinates
-    xy1 = np.array([x, y, np.ones(len(x))])
-    
-    # Compute new coordinates
-    xm, ym, _ = np.matmul(M, xy1)
-    
-    return xm, ym
-
-
-# Call the function with test arrays with 20 added to all x-values
-xm, ym = mirror(x+20, y)
-
-# Plot the mirrored coordinates
-mirror_title = 'Mirrored coordinates'
-plot_transform(xm, ym, x, y, title=mirror_title)
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
In [7]:
-
-
-
def transform(x, y, rotation=0, scaling=(1, 1), translation=(0, 0), mirroring=False):
-    '''
-    Perform a combined coordinate tranformation according to given inputs. 
-    Returns the transformed coordinates as arrays.
-    If no inputs are given, returns the unchanged coordinates.
-
-    Args:
-        x (array)                   : x-values to transform.
-        y (array)                   : y-values to transform.
-        rotate (float, optional)    : Clockwise rotation angle in [deg]. Defaults to no rotation.
-        scale (float, optional)     : Scaling factor in axes directions (cx, cy). Defaults to no scaling.
-        translate (tuple, optional) : Translation in axes directions (dx, dy). Defaults to no translation.
-        mirror (bool, optional)     : Whether or not to mirror the coordinates, Defaults to no mirroring.
-    '''
-    
-    # Rotate coordinates 
-    xt, yt = rotate(x, y, theta=rotation)
-    
-    # Scale coordinates
-    xt, yt = scale(xt, yt, scaling[0], scaling[1])
-    
-    # Translate coordinates
-    xt, yt = translate(xt, yt, translation[0], translation[1])
-    
-    # Mirror coordinates if input parameter mirroring is set to True
-    if mirroring:
-        xt, yt = mirror(xt, yt)
-
-    # Return transformed coordinates as numpy arrays
-    return xt, yt
-
-
-# Call the function with test arrays
-xt, yt = transform(x, y, rotation=45, scaling=(1.5, 2), translation=(10, 8), mirroring=True)
-
-# Plot the transformed coordinates
-transform_title = 'Transformed coordinates'
-plot_transform(xt, yt, x, y, title=transform_title)
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
- - - - - - diff --git a/Session 8 - Exercise (Interpolation)/Session 8 - Exercise (Interpolation).html b/Session 8 - Exercise (Interpolation)/Session 8 - Exercise (Interpolation).html deleted file mode 100644 index e2acf0e..0000000 --- a/Session 8 - Exercise (Interpolation)/Session 8 - Exercise (Interpolation).html +++ /dev/null @@ -1,11929 +0,0 @@ - - - -Session 8 - Interpolation - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
-
-

Session 8 - Interpolation

If you haven't already installed the packages xlrd and scipy, you can install them by opening the Anaconda Prompt and type:

-
    -
  • pip install xlrd

    -
  • -
  • pip install scipy

    -
  • -
-

Exercise 1

This exercise is mostly about reading and understanding code written by others. Which is often just as important as being able to write it oneself.

-

Exercise 1.1

Read through the code given in the scipt below and try to understand what it does and how it does it.

-

Copy the script tot he editor and run it.

-

Add print statements if you are unsure about how a certain varaible looks at any point. Remember you can print the first five rows of a dataframe with df.head().

-

The script reads two Excel files. One limitation of this is that they cannot be read while they are open. If you want to inspect these files while running the script, create a copy.

-

Exercise 1.2

The variable settlements_interpolated is a numpy array (you can see this by type(settlements_interpolated) => <class 'numpy.ndarray'>).

-

The last part of the code creates a figure object and an axis object which enables 3D plots.

-

Continue the plotting code to add:

-
    -
  • 3D scatter points of the known points $(x_{known}, y_{known}, w_{known})$

    -
  • -
  • 3D scatter points of the interpoalted settlements in every base slab node $(x_{nodes}, y_{nodes}, w_{nodes})$

    -
  • -
-

In the above $w$ denotes the settlements.

-

Exercise 1.3

All the settlement values in the base slab are in this example to be transferred to and applied in a Sofistik calculation.

-

To this end, we can create a text file with the exact syntax that the Sofistik input language accepts (this language is called CADINP and is somewhat similar to IBDAS's input language). The file has to be a .dat-file.

-

To write data to a file we can use something called context managers. Basically, it allows us to open a file and write to it. See code snippet below:

-
-
# Use a context manager to open and write ('w') to file
-with open('file_name.dat', 'w') as file:
-
-    # The file can from here on out be referred to as file
-    file.write("This text will be written inside 'file_name.dat'")
-
-
-

By using the concept the file is automatically closed after our indented block is terminated. It also creates the file in case it doesn't already exist.

-

The Sofistik input file we want has the format:

-
+PROG SOFILOAD 
-
-LC 25 type 'P' fact 1.0 facd 0.0 titl 'LT settlement all nodes'
-
-  POIN NODE 'insert first node number' WIDE 0 TYPE WZZ 'insert first settlement value'
-  ... 'one line per base slab node' ... 
-  POIN NODE 'insert last node number' WIDE 0 TYPE WZZ 'insert last settlement value'
-
-END
-
-

The indented block should print all the node/settlement pairs. The three non-indented lines should only appear once. The output file should look like the file settlement_field_generated_code_example.dat in the folder.

- -
-
-
-
-
-
-
-
-

The script

import pandas as pd
-import numpy as np
-from scipy.interpolate import griddata
-import matplotlib.pyplot as plt
-from mpl_toolkits.mplot3d import Axes3D
-
-
-# Set name of Excel file to read
-file_known = 'known_sections_plaxis.xlsx'
-
-# Set name of sheet to read from Excel file
-sheet_known = 'known_sections_plaxis'
-
-# Read data from Excel sheet into a dataframe
-df = pd.read_excel(file_known, sheet_name=sheet_known, skiprows=7)
-
-# Extract columns whose names starts with a 'Y' into new dataframe of Y-coordinates
-df_y = df[df.columns[df.columns.str.startswith('Y')]]
-
-# Extract columns whose names starts with 'Z' into new dataframe of settlements
-df_settlements_known = df[df.columns[df.columns.str.startswith('Z')]]
-
-# Flatten dataframe values into 1D array
-y_known = df_y.values.flatten()
-settlements_known = df_settlements_known.values.flatten()
-
-# Extract known x-values
-x_known = df['X']
-
-# Create X-array by repeating itself as many times as there are Y-columns
-# This will create matching(x, y)-points between arrays x and y
-x_known = np.repeat(x_known, len(df_y.columns))
-
-# Set names and read Excel file with base slab nodes
-file_nodes = 'base_slab_nodes.xlsx'
-sheet_nodes = 'XLSX-Export'
-df_nodes = pd.read_excel(file_nodes, sheet_name=sheet_nodes)
-
-# Extract x- and y-coordinates of nodes
-x_nodes = df_nodes['X [m]']
-y_nodes = df_nodes['Y [m]']
-
-# Extract node numbers
-node_no = df_nodes['NR']
-
-# Mirror known y-values and add corresponding x-values and settlements
-x_known = np.append(x_known, x_known)
-y_known = np.append(y_known, -y_known)
-settlements_known = np.append(settlements_known, settlements_known)
-
-# Arrange known (x, y) points to fit input for interpolation
-xy_known = np.array(list(zip(x_known, y_known)))
-
-# Perform interpolation calculation
-settlements_interpolated = griddata(xy_known, settlements_known, (x_nodes, y_nodes), method='cubic')
-
-
-####################
-### Exercise 1.2 ###
-####################
-# Create figure object
-fig = plt.figure()
-
-# Create axis object for 3D plot
-ax = fig.add_subplot(111, projection='3d')
-
-# Plot known settlement points as 3D scatter plot (ax.scatter(...))
-    # <Put plotting code here!>
-
-# Plot interpolated field as 3D scatter plot
-    # <Put plotting code here!>
-
-# Show figure
-    # <Put plotting code here!>
-
-
-####################
-### Exercise 1.3 ###
-####################
-# Write Sofistik input code to .dat-file for applying settlements as imposed displacements
-    # <Put plotting code here!>
-
- -
-
-
-
-
- - - - - - diff --git a/Session 8 - Exercise (Interpolation)/Session 8 - Exercise Solutions.html b/Session 8 - Exercise (Interpolation)/Session 8 - Exercise Solutions.html deleted file mode 100644 index 018a7f0..0000000 --- a/Session 8 - Exercise (Interpolation)/Session 8 - Exercise Solutions.html +++ /dev/null @@ -1,11896 +0,0 @@ - - - -Session 8 - Exercise solutions - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
-
-

Session 8 - Exercise solution

The full script is provided below.

- -
-
-
-
-
-
-
-
-
import pandas as pd
-import numpy as np
-from scipy.interpolate import griddata
-import matplotlib.pyplot as plt
-from mpl_toolkits.mplot3d import Axes3D
-
-
-# Set name of Excel file to read
-file_known = 'known_sections_plaxis.xlsx'
-
-# Set name of sheet to read from Excel file
-sheet_known = 'known_sections_plaxis'
-
-# Read data from Excel sheet into a dataframe
-df = pd.read_excel(file_known, sheet_name=sheet_known, skiprows=7)
-
-# Extract columns whose names starts with a 'Y' into new dataframe of Y-coordinates
-df_y = df[df.columns[df.columns.str.startswith('Y')]]
-
-# Extract columns whose names starts with 'Z' into new dataframe of settlements
-df_settlements_known = df[df.columns[df.columns.str.startswith('Z')]]
-
-# Flatten dataframe values into 1D array
-y_known = df_y.values.flatten()
-settlements_known = df_settlements_known.values.flatten()
-
-# Extract known x-values
-x_known = df['X']
-
-# Create X-array by repeating itself as many times as there are Y-columns
-# This will create matching(x, y)-points between arrays x and y
-x_known = np.repeat(x_known, len(df_y.columns))
-
-# Set names and read Excel file with base slab nodes
-file_nodes = 'base_slab_nodes.xlsx'
-sheet_nodes = 'XLSX-Export'
-df_nodes = pd.read_excel(file_nodes, sheet_name=sheet_nodes)
-
-# Extract x- and y-coordinates of nodes
-x_nodes = df_nodes['X [m]']
-y_nodes = df_nodes['Y [m]']
-
-# Extract node numbers
-node_no = df_nodes['NR']
-
-# Mirror known y-values and add corresponding x-values and settlements
-x_known = np.append(x_known, x_known)
-y_known = np.append(y_known, -y_known)
-settlements_known = np.append(settlements_known, settlements_known)
-
-# Arrange known (x, y) points to fit input for interpolation
-xy_known = np.array(list(zip(x_known, y_known)))
-
-# Perform interpolation calculation
-settlements_interpolated = griddata(xy_known, settlements_known, (x_nodes, y_nodes), method='cubic')
-
-
-####################
-### Exercise 1.2 ###
-####################
-# Create figure object
-fig = plt.figure()
-
-# Create axis object for 3D plot and put it on the figure
-ax = fig.add_subplot(111, projection='3d')
-
-# Plot known settlement points as 3D scatter plot (ax.scatter(...))
-ax.scatter(x_known, y_known, settlement_known, '-.', color='limegreen')
-
-# Plot interpolated field as 3D scatter plot
-ax.scatter(x_nodes, y_nodes, settlement_interpolated, '.', color='cornflowerblue', s=0.1)
-
-# Show figure
-plt.show()
-
-
-####################
-### Exercise 1.3 ###
-####################
-# Write Sofistik input code to .dat-file for applying settlements as imposed displacements
-with open(f'Generated_sofistik_code.dat', 'w') as file:
-
-    # Write the 'static' text to file 
-    file.write('''+PROG SOFILOAD 
-
-LC 25 type 'P' fact 1.0 facd 0.0 titl 'LT settlement all nodes'  \n''')
-
-    # Write the 'variable' text to file with node number/settlement pairs
-    for node, settlement in zip(node_no, settlement_interpolated):
-        file.write(f'  POIN NODE {node} WIDE 0 TYPE WZZ {settlement} \n')
-
-    # Write 'static' END statement to file
-    file.write('END')
-
- -
-
-
-
-
- - - - - - diff --git a/Session 9 - Heatmaps and merging operation/Session 9 - Exercise Solutions.html b/Session 9 - Heatmaps and merging operation/Session 9 - Exercise Solutions.html deleted file mode 100644 index e3cde90..0000000 --- a/Session 9 - Heatmaps and merging operation/Session 9 - Exercise Solutions.html +++ /dev/null @@ -1,12419 +0,0 @@ - - - -Session 9 - Exercise Solutions - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
-
-

Exercise 1

-
-
-
-
-
-
In [13]:
-
-
-
# Read pile data from csv-file
-df_piles = pd.read_csv('piles.csv')
-
-# Read steel profile data from csv-file
-df_profiles = pd.read_csv('steel_profiles.csv')
-
-# Merge dataframes on "Profile" column (similar to Excel VLOOKUP)
-df_merged = df_piles.merge(df_profiles, on='Profile', how='left')
-
- -
-
-
- -
-
-
-
In [15]:
-
-
-
# Display first five rows of dataframe of steel profiles
-df_profiles.head()
-
- -
-
-
- -
-
- - -
-
Out[15]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Profileh[mm]b[mm]Iy[mm4]Wel_y[mm3]g[kg/m]
0HE100A96100349000072.816.7
1HE120A1141206060000106.019.9
2HE140A13314010300000155.024.7
3HE160A15216016700000220.030.4
4HE180A17118025100000294.035.5
-
-
- -
- -
-
- -
-
-
-
In [14]:
-
-
-
# Display dataframe of piles
-df_piles
-
- -
-
-
- -
-
- - -
-
Out[14]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Pile_typeProfile
0P01HE200A
1P20HE220A
2P05HE240B
3P23NaN
4P04HE200A
5P01HE300B
-
-
- -
- -
-
- -
-
-
-
In [16]:
-
-
-
# Display merged dataframe 
-df_merged
-
- -
-
-
- -
-
- - -
-
Out[16]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Pile_typeProfileh[mm]b[mm]Iy[mm4]Wel_y[mm3]g[kg/m]
0P01HE200A190.0200.036900000.0389.042.3
1P20HE220A210.0220.054100000.0515.050.5
2P05HE240B240.0240.0112600000.0938.083.2
3P23NaNNaNNaNNaNNaNNaN
4P04HE200A190.0200.036900000.0389.042.3
5P01HE300B300.0300.0251700000.01680.0117.0
-
-
- -
- -
-
- -
-
-
-
-
-
-

Exercise 2

-
-
-
-
-
-
In [55]:
-
-
-
import pandas as pd
-import matplotlib.pyplot as plt
-import seaborn as sns
-
-# Set filename for dataset of forces from an IBDAS shell element
-filename = 'Crack_width_Seg7_y_direction.csv'
-
-# Read CSV-file
-df = pd.read_csv(filename, skip_blank_lines=True)
-
-# Filter dataframe for load case and criterion for critical combination
-criterion = 'max My'
-df = df[df['Criterion'] == criterion]
-
-df.head()
-
- -
-
-
- -
-
- - -
-
Out[55]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Criterionx[m]y[m]N[kN/m]M[kNm/m]w_k[mm]
2max My22.1157.603-459.666.10.002553
5max My22.2507.603-432.187.80.013417
8max My22.2507.747-443.180.90.009092
11max My22.1157.747-497.069.00.002068
14max My22.1158.159-522.556.70.000000
-
-
- -
- -
-
- -
-
-
-
In [56]:
-
-
-
# Round dataframe coordinate columns
-df['x[m]'] = round(df['x[m]'], 1)
-df['y[m]'] = round(df['y[m]'], 1)
-
-# Pivot data frame data into matrix form for heatmap plotting
-pivot_final = df.pivot(index='y[m]', columns='x[m]', values='w_k[mm]').sort_index(ascending=False)
-
-# Set max allowable crack with for concrete slab for use as max value of colorbar
-vmax = 0.3
-
-# Create figure
-plt.figure(figsize=(18, 8))
-
-# Plot heatmap wiht annotation
-sns.heatmap(pivot_final, annot=True, annot_kws={'size': 10}, vmax=vmax,
-                  fmt=".2f", square=True, cbar_kws={"orientation": "horizontal"}, cmap='Reds')
-
-# Set titles and axes labels
-plt.title(f'Crack width for Segment 7, Criterion: {criterion}', fontsize=20)
-plt.xlabel('Global x-coordinate [m]', fontsize=16)
-plt.ylabel('Global y-coordinate [m]', fontsize=16)
-
- -
-
-
- -
-
- - -
-
Out[56]:
- - - -
-
Text(142,0.5,'Global y-coordinate [m]')
-
- -
- -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Some extra stuff - Conditional coloring of values

-
-
-
-
-
-
In [61]:
-
-
-
# Create figure
-plt.figure(figsize=(18, 8))
-
-# Plot heatmap wiht annotation, save axes object so it can be accessed later
-ax = sns.heatmap(pivot_final, annot=True, annot_kws={'size': 10}, vmax=vmax,
-                  fmt=".2f", square=True, cbar_kws={"orientation": "horizontal"}, cmap='Reds')
-
-# --- Color values that exceed the max value ---
-
-# Loop over all annotations of the axes object
-for annot in ax.texts:
-    
-    # Annot will now be an object which prints 'Text(x, y, w_k)'
-    
-    # Extract the crack width part of the Text object and convert from string to float 
-    wk = float(annot.get_text())
-    
-    # Set all values that exceed vmax to bold and a special color
-    if wk > vmax: 
-        annot.set_weight('bold')
-        annot.set_color('cyan')
-        annot.set_size(12)
-
-plt.show()
-
- -
-
-
- -
-
- - -
-
- - - -
- -
- -
- -
-
- -
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
- - - - - - diff --git a/Session 9 - Heatmaps and merging operation/Session 9 - Heatmaps and merging operations.html b/Session 9 - Heatmaps and merging operation/Session 9 - Heatmaps and merging operations.html deleted file mode 100644 index 42f167c..0000000 --- a/Session 9 - Heatmaps and merging operation/Session 9 - Heatmaps and merging operations.html +++ /dev/null @@ -1,12132 +0,0 @@ - - - -Session 9 - Heatmaps and merging operations - - - - - - - - - - - - - - - - - - - -
-
- -
-
-
-
-
-

Heatmaps from dataframes

A pandas dataframe can be visualized by means of a so-called heatmap. A heatmap consists of tiles at each data point that adheres to a chosen color palette.

-

Many plotting libraries offer the possibility of creating heatmaps. A common one is called seaborn, which is built on top of matplotlib to enhance certain plot types.

-

Before the dataframe can be plotted to a heatmap, it needs to be in the right format. -If we for example have a dataframe with $x$-values in one column, $y$-values in another and the values to be plotted in a third column we can pivot the data to a new dataframe:

- -
-
-
-
-
-
In [41]:
-
-
-
import pandas as pd
-
-# Creata a dummy dataframe to pivot
-df = pd.DataFrame({
-    'x': [1, 1, 1, 2, 2, 2, 3, 3, 3],
-    'y': ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c'],
-    'values': [34, 74, 1, 9, -36, -24, 47, -27, 47]})
-
-df
-
- -
-
-
- -
-
- - -
-
Out[41]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
xyvalues
01a34
11b74
21c1
32a9
42b-36
52c-24
63a47
73b-27
83c47
-
-
- -
- -
-
- -
-
-
-
In [44]:
-
-
-
# Pivot the dataframe
-df_pivot = df.pivot(index='y', columns='x', values='values')
-df_pivot
-
- -
-
-
- -
-
- - -
-
Out[44]:
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
x123
y
a34947
b74-36-27
c1-2447
-
-
- -
- -
-
- -
-
-
-
-
-
-

A heatmap can be created from a dataframe like this:

- -
-
-
-
-
-
In [43]:
-
-
-
import seaborn as sns
-
-# Plot the pivotted dataframe as a heatmap
-sns.heatmap(df_pivot, annot=True)
-
- -
-
-
- -
-
- - -
-
Out[43]:
- - - -
-
<matplotlib.axes._subplots.AxesSubplot at 0x28d74e097b8>
-
- -
- -
-
- - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Note: Make sure you have installed the seaborn library before using this. Otherwise a ModuleNotFoundError will be raised. -Recall that libraries are installed via the Anaconda Prompt by typing pip install <library_name>.

-

Heatmap parameters

seaborn.heatmap has many parameters for tweaking the appearance of the colormap. Often a bunch of them are needed to create a good looking plot.

-

Special attention should be paid to choosing a colormap that fits the dataset well. -A bad choice in colormap can be very misleading to the reader of the plot, while a well-chosen one can convey the overall message to the reader in very few seconds.

-

See https://seaborn.pydata.org/generated/seaborn.heatmap.html

-

Some of the parameters for seaborn.heatmap:

-
    -
  • annot=True for putting the value in each tile.
  • -
  • fmt to set the number of decimals for the annotated tiles. Set equal to ".0f" for 0 decimals.
  • -
  • annot_kws={'size': 10} for setting fontsize of annotated tiles to 10.
  • -
  • square=True for ensuring that tiles are square.
  • -
  • cmap=name_of_colormap for controlling the colomap (see available colormaps here: https://matplotlib.org/users/colormaps.html, be sure to choose one that fits the content of the data).
  • -
  • vmin and vmax to define the min and max values of the colormap (and colorbar).
  • -
  • cbar_kws={"orientation": "horizontal"} for orientation of the colorbar. Here set to horizontal but is vertical as default.
  • -
- -
-
-
-
-
-
-
-
-

Merge operations on dataframes

Merge operations provide very powerful manipulation techniques in pandas. We are only gonna look at a simple example here, which will perform an operation similar ti Excel's VLOOKUP.

-
# Merge df1 and df2 on <column_to_merge_on>, retain only rows from df1  (similar to Excel VLOOKUP)
-df_merged = df1.merge(df2, on='<column_to_merge_on>', how='left')
-
-

See this page of the pandas documentation for more on merging, joining and concatenating dataframes: https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html

- -
-
-
-
-
-
-
-
-

Exercise 1

The file piles.csv in the session folder contains some piles and their profile type. Use steel_profiles.csv to insert the cross sectional parameters in each pile row. Use the merging operation described above.

- -
-
-
-
-
-
-
-
-

Exercise 2

The file Crack_width_Seg7_y_direction.csv has results of a crack width calculation in a base slab from the ESS project.

-

The sectional forces from IBDAS were exported into Excel and each $(N, M)$ pair was run through our standard spreadsheet for calculating the crack with in the Quasi-Permanent load combination. -The results were exported to this csv-file in order to create a presentable plot for the documentation report.

-

While doing the exercises below, recall that df.head() will print the first five rows of df.

-

Exercise 2.1

Load the file Crack_width_Seg7_y_direction.csv into a dataframe. Filter the dataframe so it only contains rows where the criterion column has max My.

-

Exercise 2.2

Pivot the dataframe and save it to a new dataframe. Use column 'y[m]' as index, column 'x[m]' as columns and column 'w_k[mm]' as values.

-

Exercise 2.3

Create a heatmap of the pivotted dataframe. Use parameters of your choice from the ones described above or in the pandas documentation.

-

Be sure to choose a colormap, the default one is awful. The maximum allowable crack width for this concrete slab is $w_{k.max} = 0.30$mm.

- -
-
-
-
-
-
In [ ]:
-
-
-
 
-
- -
-
-
- -
-
-
- - - - - -