PPOL564 | Data Science 1: Foundations

Lecture 18

Rules of Differentiation

Concepts Covered:

  • Delve into some of the most useful rules of derivatives. We'll cover how they are derived and the intuition underpinning them.
  • Introduction to the symbolic math library sympy
In [1]:
import numpy as np
from sympy import * # Import all functions form the module.

# Bokeh for interactive plots
from bokeh.plotting import figure, output_notebook, show
from bokeh.layouts import row, column
from bokeh.models import Span
output_notebook()

def plot(w=700,h=500,title='',x='X',y='f(X)'):
    '''Wrapper function to ease starting a new plot.
    '''
    p = figure(plot_width=w,plot_height=h,title=title,toolbar_location="below")
    p.xaxis.axis_label = x
    p.yaxis.axis_label = y
    return p
Loading BokehJS ...

Derivatives

Calculating the instantaneous rate of change in a function.



$$ \frac{d f(x)}{d x} = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h} $$



In the other notebook accompanying today's lecture, we developed a simple function that could approximate the derivative for us by taking small changes. We saw that this performed pretty well at accomplishing the basic aims of calculating the derivative.

In [2]:
def deriv(x,func,nudge=.001):
    '''
    More generic derivative function
    '''
    return (func(x + nudge) - func(x))/nudge

Let's use this function to "double check" the differentiation rules outlined below.

Rules of Differentiation

Constant Rule

Rule



$$f(x) = c$$



$$\frac{d f(x)}{d c} = 0$$



where $c$ is some arbitrary constant.

Intuition

In [3]:
def f(x):
    return 3

x = np.arange(-5,5.1,.1)
p = plot(700,300)
p.line(x,f(x),line_width=3,legend='f(x)=3')
p.line(x,deriv(x,f),line_width=3,
       line_dash='dashdot',legend="f'(x)=0",
       color="red",alpha=.5)
show(p)

The Scalar Rule



$$\frac{d f(cx)}{d x} $$



$$\lim_{h \to 0} \frac{c(f(x+h)) - c(f(x))}{h}$$



$$ \lim_{h \to 0} c(\frac{f(x+h) - f(x)}{h}) $$



$$ f'(cx) = cf'(x) $$



where $c$ is some arbitrary constant.



Intuition

In [4]:
def f1(x):
    return 3*np.sin(x)
def f2(x):
    return 1*np.sin(x)
def f3(x):
    return .5*np.sin(x)

x = np.arange(-5,5.1,.1)
p = plot(700,300)
p.line(x,deriv(x,f1),line_width=3,
       line_dash='dashdot',legend="f'(x)=3cos(x)",
       color="red",alpha=.5)
p.line(x,deriv(x,f2),line_width=3,
       line_dash='dashdot',legend="f'(x)=1cos(x)",
       color="skyblue",alpha=.5)
p.line(x,deriv(x,f3),line_width=3,
       line_dash='dashdot',legend="f'(x)=.5cos(x)",
       color="forestgreen",alpha=.5)
show(p)

The Sum Rule

Let's apply the above to more than one function.



$$ \frac{d(f + g)}{d x} $$



$$\lim_{h \to 0} \frac{(f(x+h) + g(x+h) ) - (f(x) + g(x))}{h}$$



$$ \lim_{h \to 0} \frac{(f(x+h) - f(x)) + (g(x+h) - g(x))}{h}$$



$$ \lim_{h \to 0} (\frac{f(x+h) - f(x)}{h} + \frac{g(x+h) - g(x)}{h})$$



$$ f'(x) + g'(x) $$



Naturally, this holds as well for subtraction.



$$ \frac{d(f(x) - g(x))}{d x} = f'(x) - g'(x) $$



These linear operators should look familiar!

Let's see what these rules mean in practice



$$ f(x) = x^3 + 2x^2 - 3x$$



$$ \frac{df(x)}{dx} = \frac{d(x^3 + 2x^2 - 3x)}{dx}$$



$$ \frac{df(x)}{dx} = \frac{d(x^3)}{dx} + \frac{d(2x^2)}{dx} - \frac{d(3x)}{dx}$$



$$ \frac{df(x)}{dx} = \frac{d(x^3)}{dx} + 2\frac{d(x^2)}{dx} - 3\frac{d(x)}{dx}$$



$$ \frac{df(x)}{dx} = 3x^2 + 2(2x) - 3(1)$$



$$ \frac{df(x)}{dx} = 3x^2 + 4x - 3$$



