Exercises
Targeted exercises
Automatically running different code under different conditions using if-then-else statements
Exercise 0
Write three different functions that take as an argument a p$H$ value and print a statement saying whether the value is ‘strongly acidic’, ‘acidic’, ’neutral’, ‘basic’, or ‘strongly basic’. One version can only use if statements, the second must use nested if-else statements, and the third must use if-elif-else. Prove that they work correctly by testing the values 1.0, 4.0, 7.0, 10.0, and 13.0.
def classify_pH_if_only(pH):
if pH < 3.0:
print("strongly acidic")
if 3.0 <= pH < 7.0:
print("acidic")
if pH == 7.0:
print("neutral")
if 7.0 < pH <= 11.0:
print("basic")
if pH > 11.0:
print("strongly basic")def classify_pH_nested(pH):
if pH < 7.0:
if pH < 3.0:
print("strongly acidic")
else:
print("acidic")
else:
if pH == 7.0:
print("neutral")
else:
if pH > 11.0:
print("strongly basic")
else:
print("basic")def classify_pH_elif(pH):
if pH < 3.0:
print("strongly acidic")
elif pH < 7.0:
print("acidic")
elif pH == 7.0:
print("neutral")
elif pH <= 11.0:
print("basic")
else:
print("strongly basic")Exercise 0
Explain the following joke: “Your roommate is going to the store, so you ask them to pick up a gallon of milk and, if the store has eggs, to get a dozen. Your roommate returns with a dozen gallons of milk.”
The joke is this: a ’normal’ person would expect someone to buy a dozen eggs, if the store has eggs to buy. However, if you interpret this as a logical test in the context of coding, then one could interpret it as:
if store has eggs: buy a dozen gallons of milk.
Setting the working directory using os.chdir
Exercise 2
Change your working directory to your downloads folder. Verify using os.pwd().
import os
from pathlib import Path
# Change to Downloads folder
downloads_path = Path("C:/Users/authors/Downloads")
os.chdir(downloads_path)
# Verify current working directory
print("Current working directory:", os.getcwd())Exercise 2
Using the quickSelectFolder() function in the codechembook library, write a line of code that will change your directory to one of your choosing.
Importing any Python code from anywhere on your computer
Exercise 4
Almost every library you use is (at least in part) just Python code, stored in a .py file. This is useful because you can learn about things that are not in the documentation by looking at the code itself. Find where the folder where all of your libraries are stored. Find the folder for LMFIT and open the ’lineshapes.py’ file. On our windows machine, the path is C:\Users\authors\AppData\Roaming\Microsoft\Windows\Recent\lineshapes.py.lnk, but you can also just search for this file. Explain how the sine() and gaussian() functions work.
sine()
def sine(x, amplitude=1.0, frequency=1.0, shift=0.0):
"""Return a sinusoidal function.
sine(x, amplitude, frequency, shift) =
amplitude * sin(x*frequency + shift)
"""
return amplitude*sin(x*frequency + shift)This function accepts x-values, and then has the option to specify an amplitude, frequency, and phase shift. It then just returns the corresponding y-values. gaussian()
def gaussian(x, amplitude=1.0, center=0.0, sigma=1.0):
"""Return a 1-dimensional Gaussian function.
gaussian(x, amplitude, center, sigma) =
(amplitude/(s2pi*sigma)) * exp(-(1.0*x-center)**2 / (2*sigma**2))
"""
return ((amplitude/(max(tiny, s2pi*sigma)))
* exp(-(1.0*x-center)**2 / max(tiny, (2*sigma**2))))This function accepts x values and optional amplitude, mean, and standard deviation values. It then returns the Gaussian shapes.
Handling errors without crashing using try-except-finally
Exercise 5
Update the final code from in Chapter 5 to use try-except to ensure that the user has input a number when asked for the wavelength of the feature of interest. If the user inputs a value that can not be converted to a float, then the code should handle the error by printing, “You must enter a number!”
'''
A script to fit a calibration experiment to a line, starting wtih the UV/vis
spectra
Requires: .csv files with col 1 as wavelength and col 2 as intensity
filenames should contain the concentration after an '_'
Written by: Ben Lear and Chris Johnson (authors@codechembook.com)
v1.0.0 - 250204 - initial version
'''
import numpy as np
from lmfit.models import LinearModel
from plotly.subplots import make_subplots
from codechembook.quickTools import quickSelectFolder
import codechembook.symbols as cs
# identify the files you wish to plot and the place you want to save them at
print('Select the folder with the calibration UVvis files.')
filenames = sorted(quickSelectFolder().glob('*csv'))
# Ask the user what wavelength to use for calibration
try:
l_max = float(input('What is the position of the feature of interest? '))
except:
print("You must imput a number! Try again!")
# Loop through the file names, read the files, and extract the data into lists
conc, absorb = [], [] # empty lists that will hold concentrations and absorbances
for f in filenames:
conc.append(float(f.stem.split('_')[1])) # get concentration from file name and add to list
temp_x, temp_y = np.genfromtxt(f, unpack = True, delimiter=',', skip_header = 1) # read file
l_max_index = np.argmin(abs(temp_x - l_max)) # Find index of x data point closest to l_max
absorb.append(temp_y[l_max_index]) # get the closest absorbance value and add to list
# Set up and perform a linear fit to the calibration data
lin_mod = LinearModel() # create an instance of the linear model object
pars = lin_mod.guess(absorb, x=conc) # have lmfit guess at initial values
result = lin_mod.fit(absorb, pars, x=conc) # fit using these initial values
print(result.fit_report()) # print out the results of the fit
# Print out the molar absorptivity
print(f'The molar absorptivity is {result.params['slope'].value / 1:5.2f} {cs.math.plusminus} {result.params['slope'].stderr:4.2f} M{cs.typography.sup_minus}{cs.typography.sup_1}cm{cs.typography.sup_minus}{cs.typography.sup_1}')
# Construct a plot with two subplots. Pane 1 contains the best fit and data, pane 2 the residual
fig = make_subplots(rows = 2, cols = 1) # make a blank figure object that has two subplots
# Add trace objects for the best fit, the data, and the residualto the plot
fig.add_scatter(x = result.userkws['x'], y = result.best_fit, mode = 'lines', showlegend=False, row = 1, col = 1)
fig.add_scatter(x = result.userkws['x'], y = result.data, mode = 'markers', showlegend=False, row =1, col = 1)
fig.add_scatter(x = result.userkws['x'], y = -1*result.residual, showlegend=False, row = 2, col = 1)
# Create annotation for the slope and intercept values and uncertainties as a string
annotation_string = f'''
slope = {result.params['slope'].value:.2e} {cs.math.plusminus} {result.params['slope'].stderr:.2e}<br>
intercept = {result.params['intercept'].value:.2e} {cs.math.plusminus} {result.params['intercept'].stderr:.2e}<br>
R{cs.typography.sup_2} = {result.rsquared:.3f}'''
fig.add_annotation(text = annotation_string,
x = np.min(result.userkws['x']), y = result.data[-1],
xanchor = 'left', yanchor = 'top', align = 'left',
showarrow = False, )
# Create annotation for the extinction coefficient and uncertainty
fig.add_annotation(text = f'{cs.greek.epsilon} = {result.params['slope'].value:5.2f} {cs.math.plusminus} {result.params['slope'].stderr:4.2f} M<sup>-1</sup>cm<sup>-1</sup>',
x = np.max(result.userkws['x']), y = result.data[0],
xanchor = 'right', yanchor = 'top', align = 'right',
showarrow = False, )
# Format the axes and the plot, then show it
fig.update_xaxes(title = 'concentration /M')
fig.update_yaxes(title = f'absorbance @ {l_max} nm', row = 1, col = 1)
fig.update_yaxes(title = 'residual absorbance', row = 2, col = 1)
fig.update_layout(template = 'simple_white')
fig.show('png')Comprehensive exercises
Exercise 7
Your friend is working up data from an amyloid formation assay using a plate reader and a chromophore that becomes absorptive when in the presence of amyloid. This gives him a set of data that contains absorption at $\lambda_{max}$ versus temperature, which can be used to track the concentration of amyloid. He wants to fit this data to the following modified Sigmoid function and extract $t_{50}$: \hspace{3em} $[\textrm{amyloid}](t) = (m_1t + b_1)\left(\frac{1}{\exp{\frac{t_{50}-t}{\sigma_1}}}\right) + (m_2t + b_2)\left(\frac{1}{\exp{\frac{t_{50}-t}{\sigma_2}}}\right)$
Here, $m_1$ and $b_1$ are the slope and $y$-intercept of the end of the trace, $m_2$ and $b_2$ are the slope and intercept of beginning of the trace, $\sigma_1$ and $\sigma_2$ control the shape of the curved parts of the trace, and $t_{50}$ is the time at which half of the amyloid is formed.
He provides you with a sample data file for a single well to get started. Write a script that reads the data, creates a model for this modified Sigmoid function, sets the initial guesses for the parameters and shows the user the initial model so that the initial guesses can be updated to get close enough for the fit to work. Then, once these guesses are close, call the fitter, plot the results, and print the best fitting value of $t_{50}$.
import numpy as np
from lmfit import Model
from codechembook import plotFit, quickScatter
def ModSigmoid(xdata, m1, m2, b1, b2, sigma1, sigma2, t50):
return ((m1 * xdata + b1) * (1 / (1 + np.exp((t50 - xdata)/sigma1))) +
(m2 * xdata + b2) * (1 - (1 / (1 + np.exp((t50 - xdata)/sigma2)))))
x, y, junk = np.loadtxt('/Users/cjohnson/Documents/MATLAB/amyloid_assay.csv',
delimiter = ',', unpack = True)
model = Model(ModSigmoid)
params = model.make_params(m1 = 0.00001, m2 = 0.000001, b1 = .4, b2 = 0, sigma1 = 3000, sigma2 = 100, t50 = 30000)
fig = quickScatter(x, y, mode = 'markers', output = 'None', name = 'Data')
fig.add_scatter(x = x, y = model.eval(xdata = x, params = params))
fig.show('png')
results = model.fit(y, xdata = x, params = params)
print(results.fit_report())
plotFit(results, confidence = 10, residual = True, xlabel = 'Time (seconds)', ylabel = 'Intensity', output = 'png')