Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Day 09 In-class Assignment

University of Missouri

✅  Put your name here.

✅  Put your group member names here.

Can we estimate an animal’s total body weight looking just at its femur?

The upper plane shows two geckos of different size. The bottom plane shows their respective femurs.

Credits: Heinicke et al (2023)

In this assignment you’re going to work to visualize allometric relationships. This should give you a good foundation for modeling some of our future projects!

Learning goals for today’s assignment

  • Use the numpy module in Python to compute values that can be used in computational models for real-world phenomena.

  • Use matplotlib to visualize your models.

  • Customize matplotlib plots to maximize the information they provide.

Assignment instructions

Work with your group to complete this assignment. Instructions for submitting this assignment are at the end of the Notebook. The assignment is due at the end of class.


Background: Allometry

The mouse-to-elephant curve. Metabolic rates of mammals and birds are plotted versus the body weight (mass) on log-log graph paper. .

Credits: Schmidt-Nielsen (1984)

✅  Question:

  • What information can you extract from the graph above?

  • Metabolism is a quite complex biochemical process, and yet it appears that we only need body mass to approximate it. Do you think this is mere coincidence or that there might be more to it?

  • What do you notice about the scale of the x,y-axes?

Write your plan here

Crash course in the importance of scale

Allometry is the study of how the size of one part of an organism changes in relation to the size of the whole organism or another part as the organism grows. In other words, it looks at proportional growth. For example:

  • If a baby animal grows into an adult, its legs, head, and body don't all grow at the same rate. Some parts grow faster or slower than others.

  • Think about you as baby: your head looked huge compared to your body! But as you grew, your body caught up. That's allometry in action.

Mathematically, allometry often uses a power law relationship:

y=axb.y = ax^b.

Where:

  • yy = one measurement (lengths, heights, mass, etc.)

  • xx = another body part measurement

  • aa = constant

  • bb = scaling exponent (tells you if growth is proportional or not)

In the plot above, we have y=y = Metabolic Rate, and x=x = Body Mass.


Part 1: Total animal weight based on femurs

Say we want to estimate an animal’s body mass based on its femur circumference. In paleontology, this is one of the very few measurments you have access to. Allometrically, it means that there is an equation

Total weight=a×(femur circumference)b.\text{Total weight} = a\times (\text{femur circumference}) ^ b.

1.1 Plan out your code: Calculating weight

✅  Task 1:

As a group, design a function called power_law. Its inputs should be:

  • A constant aa

  • A scaling exponent bb

  • A list of femur circumferences

It should return a a list of the corresponding weight values for each femur in the list that was passed.

You and your groups members are expected to work this out together using a whiteboard.

Things to think about:

  • how will you convert the math from the equations above into code?

  • How will you calculate the weight for every time value in the list that was passed?

  • How will you return the weight values?

Write your plan here

1.2 Translate your plan into code: Calculating Populations

✅  Task 2:

Translate your plan from the previous problem into a function that calculates the total population for a given set of initial inputs.

Work with your group to figure this out!

# Put your code here

1.3 Testing your code: Calculating Populations

✅  Task 3

Test out your code to see if it is working. Use the following values for your model:

  • Assume the constant aa is 0.00035

  • The scaling exponent bb is 0.5

  • The list of femur circumferences goes from 10mm (American red squirrel) to 100mm (blue wildebeest)

Model the animal weight for these inputs and make a plot showing the results.

Work with your group to figure this out!

# The following line creates your initial femur list (technically, it is creating a numpy array, but we won't worry about that for now)
# make an array of circumference values
import numpy as np
circumferences = np.arange(10,101)

# Put your code here

1.4 Varying the Parameters: sub- and super-linear growth

Let’s see how the different parameters effect our weight model.

✅  Task 4

Calculate the weight model for all of the same input values as 1.3 except for the scaling exponent bb, which will vary:

b_values = [1, 1.5, 2, 3]

Put the lines for all four weight models on the same plot. Use plt.legend() to add a legend to the plot so that you know which line is which. You’ll want to use the label parameter in your plot command to make sure the legend has the appropriate labels for the lines.

Make sure to add appropriate axis labels and a plot title.

Work with your group to figure this out!

# The following line creates your initial femur list (technically, it is creating a numpy array, but we won't worry about that for now)
# make an array of circumference values
circumferences = np.arange(10,101)

# Put your code here

✅  Question 5

A Bengal tiger (like Truman) has an average femur circumference of 90.5mm and weights about 145kg. What bb scaling seems to provide a better model?