Intuition

Given the function is differentiable, we can differentiate each individual component of the function individually and then add the components together.

In [5]:
# Define our function and its components
def f(x):
    return x**3 + 2*x**2 + 3*x

def f_comp1(x):
    return x**3

def f_comp2(x):
    return 2*x**2

def f_comp3(x):
    return 3*x
In [6]:
# Specify a range of x
x = np.arange(-5,5+.1,.1)

# Plot each of the component Functions 
p1 = plot(700,400,title="Each Function Individually")
p1.line(x,f_comp1(x),
        color='black',line_width=2,alpha=.5,
        legend ='f1 = x^3')
p1.line(x,f_comp2(x),
        color='orange',line_width=2,alpha=.5,
        legend ='f2 = 2x^2')
p1.line(x,f_comp3(x),
        color='green',line_width=2,alpha=.5,
        legend ='f2 = 3x')
p1.legend.location = 'top_center'


# Plot the combination of the component functions
p2 = plot(700,400,title="Combined Components")
for i in range(-5,5+1):  
    p2.line([i,i], # X-axis
            [f_comp1(i),0], # Y-Axis
            color='black',line_width=5,alpha=.4)
    p2.line([i,i], # X-axis
            [f_comp1(i)+f_comp2(i),f_comp1(i)], # Y-Axis
            color='orange',line_width=5,alpha=.4)
    p2.line([i,i], # X-axis
            [f_comp1(i)+f_comp2(i)+f_comp3(i),f_comp1(i)+f_comp2(i)], # Y-Axis
            color='green',line_width=5,alpha=.6)
p2.line(x,f(x),color='steelblue',line_width=3)    

# Print the two functions side by side
show(column(p1,p2))

Likewise, this extends to the derivatives as we are are altering the rate of change when altering the form of the function.

In [7]:
# Derivatives of each or our component functions
def df(x):
    return 3*x**2 + 4*x + 3

def df_comp1(x):
    return 3*x**2

def df_comp2(x):
    return 4*x

def df_comp3(x):
    return 3

# Plot each of the component Functions 
p1 = plot(700,400,title="The Derivative of each Function Individually")
p1.line(x,df_comp1(x),
        color='black',line_width=2,alpha=.5,
        legend="df1 = 3x^2")
p1.line(x,df_comp2(x),
        color='orange',line_width=2,alpha=.5,
       legend="df2 = 4x")        
p1.line(x,df_comp3(x),
        color='green',line_width=2,alpha=.5,
       legend="df3 = 3")
p1.legend.location = 'top_center'

# Plot the combination of the component functions
p2 = plot(700,400,title="The Derivatives Combined")
for i in range(-5,5+1):  
    p2.line([i,i],[df_comp1(i),0],color='black',line_width=5,alpha=.4)
    p2.line([i,i],[df_comp1(i)+df_comp2(i),df_comp1(i)],color='orange',line_width=5,alpha=.4)
    p2.line([i,i],[df_comp1(i)+df_comp2(i)+df_comp3(i),df_comp1(i)+df_comp2(i)],color='green',line_width=5,alpha=.6)
p2.line(x,df(x),color='steelblue',line_width=3)    

# Print the two functions side by side
show(column(p1,p2))

The Power Rule

Rule



$$ f(x) =x^n $$



$$ f'(x) = nx^{n-1} $$



Derivation

The underlying derivation of the power rule is best seen in its application to different polynomials. Let's take the derivative of three polynomials and see how the power rule emerges as a consistent pattern.

Example 1

Consider the function $f(x) = x^2$



$$ f'(x) = \lim_{h\to0}\frac{(x + h)^2 - x^2}{h} $$



$$ \frac{x^2 + 2 x (h) + h^2 - x^2}{h}$$



$$ \frac{2 x (h) + h^2}{h}$$



$$ \frac{h(2 x + h)}{h}$$



$$ 2 x + h $$



and as we shrink $h$ to zero (is taken to its limit), we end up with



$$2x$$



Example 2:

Consider the function $ f(x) = x^3 $



$$ f'(x) = \lim_{h \to 0} \frac{(x + h)^3 - x^3}{h}$$



$$\frac{(h)^3 - 3 (h)^2 x + 3(h)x^2}{h}$$



$$(h)^2 - 3 (h) x + 3x^2$$



$$3x^2$$



Example 3:

Consider the function $ f(x) = x^7 $



