MATRIX_NORMAL

Overview

The MATRIX_NORMAL function computes the probability density function (PDF), log-PDF, or draws random samples from a matrix normal distribution (also known as the matrix Gaussian distribution). This distribution is a generalization of the multivariate normal distribution to matrix-valued random variables, making it useful for modeling data that naturally takes the form of matrices, such as repeated multivariate measurements or spatiotemporal data.

The function uses SciPy’s scipy.stats.matrix_normal implementation. A random matrix X of dimensions n \times p follows the matrix normal distribution \mathcal{MN}_{n,p}(M, U, V) with mean matrix M, among-row covariance matrix U (of size n \times n), and among-column covariance matrix V (of size p \times p).

The probability density function is defined as:

p(X \mid M, U, V) = \frac{\exp\left(-\frac{1}{2}\text{tr}\left[V^{-1}(X - M)^T U^{-1}(X - M)\right]\right)}{(2\pi)^{np/2} |V|^{n/2} |U|^{p/2}}

where \text{tr} denotes the matrix trace. The matrix normal distribution is equivalent to the multivariate normal distribution with the relationship \text{vec}(X) \sim \mathcal{N}_{np}(\text{vec}(M), V \otimes U), where \otimes denotes the Kronecker product and \text{vec} is the vectorization operator.

The separate row and column covariance matrices capture dependencies in two dimensions: U models correlations among the rows of X, while V models correlations among the columns. This separable covariance structure reduces the number of parameters compared to a full multivariate normal covariance matrix from (np)^2 to n^2 + p^2.

For further theoretical background, see the Matrix normal distribution article on Wikipedia. The SciPy implementation documentation is available in the SciPy reference.

This example function is provided as-is without any representation of accuracy.

Excel Usage

=MATRIX_NORMAL(x, mean, rowcov, colcov, mn_method, size, seed)
  • x (list[list], optional, default: null): Matrix at which to evaluate the function or template for sample shape.
  • mean (list[list], optional, default: null): Mean matrix of the distribution. Must match shape of x. Default is zero matrix.
  • rowcov (list[list], optional, default: null): Among-row covariance matrix. Must be square with size equal to number of rows in x. Default is identity matrix.
  • colcov (list[list], optional, default: null): Among-column covariance matrix. Must be square with size equal to number of columns in x. Default is identity matrix.
  • mn_method (str, optional, default: “pdf”): Method to compute. Valid options are pdf, logpdf, or rvs.
  • size (int, optional, default: 1): Number of samples to draw if method is rvs.
  • seed (int, optional, default: null): Random seed for reproducibility.

Returns (list[list]): 2D list of results, or error message string.

Example 1: PDF with identity covariances

Inputs:

x mean rowcov colcov mn_method
1 2 0 0 1 0 1 0 pdf
3 4 0 0 0 1 0 1

Excel formula:

=MATRIX_NORMAL({1,2;3,4}, {0,0;0,0}, {1,0;0,1}, {1,0;0,1}, "pdf")

Expected output:

7.7486e-9

Example 2: LogPDF with identity covariances

Inputs:

x mean rowcov colcov mn_method
1 2 0 0 1 0 1 0 logpdf
3 4 0 0 0 1 0 1

Excel formula:

=MATRIX_NORMAL({1,2;3,4}, {0,0;0,0}, {1,0;0,1}, {1,0;0,1}, "logpdf")

Expected output:

-18.6758

Example 3: PDF with default mean (zero matrix)

Inputs:

x rowcov colcov mn_method
0 0 1 0 1 0 pdf
0 0 0 1 0 1

Excel formula:

=MATRIX_NORMAL({0,0;0,0}, {1,0;0,1}, {1,0;0,1}, "pdf")

Expected output:

0.0253303

Example 4: LogPDF with scaled covariance matrices

Inputs:

x mean rowcov colcov mn_method
0.5 0.5 0 0 2 0 2 0 logpdf
0.5 0.5 0 0 0 2 0 2

Excel formula:

=MATRIX_NORMAL({0.5,0.5;0.5,0.5}, {0,0;0,0}, {2,0;0,2}, {2,0;0,2}, "logpdf")

Expected output:

-6.57334

Python Code

Show Code
from scipy.stats import matrix_normal as scipy_matrix_normal

