Skip to content

SciPy - Basic Functionality

SciPy - Basic Functionality and NumPy Interoperability

Section titled “SciPy - Basic Functionality and NumPy Interoperability”

SciPy is fundamentally built upon NumPy. All NumPy functions are available through the SciPy namespace (e.g., scipy.array is numpy.array). Therefore, importing SciPy typically makes explicit importing of NumPy unnecessary for array operations, though it’s common practice to import NumPy as np for clarity.

The core data structure in NumPy, and thus extensively used in SciPy, is the ndarray (N-dimensional array). It’s a table of elements, usually numbers, all of the same data type, indexed by a tuple of non-negative integers. In NumPy, dimensions are called axes, and the number of axes is the rank of the array.

Understanding NumPy array creation and manipulation is crucial before diving deep into SciPy’s modules, as most SciPy functions expect ndarray objects as input and often return them as output.

Arrays, which can be vectors (1D arrays) or matrices (2D arrays), can be created in several ways.

The most straightforward way to create an array is from a Python list or tuple using numpy.array():

import numpy as np
# Creating a 1D array (vector)
python_list = [1, 2, 3, 4, 5]
arr_1d = np.array(python_list)
print("1D Array (Vector):")
print(arr_1d)
# Creating a 2D array (matrix)
python_nested_list = [[1, 2], [3, 4], [5, 6]]
arr_2d = np.array(python_nested_list)
print("\n2D Array (Matrix):")
print(arr_2d)

Output:

1D Array (Vector):
[1 2 3 4 5]
2D Array (Matrix):
[[1 2]
[3 4]
[5 6]]

NumPy provides several functions to create arrays from scratch with predefined content.

The zeros(shape, dtype=float) function creates an array filled with zeros, and ones(shape, dtype=float) creates an array filled with ones. The shape is a tuple specifying the dimensions.

import numpy as np
zeros_arr = np.zeros((2, 3)) # A 2x3 array of zeros
print("Zeros Array (default dtype float64):")
print(zeros_arr)
ones_arr = np.ones((3, 2), dtype=int) # A 3x2 array of ones with integer type
print("\nOnes Array (dtype int):")
print(ones_arr)

Output:

Zeros Array (default dtype float64):
[[0. 0. 0.]
[0. 0. 0.]]
Ones Array (dtype int):
[[1 1]
[1 1]
[1 1]]

The arange([start,] stop[, step,], dtype=None) function creates arrays with regularly incrementing values, similar to Python’s range() but returns an ndarray.

import numpy as np
arr_seq = np.arange(7) # 0 to 6
print("Sequence from 0 to 6:")
print(arr_seq)
arr_step = np.arange(2, 10, 2) # Start at 2, end before 10, step by 2
print("\nSequence with a step:")
print(arr_step)

Output:

Sequence from 0 to 6:
[0 1 2 3 4 5 6]
Sequence with a step:
[2 4 6 8]

You can explicitly define the data type of array elements using the dtype argument. Common types include int, float, np.float32, np.int64, etc. Using float is equivalent to np.float64 (double precision) by default in many NumPy contexts.

import numpy as np
arr_float = np.arange(5, dtype=float) # Default float is usually np.float64
print("Array with float dtype:")
print(arr_float)
print(f"Data Type: {arr_float.dtype}")
arr_int32 = np.array([10, 20, 30], dtype=np.int32)
print("\nArray with int32 dtype:")
print(arr_int32)
print(f"Data Type: {arr_int32.dtype}")

Output:

Array with float dtype:
[0. 1. 2. 3. 4.]
Data Type: float64
Array with int32 dtype:
[10 20 30]
Data Type: int32

The linspace(start, stop, num=50, endpoint=True, dtype=None) function creates arrays with a specified number (num) of elements, evenly spaced over a specified interval [start, stop].

import numpy as np
# Create 6 evenly spaced numbers between 1.0 and 4.0 (inclusive)
arr_spaced = np.linspace(1.0, 4.0, num=6)
print("Evenly spaced numbers:")
print(arr_spaced)

Output:

Evenly spaced numbers:
[1. 1.6 2.2 2.8 3.4 4. ]

NumPy also has a matrix class (np.matrix). While np.matrix objects are strictly 2-dimensional and have convenient operator overloading for matrix multiplication (*) and power (**), the general recommendation for new code is to use np.ndarray for all array operations. ndarrays can be N-dimensional, and matrix multiplication is performed using the @ operator or np.matmul() function.

Example using np.matrix (less common in modern code):

import numpy as np
mat = np.matrix('1 2; 3 4') # Create a matrix from a string
print("NumPy Matrix:")
print(mat)
# Matrix multiplication with *
mat_squared = mat * mat
print("\nMatrix squared (using *):")
print(mat_squared)

Output:

NumPy Matrix:
[[1 2]
[3 4]]
Matrix squared (using *):
[[ 7 10]
[15 22]]

Equivalent using np.ndarray (preferred):

import numpy as np
arr = np.array([[1, 2], [3, 4]])
print("NumPy ndarray:")
print(arr)
# Matrix multiplication with @
arr_squared = arr @ arr
print("\nArray squared (using @):")
print(arr_squared)

Output:

NumPy ndarray:
[[1 2]
[3 4]]
Array squared (using @):
[[ 7 10]
[15 22]]

For an ndarray (or np.matrix):

  • .T attribute: Returns the transpose of the array.
  • .conj().T or .H (for np.matrix): Returns the conjugate (Hermitian) transpose. For real arrays, transpose and conjugate transpose are identical.
import numpy as np
# Real array
real_arr = np.array([[1, 2], [3, 4]])
print("Original Real Array:")
print(real_arr)
print("\nTranspose of Real Array (.T):")
print(real_arr.T)
# Complex array
complex_arr = np.array([[1+1j, 2+2j], [3+3j, 4+4j]])
print("\nOriginal Complex Array:")
print(complex_arr)
print("\nTranspose of Complex Array (.T):")
print(complex_arr.T)
print("\nConjugate Transpose of Complex Array (.conj().T):")
print(complex_arr.conj().T)

Output:

Original Real Array:
[[1 2]
[3 4]]
Transpose of Real Array (.T):
[[1 3]
[2 4]]
Original Complex Array:
[[1.+1.j 2.+2.j]
[3.+3.j 4.+4.j]]
Transpose of Complex Array (.T):
[[1.+1.j 3.+3.j]
[2.+2.j 4.+4.j]]
Conjugate Transpose of Complex Array (.conj().T):
[[1.-1.j 3.-3.j]
[2.-2.j 4.-4.j]]

SciPy’s modules extensively use these NumPy array features. A solid grasp of NumPy is therefore the first step to effectively using SciPy for scientific and technical computing.