$$f'(x) = \lim_{h \to 0} \frac{(x + h)^7 - x^7}{h}$$



$$\frac{(h)^7 + 7(h)^6 x + 21 (h)^5 x^2 + 35 (h)^3 x^4 + 21 (h)^2 x^5 + 7(h) x^6}{h}$$



$$\frac{ h(h^6 + 7 h^5 x + 21 h^4 x^2 + 35 h^2 x^4 + 21 h x^5 + 7 x^6)}{h}$$



$$ h^6 + 7 h^5 x + 21 h^4 x^2 + 35 h^2 x^4 + 21 h x^5 + 7 x^6$$



Which ultimately reduces to as $h$ approaches 0.



$$ 7x^6 $$



The emerging pattern reveals a surprisingly simple rule, which is the power rule



$$ n x ^{n-1} $$



Intuition

What is a square?



$$ x^2$$



$$ x \cdot x$$



$$ area = base \times height $$



What is the change in area when we increase the area of the square a little?

In [29]:
# Parameters 
base = 2 # Base 
height = 2 # Height
h = .1 # our slight change


# PLOT 1
p1 = plot(475,475,y='x',x='x')

# plot our square
p1.quad(top=[height], bottom=[0], left=[0],
       right=[base], color="steelblue",
       legend="Initial Area")

# Plot our square with a slight h
p1.quad(top=[height+h], bottom=[0], left=[0],
       right=[base+h], color="gold",alpha=.2,
       legend="Area + some increase (h)")
p1.legend.location = 'bottom_left'


# PLOT 2
# Lets color the areas being changes
p2 = plot(475,475,x='x',y='x')

# Change in the height as we adjust x. 
p2.quad(top=[height + h], bottom=[height], left=[0],
       right=[base], color="red",alpha=.2,
      legend="x(h)")

# Change in the base as we adjust x.
p2.quad(top=[height], bottom=[0], left=[base],
       right=[base + h], color="purple",alpha=.2,
      legend="(h)x")

# Change in the base as we adjust x.
p2.quad(top=[height+h], bottom=[height], left=[base],
       right=[base+h], color="gold",alpha=.2,
      legend="h(h)")
p2.legend.location = 'bottom_left'
show(column(p1,p2))

What is the resulting area of the change?



$$ new~area = xh + xh + h^2 $$



$$ 2xh + h^2 $$



Divide $h$ out



$$ \frac{2xh + h^2}{h} $$



$$ \lim_{h \to 0} 2x + h $$



Again, as we shrink the size of $h$ (the change) down into smaller and smaller bits, we end up with



$$2x$$



Can you see how this would extend to any polynomial?

The Product Rule

Rule



$$ F(x) = f(x)g(x) $$



$$ F'(x) = f'(x) g(x) + f(x) g'(x) $$



Derivation



$$ F'(x) = \lim_{h \to 0} \frac{f(x+h)g(x+h) - f(x)g(x)}{h} $$



We'll need to perform a trick to manipulate this expression algebraically. We can do this by adding and subtracting the same value in the numerator. Note that this doesn't change anything in the expression, but it does allow us a way to manipulate it.



$$ \lim_{h \to 0} \frac{f(x+h)g(x+h) \textbf{- f(x)g(x+h) + f(x)g(x+h)} - f(x)g(x)}{h} $$



Regroup values and use what we know of addition



$$ \lim_{h \to 0} \frac{f(x+h)g(x+h) - f(x)g(x+h)}{h} + \lim_{h \to 0} \frac{f(x)g(x+h) - f(x)g(x)}{h} $$



Factor out values



$$ \lim_{h \to 0} g(x+h)\frac{f(x+h)-f(x)}{h} + \lim_{h \to 0} f(x) \frac{g(x+h)-g(x)}{h}$$



Take the limit of each component



$$ (\lim_{h \to 0} g(x+h)) (\lim_{h \to 0} \frac{f(x+h)-f(x)}{h}) + (\lim_{h \to 0} f(x)) (\lim_{h \to 0}\frac{g(x+h)-g(x)}{h})$$



$$ g(x)f'(x) + f(x)g'(x) $$



More commonly arranged as



$$ f'(x)g(x) + f(x)g'(x) $$



Example



$$ f(x) = 2x + 3 $$



$$ g(x) = x^2 $$



$$ F(x) = f(x)g(x) $$



$$ F(x) = (2x + 3)(x^2) $$



$$ F'(x) = (2)(x^2) + (2x + 3)2x $$