def matrix_normal(x=None, mean=None, rowcov=None, colcov=None, mn_method='pdf', size=1, seed=None):
    """
    Computes the PDF, log-PDF, or draws random samples from a matrix normal distribution.

    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.matrix_normal.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        x (list[list], optional): Matrix at which to evaluate the function or template for sample shape. Default is None.
        mean (list[list], optional): Mean matrix of the distribution. Must match shape of x. Default is zero matrix. Default is None.
        rowcov (list[list], optional): Among-row covariance matrix. Must be square with size equal to number of rows in x. Default is identity matrix. Default is None.
        colcov (list[list], optional): Among-column covariance matrix. Must be square with size equal to number of columns in x. Default is identity matrix. Default is None.
        mn_method (str, optional): Method to compute. Valid options are pdf, logpdf, or rvs. Valid options: PDF, LogPDF, RVS. Default is 'pdf'.
        size (int, optional): Number of samples to draw if method is rvs. Default is 1.
        seed (int, optional): Random seed for reproducibility. Default is None.

    Returns:
        list[list]: 2D list of results, or error message string.
    """
    def to2d(val):
        return [[val]] if not isinstance(val, list) else val

    try:
        x = to2d(x)
        mean = to2d(mean) if mean is not None else None
        rowcov = to2d(rowcov) if rowcov is not None else None
        colcov = to2d(colcov) if colcov is not None else None

        if not isinstance(x, list) or len(x) < 1 or not all(
            isinstance(row, list) and len(row) > 0 for row in x
        ):
            return "Error: Invalid input: x must be a 2D list with at least one row."
        try:
            x_mat = [[float(val) for val in row] for row in x]
        except Exception:
            return "Error: Invalid input: x must contain numeric values."

        n_rows = len(x_mat)
        n_cols = len(x_mat[0])

        if mean is not None:
            if not isinstance(mean, list) or len(mean) != n_rows or not all(
                isinstance(row, list) and len(row) == n_cols for row in mean
            ):
                return "Error: Invalid input: mean must be a 2D list with same shape as x."
            try:
                mean_mat = [[float(val) for val in row] for row in mean]
            except Exception:
                return "Error: Invalid input: mean must contain numeric values."
        else:
            mean_mat = [[0.0 for _ in range(n_cols)] for _ in range(n_rows)]

        if rowcov is not None:
            if not isinstance(rowcov, list) or len(rowcov) != n_rows or not all(
                isinstance(row, list) and len(row) == n_rows for row in rowcov
            ):
                return "Error: Invalid input: rowcov must be a square 2D list with shape (n_rows, n_rows)."
            try:
                rowcov_mat = [[float(val) for val in row] for row in rowcov]
            except Exception:
                return "Error: Invalid input: rowcov must contain numeric values."
        else:
            rowcov_mat = [[float(i == j) for j in range(n_rows)] for i in range(n_rows)]

        if colcov is not None:
            if not isinstance(colcov, list) or len(colcov) != n_cols or not all(
                isinstance(row, list) and len(row) == n_cols for row in colcov
            ):
                return "Error: Invalid input: colcov must be a square 2D list with shape (n_cols, n_cols)."
            try:
                colcov_mat = [[float(val) for val in row] for row in colcov]
            except Exception:
                return "Error: Invalid input: colcov must contain numeric values."
        else:
            colcov_mat = [[float(i == j) for j in range(n_cols)] for i in range(n_cols)]

        valid_methods = {"pdf", "logpdf", "rvs"}
        if mn_method not in valid_methods:
            return f"Error: Invalid input: mn_method must be one of {sorted(valid_methods)}."

        try:
            dist = scipy_matrix_normal(mean=mean_mat, rowcov=rowcov_mat, colcov=colcov_mat)
        except Exception as e:
            return f"Error: scipy.stats.matrix_normal error: {e}"

        if mn_method in ("pdf", "logpdf"):
            try:
                val = dist.pdf(x_mat) if mn_method == "pdf" else dist.logpdf(x_mat)
                return [[float(val)]]
            except Exception as e:
                return f"Error: scipy.stats.matrix_normal {mn_method} error: {e}"

        if mn_method == "rvs":
            if size is not None:
                try:
                    size_int = int(size)
                except Exception:
                    return "Error: Invalid input: size must be an integer."
                if size_int < 1:
                    return "Error: Invalid input: size must be >= 1."
            else:
                size_int = 1
            try:
                rng_seed = int(seed) if seed is not None else None
                samples = dist.rvs(size=size_int, random_state=rng_seed)
                if size_int == 1:
                    return [[float(val) for val in row] for row in samples]
                return [[float(val) for row in sample for val in row] for sample in samples]
            except Exception as e:
                return f"Error: scipy.stats.matrix_normal rvs error: {e}"

        return "Error: Unknown error."
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

Matrix at which to evaluate the function or template for sample shape.
Mean matrix of the distribution. Must match shape of x. Default is zero matrix.
Among-row covariance matrix. Must be square with size equal to number of rows in x. Default is identity matrix.
Among-column covariance matrix. Must be square with size equal to number of columns in x. Default is identity matrix.
Method to compute. Valid options are pdf, logpdf, or rvs.
Number of samples to draw if method is rvs.
Random seed for reproducibility.