Python101/Session 6 - Exercise (shear.../Session 6 - Exercise Soluti...

258 KiB

<html> <head> </head>

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]:
LC LC-title NR NG vx[kN/m]
0 4031 Shear keys - BS - 2553/2554 20101 201 2.33
1 4031 Shear keys - BS - 2553/2554 20102 201 1.29
2 4031 Shear keys - BS - 2553/2554 20103 201 0.87
3 4031 Shear keys - BS - 2553/2554 20104 201 0.74
4 4031 Shear keys - BS - 2553/2554 20105 201 0.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.

</html>