$$ F'(x) = 2x^2 + 4x^2 + 6x $$



$$ F'(x) = 6x^2 + 6x $$



Intuition

Let's consider the following functions:



$$ f(x) = \sin(x) $$



$$ g(x) = x^2 $$



What is the resulting product of these two functions as we nudge x incrementally? As we've seen, when dealing with products, it's sometimes useful to think in terms of the area when multiplied by one another.

In [9]:
def f(x):
    return np.sin(x)

def g(x):
    return x**2
In [30]:
# Parameters
base = 1 # Base 
height = 1 # Height
area = base*height
h = .1 # our slight change

# PLOT 1

# plot our square
p1 = plot(700,475,x='x^2',y='sin(x)')

p1.quad(top=[f(height)], bottom=[0], left=[0],
       right=[g(base)], color="steelblue",
       legend='Initial Area')

# Plot our square with a slight nudge
p1.quad(top=[f(height + h)], bottom=[0], left=[0],
       right=[g(base + h)], color="gold",alpha=.2,
       legend='Initial Area + some increase (h)')
p1.legend.location = 'bottom_left'

# PLOT 2
# Lets color the areas being changes
p2 = plot(700,475,x='x^2',y='sin(x)')

# p2.quad(top=[f(height)], bottom=[0], left=[0],
#        right=[g(base)], color="steelblue",alpha=.05,
#        legend="x^2 sin(x)")

# Change in the height as we adjust x. 
p2.quad(top=[f(height + h)], bottom=[f(height)], left=[0],
       right=[g(base)], color="red",alpha=.2,
      legend="x^2 sin(x+h)")

# Change in the base as we adjust x.
p2.quad(top=[f(height)], bottom=[0], left=[g(base)],
       right=[g(base + h)], color="purple",alpha=.2,
      legend="sin(x) (x+h)^2")

# Change in the base as we adjust x.
p2.quad(top=[f(height+h)], bottom=[f(height)], left=[g(base)],
       right=[g(base+h)], color="gold",alpha=.2,
      legend="(x+h)^2 (sin(x+h))")
p2.legend.location = 'bottom_left'
show(column(p1,p2))

Let's gather these pieces



$$ \frac{\sin(x+h)-sin(x)}{h} x^2 + \sin(x) \frac{(x+h)^2-x^2}{h} + \frac{\sin(x+h)-sin(x)}{h}\frac{(x+h)^2-x^2}{h} $$



We can essentially ignore $\sin(x+h)(x+h)^2$ as that area will shrink to a trivially small size as $h \to 0 $



This leaves us with



$$ \frac{\sin(x+h)-sin(x)}{h} x^2 + \sin(x) \frac{(x+h)^2-x^2}{h}$$



$$ \cos(x) x^2 + \sin(x) 2x $$



We are simply adding the respective area. This is the essence of the product rule.



$$ f'(x) g(x) + f(x) g'(x) $$



The Chain Rule

The chain rule is dictates how we deal with function compositions. That is, functions within functions.



$$ g: x \mapsto z $$



$$ f: z \mapsto y $$



$$ g \circ f: x \mapsto y $$



$$ f(g(x))= y $$



We saw something similar to this when we took multiple matrix transformations.



$$\textbf{A}\textbf{B}\vec{x} = \vec{y}$$



Rule



$$ F(x) = f(g(x)) $$



$$ F'(x) = f'(g(x))g'(x) $$



Derivation



$$ F'(x) = \lim_{h \to 0}\frac{f(g(x+h)) - f(g(x))}{h} $$



Now let's multiply and divide by $g(x+h) - g(x)$. Recall that this doesn't change underlying value of the function as long as we multiply this value into both the numerator and the denominator.



$$ \lim_{h \to 0}\frac{f(g(x+h)) - f(g(x))}{h} \frac{g(x+h) - g(x)}{g(x+h) - g(x)} $$



$$ \lim_{h \to 0}\frac{f(g(x+h)) - f(g(x))}{g(x+h) - g(x)} \frac{g(x+h) - g(x)}{h} $$



Recall that $h$ is really $(x + h) - x$, that is, it's the difference. When we think in these terms we can see that we have two similar types of statements...



$$ \lim_{h \to 0}\frac{f(g(x+h)) - f(g(x))}{g(x+h) - g(x)}\lim_{h \to 0}\frac{g(x+h) - g(x)}{(x+h) - x} $$



