Contents
Deep Breakdown
What it tells me (Situation Awareness): Integration by parts (IBP) converts one integral into another, hopefully simpler, one. It is the integration analogue of the product rule for derivatives. When the integrand is a product of two functions and you cannot find an antiderivative directly, IBP is the tool: pick one factor to differentiate () and the other to integrate (). The method is useful in DS/DA for: deriving expected values for continuous distributions (the moments), proving that (the factorial integral identity), showing entropy formulas hold (), deriving the survival function identity used in customer lifetime value (LTV) models, and establishing the log-derivative trick behind the REINFORCE policy gradient algorithm.
Flowchart
Real-world use
Where it is used
When an integrand is a product of two qualitatively different functions (polynomial times exponential, polynomial times logarithm, exponential times trigonometric) and no direct antiderivative rule applies. In probability and statistics: computing moments, deriving entropy expressions, proving identities involving expectations. In machine learning theory: deriving gradient estimators and variance decompositions.
Problem it solves
Direct integration fails on products. The product rule for derivatives tells us . Rearranging and integrating both sides transforms the hard integral into a boundary term plus a different integral. The goal is to choose and so the new integral is easier than the original.
How it solves the problem
Derivation from the product rule: Substituting :
LIATE rule for choosing (pick the type appearing earliest in the list): Logarithms, Inverse trig, Algebraic (polynomials), Trigonometric, Exponential. This ordering reflects what differentiates most cleanly: (polynomial), polynomial lower degree, trig/exp themselves.
Example in a business
Customer churn analysis: a customer's active lifetime follows an exponential distribution with rate (mean lifetime ). Expected lifetime . IBP with , gives . This closed-form result is foundational for computing Expected Customer Lifetime Value (eLTV) = (Average Revenue per Period) / (Churn Rate). Without IBP, you would need numerical integration for every parameter value in a sensitivity analysis.
The formula, piece by piece
Compact notation: Each symbol:
- : the factor you choose to differentiate — should simplify when you take
- : the factor you choose to integrate — should be integrable to get
- : the boundary term — a product evaluated (no integral sign)
- : the new integral — hopefully simpler than the original
- : derivative of with respect to
- : antiderivative of (the drops out and is absorbed into the final ) For a definite integral:
The formula in words
"The integral of times equals the product times (evaluated at the bounds if definite), minus the integral of times . You have transferred the derivative from onto : you pay the cost of the boundary term , and what you get back is a new integral with the roles swapped."
Critical Concepts
- Derived from product rule: IBP is not a standalone trick; it is the product rule run backwards.
- LIATE: Logarithms integrate poorly but differentiate cleanly. Exponentials and trig differentiate/integrate to themselves. Polynomials reduce degree. So: choose = the function that simplifies when differentiated.
- Repeated IBP: For ( an integer), apply IBP times until the polynomial is gone. The tabular (DI) method speeds this up: write and its derivatives in one column, and its integrals in another, then alternate signs and multiply diagonally.
- Circular IBP: requires two applications of IBP and returns an expression containing the original integral. Solve algebraically: move it to one side and divide.
- Boundary term at infinity: For improper integrals like , the boundary term must be evaluated carefully using L'Hôpital's Rule (the as part).
- Entropy by parts: often requires IBP to evaluate for specific distributions.
Step-by-step calculation (tiny example)
Compute .
Step 1: Choose u and dv using LIATE
- is Algebraic, is Exponential. Algebraic comes before Exponential in LIATE.
- Set and .
Step 2: Differentiate u and integrate dv
- , so
- (no needed here)
Step 3: Apply the formula
Step 4: Solve the remaining integral
Step 5: Combine and add C
Step 6: Verify by differentiating (reversibility check)
Code examples
Beginner example (Python)
"""
Demonstrate IBP numerically: int_0^2 x * exp(x) dx.
Analytic result via IBP: u = x, dv = e^x dx -> du = dx, v = e^x
int x*e^x dx = x*e^x - int e^x dx = x*e^x - e^x + C = e^x(x-1) + C
Definite: [e^x(x-1)]_0^2 = e^2(1) - e^0(-1) = e^2 + 1
"""
import numpy as np
from scipy import integrate
def integrand(x: float) -> float:
"""Product of polynomial and exponential: x * e^x."""
return x * np.exp(x)
# Numerical
area_numerical, error_est = integrate.quad(integrand, 0, 2)
# Analytic via IBP: antiderivative F(x) = e^x(x - 1)
def antiderivative(x: float) -> float:
return np.exp(x) * (x - 1)
area_analytic = antiderivative(2) - antiderivative(0)
# = e^2 * (2-1) - e^0 * (0-1) = e^2 + 1
print(f"Numerical (scipy.quad): {area_numerical:.8f}")
print(f"Analytic (IBP, e^2 + 1): {area_analytic:.8f}")
print(f"Match: {abs(area_numerical - area_analytic) < 1e-8}")
print(f"e^2 + 1 = {np.exp(2) + 1:.8f}")
# Verify IBP formula at a point: d/dx[e^x(x-1)] should equal x*e^x
x_check = 1.5
h = 1e-7
deriv_numerical = (antiderivative(x_check + h) - antiderivative(x_check - h)) / (2 * h)
deriv_exact = x_check * np.exp(x_check)
print(f"\nDerivative check at x=1.5:")
print(f" d/dx[F(x)] numerical: {deriv_numerical:.8f}")
print(f" x*e^x exact: {deriv_exact:.8f}")
print(f" Match: {abs(deriv_numerical - deriv_exact) < 1e-5}")Beginner example (R)
# IBP: int_0^2 x * exp(x) dx
# Analytic: [e^x(x-1)]_0^2 = e^2*(2-1) - e^0*(0-1) = e^2 + 1
integrand <- function(x) x * exp(x)
# Numerical
result_numerical <- integrate(integrand, lower = 0, upper = 2)$value
# Analytic antiderivative F(x) = e^x * (x - 1)
antiderivative <- function(x) exp(x) * (x - 1)
result_analytic <- antiderivative(2) - antiderivative(0) # e^2 + 1
cat("Numerical (integrate):", result_numerical, "\n") # ~8.389056
cat("Analytic (IBP): ", result_analytic, "\n") # ~8.389056
cat("e^2 + 1 = ", exp(2) + 1, "\n") # ~8.389056
cat("Match:", isTRUE(all.equal(result_numerical, result_analytic, tol = 1e-7)), "\n")
# Derivative check: d/dx [e^x(x-1)] should be x*e^x
x_check <- 1.5
h <- 1e-7
deriv_num <- (antiderivative(x_check + h) - antiderivative(x_check - h)) / (2 * h)
deriv_exact <- x_check * exp(x_check)
cat("\nDerivative check at x=1.5:\n")
cat(" Numerical d/dx[F]: ", deriv_num, "\n") # 6.72...
cat(" x*exp(x) exact: ", deriv_exact, "\n") # 6.72...
cat(" Match:", isTRUE(all.equal(deriv_num, deriv_exact, tol = 1e-5)), "\n")Professional example (Python)
"""
IBP in survival analysis: E[T] = int_0^inf S(t) dt.
Where S(t) = P(T > t) is the survival function. This identity is derived via IBP:
E[T] = int_0^inf t * f(t) dt
u = t, dv = f(t) dt
du = dt, v = -S(t) [since S'(t) = -f(t)]
IBP: [t*(-S(t))]_0^inf + int_0^inf S(t) dt = 0 + int_0^inf S(t) dt
[boundary term vanishes: t*S(t) -> 0 as t->inf]
For Exp(lambda): S(t) = e^{-lambda*t}
E[T] = int_0^inf e^{-lambda*t} dt = 1/lambda.
Business use: Expected Customer Lifetime = 1 / monthly_churn_rate.
We verify the IBP identity numerically for two distributions.
"""
import math
import numpy as np
from scipy import integrate, stats
def expected_lifetime_direct(pdf_fn: object, t_max: float = 200.0) -> float:
"""E[T] via direct definition: int t * f(t) dt."""
result, _ = integrate.quad(lambda t: t * pdf_fn(t), 0, t_max)
return result
def expected_lifetime_survival(survival_fn: object, t_max: float = 200.0) -> float:
"""E[T] via IBP identity: int S(t) dt [S = survival function]."""
result, _ = integrate.quad(survival_fn, 0, t_max)
return result
# --- Exponential distribution: Exp(lambda=0.1) -> E[T] = 10 months ---
LAMBDA_EXP = 0.1
exp_dist = stats.expon(scale=1.0 / LAMBDA_EXP)
e_t_direct_exp = expected_lifetime_direct(exp_dist.pdf)
e_t_survival_exp = expected_lifetime_survival(exp_dist.sf) # sf = survival function
e_t_analytic_exp = 1.0 / LAMBDA_EXP # = 10
print("=== Exponential(lambda=0.1) ===")
print(f" E[T] direct (int t*f(t) dt): {e_t_direct_exp:.4f}")
print(f" E[T] IBP (int S(t) dt): {e_t_survival_exp:.4f}")
print(f" E[T] analytic (1/lambda): {e_t_analytic_exp:.4f}")
print(f" IBP identity holds: {abs(e_t_direct_exp - e_t_survival_exp) < 0.01}")
# --- Weibull distribution: shape=2, scale=5 -> E[T] = 5*Gamma(1.5) = 5*sqrt(pi)/2 ---
SHAPE_W = 2.0
SCALE_W = 5.0
weibull_dist = stats.weibull_min(c=SHAPE_W, scale=SCALE_W)
e_t_analytic_weibull = SCALE_W * math.gamma(1 + 1.0 / SHAPE_W)
e_t_direct_w = expected_lifetime_direct(weibull_dist.pdf, t_max=50.0)
e_t_survival_w = expected_lifetime_survival(weibull_dist.sf, t_max=50.0)
print("\n=== Weibull(shape=2, scale=5) ===")
print(f" E[T] direct (int t*f(t) dt): {e_t_direct_w:.4f}")
print(f" E[T] IBP (int S(t) dt): {e_t_survival_w:.4f}")
print(f" E[T] analytic (scale*Gamma(1+1/shape)): {e_t_analytic_weibull:.4f}")
print(f" IBP identity holds: {abs(e_t_direct_w - e_t_survival_w) < 0.01}")
# --- Business implication ---
monthly_churn_rate = LAMBDA_EXP # 10% churn per month
avg_revenue_per_month = 50.0 # euros
expected_ltv = avg_revenue_per_month / monthly_churn_rate
print(f"\n=== Business Calculation ===")
print(f" Monthly churn rate: {monthly_churn_rate:.0%}")
print(f" Avg revenue/month: EUR {avg_revenue_per_month:.2f}")
print(f" Expected LTV = rev/churn: EUR {expected_ltv:.2f}")
print(f" [Derived via IBP: E[T] = int_0^inf S(t) dt = 1/lambda]")Source: 1300 p. 257