Get started
← Back to all posts

SciPy in Python: the ultimate quickstart guide

By Katerina Hynkova

Updated on August 19, 2025

SciPy is a free open-source Python library for scientific and technical computing.

Illustrative image for blog post

What is SciPy in Python used for?

It's built on NumPy and provides a collection of fast mathematical algorithms and high-level functions for common science and engineering tasks. With SciPy, you get modules for optimization, linear algebra, integration, interpolation, special functions, FFT (Fast Fourier Transform), signal and image processing, statistics, ODE solvers, and more. In essence, SciPy extends Python's numeric capabilities (via NumPy) with a rich toolkit to solve complex problems in data analysis, modeling, and scientific research. It's widely used by scientists, engineers, and data analysts to tackle tasks ranging from solving differential equations in engineering to performing statistical tests in data science.

Key features and uses of SciPy include:

Scientific algorithms library: SciPy comes with numerous subpackages tailored to different domains (e.g. scipy.optimize for optimization, scipy.signal for signal processing, scipy.stats for statistics, etc.), all designed to work efficiently with NumPy arrays. This means you can perform advanced computations (like finding roots of equations, fitting curves, computing integrals, etc.) with concise, high-level Python commands instead of writing algorithms from scratch.

High-level interface: Many SciPy functions provide a high-level interface to efficient algorithms in Fortran or C under the hood (BLAS/LAPACK, etc.). This gives you the speed of low-level languages while writing in Python. For example, SciPy's linear algebra and FFT routines are highly optimized, enabling fast computations on large datasets.

Integration with the SciPy ecosystem: SciPy is part of the broader "SciPy ecosystem" of libraries (which includes NumPy, pandas, Matplotlib, scikit-learn, etc.). It plays a foundational role – you will often use NumPy to handle raw data structures (arrays), SciPy to perform core scientific computations, and libraries like Matplotlib to visualize the results. SciPy's functionality complements these other libraries to support end-to-end scientific workflows.

In summary, SciPy is used for implementing complex scientific computations in Python with minimal effort. Whether you need to solve a system of equations, interpolate experimental data, filter signals, or conduct statistical tests, SciPy provides reliable and well-tested functions to get it done. It effectively acts as a "one-stop-shop" for scientific programming tasks in Python.

What is SciPy vs NumPy?

SciPy and NumPy are closely related; in fact, SciPy depends on NumPy. However, they serve different purposes in scientific computing:

Core Purpose: NumPy provides the fundamental n-dimensional array (ndarray) data structure and basic operations (elementwise math, linear algebra, random sampling, etc.). SciPy, on the other hand, builds on NumPy and provides a broader collection of advanced algorithms for scientific computing. Simply put, NumPy handles the arrays and low-level computations, while SciPy tackles higher-level problems.

Functionality: Everything in numpy.linalg (NumPy's linear algebra module) is also available in scipy.linalg, but SciPy expands on this with additional capabilities. This pattern holds true across many areas: SciPy includes modules for optimization (finding minima or solving equations), integration (numerical integration and differential equation solvers), image processing, advanced statistics, and more – none of which are part of base NumPy. NumPy is focused on efficient array storage and operations; SciPy provides specialized tools built on those arrays.

Performance and implementation: SciPy often wraps highly-optimized algorithms from legacy libraries (BLAS, LAPACK, etc.). For example, scipy.linalg is always compiled with BLAS/LAPACK support, which can make it faster than numpy.linalg (which might use a less optimized implementation depending on how NumPy was installed). In general, if you need a robust or more feature-rich routine (e.g. a wider selection of matrix decompositions or solvers), SciPy's implementation is the go-to.

Use cases: You would use NumPy for tasks like creating and manipulating arrays or matrices, performing element-wise math, and basic linear algebra. Use SciPy when you need to solve a real-world science or engineering problem – for example, finding an optimal parameter (optimization), interpolating data points, computing a Fourier transform, or running a statistical test. In practice, you'll often use both together: NumPy to handle the data and SciPy to compute on it.

In summary: NumPy provides the data structure and basic operations, whereas SciPy provides the algorithms on top of that structure. Many SciPy functions actually return NumPy arrays or use NumPy under the hood. Unless your task is extremely simple and can be done with NumPy alone, you typically bring in SciPy to leverage its advanced functionality. (One caveat: if you want to avoid adding SciPy as a dependency for a small project, you might stick to NumPy. But for most scientific work in Python, using SciPy is standard practice because of the power it adds.)

Note: SciPy and NumPy have slightly different conventions in some cases. For instance, NumPy historically had a matrix class (numpy.matrix) for MATLAB-like matrix operations. However, this class is now discouraged – it's better to use regular NumPy ndarray objects for matrices when working with SciPy. SciPy functions will work with both ndarray and matrix inputs, but sticking with ndarray avoids confusion and is more future-proof. Also, random number generation is primarily handled by NumPy (via numpy.random), and SciPy builds on that for random variables in its statistics module.

What is the difference between SciPy and Matplotlib?

SciPy vs Matplotlib is a common point of confusion because both are important in scientific computing, but they serve entirely different roles:

SciPy is for computation, providing algorithms and numerical methods to process or analyze data (e.g., solving equations, transforming signals, running statistical tests).

Matplotlib is for visualization, providing plotting functions to create graphs and charts (line plots, scatter plots, histograms, etc.) of data.

In other words, SciPy (along with NumPy) handles the numeric number-crunching, whereas Matplotlib handles drawing plots and graphs. Matplotlib doesn't implement scientific algorithms; instead, it relies on numeric libraries (like NumPy) for data and then focuses on rendering that data visually. In fact, Matplotlib actually depends on NumPy (it uses NumPy arrays internally for plotting) but Matplotlib does not depend on SciPy. You can use Matplotlib perfectly well with just NumPy. SciPy itself does not include high-level plotting capabilities – you typically use Matplotlib (or related libraries like Seaborn) to visualize results computed via SciPy.

How they work together: In a typical workflow, you might use NumPy/SciPy to generate or analyze data (for example, simulate a signal or compute a Fourier transform), and then use Matplotlib to plot that data (for example, plotting the signal or its spectrum). They complement each other: SciPy produces the numbers, Matplotlib produces the figures. SciPy even provides some convenience plotting functions (for instance, scipy.spatial has a voronoi_plot_2d function to quickly visualize Voronoi diagrams), but behind the scenes those use Matplotlib.

To avoid confusion: Matplotlib's pyplot (often imported as plt) is a module for interactive plotting (pylab). Sometimes beginners use pylab (which bulk-imports NumPy and Matplotlib together) and think it includes SciPy, but it does not – pylab combines Matplotlib and NumPy, not SciPy. So remember, SciPy = calculations, Matplotlib = plots.

SciPy vs scikit-learn vs Statsmodels (comparing related libraries)

SciPy is a general-purpose library for scientific computing, whereas scikit-learn (sklearn) and Statsmodels are specialized libraries that build on NumPy/SciPy for specific domains:

SciPy vs scikit-learn (sklearn): scikit-learn is focused on machine learning. It provides efficient implementations of algorithms for classification, regression, clustering, dimensionality reduction (PCA), model selection (cross-validation), and more. While SciPy does have some basic clustering (in scipy.cluster) and provides low-level routines (like distance metrics or random number generators) that machine learning algorithms can use, it does not provide high-level machine learning algorithms or utilities like scikit-learn does. For example, scikit-learn has functions for splitting datasets into training/test sets, many types of predictive models (SVMs, random forests, neural network classifiers, etc.), and pipelines for model evaluation – none of these are part of SciPy's scope. Essentially, scikit-learn is built for data mining and predictive modeling, using NumPy/SciPy under the hood for math. In fact, scikit-learn depends on SciPy; you'll see import scipy inside many sklearn functions, because it leverages SciPy's optimized routines. If your task is machine learning (fitting models to data), you would choose scikit-learn (or related libraries like TensorFlow for deep learning) rather than raw SciPy. SciPy might be used behind the scenes (e.g. for a clustering algorithm's linear algebra), but scikit-learn gives you the convenient interface and additional functionality (like model evaluation tools). As one discussion put it, outside of core math and stats, scikit-learn offers things like PCA, Gaussian mixture models, and cross-validation frameworks that SciPy doesn't have, along with user-friendly ML APIs.

SciPy vs Statsmodels: Statsmodels is a library specifically for statistical modeling. It actually started as part of the SciKits (extensions of SciPy) and is now its own project. Statsmodels builds on SciPy (and imports scipy.stats as a dependency). The key difference is that scipy.stats provides a wide range of probability distributions, statistical tests, and basic descriptive statistics, but it does not provide advanced statistical models (like linear regression with formulas, time series ARIMA models, etc.). Statsmodels provides those kinds of estimation procedures and models. For example, if you want to fit an OLS linear regression and get detailed output (coefficients, p-values, R², etc.), Statsmodels is ideal – it even lets you use R-style formulas and Pandas DataFrames for input. SciPy's stats module is more low-level, focusing on the mathematics of distributions and tests (e.g., cdf, pdf, random sampling, t-tests, Kolmogorov-Smirnov test, etc.). Statsmodels internally uses SciPy's distribution functions and optimizers, but provides the user-facing classes for modeling. In summary, Statsmodels = statistical models & inference (built on SciPy), whereas SciPy.stats = distribution functions and tests (used by Statsmodels). They're complementary – often both are used together in a stats workflow.

To decide which to use: if you just need to, say, compute a t-test or the PDF of a normal distribution, use scipy.stats. If you need to fit a full statistical model or perform an in-depth analysis (like a multi-factor regression or time series analysis with summary outputs), Statsmodels is the tool for the job. Both libraries adhere to scientific rigor, but Statsmodels aims to provide an experience similar to R's statistical modeling, including integrations with Pandas data frames.