The first product is taking the change in $f(\cdot)$ with respect to the change in $g(x)$ and the second takes the change in $g(\cdot)$ with respect to the change in $x$.



$$ f'(g(x))g'(x) $$



Intuition



$$ x \mapsto z \mapsto y $$



How does a change in $x$ correspond to a change in $z$ and how does that resulting change in $z$ correspond to a change in $y$?



$$ f(x) = \sin(x) $$



$$ g(x) = x^2 $$



In [11]:
# Define Functions
def f(x):
    return np.sin(x)

def g(x):
    return x**2

Below is function that will help us analyze the changes discretely.

In [12]:
def chain(x,nudge=.1):
    x1 = x
    x2 = x + nudge
    print(f'''
    Changing x from {x1} to {x2}
    x1: {x1}\t\t --> g(x1): {round(g(x1),1) }\t\t --> f(g(x1)): {round(f(g(x1)),1)}
    x2: {x2}\t\t --> g(x2): {round(g(x2),1) }\t\t --> f(g(x2)): {round(f(g(x2)),1)}
    ch: {round(x2-x1,2)}\t\t --> {round(g(x2)-g(x1),1)}\t\t --> {round(f(g(x2))-f(g(x1)),1)}
    ''')
In [13]:
for i in [1,5,10,20,50]:
    chain(i)
    Changing x from 1 to 1.1
    x1: 1		 --> g(x1): 1		 --> f(g(x1)): 0.8
    x2: 1.1		 --> g(x2): 1.2		 --> f(g(x2)): 0.9
    ch: 0.1		 --> 0.2		 --> 0.1
    

    Changing x from 5 to 5.1
    x1: 5		 --> g(x1): 25		 --> f(g(x1)): -0.1
    x2: 5.1		 --> g(x2): 26.0		 --> f(g(x2)): 0.8
    ch: 0.1		 --> 1.0		 --> 0.9
    

    Changing x from 10 to 10.1
    x1: 10		 --> g(x1): 100		 --> f(g(x1)): -0.5
    x2: 10.1		 --> g(x2): 102.0		 --> f(g(x2)): 1.0
    ch: 0.1		 --> 2.0		 --> 1.5
    

    Changing x from 20 to 20.1
    x1: 20		 --> g(x1): 400		 --> f(g(x1)): -0.9
    x2: 20.1		 --> g(x2): 404.0		 --> f(g(x2)): 1.0
    ch: 0.1		 --> 4.0		 --> 1.8
    

    Changing x from 50 to 50.1
    x1: 50		 --> g(x1): 2500		 --> f(g(x1)): -0.7
    x2: 50.1		 --> g(x2): 2510.0		 --> f(g(x2)): 0.1
    ch: 0.1		 --> 10.0		 --> 0.8
    
In [14]:
# Show graphically

x = np.arange(-5,5 + .01 , .01)

# Plot functional forms
p1 = plot(700,400,y='')
p1.line(x,g(x),color='steelblue',legend='g(x)',line_width=3,alpha=.5)
p1.line(x,f(x),color='red',legend="f(x)",line_width=3,alpha=.5)
p1.line(x,f(g(x)),color='purple',legend='f(g(x))',line_width=3,alpha=.5)




# Define composite function
def F(x):
    '''Composite function of f and g'''
    return f(g(x))

# Plot derivatives
p2 = plot(700,400,y='')
p2.line(x,deriv(x,func=g,nudge=.001),color='steelblue',
       line_dash="dashed",
       legend="g'(x)",
       line_width=3,alpha=.5)
p2.line(x,deriv(g(x),func=f,nudge=.001),color='purple',
       line_dash="dashed",
       legend="f'(g(x))",
       line_width=3,alpha=.5)
p2.line(x,F(x),color='grey',
        line_dash="dashed",
        line_width=3,alpha=.5,
        legend="F'(x) = f'(g(x))g'(x)")
show(column(p1,p2))

The Exponential Rule

Rule



$$ f(x) = e^x $$



$$ f'(x) = e^x $$



Derivation



$$ f'(x) = \lim_{h \to0} \frac{e^{x+h} - e^x}{h} $$



$$ \lim_{h \to0} \frac{e^{x}e^{h} - e^x}{h} $$



Factor out $e^{x}$



$$ e^{x} \lim_{h \to0} \frac{e^{h} - 1}{h} $$



$\frac{e^{h} - 1}{h}$ converges to $1$ in the limit (see reading), leaving



$$ e^{x} $$



