5.4.8 · D5Scientific Computing (Python)

Question bank — SciPy — overview of submodules

1,967 words9 min readBack to topic

Before we start, we sketch every word this bank leans on inline, so you never have to leave the page to follow an answer.

Two more ideas deserve a picture, because words alone make them slippery:


True or false — justify

SciPy replaces NumPy — once you have SciPy you never need NumPy.
False. SciPy is built on NumPy and every routine consumes and returns NumPy arrays (the fast grids of numbers); SciPy is the algorithms, NumPy is the array they run on. You always need both.
numpy.linalg.solve and scipy.linalg.solve give different numeric answers on the same small system.
False (usually). They agree to floating-point precision on well-behaved small systems; the difference is that scipy.linalg has more routines and is always LAPACK-backed (hands work to that optimised Fortran engine), not that its arithmetic differs.
integrate.quad returns a single float — the value of the integral.
False. It returns a tuple (value, abserr): the estimate and the absolute error bound. Reading only value throws away the "how much can I trust this" number.
In modern SciPy you must write from scipy import stats or scipy.stats raises AttributeError.
False for SciPy ≥ 1.8. Submodules lazy-load via __getattr__, so bare import scipy; scipy.stats.norm works. The explicit import is still good style, just no longer strictly required.
optimize.minimize needs only the function to minimize.
False. It also needs a starting guess x0, because it is iterative — it walks downhill from wherever you drop it, so it has to be dropped somewhere.
A dense float64 matrix is fine as long as your algorithm is correct.
False in practice. It would need about bytes terabytes of RAM. Correctness is irrelevant if it never fits in memory — that is what sparse storage exists to avoid.
scipy.fft and scipy.signal are the same thing.
False. scipy.fft computes the Fourier transform (raw frequency content); scipy.signal is the broader workshop — filters, convolution, windowing — that often uses an FFT internally but does much more.
Because quad's error bound is tiny (e.g. 1e-15), the answer is guaranteed correct to that many digits.
False. abserr is an estimate of the error, not a certificate. For smooth functions it is trustworthy; for wildly oscillatory or singular integrands it can badly under-report the true error.

Spot the error

val = integrate.quad(lambda x: x**2, 0, 1) then print(val + 1).
quad returns a tuple, so val is (0.333…, abserr) and val + 1 raises a TypeError. You must unpack: value, abserr = quad(...).
res = optimize.minimize(lambda x: (x-3)**2, x0=0.0); print(res) and reading res as the answer.
minimize returns a result object, not a number. The minimizer is res.x (an array) and the minimum value is res.fun; printing res dumps the whole diagnostic bundle.
from scipy import linalg; linalg.solve([[3,1],[1,2]], [9,8]) passing raw Python lists.
It works because SciPy quietly converts them, but the intent is fragile — you should pass NumPy arrays (np.array(...)) so dtype and shape are explicit and no surprise integer-truncation happens.
To integrate you write import scipy; scipy.integrate.quad(...) but expect it to fail on old SciPy.
On old SciPy this indeed fails with AttributeError (no lazy loading), which is exactly why tutorials say from scipy import integrate. The "error" is version-dependent, not a syntax bug.
Solving by computing linalg.inv(A) @ b.
Not wrong-answer-wrong, but wasteful and less stable: forming the inverse (the matrix that undoes ) costs more work and amplifies rounding error. linalg.solve uses LU decomposition instead — prefer it.
Using scipy.integrate.quad to integrate a table of measured data points.
quad needs a callable function it can sample anywhere. For fixed data points use integrate.trapezoid/simpson (see Numerical integration (trapezoid & Simpson)) — the trapezoid/Simpson rules only need the samples you already have.

Why questions

Why is SciPy split into submodules instead of one giant flat namespace?
So you pay only for the mathematics you use — importing every algorithm would be slow and memory-heavy. It mirrors the 80/20 idea: a few submodules cover most work.
Why does optimize.minimize sometimes land on a different minimum than expected?
Because it descends from x0 and can settle in the nearest valley of the landscape (a local minimum). On a non-convex (multi-valley) function, your starting guess decides which valley you fall into — like gradient descent rolling into whichever dip is closest.
Why prefer scipy.linalg over numpy.linalg for serious linear algebra?
scipy.linalg is always compiled against LAPACK (the optimised Fortran linear-algebra engine), exposes more routines (lu, qr, schur, expm, …), and is often faster and more numerically stable.
Why does quad return an error estimate at all — isn't an integral exact?
The true integral is one number, but quad only approximates it by adaptively sampling. The abserr tells you how far its approximation might be from the truth, so the estimate is usable, not blind.
Why does adaptive quadrature "refine where the curve is wiggly" rather than using equal strips everywhere?
Wiggly regions carry most of the approximation error; equal strips would waste effort on flat parts and under-sample sharp ones. Concentrating samples where the curve bends (see the split-subinterval figure above) gets more accuracy for the same work.
Why store a huge mostly-zero matrix as sparse rather than dense?
A dense store keeps every zero, exploding memory (terabytes for ). Sparse keeps only non-zeros plus their positions, so cost scales with the number of non-zeros, not the grid size.

Edge cases

What does quad return for a divergent integral like ?
It cannot converge to a finite truth; expect a warning and an unreliable/huge abserr. A large returned error is your signal the integrand is singular — not a number to trust.
How do you integrate over an infinite limit like with quad?
Pass np.inf as the limit: quad(lambda x: np.exp(-x), 0, np.inf) returns ≈ 1.0. quad internally transforms the infinite range to a finite one, so semi-infinite and doubly-infinite (-np.inf, np.inf) integrals are supported — you don't chop them by hand.
What if you pass a singular matrix (rows linearly dependent, so one row is a combination of others) to linalg.solve?
There is no unique solution, so solve raises LinAlgError (singular matrix). The $Ax=b$ system either has no solution or infinitely many — you need lstsq or a rank check instead.
What does optimize.minimize do on a perfectly flat function ?
Every point is a minimum, the gradient (slope arrow) is zero everywhere, so it "converges" immediately at x0 reporting fun = 5. It isn't finding anything — it just cannot go downhill.
What happens if you give minimize a starting guess exactly at the minimum?
It detects a zero gradient right away and stops after essentially no steps (instant convergence), returning x0. Good starting guesses are cheap wins; here the guess was the answer.
In higher dimensions, why is x0 for minimize trickier than a single number?
For an -variable function x0 must be a length- array (e.g. x0=[0,0,0]), and it drops the walker into an -dimensional landscape with many valleys and saddle points. A poor multi-dimensional guess can land in a shallow local minimum or stall on a flat saddle, so the starting point matters far more than in 1-D.
What is the integral quad reports when (empty interval)?
Zero — there is no width to accumulate area over. With zero strips the sum is empty, so the result is exactly (this is the trapezoid idea from Numerical integration (trapezoid & Simpson) with no strips at all).
For a probability question, why reach for scipy.stats rather than writing the bell-curve formula by hand?
scipy.stats (see Probability distributions) ships tested pdf, cdf, ppf, and sampling for dozens of distributions. Hand-coding invites off-by-normalisation and tail-precision bugs the library already solved.

Recall One-line self-test

Cover everything above. If you can state, for each trap, the reason and not just the verdict, you own this topic. If you found yourself answering "yes/no", go back — the reason is the point.