Hint: Try adding a plt.grid() after your plot commands to add a grid to the plot. This can be helpful for seeing where a specific point is.

Put your answer here

✅  Question 6

  • Can you visually distinguish the model b = 3 from b = 1 (like in Task 3)?

  • Can you visually tell apart b = 1 from b = 2?

Put your answer here

1.5 Log-transform

You probably realized that most of the models are visually undistinguishable. We can improve this by log-transforming the axes: compute log10\log_{10} (logarithm base 10) on both sides of the equation:

Total weight=a×(femur circumference)b.\text{Total weight} = a\times (\text{femur circumference}) ^ b.

For allometry, log-transforming has an added advantage. If you recall from College Algebra, the equation above looks almost like that of a line. We just have a pesky exponent. Log-transforming it actually makes it a line:

log10(Total weight)=b×log10(femur circumference)+log10(a).\log_{10}(\text{Total weight}) = b\times\log_{10}(\text{femur circumference}) + \log_{10}(a).

Note: Any logarithmic base is good as any. But log10\log_{10} is preferred because it is easier to interpret (it is just about moving the decimal point and counting zeroes).

  • log10(0.01)=2\log_{10}(0.01) = -2

  • log10(0.1)=1\log_{10}(0.1) = -1

  • log10(1)=0\log_{10}(1) = 0

  • log10(10)=1\log_{10}(10) = 1

  • log10(100)=2\log_{10}(100) = 2

✅  Task 7

Now, instead of plotting estimated weight versus femur circumference, plot their log-transformed values.

  • Log-transform the list of femur circumferences and estimated weights

    • Hint: There is a numpy function for that

    • Hint: The numpy function can take either single values or a whole list at once. In the latter case, it will return a new list.

  • Make a new plot with these log-transformed lists

  • Rescale the xx range of the plot so that you only see values 1 through 2.

    • Hint: you’ll want to use the xlim functions in pyplot.

    • If you want to also limit the yy range, there is a ylim function.

Make sure you still include the legend in your plot and that the axes have appropriate labels!

# The following line creates your initial femur list (technically, it is creating a numpy array, but we won't worry about that for now)
# make an array of circumference values
circumferences = np.arange(10,101)

# Put your code here
fig, ax = plt.subplots(1,1,figsize=(6,4))
for b in bs:
    ax.plot(np.log10(circumferences), np.log10(power_law(circumferences, b=b)), label=np.round(b,2))
ax.set_ylabel('Weight')
ax.set_xlabel('Femur circumference')
#ax.set_aspect('equal', 'datalim')
ax.grid()
ax.legend();
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[5], line 1
----> 1 fig, ax = plt.subplots(1,1,figsize=(6,4))
      2 for b in bs:
      3     ax.plot(np.log10(circumferences), np.log10(power_law(circumferences, b=b)), label=np.round(b,2))

NameError: name 'plt' is not defined

✅  Question 8

  • Can you visually distinguish the model b = 3 from b = 1?

  • Can you visually tell apart b = 1 from b = 2?

Put your answer here


A SHORT DETOUR: Axes and one or multiple plots in the same figure using subplots()

Drake meme. Above, he rejects using .plt, while below he approves using Axes.

Credits: Matplotlib Journey

So far you have been using the pyplot interface everytime you do plt.something(). This interface is good when you want a quick plot to make sure your data behaves as you expect. But if you want something more complex—like a plot with multiple subplots—the Axes interface is the way to go! Even if you want a single plot, it better to use Axes.

fig, ax = plt.subplots( figsize=(4,2) )

The above command creates and returns a single plot of size 4 x 2 inches. We can re-write the pre-class smiley face with the Axes interface:

# Smile for the Axes interface
import matplotlib.pyplot as plt

x1 = [2,4,6,8,10,12,14,16,18]
y1 = [10,8.25,7.5,7,6.75,7,7.5,8.25,10]

x2 = [5, 15]
y2 = [15, 15]

fig, ax = plt.subplots(figsize=(4,2))

ax.plot(x1, y1, color='b', ls='dashed', lw=5)
ax.scatter(x2, y2, color='r', marker='d')
ax.set_xlim(0,20) # Compare it to plt.xlim
ax.set_ylim(6,16) # Compare it to plt.ylim
ax.set_xlabel("Smile!") # Similarly, plt.xlabel vs ax.set_xlabel
ax.set_ylabel("Don't worry, be happy");
<Figure size 400x200 with 1 Axes>

One immediate advantage of the Axes interface is that we can easily have multiple subplots within a larger plot