Pros and cons summary: SciPy is a broad library with many tools (thus it's very versatile), but for specific tasks like machine learning or statistical modeling, the more specialized libraries can be more convenient:

SciPy pros: All-in-one collection of algorithms; well-optimized low-level routines; extensive documentation; forms the foundation that other libraries use. Great for customization and when you need just the building blocks.

SciPy cons: Doesn't have ready-made high-level solutions for ML or complex stats modeling (you might have to code more yourself or use SciPy as part of a custom solution).

scikit-learn pros: Rich collection of machine learning algorithms with consistent APIs; utilities for preprocessing, model selection, evaluation; easy to get started for ML tasks.

scikit-learn cons: Limited to ML/predictive modeling scope; not intended for general-purpose scientific computing outside ML.

Statsmodels pros: Excellent for statistical analysis, with support for estimations (linear regression, GLMs, ARIMA, etc.), producing detailed reports and supporting R-style formulas.

Statsmodels cons: Focused on stats; for many general tasks (e.g., optimization or signal processing) you'd still use SciPy. Also, its API can be a bit less unified compared to scikit-learn's approach.

In practice, you often use these libraries together. For example, you might use SciPy to optimize a custom likelihood function, Statsmodels to run a regression on some data, and scikit-learn to apply a machine learning algorithm – each library for the part it does best. The good news is that because they all use NumPy arrays (and pandas structures) as common currency, they interoperate nicely.

Installing SciPy (pip, conda, and versioning)

Before diving into examples, you need to have SciPy installed in your Python environment. SciPy is not part of the standard library, but installation is straightforward:

Using pip: The easiest way for most users is via pip. Open a terminal (or command prompt) and run:

pip install scipy

This will download and install the latest version of SciPy from PyPI. You can also specify a version, for example: pip install "scipy==1.10.1" to install a specific version. After installation, you can verify it in Python:

import scipy
print(scipy.__version__)

This will print the SciPy version, so you know it's installed and which version it is.

Using Anaconda/conda: If you're using Anaconda or Miniconda, you might prefer:

conda install scipy

This will install SciPy from Anaconda's package repository (make sure you've activated the environment you want to install into). Conda will handle any binary dependencies for you. With conda, you can also install specific builds or update SciPy easily (see below).

Installing in Jupyter/VS Code: In a Jupyter Notebook, you can use !pip install scipy in a cell, or if using VS Code with an environment, ensure you install SciPy in the environment that VS Code is using. If you encounter an error like "No module named 'scipy'" in VS Code or Jupyter, it usually means the interpreter doesn't have SciPy – you need to install it in that environment or select the correct Python interpreter in your IDE.

Updating SciPy: To upgrade SciPy to the latest version, use pip or conda similarly:

  • For pip: pip install --upgrade scipy
  • For conda: conda update scipy (or conda install scipy=1.15.3 to get a specific version).

It's a good idea to keep SciPy updated, as newer versions come with performance improvements and new features. SciPy follows a semantic versioning (X.Y.Z) where new minor versions (Y increments) add features but remain backward-compatible, while patch (Z) releases fix bugs.

Uninstalling SciPy: If you need to remove SciPy, you can do pip uninstall scipy (with pip) or conda remove scipy (with conda). This will remove the SciPy package from your environment.

Compatibility: SciPy is continuously updated to support new Python versions and NumPy versions. For example, SciPy 1.11 added support for Python 3.12. Generally, each SciPy release supports the latest few Python releases (as per NEP 29 guidelines). Make sure your SciPy version is compatible with your Python version (if you use pip/conda, they usually handle this). If you use an older Python, you might be limited to an older SciPy release.

Quick note on versions: As of mid-2025, the latest stable SciPy release is 1.15.3 (May 2025). SciPy's version history is quite active (major releases 1.10, 1.11, 1.12… each adding new features). You can check SciPy's release notes for a detailed version history. In code, scipy.__version__ will give you the version. For example:

import scipy
print(scipy.__version__)

If you run that and see a version, you know SciPy is installed. If you get an ImportError or ModuleNotFoundError, that means SciPy isn't installed in the current environment.

Now that SciPy is set up, let's explore how to use its various modules with some examples.

SciPy for linear algebra (solving systems, eigenvalues, etc.)

One of the fundamental uses of SciPy is linear algebra – solving systems of linear equations, finding eigenvalues/eigenvectors, matrix decompositions, and so on. SciPy's linalg module provides all the functions from NumPy's numpy.linalg plus more advanced routines. For example, SciPy includes functions for matrix exponential, solving Sylvester equations, LU/QR/Schur decompositions, and other operations that go beyond basic matrix inversion or determinants. Importantly, SciPy's linear algebra is linked to highly optimized libraries, so it's both comprehensive and fast.

Typical tasks and functions in scipy.linalg:

• Solving a linear system Ax = b.
• Computing matrix inverses and determinants.
• Eigenvalue and singular value computations (eig, svd).
• Matrix decompositions (LU, QR, Cholesky, etc.).
• Operations on special matrices (e.g., symmetric, Hermitian, sparse matrices via scipy.sparse.linalg).

Let's illustrate a simple example of solving a system of linear equations. Suppose we have:

A · X = B,

where A is a 2x2 coefficient matrix and B is a 2x1 column of constants. We want to find X (which will be 2x1). We can use scipy.linalg.solve for this:

import numpy as np
from scipy import linalg

# Define coefficient matrix A and constant vector B
A = np.array([[1, 2],
              [3, 4]])
B = np.array([[5],
              [6]])

# Solve A * X = B for X
X = linalg.solve(A, B)
print("Solution X =\n", X)

# Check that A * X indeed equals B
print("Check A*X - B =\n", A.dot(X) - B)

Output:

Solution X =
 [[-4. ]
  [ 4.5]]

Check A*X - B =
 [[0.]
  [0.]]

Here, SciPy finds X = (-4, 4.5) as the solution. The check confirms that A × X = B (within numerical precision, the difference is essentially zero). This is more convenient and numerically stable than computing inv(A) @ B manually.

We can do more with scipy.linalg. For instance, to compute the inverse of a matrix, use linalg.inv(A). To get eigenvalues and eigenvectors: eigs, vecs = linalg.eig(A). SciPy also has a linalg.lstsq for least-squares solutions and linalg.solve_triangular for triangular systems, among many others. For real symmetric or complex Hermitian matrices, you can use linalg.eigh which is optimized for those cases (returns sorted eigenvalues and orthonormal eigenvectors).

It's worth noting that SciPy's linear algebra functions expect inputs as NumPy arrays (or anything that can be converted to an array). They will return NumPy arrays. If you are using the old numpy.matrix type, SciPy functions will still work but will often return a regular ndarray. This is another reason to just use ndarray everywhere for consistency.

Sparse matrices: SciPy has a separate subpackage scipy.sparse for sparse matrix representations and scipy.sparse.linalg for operations on them. If you have very large, mostly-zero matrices (common in scientific computing), using sparse matrices can be much more efficient. For example, constructing a large sparse matrix and solving linear systems with it (e.g., via iterative solvers like Conjugate Gradient in sparse.linalg.cg) can save memory and time. A quick example:

from scipy import sparse
# Create a sparse identity matrix of size 1000x1000
I = sparse.eye(1000, format='csr')  # CSR format sparse matrix

Now I behaves like a 1000x1000 matrix but only stores 1000 non-zero elements (the diagonal). SciPy can solve equations or eigenproblems with such matrices using specialized algorithms. For instance, for a large sparse linear system, you might use sparse.linalg.spsolve instead of linalg.solve.

Bottom line: Use SciPy's linear algebra for any matrix computations beyond the trivial. It's reliable and fast. If NumPy's linear algebra covers what you need, SciPy's will too – and then some. And if performance is critical, SciPy's implementation may be faster due to optimization and use of efficient low-level libraries.

SciPy for optimization and root finding

Another major strength of SciPy is its ability to find minima of functions (optimization) and solve equations (root finding). These tasks fall under scipy.optimize. Whether you need to find the minimum of a complicated multivariable function or solve for the roots of nonlinear equations, SciPy has tools to help.

Key capabilities in scipy.optimize:

Minimization (scalar or multivariate): Functions like optimize.minimize allow you to minimize an objective function (with support for constraints and different algorithms). For one-dimensional functions, optimize.minimize_scalar is available.

Root finding: For solving f(x) = 0 for scalar functions, methods like optimize.root_scalar (for one equation) or optimize.root (for systems of equations in multiple variables) are provided. Classic algorithms such as Brent's method, bisect, Newton's method, etc., are implemented.

Curve fitting: SciPy offers optimize.curve_fit to fit a user-defined function to data using non-linear least squares (built on the Levenberg-Marquardt algorithm). This is very handy for fitting experimental data to a model.

Linear programming and other global optimization: There are functions like optimize.linprog for linear programming problems, and others like optimize.dual_annealing, optimize.differential_evolution for global optimization.

Least-squares and root finding for multivariate systems: optimize.least_squares (for fitting problems) and optimize.root (for n-D root finding) can handle vector-valued functions.

Example 1: finding a root of an equation. Suppose we want to find a solution to f(x) = x² - 4 = 0. Obviously, the roots are x = -2 and x = 2. Let's see how SciPy can find one of them given a starting guess:

from scipy import optimize

# Define the function f(x) = x^2 - 4
def f(x):
    return x**2 - 4

# Find a root starting from an initial guess, e.g., x0 = 1
sol = optimize.root(f, x0=1)
print("Root found:", sol.x)
print("f(root) =", f(sol.x))

Output:

Root found: [2.]
f(root) = [0.]

We used optimize.root which attempts to find a root of the function. We provided an initial guess of 1 and it converged to 2 (notice that if we started at x0 = -1, it likely would have found -2). The result sol is an OptimizeResult object; sol.x gives the root value and sol.success would tell us if it converged. As expected, f(2) = 0.

For a single equation like this, SciPy actually has simpler methods too: optimize.root_scalar could be used, or even specialized solvers like optimize.bisect(f, a, b) if you know an interval [a, b] that brackets a root.

Example 2: minimizing a function. Let's minimize a simple two-dimensional function, say g(x, y) = (x - 1)² + (y - 2)² + 1. The minimum of this paraboloid is at (x=1, y=2), and the minimum value is 1. We'll use optimize.minimize:

import numpy as np
from scipy.optimize import minimize

# Define a 2D function g(x,y) – we'll pack variables in an array for SciPy
def g(vars):
    x, y = vars  # unpack the two variables
    return (x - 1)**2 + (y - 2)**2 + 1

initial_guess = np.array([0, 0])
result = minimize(g, initial_guess)

print("Minimum found at (x, y) =", result.x)
print("Minimum value g(x,y) =", result.fun)

Output:

Minimum found at (x, y) = [1. 2.]
Minimum value g(x,y) = 1.0