In [15]:
# Non-rigorous but a computational proof of this
for h in [.01,.001,.00001,.0000001]:
    print((np.exp(h)-1)/h)
1.005016708416795
1.0005001667083846
1.000005000006965
1.0000000494336803

Intuition

This isn't as much an intuition as watching the function behave when we nudge it.

In [16]:
def f(x):
    return np.exp(x)

x = np.arange(-5,5.1,.1)
p = plot(700,300)
p.line(x,f(x),line_width=3,color="black")
p.line(x,deriv(x,f,nudge=.001),line_width=6,color="red",alpha=.4)
show(p)

Summary of the Rules

We covered the three most important rule of differentiation (of which the other rules can usually be derived). However, it's useful to list off all the rules that we'll encounter most often for future reference.

Rule Name Function Derivative
Sum $ f(x) + g(x)$ $f'(x) + g'(x)$
Difference $ f(x) - g(x)$ $f'(x) - g'(x)$
Scalar $ f(ax) $ $af'(x)$
Product $ f(x)g(x)$ $f'(x)g(x) + f(x)g'(x)$
Quotient $ \frac{f(x)}{g(x)}$ $\frac{f'(x)g(x) - f(x)g'(x)}{g(x)^2}$
Chain $ f(g(x))$ $f'(g(x))g'(x)$
Constant $ f(c)$ $0$
Exponential $ e^x$ $ e^x$
Natural Log $ ln(x)$ $\frac{1}{x}$
Cosine $ \cos(x) $ $-\sin(x)$
Sine $ \sin(x) $ $\cos(x)$
Tangent $ \tan(x) $ $1 + \tan^2(x)$

(Note that I've bolded the rules that we covered today).

Sympy: Symbolic Math in Python

Python has a useful library for calculating the derivative and other mathematical derivations. With sympy, we can easily calculate the mathematical derivations outlined above (and much more). For now, I'll put it on our radar as it offers us a tool in python to quickly calculate the derivatives of trick functions.

In [17]:
z = symbols('z') # specify the symbol used in an expression
init_printing(use_unicode=True)
expr = z**3
expr
Out[17]:
$$z^{3}$$

Calculating the Derivative

In [18]:
# Calculate the derivative
expr.diff()
Out[18]:
$$3 z^{2}$$
In [19]:
# Implements all the tools and logic outlined above 
expr2 = sin(z**2)
expr2.diff()
Out[19]:
$$2 z \cos{\left (z^{2} \right )}$$

We can evaluate the symbolic outputs using the evalf and lambdify methods.

In [20]:
expr.evalf(subs={z: 2})
Out[20]:
$$8.0$$
In [21]:
expr.diff().evalf(subs={z: 2})
Out[21]:
$$12.0$$

lambdify allows us to efficiently pass whole arrays of numbers through. We convert each expression into a special "lambda-like" function that we can then pass numerical values generated by numpy.

In [22]:
# Define our expressions
expr = z**3 - 2*z**2 - 1*z
f = lambdify(z, expr, "numpy")  # function 
df = lambdify(z, expr.diff(), "numpy") # first derivative
expr
Out[22]:
$$z^{3} - 2 z^{2} - z$$

Let's plot the resulting values as we've done this entire time using the numerical arrays provided.

In [23]:
a = np.arange(-5,5,.1) 
p = plot()
hline = Span(location=0, dimension='width', line_color='grey', line_width=2)
p.line(a,f(a),legend=f'f(x)={str(expr)}',alpha=.5,line_width=3)
p.line(a,df(a),color="red",
       legend=f"f'(x)={str(expr.diff())}",alpha=.5,line_width=3,
       line_dash = 'dashdot')
p.renderers.extend([hline])
show(p)

Expansions

In [24]:
expr3 = (z**3+z)*(z*9+z**2)
expr3
Out[24]:
$$\left(z^{2} + 9 z\right) \left(z^{3} + z\right)$$
In [25]:
expr3.expand()
Out[25]:
$$z^{5} + 9 z^{4} + z^{3} + 9 z^{2}$$

Simplifications

In [26]:
simplify(expr3)
Out[26]:
$$z^{2} \left(z + 9\right) \left(z^{2} + 1\right)$$

Printing

Express output equations in terms of python or latex.

In [27]:
print(expr3)
(z**2 + 9*z)*(z**3 + z)
In [28]:
latex(expr3)
Out[28]:
'\\left(z^{2} + 9 z\\right) \\left(z^{3} + z\\right)'