fig, ax = plt.subplots(2,3, figsize=(12,6), sharey=True, sharex=True )
ax = ax.ravel()
  • The first line makes a sequence of 2*3 = 6 subplots to be arranged into 2 rows and 3 columns.

    • We also say that all these subplots will share the same x- and y-axes scales, so they are comparable to each other.

  • The second line makes this sequence into a list, to be read left to right and top to bottom—like you usually read.

The following bit of code provides an example of how the subplots function can be used. Make sure you read and understand what the code is doing! There’s also a way to summarize code in a loop below!

You may also find this page of subplots examples useful.

import numpy as np

# initialize a NumPy array with 100 values between 0 and 2*pi
# We will discuss more about NumPy next pre-class
x_array = np.linspace(0,2*np.pi,100)

# The cool thing of NumPy is that we can apply a math operation to an array as whole. No need for loops!
# We will discuss more about NumPy next pre-class
y1 = np.sin(x_array)
y2 = np.sin(2*x_array)
y3 = np.sin(3*x_array)
y4 = np.sin(4*x_array)
y5 = np.sin(5*x_array)
y6 = np.sin(6*x_array)

fig, ax = plt.subplots(2,3,figsize=(10,4), sharex=True, sharey=True)
ax = ax.ravel()

fig.suptitle('A bunch of sine plots')  # title for the whole figure

# plot each curve on a separate subplot
ax[0].plot(x_array, y1, color='red') # plot points in the first subplot
ax[0].set_xlabel('x')
ax[0].set_ylabel('y')
ax[0].set_title('y=sin(x)')  # title for 1st subplot

ax[1].plot(x_array, y2, color='blue') # plot points in the second subplot
ax[1].set_xlabel('x')
ax[1].set_ylabel('y')
ax[1].set_title('y=sin(2x)')  # title for 2nd subplot

ax[2].plot(x_array, y3, color='green') # plot points in the third subplot
ax[2].set_xlabel('x')
ax[2].set_ylabel('y')
ax[2].set_title('y=sin(3x)')  # title for 3rd subplot

ax[3].plot(x_array, y4, color='orange') # plot points in the fourth subplot
ax[3].set_xlabel('x')
ax[3].set_ylabel('y')
ax[3].set_title('y=sin(4x)')  # title for 4th subplot

ax[4].plot(x_array, y5, color='black') # plot points in the fifth subplot
ax[4].set_xlabel('x')
ax[4].set_ylabel('y')
ax[4].set_title('y=sin(5x)')  # title for 5th subplot

ax[5].plot(x_array, y6, color='purple') # plot points in the sixth subplot
ax[5].set_xlabel('x')
ax[5].set_ylabel('y')
ax[5].set_title('y=sin(6x)')  # title for 6th subplot

plt.tight_layout()
<Figure size 1000x400 with 6 Axes>

Notice how each of the above six plots are made. Also realize that most of the code was copy/pasted over and over again. We can use a for loop to make our life easier!

# initialize NumPy array and a list of colors
colors = ['red', 'blue', 'green', 'orange', 'black','purple']
x_array = np.linspace(0,2*np.pi,100)

fig, ax = plt.subplots(2,3,figsize=(10,4), sharex=True, sharey=True)
ax = ax.ravel()

fig.suptitle('A bunch of sine plots (made with a loop!)', fontsize=14)  # title for the whole figure

for i in range(len(ax)):
    y_array = np.sin( (i+1)*x_array)
    ax[i].plot(x_array, y_array, color=colors[i]) # plot points in the subplot
    ax[i].set_xlabel('x', fontsize=12)
    ax[i].set_ylabel('y', fontsize=12)
    ax[i].set_title('y=sin(' + str(i+1) + 'x)', fontsize=12)  # title for the subplot

plt.tight_layout()
<Figure size 1000x400 with 6 Axes>

✅  Question 10

What does the last command, plt.tight_layout() in the example code do? Try commenting it out to see how it changes things.

Put your answer here


Congratulations, you’re done!

Submit this assignment by uploading it to the course Canvas web page. Go to the “In-class assignments” folder, find the appropriate submission link, and upload it there.

See you next class!

© Copyright 2026, Division of Plant Science & Technology—University of Missouri

References
  1. Heinicke, M. P., Nielsen, S. V., Bauer, A. M., Kelly, R., Geneva, A. J., Daza, J. D., Keating, S. E., & Gamble, T. (2023). Reappraising the evolutionary history of the largest known gecko, the presumably extinct Hoplodactylus delcourti, via high-throughput sequencing of archival DNA. Scientific Reports, 13(1). 10.1038/s41598-023-35210-8