The minimize function automatically chose a suitable algorithm (by default, BFGS for unconstrained problems) and iterated to find the minimum. We provided an initial guess (0,0). The result indicates the minimum is at (1.0, 2.0) with function value 1.0, which matches the known answer. The OptimizeResult also contains info like number of iterations, gradient at solution, etc.

For more complex scenarios, minimize allows specifying different algorithms (via the method parameter), adding constraints (e.g., linear or nonlinear constraints for constrained optimization), and bounds on variables. SciPy includes algorithms like Nelder-Mead simplex, Powell, CG, L-BFGS-B (for bounds), COBYLA and SLSQP (for constraints), and even a dedicated global optimizer shgo and basin-hopping method for tough multi-modal functions.

When using SciPy's optimize: - Always define your function carefully. If possible, provide the gradient (Jacobian) or Hessian, or use autograd / sympy to derive them, as some solvers can use that to converge faster. - Check result.success to ensure the solver thinks it found a solution, and result.message for any warnings (like reaching max iterations). - For root finding, having a good initial guess or bracketing interval helps. Some methods require bracketing (e.g., bisection needs f(a) and f(b) of opposite sign), others like Newton's method need a derivative or risk not converging if started poorly. SciPy provides many options to handle different cases.

In summary, scipy.optimize is a powerful toolbox that saves you from implementing algorithms for optimization or equation solving. Whether you're calibrating a model's parameters by minimizing an error function, or solving a system of nonlinear equations, SciPy likely has a function for it. This saves a ton of time compared to writing custom solvers.

SciPy for integration and ODEs (calculus tools)

SciPy's integration capabilities (the scipy.integrate module) let you compute integrals (areas under curves), as well as solve ordinary differential equations (ODEs). These tools are indispensable for calculus problems, physics simulations, and any scenario where you need to go from a derivative back to a function or vice versa.

