Not really sure what to call this post. I was working replicating some images from a book I am/was working on (early January 2023). I wanted to draw a line between two points on a quadratic curve. But, I was using a parametric equation to generate the curve. No direct relationship between the ys and the xs. The x and y points for the plot were being generated from a series of angles. So, it was virtually impossilbe to say that a specific x value would be in the generated datasets.

I also wanted, for a different image, to have the axes in the middle of the plot rather than on the left and bottom. Which, the latter, is how I have to-date been generating plots using matplotlib.

It was all a bit of a learning experience and I thought I should document what I learned. Probably of little or no use to most, but I like to make lots of notes. This is one such page of notes.

Average Rate of Change

As to the first image I was trying to produce, here it is. It is the beginnings of an attempt to geometrically explain a derivative.

image of curve with a line to estimate its derivative

I wanted the two x values to be 0.2 and 0.5. But, those values do not necessarily exist in the curve data which is generated by the following function.

def gen_arc(cntr=(0.53, 0.72), w_h=(6e-1, 10e-1), angl=-45, st_nd=(120.0, 240.0)):
  xcenter, ycenter = cntr
  width, height = w_h
  angle = angl
  e_st, e_nd = st_nd

  theta = np.deg2rad(np.arange(e_st, e_nd, 1.0))
  x = 0.5 * width * np.cos(theta)
  y = 0.5 * height * np.sin(theta)

  rtheta = np.radians(angle)
  R = np.array([
      [np.cos(rtheta), -np.sin(rtheta)],
      [np.sin(rtheta),  np.cos(rtheta)],
      ])

  ex, ey = np.dot(R, [x, y])
  ex += xcenter
  ey += ycenter

  return ex, ey

The solution is really rather simple. But, having NumPy at hand helped considerably.

For each number, I would subtract that from every member of the x dataset. Then get the index of the result that was closest to 0. I could then use that index on the two datasets to get the desired x and y values for both spots on the curve. Then a bit of arithmetic to get an equation for the line, which I could use for plotting.

The power of NumPy certainly helps keep the code to a minimum. No need to traverse the dataset each time. The following function determines and returns the two array indices. We want the absolute value so we don’t need to worry about the sign of the result of the subtraction.

def get_ndxs(xs, xse=(0.2, 0.5)):
  x1, x2 = xse
  diff_x1s = np.absolute(xs - x1)
  ndx_x1 = diff_x1s.argmin()
  diff_x2s = np.absolute(xs - x2)
  ndx_x2 = diff_x2s.argmin()

  return ndx_x1, ndx_x2

I know there’s a lot going on behind those lines of code. But, from my perspective, rather simple—once you know what you wish to do. I could likely have also passed in the y data, then determined and returned the two sets of x and y values. But, I felt that was doing too much in one function. I pretty much look after that in the function I use to generate the equation of the line passing through the two points. Here’s that bit of code.

def get_fn(x):
  global ndx_x1, ndx_x2
  ln_slp = (ey[ndx_x2] - ey[ndx_x1]) / (ex[ndx_x2] - ex[ndx_x1])
  ln_int = ey[ndx_x1] - (ln_slp * ex[ndx_x1])
  return (ln_slp * x) + ln_int

Again having the indices for the two x values, made it pretty straight forward. A bit of inconsistent behaviour. I am using global for the indices but not for the datasets (arrays). And, an extremely poor name for the function. But, I was doing this all in a bit of a hurry, and didn’t expect to use the code again. If I do, I will most certainly sort these issues out.

But, all in all I was rather pleased with the solution. And, here’s my code to generate the plot.

  ex, ey = gen_arc()
  ax.plot(ex, ey)

  ndx_x1, ndx_x2 = get_ndxs(ex, ey, xse=(0.2, 0.5))

  p1_x, p2_x= 0.0, 0.7
  p1_y, p2_y = get_fn(p1_x), get_fn(p2_x)
  ax.plot([p1_x, p2_x], [p1_y, p2_y])

  ax.plot([ex[ndx_x1], ex[ndx_x2]], [ey[ndx_x1], ey[ndx_x1]], c='k', lw=1)
  ax.annotate('x2 - x1',
            xy=(0.35, 0.75), xycoords='data', horizontalalignment='center', verticalalignment='bottom', size=12)

  ax.plot([ex[ndx_x2], ex[ndx_x2]], [ey[ndx_x1], ey[ndx_x2]], c='k', lw=1)
  t = ax.text(0.515, 0.9, "f(x2) - f(x1)", ha="left", va="center", size=12)

Centered Axes

The second image type was going to show side by side plots for a function and its derivative. Here’s one example.

image of cubic curve and its differential

Now in matplotlib terminology those axes are referred to as spines. Without doing anything about those spines, this is how my plots looked.

image of cubic curve and its differential with spines around plot, failed attempt 1

I guess it gets the job done, but I don’t think it is as informative as the ones above. So, we will move the spines.

  fig, axs = plt.subplots(1, 2, figsize=(fig_wd, fig_ht), dpi=dpi, sharey=True)

  for ax in axs:

    # Set the axis's spines to be centered at the desired point
    ax.spines[['left', 'bottom']].set_position(('data', 0))
    # remove the other spines
    ax.spines[['top', 'right']].set_visible(False)

And, I am getting closer.

image of cubic curve and its differential with spines around plot, failed attempt 2

But, that wasn’t quite what I wanted. That sharey=True caused the right y-axis to not show any values based on my use of subplots(). The example code I looked at used subplot_mosaic() which works a little differently.

Adding the following code at the bottom of the loop (for ax in axs:) did the job.

    # Show ticks in the left and lower axes only, otherwise no numbers on right plot y-axis
    ax.xaxis.set_ticks_position('bottom')
    ax.yaxis.set_ticks_position('left')

And, with that I get the image at the top of this section. Perhaps I will experiment with subplot_mosaic(). Live and learn, eh?

Done

That’s it for this learning adventure. I assure you, I was most pleased once I got both images working the way I wanted. As well as the arithmetic for the first one. And, I am happy to have documented the road to victory in this post.

Resources