Chapter 9: SciPy Matlab Arrays
SciPy Matlab Arrays
First things first: there is no special thing called “Matlab Arrays” inside SciPy as a distinct data type (like scipy.sparse or scipy.optimize has its own classes).
What people usually mean when they search for or talk about “SciPy Matlab Arrays” is:
How SciPy lets you read from / write to MATLAB’s .mat files so you can exchange NumPy arrays (and other Python data) with MATLAB / Octave users very easily.
The actual tools live in the submodule scipy.io (specifically the MATLAB I/O part: scipy.io.loadmat, scipy.io.savemat, scipy.io.whosmat).
This is the bridge between Python/NumPy/SciPy world and the classic MATLAB .mat file world.
Why is this useful? (real-teacher motivation)
- You collaborate with people who only use MATLAB
- You have legacy experimental data saved as .mat files from old instruments / simulations
- You want to prototype something fast in MATLAB but do heavy analysis / ML / parallelization in Python
- You publish code + data and want it usable in both ecosystems
SciPy makes this almost seamless — no need to export to CSV/text → lose precision / metadata / structs / cell arrays.
The two main functions you will use 95% of the time
- scipy.io.loadmat(file) → Read .mat file → get Python dictionary with NumPy arrays (and some structured types)
- scipy.io.savemat(file, mdict) → Save Python dictionary of variables → .mat file that MATLAB can open
Let’s do it step-by-step with real examples (2026 style — SciPy 1.17.x)
Always start like this:
|
0 1 2 3 4 5 6 7 |
import numpy as np from scipy import io # ← this is scipy.io |
Example 1 — Reading a .mat file (loadmat — most common starting point)
Suppose your colleague sent you experiment_data.mat containing:
- signal: 1×10000 double array
- fs: scalar sampling frequency
- metadata: struct with fields patient_id, date
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
data = io.loadmat('experiment_data.mat') # What do we get? print(type(data)) # <class 'dict'> print(data.keys()) # ['__header__', '__version__', '__globals__', 'signal', 'fs', 'metadata'] # The first three are MATLAB file metadata — you usually ignore them # Access variables — notice: no .mat extension needed in keys signal = data['signal'] # NumPy array, shape (1, 10000) or (10000,) after squeeze fs = data['fs'] # usually shape (1,1) → scalar meta = data['metadata'] # structured array or object array print(signal.shape) # e.g. (1, 10000) print(fs[0,0]) # 500.0 ← MATLAB scalars come as (1,1) arrays # Handy: squeeze unit dimensions signal = np.squeeze(signal) # now (10000,) |
Important options people often need (2026 best practice):
|
0 1 2 3 4 5 6 7 8 9 |
data = io.loadmat('file.mat', squeeze_me=True, # remove unnecessary (1,1) or (1,N) → scalar / 1D chars_as_strings=True, # char arrays become Python str / list of str struct_as_record=False) # structs as dict-like objects (more MATLAB-like) |
Example 2 — Saving Python data to .mat file (savemat)
You processed the data in Python and want to send it back to MATLAB colleague.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# Some results we computed processed = signal * 2.5 + np.random.randn(len(signal)) * 0.1 peaks = np.array([120, 450, 780, 1200]) # indices of peaks params = {'method': 'wavelet', 'level': 5, 'threshold': 0.3} # Put everything in a dictionary — keys become variable names in MATLAB mdict = { 'processed_signal': processed, 'peak_indices': peaks, 'processing_params': params, 'original_fs': fs } # Save — default is MATLAB v5/v7 format (very compatible) io.savemat('processed_results.mat', mdict) # Compressed + modern format (smaller file, still compatible with recent MATLAB) io.savemat('processed_results_compressed.mat', mdict, do_compression=True, format='7.3') # HDF5-based, good for large arrays |
In MATLAB your colleague can now simply do:
|
0 1 2 3 4 5 6 7 8 |
load('processed_results.mat') whos % → sees processed_signal, peak_indices, processing_params, original_fs |
Example 3 — Round-trip demo (save → load → compare)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# Create some representative data A = np.random.rand(5, 4) * 100 B = np.array([[1+2j, 3-4j], [5, 6]]) C = np.arange(10) struct_like = {'name': 'trial_01', 'value': 42.5, 'vector': np.linspace(0,1,20)} data_to_save = { 'matrix_A': A, 'complex_B': B, 'vector_C': C, 'trial_info': struct_like } # Save io.savemat('roundtrip_test.mat', data_to_save, do_compression=True) # Load back loaded = io.loadmat('roundtrip_test.mat', squeeze_me=True) # Check print(np.allclose(loaded['matrix_A'], A)) # True print(loaded['trial_info']['name']) # 'trial_01' (string) print(loaded['trial_info']['vector'].shape) # (20,) |
Quick cheat sheet — common gotchas & fixes
| Problem / Surprise | Reason | Fix / Best practice |
|---|---|---|
| Scalars come as (1,1) arrays | MATLAB stores almost everything as array | Use squeeze_me=True or np.squeeze(var) |
| Structs look weird | Loaded as structured arrays or object dtype | struct_as_record=False → more dict-like |
| Cell arrays become object arrays | MATLAB cells = heterogeneous containers | Access with [0] indexing, convert manually |
| File too big / slow | Default no compression | do_compression=True, format=’7.3′ |
| v7.3 (HDF5) files not loading | Need h5py library | pip install h5py (usually already there) |
| Char arrays are byte arrays | Old MATLAB behavior | chars_as_strings=True |
Summary — when to think “SciPy Matlab Arrays”
→ Whenever you see .mat files or need to talk to MATLAB users → The real name is MAT-file I/O in scipy.io → Core functions: loadmat (read), savemat (write), whosmat (just list variables without loading)
Official docs (still excellent in 2026): https://docs.scipy.org/doc/scipy/reference/generated/scipy.io.loadmat.html https://docs.scipy.org/doc/scipy/reference/generated/scipy.io.savemat.html https://docs.scipy.org/doc/scipy/tutorial/io.html#matlab-files
Got some .mat files you’re working with? Or want to see how cell arrays / structs / sparse matrices travel between Python ↔ MATLAB? Tell me and we’ll do a more specific round-trip example together! 🚀