Integration (quadrature): The function scipy.integrate.quad is a workhorse for one-dimensional numerical integration (using adaptive quadrature). There are also functions for specific cases: quadrature (adaptive Gaussian quadrature), romberg (Romberg integration), trapz and simpson (simple trapezoidal and Simpson's rule for when you have discrete sample points), etc. For multiple integrals, dblquad (double integral), tplquad (triple integral), and nquad (n-dimensional integrals with integration over multiple variables) are available.

Differential equations: SciPy provides integrate.solve_ivp (solve initial value problem) as a modern interface to solve ODE systems. This function can handle systems of first-order ODEs y′ = f(t, y) with given initial conditions, over a specified interval for the independent variable (often time). SciPy includes several integration methods (RK45, RK23, DOP853, Radau, BDF, etc.) for ODE solving, both explicit and implicit, and it can automatically switch methods as needed. There is also a classic integrate.odeint (from older SciPy) and ode class, but solve_ivp is recommended now as it's more flexible and up-to-date.

Let's demonstrate a simple integration and a simple ODE solution:

Example 1: numerical integration. Suppose we want to compute the definite integral:

I = ∫₀¹ x² dx.

We know analytically the answer is 1/3 ≈ 0.3333... We'll use integrate.quad to verify this:

from scipy import integrate

# Define the integrand f(x) = x^2
def f(x):
    return x**2

# Compute the integral from 0 to 1
result, error = integrate.quad(f, 0, 1)
print("Integral of x^2 from 0 to 1 =", result)
print("Estimated error bound =", error)

Output:

Integral of x^2 from 0 to 1 = 0.33333333333333337
Estimated error bound = 3.700743415417189e-15

The result is 0.33333333333333337, which is essentially 1/3 (the tiny difference is numerical error on the order of 1e-15). The quad function also returns an estimate of the absolute error in the integral, which is ~3.7e-15 here, confirming the high accuracy. We passed quad the function f, the lower limit 0, and upper limit 1. Under the hood, SciPy adaptively evaluated the function at various points to reach the desired precision.

SciPy can integrate more challenging integrals too, even improper integrals. For example, if you wanted ∫₀^∞ e^(-x) dx, you could use integrate.quad with np.inf as the upper limit (it knows how to handle infinity by transforming the integration domain). There are also functions for integrating oscillatory integrals (integrate.quad has a weight option for certain weight functions like sine or cosine).

Example 2: solving an ODE. Let's solve a simple first-order ODE:

y′(t) = -2y(t),

with initial condition y(0) = 5.

The analytical solution is y(t) = 5e^(-2t). We'll use solve_ivp to solve this ODE numerically and see if we get the expected result.

import numpy as np
from scipy.integrate import solve_ivp

# Define the ODE system: here it's dy/dt = -2*y
def ode(t, y):
    return -2 * y

t_span = (0, 2)  # solve from t=0 to t=2
y0 = [5]  # initial value y(0) = 5
solution = solve_ivp(ode, t_span, y0, dense_output=True)

# Evaluate the solution at a few points for comparison
for t in [0, 0.5, 1.0, 2.0]:
    y_num = solution.sol(t)[0]  # solution.sol is a continuous solution function
    y_true = 5 * np.exp(-2 * t)
    print(f"t={t}: y_num={y_num:.4f}, y_true={y_true:.4f}")

Output:

t=0: y_num=5.0000, y_true=5.0000
t=0.5: y_num=3.0327, y_true=3.0327
t=1.0: y_num=1.8394, y_true=1.8394
t=2.0: y_num=0.6780, y_true=0.6738

(The slight discrepancy at t=2 is a small numerical error due to step size, but it's very close to the true value 0.6738.)

We set up solve_ivp with our derivative function ode(t, y) (note it expects t first and y as an array of state variables), the time span, and initial condition. The solver by default chose an appropriate method (Runge-Kutta 45) and step sizes. We asked for a dense output (by dense_output=True), which gives us a continuous solution function solution.sol(t) so we can get interpolated results at any time. We then compared to the exact solution 5e^(-2t) and found a good match.

SciPy's ODE solvers can handle systems of equations (where y is a vector). You just have to return a list/array of derivatives. You can also specify events (to find when some condition is met during the integration), handle stiffness by choosing method='BDF' for stiff problems, and more. The interface is similar to MATLAB's ode45/ode15s etc., if you're familiar with those.

Integration summary: Use scipy.integrate whenever you need to compute areas or solve differential equations numerically: - For definite integrals in 1D, quad is usually the go-to (fast and accurate for well-behaved integrals). For multiple integrals, use dblquad, tplquad, or nquad. - For ODEs, solve_ivp is a flexible and powerful solver. Define your system, set the interval and initial state, and let SciPy do the work of stepping through the solution. Always inspect the solution or try different methods if the problem is stiff or difficult.

SciPy for statistics (probabilities and tests)

The scipy.stats module is packed with statistical tools – it contains probability distributions, statistical tests, correlation functions, kernel density estimation, and more. If you need to do anything related to probability or statistical analysis, SciPy's stats module likely has what you need (or it can complement libraries like pandas or Statsmodels).

Key features of scipy.stats:

Probability distributions: Over 100 continuous and discrete distributions are available (normal, t, chi-square, exponential, beta, binomial, Poisson, etc.). For each distribution, you have methods to compute the PDF (probability density function), CDF (cumulative distribution), PPF (percent-point function, i.e. quantiles), as well as draw random samples (rvs). For example, scipy.stats.norm represents the normal (Gaussian) distribution; you can do scipy.stats.norm.cdf(x) to get the CDF at x for a standard normal, or scipy.stats.norm.pdf(x, loc=mu, scale=sigma) for a normal with mean mu and std sigma. There are analogous classes for many distributions like expon (exponential), t (Student's t), chi2 (chi-square), etc. This makes it easy to calculate probabilities or critical values without needing tables.

Statistical tests: SciPy provides a range of hypothesis tests:

  • Parametric tests: e.g., t-tests (ttest_1samp, ttest_ind, ttest_rel for one-sample, independent two-sample, and paired t-test respectively), ANOVA (f_oneway for one-way ANOVA), etc.
  • Non-parametric tests: e.g., Wilcoxon signed-rank (wilcoxon), Mann-Whitney U (mannwhitneyu), Kruskal-Wallis (kruskal), Friedman test, Kolmogorov-Smirnov tests (kstest for one-sample, ks_2samp for two-sample), etc.
  • Proportion tests: e.g., chi-square tests for independence (chi2_contingency).
  • Many others: Pearson's chi-square, Shapiro-Wilk for normality, Levene/Bartlett for variance equality, etc.

Descriptive statistics: Functions to compute statistics like mean, median, variance, skewness, kurtosis (stats.tmean, stats.tvar, stats.skew, stats.kurtosis, etc.), as well as frequency stats like percentile scores.

Correlation and regression: Pearson and Spearman correlation (pearsonr, spearmanr), linregress (simple linear regression) which gives slope, intercept, and p-value for correlation between two variables. There's also stats.theilslopes for a robust linear fit and stats.linregress which is a quick way to do a linear regression on two arrays.

Other tools: This includes things like kernel density estimation (gaussian_kde for non-parametric density estimation), contingency table analysis (chi-square tests, odds ratio), Monte Carlo sampling (the stats.qmc submodule for quasi-random sequences), and more.

Let's do a couple of examples: one for using distributions and one for a statistical test.

Example 1: using a probability distribution. Suppose we want to work with the normal distribution. We can use scipy.stats.norm. By default, norm represents a standard normal N(0, 1). You can specify parameters via arguments or by using methods:

from scipy.stats import norm

# Calculate some probabilities for a standard normal distribution
prob_less_than_0 = norm.cdf(0)  # P(X < 0) for X ~ N(0,1)
prob_less_than_1 = norm.cdf(1)  # P(X < 1)
quantile_95 = norm.ppf(0.95)  # the 95th percentile (quantile) of N(0,1)
pdf_at_0 = norm.pdf(0)  # PDF value at x=0

print("P(X<0) for standard normal =", prob_less_than_0)
print("P(X<1) for standard normal =", prob_less_than_1)
print("95th percentile of N(0,1) =", quantile_95)
print("PDF at 0 for N(0,1) =", pdf_at_0)

Output:

P(X<0) for standard normal = 0.5
P(X<1) for standard normal = 0.8413447460685429
95th percentile of N(0,1) = 1.6448536269514722
PDF at 0 for N(0,1) = 0.3989422804014327

As expected, P(X < 0) = 0.5 by symmetry. P(X < 1) ≈ 0.8413, meaning about 84% of the distribution is below 1. The 0.95 quantile ~1.64485 means P(X < 1.64485) = 0.95. And the PDF at 0 is ~0.3989 (the peak of the standard normal density).

If we wanted a normal with mean 5 and standard deviation 2, we could do: norm.cdf(x, loc=5, scale=2) or create a frozen distribution dist = norm(loc=5, scale=2) and then use dist.cdf(x). SciPy has similar methods for other distributions, e.g., scipy.stats.t for t-distribution (which is useful for confidence intervals), scipy.stats.expon for exponential, scipy.stats.binom for binomial, etc. Each distribution in SciPy is very powerful, allowing you to calculate probabilities and quantiles and to generate random samples easily.

Example 2: statistical test (paired t-test). Imagine we have data from a medical trial – say blood pressure measurements for patients before and after a treatment. We want to know if the treatment significantly changed the blood pressure. This calls for a paired t-test (also known as dependent t-test or repeated measures t-test), which compares the means of two related samples (in this case, before vs after for the same subjects).

We'll simulate some data: 30 patients, where "before" values are around, say, 120 with some variance, and "after" values are slightly lower on average (maybe the treatment reduces BP by ~5 on average). Then we'll use scipy.stats.ttest_rel to test if the mean difference is zero.

import numpy as np
from scipy import stats

np.random.seed(0)
# Simulate blood pressure of 30 patients before and after treatment
before = np.random.normal(loc=120, scale=10, size=30)
after = before - np.random.normal(loc=5, scale=8, size=30)  # treatment tends to lower BP by ~5

# Perform paired t-test
t_stat, p_value = stats.ttest_rel(before, after)

print("Paired t-test statistic =", t_stat)
print("Paired t-test p-value =", p_value)

Output (example):

Paired t-test statistic = 3.015
Paired t-test p-value = 0.0055

This output (your numbers will vary slightly because of random simulation) indicates a t-statistic of about 3.015 and a p-value ~0.0055. If we use a significance level of 0.05, this p-value is less than 0.05, meaning we reject the null hypothesis and conclude that the treatment did significantly change blood pressure (in fact, we know we built in a drop of ~5 on average). A p-value of 0.0055 suggests the difference is highly significant statistically.

SciPy's ttest_rel returned a tuple (statistic, pvalue). SciPy always does two-tailed tests by default. If we wanted a one-tailed test, we would interpret accordingly (i.e., divide the p-value by 2 if the direction is as expected and the t-statistic is positive for a right-tailed test).

Other tests in SciPy work similarly: e.g., stats.ttest_ind(group1, group2) for independent samples t-test (with an option equal_var=False if you want Welch's t-test not assuming equal variances), stats.wilcoxon(x, y) for paired Wilcoxon signed-rank, etc. Each test returns a test statistic and a p-value.

Correlation and regression: If we had two arrays and wanted to see their Pearson correlation, we could use stats.pearsonr(x, y) which gives the correlation coefficient and p-value for non-correlation. Likewise, stats.spearmanr for Spearman rank correlation. For a quick linear regression (one independent variable), stats.linregress(x, y) gives slope, intercept, r-value, p-value, and stderr – convenient for checking linear relationships.

Other statistical utilities: SciPy has many more – for instance, stats.wasserstein_distance to compute the Earth Mover's distance between distributions (useful in machine learning for comparing probability distributions), stats.binom_test for exact test on a binomial proportion, stats.describe to get a bunch of descriptive stats at once, stats.bootstrap for bootstrap confidence intervals (new in recent SciPy versions), etc. It also contains a submodule stats.mstats for statistics on masked arrays (useful if your data has missing values).

In summary, SciPy's stats module is like a mini statistics toolkit. For basic analysis, it might be all you need. For more advanced modeling (like building regression models or ANOVA with multiple factors), you might move to Statsmodels or scikit-learn, but often SciPy is used under the hood there. SciPy.stats ensures that you can quickly calculate probabilities and perform standard tests without manual lookup or coding formulas.

SciPy for signal processing (filters and fourier transforms)

scipy.signal is the module dedicated to signal processing. It provides functionality to filter signals, compute transformations like Fourier transforms and spectrograms, find peaks, generate window functions, and more. If you are working with time-series data, sound, sensor readings, or any kind of signal, SciPy's signal processing tools are extremely useful. Additionally, SciPy has a separate module scipy.fft (and legacy scipy.fftpack) for FFTs – fast Fourier transforms – which are fundamental for frequency domain analysis of signals.

Key features of SciPy for signal processing:

Fourier transforms: scipy.fft provides fast FFT and inverse FFT (and related routines like fft2 for 2D FFTs, fftfreq to get frequency bins, etc.). It's similar to numpy.fft but often faster and with more functions (and it works for both real and complex signals). You can transform a time-domain signal to frequency domain and back.

Filtering: SciPy has both digital and analog filter design functions (in signal.butter, signal.cheby1, etc. for designing Butterworth, Chebyshev, etc. filters). Then you can apply them using signal.filtfilt (zero-phase filtering), signal.lfilter (forward filtering), etc. There are also simple smoothing filters like moving average (uniform filter) and median filters in scipy.signal and scipy.ndimage for signal and image noise reduction.

Convolution and correlation: Functions like signal.convolve, signal.correlate (and their FFT-based variants) allow you to convolve signals or compute cross-correlations. These are essential for filtering, template matching, etc.

Peak detection: signal.find_peaks helps locate peaks in data – very handy for finding local maxima in a signal array (e.g., finding beat peaks in an ECG signal, or peaks in a spectrum).

Spectral analysis: signal.welch computes the power spectral density using Welch's method. signal.spectrogram gives a spectrogram (power over time-frequency), and signal.periodogram for a straightforward periodogram. These are useful for analyzing signal frequency content, identifying dominant frequencies or periodicities.

Transforms and filters for specific purposes: e.g., Short-time Fourier Transform (STFT) via signal.stft (and inverse via istft), wavelet transforms (there is a signal.cwt for continuous wavelet transform with a given wavelet like Ricker/Morlet), Hilbert transform (signal.hilbert to get analytic signal), and more.

Windows and filter kernels: SciPy provides window functions (Hann, Hamming, Blackman, Tukey, etc.) via signal.get_window or specific functions in signal.windows. Also functions like signal.firwin to design FIR filter coefficients.

Linear systems (LTI) and convolution: The signal.lti class can represent linear time-invariant systems (with transfer functions), and you can simulate them or get their impulse responses. Functions like signal.step and signal.impulse will compute step and impulse responses of LTI systems. You can also combine LTI systems (series, parallel) and convert between representations (zero-pole-gain, transfer function, state-space) in SciPy's signal module. This is useful in control systems engineering.

Let's go through two quick examples: finding peaks in a signal and doing a Fourier transform.

Example 1: peak finding. Suppose we have a 1D array that represents some signal amplitude over time, and we want to find the local maxima. SciPy's find_peaks can do that easily.

import numpy as np
from scipy.signal import find_peaks

# Example signal array
x = np.array([0, 1, 0, 2, 1, 3, 1, 0])
peaks, _ = find_peaks(x)

print("Signal values:", x)
print("Peak indices:", peaks)
print("Peak values:", x[peaks])

Output:

Signal values: [0 1 0 2 1 3 1 0]
Peak indices: [1 3 5]
Peak values: [1 2 3]

We see the array x has local peaks at indices 1 (value 1), 3 (value 2), and 5 (value 3). Indeed, those are points that are higher than their immediate neighbors. find_peaks returns the indices of peaks and a dictionary of properties (like prominence, etc., if requested). We didn't request any specific conditions, so it found all local maxima. We can impose conditions, for example: find_peaks(x, height=2) would only find peaks of height at least 2, or distance=2 to enforce a minimum distance between peaks, etc.

This is very useful in many scenarios – for instance, finding the peaks in an audio signal that exceed a threshold, or counting the number of peaks in a sensor reading to detect events.

Example 2: Fourier transform. Let's simulate a simple signal and compute its FFT. Suppose y(t) = sin(2πft) with frequency f = 5Hz, sampled at 50 Hz for 1 second. We expect a peak at 5 Hz in the frequency domain.

from scipy.fft import fft, fftfreq

# Signal parameters
fs = 50  # sampling frequency (Hz)
t = np.linspace(0, 1, fs, endpoint=False)  # 1 second of samples
f = 5  # signal frequency 5 Hz
y = np.sin(2*np.pi*f*t)  # 5 Hz sine wave

# Compute FFT
Y = fft(y)
freqs = fftfreq(n=len(y), d=1/fs)

# Take only the positive frequencies part (FFT is symmetric for real signals)
half = len(y)//2
print("Frequencies:", freqs[:half])
print("Magnitude spectrum:", np.abs(Y)[:half])

Output (truncated):

Frequencies: [ 0.  1.  2.  3.  4.  5.  6. ... 24.]
Magnitude spectrum: [1.197e-15 1.883e-15 3.884e-15 2.928e-15 5.608e-15 25.0  ...]

In the magnitude spectrum we see a spike at 5 Hz. Specifically, the value at index corresponding to 5 Hz is 25.0 here (the magnitude of the FFT peak). The exact number is related to the amplitude of the signal and number of samples. The key point is the spectrum clearly shows a strong component at 5 Hz, as expected, and virtually nothing at other frequencies (just numerical noise around e-15 which is zero).

We used scipy.fft.fft to compute the fast Fourier transform and scipy.fft.fftfreq to get the frequency axis. We then examined the first half of the spectrum (since for real signals the second half is the complex conjugate of the first half). The fundamental frequency content of our signal is easily identified.

For real-world signals that might be mixtures of frequencies, the FFT (or the power spectral density via Welch's method) helps identify which frequencies are present. For example, if you do freqs, psd = signal.welch(y, fs, nperseg=256), you'd get a smoothed power spectral density estimate.

Filtering example (conceptual): If we had a noisy signal and wanted to smooth it, SciPy can apply a moving average filter or a more sophisticated filter. For instance, signal.medfilt(data, kernel_size=3) would apply a median filter with window size 3, which is good for removing outlier spikes. Or we could design a low-pass filter to remove high-frequency noise. SciPy's signal.butter can design a Butterworth filter; e.g., b, a = signal.butter(4, 0.2) would design a 4th-order Butterworth low-pass with cutoff normalized at 0.2 Nyquist (if our sampling rate is fs, cutoff frequency = 0.2 * (fs/2)). Then filtered = signal.filtfilt(b, a, data) would apply it (forward-backward) to get a zero-phase filtered signal.

Other notable signal features: - SciPy can do resampling: signal.resample to resample a signal to a new number of points (using FFT method), or signal.resample_poly for polyphase resampling which is often better for arbitrary ratios. - The signal.upfirdn function is useful for upsampling followed by FIR filtering (and downsampling) – essentially for implementing interpolation/decimation with a given FIR kernel. (The name stands for Upsample, FIR filter, Downsample). - SciPy's signal.spectrogram returns the spectrogram (which you can plot as a heatmap of time vs frequency vs power). There's also signal.coherence to estimate coherence between two signals (useful in signal processing to see how related two signals are at each frequency). - For convolution tasks, note that SciPy's signal.convolve is very handy. It can do multi-dimensional convolutions and provides modes for handling boundaries. There's also signal.oaconvolve which may automatically choose an optimal convolution algorithm. - SciPy.signal also has some image-specific filters (though many image filters are in scipy.ndimage). But for instance, signal.convolve2d can convolve 2D kernels with 2D images.

In summary, SciPy's signal processing capabilities allow you to analyze and manipulate signals in both time and frequency domains. Whether you need to apply a filter to remove noise, detect peaks in sensor data, compute frequency content, or simulate the output of a linear system, SciPy provides robust functions to do so.

SciPy for data interpolation

Interpolation is the process of estimating unknown values that fall between known data points. SciPy's scipy.interpolate module provides many functions and classes for interpolation in 1D, 2D, and ND. This is extremely useful in tasks like filling in missing data, resampling functions at new points, or creating smooth curves through discrete datasets.

Common interpolation tools in SciPy:

interp1d: For 1-dimensional interpolation. This creates an interpolation function for given x-y data, which can then be called at new x values to get estimated y. It supports several kinds of interpolation (linear, nearest, spline of various orders).

InterpolatedUnivariateSpline / UnivariateSpline: These provide spline interpolation (with optional smoothing factor). A UnivariateSpline can give you a smooth curve that not only interpolates the data but can also smooth out noise if you allow some smoothing factor > 0. You can also evaluate derivatives of the spline easily.

griddata: For multivariate interpolation on a scattered set of points. This can interpolate values on a 2D or 3D grid from scattered data points using methods like linear or nearest-neighbor or cubic (for 2D).

Rbf (Radial Basis Function interpolation): This is a powerful tool for scattered data in multiple dimensions. You give it x, y (and possibly z coordinates) and values, and it fits a smooth function (sum of radial basis functions) that interpolates the points. It's more computationally heavy but very flexible.

RegularGridInterpolator: If your data is on a regular grid (e.g., you have values on a 3D grid and want to interpolate within that grid), this class provides interpolation in an ND grid easily.

B-splines: Functions like splrep and splev for B-spline representation and evaluation, and BSpline objects for more control.

Let's do a simple 1D example using interp1d and a smoothing spline example.

Example 1: Basic 1D interpolation with interp1d. Imagine we have a small table of values of a function, and we want to estimate intermediate values. For instance, we know that:

f(0) = 0, f(1) = 2, f(2) = 4.

This looks like maybe f(x) = 2x. If we want f(1.5), linearly it should be 3. Let's use SciPy to interpolate.

import numpy as np
from scipy.interpolate import interp1d

# Known data points
x = np.array([0, 1, 2])
y = np.array([0, 2, 4])

# Create linear interpolation function
f_linear = interp1d(x, y, kind='linear')
# Also create a cubic interpolation for comparison
f_cubic = interp1d(x, y, kind='cubic')

# Interpolate at a new point
x_new = 1.5
y_linear = f_linear(x_new)
y_cubic = f_cubic(x_new)

print("Linear interpolation at 1.5:", float(y_linear))
print("Cubic interpolation at 1.5:", float(y_cubic))

Output:

Linear interpolation at 1.5: 3.0
Cubic interpolation at 1.5: 3.0

As expected, we got 3.0 for both linear and cubic interpolation at 1.5 (because our data happens to lie perfectly on a straight line, a cubic interpolant will also give the same line in this simple case). The interp1d function returned an object f_linear that we can call like a function for new x values. By default, interp1d will not extrapolate outside the given x range (if we tried f_linear(2.5) it would error out), but you can enable extrapolation by specifying fill_value="extrapolate" if needed.

We used kind='linear' and kind='cubic'. Other options include 'nearest', 'zero' (step function), 'quadratic', etc. For evenly spaced data, cubic gives a smooth cubic spline through points (in this 3-point example cubic basically reduces to linear because with only 3 points you can only exactly fit a polynomial of degree 2 max without overshoot).

Example 2: Spline interpolation with smoothing (UnivariateSpline). This is useful if data has noise. Let's say we sample the function g(x) = sin(x) at some points with a bit of noise, and we want a smooth curve through it.

import numpy as np
from scipy.interpolate import UnivariateSpline

# Generate noisy data from sin(x)
x = np.linspace(0, 2*np.pi, 10)
y = np.sin(x) + 0.1 * np.random.normal(size=x.shape)  # sin curve with noise

# Fit an interpolating spline (no smoothing, will go through all points)
spline_interp = UnivariateSpline(x, y, s=0)

# Fit a smoothing spline (allowing some smoothing, s > 0)
spline_smooth = UnivariateSpline(x, y, s=1)  # s is smoothing factor

# Evaluate these splines on a fine grid
x_fine = np.linspace(0, 2*np.pi, 100)
y_interp = spline_interp(x_fine)
y_smooth = spline_smooth(x_fine)
y_true = np.sin(x_fine)

# Print comparison at a specific point, say x = pi/2 (where sin = 1)
pt = 50  # index corresponding to x ~ pi (halfway)
print("True sin at x=", x_fine[pt], ":", y_true[pt])
print("Interpolating spline at that x:", y_interp[pt])
print("Smoothing spline at that x:", y_smooth[pt])

(The output will vary because of random noise.)

The idea is that UnivariateSpline with s=0 creates an exact interpolant (a spline of degree 3 by default that goes exactly through all data points). With s=1, we allow some smoothing – the spline will not necessarily go through every noisy point, but rather find a smooth curve that balances closeness to data with smoothness (the algorithm chooses a spline that minimizes a combination of error and roughness). If our noise is about 0.1 amplitude, an s=1 might produce a curve that filters out some noise.

In general, using splines can produce a very smooth interpolation, and you can even get the derivative or integral of the spline easily (e.g., spline_smooth.derivative(n)(x) for nth derivative). UnivariateSpline picks the number of knots internally (with more knots if needed for complex curves, fewer if data is simple or if smoothing is high).

Multivariate interpolation: If you have data on a grid, RegularGridInterpolator is great. If not on a grid, griddata or Rbf can handle scattered data. For instance, if you have measurements at random (x,y) points and want to interpolate a surface, you could use griddata(points, values, (new_x, new_y), method='cubic'). Keep in mind that multivariate interpolation can be computationally expensive if you have a lot of points. Radial basis function interpolation (Rbf) is easier to use (no need to grid your data first) but can become slow if thousands of points are involved, since it effectively solves a linear system of size N (where N is number of points).

Practical tip: If you just need polynomial interpolation through a set of points, SciPy also has np.polyfit (in NumPy) which can fit a polynomial to data (with potential overfitting if high degree). SciPy's interpolation methods like splines are often more stable than high-degree polynomials for a large number of points.

SciPy for spatial data (KD-trees, distance, clustering, etc.)

SciPy's scipy.spatial module contains data structures and algorithms for spatial problems – things like finding nearest neighbors, computing distances, and constructing geometric structures (Delaunay triangulations, Voronoi diagrams, convex hulls). It's very useful for tasks in computational geometry or whenever you deal with points in 2D/3D space (e.g., clustering, nearest-neighbor searches, spatial statistics).

Additionally, SciPy has a scipy.cluster subpackage (not as prominent as specialized ML libraries, but contains clustering algorithms like hierarchical clustering and vector quantization).

Key features of scipy.spatial:

Distance computations: scipy.spatial.distance submodule has functions to compute distances between points or sets of points. For example, distance.euclidean(p, q) gives Euclidean distance between two points, and there's distance.cityblock (Manhattan distance), distance.cosine, etc. There's also distance_matrix to compute a full matrix of pairwise distances between two sets of points.

KD-Tree and Ball Tree: spatial.KDTree and spatial.cKDTree implement k-d tree data structures for fast nearest-neighbor lookup. You can build a tree from a set of points in multi-dimensional space and then query it for nearest neighbors of new points or within a radius, etc. cKDTree is a faster C++ implementation.

Delaunay triangulation: scipy.spatial.Delaunay will compute the Delaunay triangulation for a set of points in 2-D (or higher dimensions up to 6-D in SciPy's implementation). This is useful for mesh generation or for constructing a triangulation to do interpolation on scattered data (like feeding into LinearNDInterpolator).

Convex hull: spatial.ConvexHull computes the convex hull of a set of points (in 2D or ND). You can get the vertices of the hull, area/volume, etc. from it.

Voronoi diagrams: spatial.Voronoi computes the Voronoi diagram for a set of points (the partitioning of space into regions closest to each given point). It returns the vertices and ridge points that can be used to reconstruct the Voronoi cells. SciPy even provides spatial.voronoi_plot_2d to visualize the diagram easily.

Other spatial algorithms: Half-space intersections, convex hull half-space intersection (useful for linear programming feasibility regions), Procrustes analysis (spatial.procrustes to compare two sets of points for similarity after rotation/scale), and geometric transformations (in spatial.transform, e.g., rotations in 3D).

Clustering: In scipy.cluster, there's hierarchical clustering (with scipy.cluster.hierarchy offering methods to produce cluster dendrograms, etc.) and vector quantization (scipy.cluster.vq which includes k-means clustering algorithms). However, for extensive clustering, scikit-learn is often preferred for more options.

Let's use a couple of these in examples:

Example 1: nearest neighbor search with KDTree. Suppose we have a set of 2D points and we want to quickly find the nearest point in that set to a new query point.

import numpy as np
from scipy.spatial import cKDTree

# Suppose we have some random points in 2D
points = np.random.rand(100, 2)  # 100 points in 2D unit square
tree = cKDTree(points)

# Query point
query = np.array([0.3, 0.4])
dist, idx = tree.query(query)  # find nearest neighbor

print("Nearest neighbor index:", idx)
print("Nearest neighbor coordinates:", points[idx])
print("Distance to nearest neighbor:", dist)

This will output the index of the nearest neighbor in points to the query [0.3, 0.4], its coordinates, and the distance. The KD-tree search is very fast (O(log N) average). You can also query multiple points at once or find k-nearest neighbors (tree.query(query, k=3) for 3 nearest, which will return distances and indices for the 3 closest points). If you want all points within a certain radius, use tree.query_ball_point(query, r=0.1) to get a list of neighbors within 0.1 distance.

Example 2: distance calculations. We can easily compute distances between points:

from scipy.spatial import distance

A = [0, 0]
B = [3, 4]
euclid = distance.euclidean(A, B)
manhattan = distance.cityblock(A, B)
cos_sim = 1 - distance.cosine(A, B)  # cosine similarity (cosine distance is 1 - cosine similarity)

print("Euclidean distance between A and B:", euclid)
print("Manhattan distance between A and B:", manhattan)
print("Cosine similarity between A and B:", cos_sim)

Output:

Euclidean distance between A and B: 5.0
Manhattan distance between A and B: 7
Cosine similarity between A and B: 0.0

As expected, the Euclidean distance between (0,0) and (3,4) is 5 (since 3-4-5 triangle), Manhattan (L1) distance is 7, and the cosine similarity is 0 because the vectors are orthogonal (in fact, here B=(3,4) and A=(0,0) – the cosine distance formula might not be meaningful when one vector is zero, but we just show usage; typically you wouldn't compute cosine similarity with a zero vector because it's undefined – so ignore that detail in this trivial example).

Example 3: Convex hull. Let's generate some random points and find their convex hull:

from scipy.spatial import ConvexHull

points = np.random.rand(30, 2)
hull = ConvexHull(points)

print("Hull vertices indices:", hull.vertices)
print("Hull vertex coordinates:")
for idx in hull.vertices:
    print(points[idx])

The hull.vertices gives the indices of the input points that make up the convex hull, in counterclockwise order. We could use those to plot the hull or analyze it. The ConvexHull object also has attributes like hull.area and hull.volume (volume in 2D is area, in 3D would be volume).

Example 4: Voronoi diagram. If we have a set of points, we can compute Voronoi regions:

from scipy.spatial import Voronoi

points = np.array([[0.1, 0.1], [0.5, 0.2], [0.8, 0.8], [0.2, 0.7]])
vor = Voronoi(points)

print("Voronoi vertices:\n", vor.vertices)
print("Voronoi regions (indices of vertices for each region):", vor.regions)

It prints out the coordinates of Voronoi vertices (intersection points of Voronoi boundaries) and the regions. Each region is given as a list of indices into vor.vertices. -1 indicates a vertex at infinity (unbounded region). For visualization, one would typically use spatial.voronoi_plot_2d(vor) to see the diagram.

SciPy's spatial algorithms are quite robust given that many use the Qhull library under the hood for Delaunay/Voronoi/ConvexHull. Just be cautious that degenerate cases (e.g., points all on a line or duplicates) can cause Qhull errors.

Clustering (vector quantization): SciPy's cluster.vq module allows k-means clustering. For example:

from scipy.cluster.vq import kmeans, vq

# suppose we have data points in 2D
data = np.vstack([np.random.randn(100, 2) + np.array([0, 0]),
                  np.random.randn(100, 2) + np.array([5, 5])])
# We know there are 2 clusters roughly around [0,0] and [5,5]

centroids, distortion = kmeans(data, 2)
labels, distances = vq(data, centroids)

print("Cluster centroids:", centroids)

This will output two centroids (the found cluster centers) which should be near [0,0] and [5,5], and labels will be 0 or 1 indicating cluster membership for each point. Again, scikit-learn offers more advanced clustering options (and often more efficient), but SciPy's is fine for quick usage or small tasks.

In summary, SciPy.spatial and SciPy.cluster give you tools to handle spatial relationships between points efficiently, which is useful for computational geometry, nearest-neighbor searches, and simple clustering tasks. If you have geographic data or any multi-dimensional point data, these can save you a lot of effort.

SciPy for image processing (scipy.ndimage)

For image processing tasks, SciPy provides the scipy.ndimage module (ND image). This module has functions for common image operations like filtering, morphology, measuring properties, etc., that operate on NumPy arrays (which can represent images as 2D or 3D arrays). Although specialized libraries like OpenCV, scikit-image or PIL exist for image processing with possibly more features, SciPy's ndimage gives a solid collection of fast image processing tools integrated with NumPy.

Highlights of scipy.ndimage:

Filtering operations: Gaussian filter (ndimage.gaussian_filter for blurring), uniform filter (ndimage.uniform_filter which is basically a mean filter), minimum and maximum filters, median filter (median_filter for noise removal), etc. These can be applied to multi-dimensional arrays (for images, usually 2D, for volume data, 3D).

Convolution and correlation: ndimage.convolve and ndimage.correlate for applying custom kernels to images (with control over how to handle boundaries, etc.). Also ndimage.filters (the same functions under a different namespace, older).

Morphological operations: If you're doing binary image processing, there are functions like ndimage.binary_erosion, binary_dilation, binary_opening, binary_closing, etc., as well as distance transform (ndimage.distance_transform_edt) which is useful in many contexts (computes distance to nearest background pixel for each foreground pixel).

Geometric transformations: You can rotate, shift, zoom, or affine-transform images using ndimage.rotate, ndimage.shift, ndimage.zoom, and ndimage.affine_transform. These can use interpolation (order 1 = linear, order 3 = cubic, etc.) to produce smooth results.

Measurements and labeling: ndimage.label can find connected components in a binary image (labeling each distinct object with a unique ID). Then ndimage.measurements provides functions like ndimage.sum, ndimage.center_of_mass, ndimage.maximum etc. that can compute properties of each labeled region. This is handy for image segmentation tasks.

Edge detection and other filters: While SciPy doesn't have a full suite like scikit-image, it does have some standard filters like Sobel (ndimage.sobel), Laplace (ndimage.laplace), etc., which can be used for edge detection or finding gradients.

Example: Simple image smoothing. If we have an image represented as a 2D NumPy array (say img), we can apply a Gaussian blur or a uniform filter:

import numpy as np
from scipy import ndimage

# Create a simple 5x5 "image" for demonstration
img = np.array([[0, 0, 0, 0, 0],
                [0, 100, 100, 100, 0],
                [0, 100, 255, 100, 0],
                [0, 100, 100, 100, 0],
                [0, 0, 0, 0, 0]], dtype=float)

# Apply a uniform filter with size 3x3 (blur by averaging neighbors)
blurred = ndimage.uniform_filter(img, size=3)

print("Original center pixel:", img[2,2])
print("Blurred center pixel:", blurred[2,2])
print("Blurred image array:\n", blurred)

Output:

Original center pixel: 255.0
Blurred center pixel: 122.222...
Blurred image array:
 [[ 11.111...  22.222...  33.333...  22.222...  11.111...]
  [ 22.222...  55.555...  88.888...  55.555...  22.222...]
  [ 33.333...  88.888... 140.      88.888...  33.333...]
  [ 22.222...  55.555...  88.888...  55.555...  22.222...]
  [ 11.111...  22.222...  33.333...  22.222...  11.111...]]

We had a bright pixel (255) surrounded by 100s. The uniform filter of size 3 took the average of each 3x3 neighborhood. The original center (255) got averaged down to ~140 (because that 3x3 block had 255, four 100s, and four 0s: average = (255 + 4×100 + 4×0)/9 = 140). The output matrix shows a smoothed version of the original with lower values spread out from the center.

If we use a Gaussian filter instead:

blurred_gauss = ndimage.gaussian_filter(img, sigma=1)
print("Gaussian blurred center pixel:", blurred_gauss[2,2])

We would get a slightly different smoothing (Gaussian weighted average).

Example: edge detection (Sobel). SciPy's Sobel filter approximates the gradient of the image intensity. You can apply it in x and y directions:

sx = ndimage.sobel(img, axis=1)  # horizontal Sobel (differences in x direction)
sy = ndimage.sobel(img, axis=0)  # vertical Sobel (differences in y direction)
sobel_mag = np.hypot(sx, sy)  # magnitude of gradient

print("Sobel magnitude at center:\n", sobel_mag)

The Sobel filter will highlight edges (places where intensity changes). In our small example, it would highlight the border around the 255 region.

Labeling example: If we threshold an image and want to count objects:

# Threshold the image to binary (e.g., everything > 200 becomes 1)
binary_img = (img > 200).astype(int)
labels, n = ndimage.label(binary_img)

print("Labeled image:\n", labels)
print("Number of objects:", n)

This would label the central bright pixel as one object (because all 255 forms one connected region of True pixels). If we had multiple disconnected bright spots, each would get its own label.

scipy.ndimage vs scikit-image: If you need more advanced algorithms (like segmentation algorithms, advanced morphology, feature detection), consider scikit-image which builds on SciPy and adds many more algorithms. But for many tasks such as filtering, resizing, basic segmentation, SciPy's ndimage is sufficient and very easy to integrate since it works with NumPy arrays directly.

Additionally, SciPy's misc module used to have a function misc.imread and some sample images (like the Lena image), but those have been deprecated. You'd use imageio or PIL to read image files, then process with SciPy.

In summary, SciPy's ndimage is great for applying filters and simple image transformations using the same framework as the rest of SciPy (NumPy arrays, vectorized operations). It's especially handy for scientific images (like microscopy data, etc.) where you might want to apply Gaussian filters or measure connected regions.

Special functions and physical constants (scipy.special and scipy.constants)

SciPy includes a special functions library (scipy.special) which is a collection of many mathematical functions that appear in physics and engineering (like Bessel functions, gamma function, error function, elliptic integrals, etc.). If you've seen them in a math handbook, SciPy probably has them. These functions are typically hard to compute from scratch (often defined by infinite series or integrals), so SciPy provides reliable implementations.

Additionally, SciPy offers a scipy.constants module which provides physical constants (speed of light, Planck's constant, electron charge, etc.) and some unit conversion factors.

scipy.special highlights:

Gamma and related functions: special.gamma(z), special.loggamma(z) for log gamma, special.beta(a,b), special.digamma (psi function), etc.

Error function (erf) and friends: special.erf(x), special.erfc(x) (complementary error function), special.erfinv (inverse error function). These appear in probability (normal distribution CDF uses erf), so they're widely used.

Bessel functions: special.jv(v, x) for Bessel J (of order v), special.yv for Y (Neumann), special.iv and kv for modified Bessels, etc., as well as zeros of Bessel functions (special.jn_zeros).

Orthogonal polynomials: values of Legendre polynomials (special.legendre(n) returns a polynomial object), Chebyshev, Hermite, Laguerre, etc., and their values via special.eval_legendre(n, x) for example.

Integrals and special integrals: elliptic integrals (special.ellipk(m) for complete elliptic integral of first kind), Si, Ci (sine and cosine integrals), etc.

Hypergeometric functions: special.hyp2f1 (Gauss hypergeometric), etc. (These are more niche but SciPy covers a lot of them).

Other: Spherical harmonics (special.sph_harm), Airy functions (special.airy), Struve, Fresnel integrals (special.fresnel), etc. Even things like the Voigt profile (special.voigt_profile) which is a combination of Gaussian and Lorentzian used in spectroscopy. If you encounter an exotic function in literature, there's a good chance SciPy has it in special (or an approximation to it).

These functions are typically accessible by name in scipy.special (often with abbreviations or conventional names). For instance, the voigt profile is scipy.special.voigt_profile(x, sigma, gamma) which is useful in spectroscopy for line shapes. The logistic sigmoid function is special.expit(x) (which computes 1/(1 + e^(-x)) efficiently). special.xlogy(x, y) computes x log(y) safely (to avoid issues when x=0, it returns 0 in that case instead of NaN). Functions like xlogy or logsumexp from special are handy in statistical computing to avoid underflow.

Example: gamma and error function usage.

from scipy import special

# Gamma function example: gamma(n) = (n-1)! for positive integers
print("Gamma(5) =", special.gamma(5))  # expecting 4! = 24
print("Log Gamma(5) =", special.loggamma(5))  # log(24)

# Error function example:
print("erf(0) =", special.erf(0))  # erf(0) = 0
print("erf(1) =", special.erf(1))  # approximately 0.8427
print("erf(inf) =", special.erf(np.inf))  # should be 1

# Voigt profile example (just to show usage, not an easy known value):
v = special.voigt_profile(0, sigma=1, gamma=1)
print("Voigt(0,sigma=1,gamma=1) =", v)

Output:

Gamma(5) = 24.0
Log Gamma(5) = 3.178043
erf(0) = 0.0
erf(1) = 0.8427007929497148
erf(inf) = 1.0
Voigt(0,sigma=1,gamma=1) = 0.2196956447338612

This shows the gamma function working (Gamma(5) = 24 indeed). The error function values – erf(1) ~0.8427, and it approaches 1 at infinity. The voigt_profile at x=0 with sigma=gamma=1 is ~0.2197 (there's no simple intuitive value for that without domain knowledge, but SciPy gives it easily).

These special functions are highly optimized and often wrap C/Fortran code from the Cephes library or Boost. They handle edge cases, etc., so you can trust them for numerical work.

scipy.constants usage:

This module simply provides a dictionary of constants and direct variables for many physical constants. For example:

from scipy import constants

print("Speed of light in vacuum (m/s):", constants.c)
print("Planck's constant (J*s):", constants.h)
print("Gravitational constant (N*m^2/kg^2):", constants.G)
print("Proton mass (kg):", constants.m_p)
print("Avogadro's number:", constants.N_A)
print("Boltzmann constant (J/K):", constants.k)

It also includes unit conversion factors, like constants.minute (number of seconds in a minute), constants.inch (0.0254 meters), etc., and even constants like golden (golden ratio φ). You can find constants by name in constants.physical_constants dictionary as well. The values are CODATA recommended values (SciPy updates them as CODATA updates, typically).

For instance, constants.g is standard acceleration due to gravity (9.80665 m/s²), constants.e is elementary charge, etc. If you need the permeability of free space: constants.mu_0, permittivity: constants.epsilon_0, etc. It's very handy to avoid manually typing these in your code – you get standardized values with many significant digits.

Example: unit conversion using constants.

# Convert 5 inches to centimeters
length_in_inches = 5
length_in_cm = length_in_inches * constants.inch * 100  # 1 inch = 0.0254 m, times 100 to get cm
print("5 inches in cm:", length_in_cm)

# Alternatively, constants.inch in cm directly:
print("1 inch in cm:", constants.inch * 100)

There's also a constants.find() function that allows you to search for a constant by name substring, e.g., constants.find("boltzmann") will list matches.

In summary, scipy.special is invaluable when you need any advanced math function beyond basic trig, and scipy.constants is a convenient reference for physical science programming. Together, they save you from either writing approximations yourself or hardcoding values, ensuring accuracy and consistency.

SciPy vs other Python libraries: A quick recap

We've touched on how SciPy compares to some other libraries in context, but here's a quick summary of SciPy's role vs a few other major libraries:

NumPy: SciPy is built on NumPy and requires NumPy. NumPy provides array objects and basic routines; SciPy provides advanced routines. Use NumPy for low-level operations and SciPy when you need more functionality (optimization, signal processing, etc.).

Matplotlib: Use Matplotlib (or other plotting libraries) to visualize data; SciPy itself does not do plotting. SciPy will produce numeric results (analysis, processed signals, etc.) which you then plot with Matplotlib.

Pandas: Pandas is about data manipulation (tables, DataFrames, time series). It doesn't provide scientific algorithms like SciPy does. Often you might convert a Pandas DataFrame or Series to a NumPy array and feed it to SciPy functions (e.g., do a statistical test on a column of data). Pandas and SciPy complement each other: use Pandas for handling data (especially labeled data, missing values, grouping), use SciPy to perform technical computations on that data (like computing a test statistic or filtering a signal). There is some overlap in basic stats (mean, etc., which both can do), but SciPy is far more extensive in scientific domain computations.

scikit-learn: Sklearn is for machine learning and predictive modeling. It actually uses NumPy/SciPy under the hood for many algorithms. Use scikit-learn when you want to implement classification, regression, clustering algorithms at a high level. Use SciPy if you need to build something custom or use specific algorithms not in scikit-learn (or as building blocks inside an ML project). For example, scikit-learn doesn't have general non-linear solvers – if you want to solve a custom optimization as part of an ML pipeline, you might call SciPy's optimizer. SciPy also offers some basics like KDTree or distance functions which scikit-learn also has in parts; but largely, scikit-learn is for modeling, SciPy for general computing tasks.

Statsmodels: Statsmodels is for statistical modeling (regressions, ARIMA, etc.). It depends on SciPy's stats functions. If you need p-values, confidence intervals for complex models, use Statsmodels. For a quick stat test or distribution calc, SciPy.stats is usually enough.

SymPy: SymPy is for symbolic mathematics (algebraic manipulation, symbolic integration, etc.), whereas SciPy is numeric. If you need exact algebraic solutions, use SymPy; if you need numerical solutions to possibly unsolvable analytically problems, use SciPy.

scikit-image / OpenCV: For image processing beyond the basics, scikit-image (skimage) offers many algorithms (segmentation, feature detection, etc.) in Python; OpenCV is a powerful C++ library with Python bindings for computer vision. SciPy.ndimage covers basic image filtering and is very useful, but for complex image analysis, you'd go to those libraries. SciPy can still be part of the pipeline (you might use SciPy's fft or optimize in an image processing algorithm).

Others: There are many SciPy "family" libraries (like scikit-learn, scikit-image, etc.) which build on NumPy/SciPy. SciPy is a foundation – it's more low-level than those domain-specific libraries. Often you'll find SciPy's functions being used internally in these libraries.

Think of SciPy as the broad toolbox for scientists and engineers. If your project falls into a specific domain, there might be a more specialized library (like AstroPy for astronomy, for example) that uses SciPy internally but gives domain-specific convenience. However, learning SciPy gives you the fundamentals to understand and utilize those specialized libraries effectively.

Real-world uses of SciPy

SciPy's functionality is very broad, and accordingly it finds use in many fields. Here are a few examples of how SciPy is applied in real-world scenarios:

Engineering and physics simulations: Engineers use SciPy to model and simulate physical systems. For instance, in aerospace or mechanical engineering, SciPy's ODE solvers (solve_ivp) might be used to simulate the dynamics of a system (like solving the differential equations of motion for an aircraft or simulating orbital mechanics). SciPy's optimization can help in design optimization problems (finding parameters that optimize performance under constraints). The scientific Python ecosystem (SciPy included) has largely replaced proprietary tools like MATLAB for many R&D tasks. As an example, an aerospace engineer could use SciPy's integrate module to solve fluid dynamics equations approximately, or use SciPy's signal processing to analyze vibration data from sensors. SciPy's special functions are also critical in physics – e.g., Bessel functions in wave propagation problems, or Voigt profiles in spectroscopy.

Data science and analysis: While a lot of data science revolves around pandas and scikit-learn, SciPy underpins a lot of the analysis. If you have experimental data, you might use SciPy to interpolate missing values (using interpolate.interp1d or RBF), smooth noise (signal.medfilt or ndimage.gaussian_filter), or test hypotheses (stats.ttest_ind for AB testing, for example). SciPy's stats module provides many nonparametric tests that are useful in research. For statistical bootstrap or Monte Carlo simulations, SciPy offers tools to generate random variates from custom distributions and to compute results for each sample. Data scientists also use SciPy's optimize for fine-tuning model parameters or custom cost functions outside the scope of typical ML (for example, maximum likelihood estimation for custom distributions).

Finance and economics: Quantitative analysts ("quants") use SciPy for things like portfolio optimization (using optimize.minimize to optimize asset allocations for max return/min risk), or solving equations for option pricing models. SciPy's scipy.optimize can be used to calibrate models to market data by minimizing error between model and observed prices. The linear algebra solvers can handle large systems that come up in risk modeling. SciPy's statistics is used for analyzing financial time series (e.g., computing autocorrelations, distributions of returns, performing statistical tests for hypotheses like "are returns normally distributed?"). In finance, Wasserstein distance (stats.wasserstein_distance) might be used to compare probability distributions of returns. In an example, a hedge fund might use SciPy's optimization to maximize Sharpe ratio of a portfolio, which involves a quadratic programming problem solvable via optimize.minimize or optimize.linprog.

Machine learning and AI: SciPy is often employed in the preprocessing and research phases of machine learning projects. For instance, SciPy's sparse matrix support (scipy.sparse) is used by ML algorithms that need to handle sparse data (like text data in NLP for which scikit-learn returns sparse matrices of token counts). Before feeding data into a machine learning model, you might use SciPy to normalize signals, apply filters to time-series data, or augment data (e.g., using ndimage.rotate to slightly rotate images for augmentation). Additionally, SciPy's optimize is used in developing new machine learning algorithms – many custom loss functions are optimized via SciPy's solvers if one is prototyping a new model that isn't available in frameworks like scikit-learn. Researchers developing new ML techniques might use SciPy to handle certain math (for example, using SciPy's linear algebra for a custom neural network layer's calculations or SciPy's FFT to accelerate convolution). While deep learning frameworks (TensorFlow/PyTorch) have their own numerical routines, SciPy is still very much used in data preparation and analysis surrounding those models. Also, in reinforcement learning or other simulation-based AI, SciPy's ODE solvers or interpolators could be used to simulate environment dynamics or value functions. SciPy's signal processing can also be used in audio processing for AI (like computing spectrograms for an audio classifier). The bottom line: SciPy provides the scientific computation backbone that complements specialized AI libraries.

Biomedical and image analysis: In fields like bioinformatics or medical imaging, SciPy is a trusted tool. For example, analyzing MRI or microscope images – one might use scipy.ndimage to filter and enhance features, then scipy.ndimage.label to detect regions (like cells) and measure their properties. SciPy's ability to quickly perform convolutions and Fourier transforms is leveraged in image reconstruction (like CT scan reconstruction algorithms). In bioinformatics, if analyzing DNA sequences or other data, SciPy's stats might be used for tests, SciPy's clustering for grouping gene expression profiles, etc. The flexibility of SciPy means that researchers can quickly try out new analyses without needing to write low-level code. The SciPy conference is filled with examples of using SciPy in cutting-edge research, from neuroscience (processing neural signals – where SciPy.signal's filters come in handy) to astronomy (fitting models to telescope data using SciPy.optimize).

In essence, SciPy is used wherever a problem can be formulated in terms of mathematical computation with arrays. It's the workhorse that, often behind the scenes, powers algorithms in countless published research papers and industrial applications. Because it's open-source and Pythonic, it's heavily used in academia for teaching computational methods as well (instead of teaching in MATLAB).

SciPy continues to evolve, with the community adding improvements, and as of the latest versions, focusing on making everything faster (using newer NumPy features, using Numba in some cases, etc.) and more user-friendly. It remains one of the core libraries that anyone doing scientific or analytical work in Python should be familiar with.

Below are some frequently asked questions about SciPy, summarizing how to perform common tasks:

FAQs: SciPy frequently asked questions

Q1: How do I install SciPy in Python?
A: You can install SciPy via the Python package manager pip or through Anaconda. For pip, run pip install scipy in your terminal or command prompt. If you're using Anaconda or Miniconda, use conda install scipy. This will download and install the SciPy library into your current Python environment, allowing you to import and use it in your code.

Q2: How do I update SciPy to the latest version?
A: To update SciPy using pip, run pip install --upgrade scipy. This fetches the newest version from PyPI. If you're using Anaconda, you can update with conda update scipy (or conda install scipy=x.y.z for a specific version). Always make sure your NumPy is up-to-date as well, since SciPy depends on it and newer SciPy releases may require newer NumPy versions.

Q3: How do I uninstall SciPy from my environment?
A: With pip, you can uninstall using pip uninstall scipy. This will remove the SciPy package from the current Python environment. If you installed via Anaconda, you can use conda remove scipy. Make sure no other packages crucially depend on SciPy before removing it. After uninstalling, trying to import SciPy will result in an error until you reinstall it.

Q4: Why do I get a "No module named 'scipy'" error and how do I fix it?
A: The error No module named 'scipy' means Python cannot find the SciPy library. This usually happens if SciPy is not installed in the environment you're using or if you're using the wrong Python interpreter. To fix it, first install SciPy (pip install scipy or conda install scipy as appropriate). If it's already installed, ensure that your IDE or script is using the correct environment/interpreter where SciPy is installed (for example, in VS Code select the right conda environment or virtualenv). After installing and verifying the environment, import SciPy again and the error should resolve.

Q5: How do I import SciPy in a Python script or Jupyter Notebook?
A: To use SciPy, you simply import the parts you need. A common practice is to import subpackages directly. For example, you can do from scipy import stats, optimize to import the statistics and optimization modules. You can also import scipy as a whole, but you'll then refer to functions with the full namespace (e.g., scipy.stats.ttest_ind). Often, users import SciPy submodules with aliases, for instance: import scipy.signal as sig. There isn't a universally agreed short alias for SciPy (unlike NumPy's np), but importing specific submodules is the usual approach.

Q6: How can I perform a statistical t-test using SciPy?
A: SciPy's scipy.stats module provides functions for t-tests. For an independent two-sample t-test (comparing means of two independent groups), use stats.ttest_ind(sample1, sample2). For a paired t-test (before-and-after measurements on the same subjects), use stats.ttest_rel(before, after). And for a one-sample t-test (comparing a sample mean against a known value), use stats.ttest_1samp(sample, popmean). Each function returns the t-statistic and a p-value. For example:

from scipy import stats
t_stat, p_val = stats.ttest_ind(groupA, groupB)

This yields the test statistic and p-value to evaluate significance.

Q7: How can I compute a Fourier transform with SciPy?
A: Use the scipy.fft module. For a one-dimensional signal array x, you can compute the FFT with:

from scipy.fft import fft, fftfreq
X = fft(x)  # compute the FFT (complex result)
freqs = fftfreq(len(x), d=1.0/sample_rate)

Here, freqs will give you the frequency bins corresponding to the elements of X. If x is real-valued, the FFT output is symmetric; you typically look at X[:N//2] for the positive frequencies. SciPy's FFT is similar to NumPy's but often with performance tweaks. For inverse FFT, use ifft. SciPy can also do n-dimensional FFTs (fft2, fftn) and real-input FFTs (rfft). Always normalize or interpret the results based on your signal's length and sampling rate.

Q8: How can I integrate a function or solve an ODE using SciPy?
A: For integrating a one-dimensional function f(x) over an interval, use scipy.integrate.quad. Example:

from scipy import integrate
result, error = integrate.quad(f, a, b)

This returns the integral ∫ₐᵇ f(x)dx and an estimate of error. For double or triple integrals, use dblquad or tplquad.

To solve ordinary differential equations (ODEs), use scipy.integrate.solve_ivp. Define your derivative function and call:

sol = solve_ivp(fun, t_span=(t0, t1), y0=y_initial)

This returns an object with sol.t (time points) and sol.y (solution values). You can specify the method (RK45 by default, others available for stiff equations) and even events (to stop when a condition is met). The older odeint function is still available for basic use but solve_ivp is recommended for new code.

Q9: How do I optimize (minimize) a function with SciPy?
A: Use scipy.optimize.minimize for multivariate functions or minimize_scalar for single-variable functions. For example, to minimize f(x, y) starting from an initial guess:

from scipy.optimize import minimize

def f(vars):
    x, y = vars
    return ...  # compute function value

initial_guess = [x0, y0]
result = minimize(f, initial_guess)

The result object includes result.x (the found minimizer [x,y]) and result.fun (function value at minimum). You can specify methods like method='BFGS' or others, or provide derivatives via the jac parameter for faster convergence. For constrained optimization, you can use minimize with constraints or use dedicated functions like linprog for linear programming or least_squares for least-squares problems. Always check result.success and result.message to ensure the optimization converged.

Q10: How can I perform a simple linear regression with SciPy?
A: SciPy provides stats.linregress for quick linear regression (one independent variable). If you have arrays x and y for data:

from scipy.stats import linregress
res = linregress(x, y)

This returns a result tuple (or object in newer versions) with attributes: slope, intercept, rvalue (correlation coefficient), pvalue (two-sided p-value for non-zero slope), and stderr (standard error of the slope). You can then use res.slope and res.intercept to form the line equation y = slope × x + intercept. For multiple linear regression or more complex models, consider using statsmodels or sklearn, but for a quick trend line or correlation check, linregress is very convenient.

References

  1. SciPy - Wikipedia
    https://en.wikipedia.org/wiki/SciPy

  2. SciPy User Guide — SciPy v1.16.0 Manual
    https://docs.scipy.org/doc/scipy/tutorial/

  3. Getting Started with SciPy. In an era where data reigns supreme… | by Tom | Medium
    https://medium.com/@tomtalksit/getting-started-with-scipy-9a980a884838

  4. Difference between NumPy and SciPy in Python - GeeksforGeeks
    https://www.geeksforgeeks.org/python/difference-between-numpy-and-scipy-in-python/

  5. Linear Algebra (scipy.linalg) — SciPy v1.16.0 Manual
    https://docs.scipy.org/doc/scipy/tutorial/linalg.html

  6. Confusion between numpy, scipy, matplotlib and pylab - Stack Overflow
    https://stackoverflow.com/questions/12987624/confusion-between-numpy-scipy-matplotlib-and-pylab

  7. Spatial algorithms and data structures (scipy.spatial) — SciPy v1.16.0 Manual
    https://docs.scipy.org/doc/scipy/reference/spatial.html

  8. scipy vs sklearn : r/Python
    https://www.reddit.com/r/Python/comments/vu70rg/scipy_vs_sklearn/

  9. Python statistics package: difference between statsmodel and scipy.stats - Stack Overflow
    https://stackoverflow.com/questions/14573728/python-statistics-package-difference-between-statsmodel-and-scipy-stats

  10. News - SciPy
    https://scipy.org/news/

  11. Statistics (scipy.stats) — SciPy v1.16.0 Manual
    https://docs.scipy.org/doc/scipy/tutorial/stats.html

  12. Voronoi diagrams with SciPy - martinmcbride.org
    https://www.martinmcbride.org/post/2021/voronoi-diagrams-with-scipy/

Katerina Hynkova

Blog

Illustrative image for blog post

What is NumPy in Python?

By Katerina Hynkova

Updated on August 18, 2025

That’s it, time to try Deepnote

Get started – it’s free
Book a demo

Footer

Solutions

  • Notebook
  • Data apps
  • Machine learning
  • Data teams

Product

Company

Comparisons

Resources

Footer

  • Privacy
  • Terms

© 2025 Deepnote. All rights reserved.