Actually, pandas
is not required here, outer product from numpy
will suffice and should be faster to compute the weighted (unbiased) sample covariance matrix, using just couple of lines of code:
mu = np.sum(weights.reshape(-1,1)*X, axis=0) / np.sum(weights)
Sigma2 = (weights.reshape(-1,1)*(X-mu)).T@(X-mu) / (weights.sum()-1)
Here $X$ is the data matrix (numpy ndarray
) and weights
is the vector of weights, one for each observation (row) in $X$.
We can test from the next code snippet that it produces the same result and is faster (at least with a small synthetic data):
with numpy
only:
from time import time
import numpy as np
# assuming that the same data is stored as matrix
np.random.seed(1)
X1 = np.random.randint(0,100,size=(100, 4))
weights = np.random.randint(0,100,size=100)
start = time()
mu = np.sum(weights.reshape(-1,1)*X1, axis=0) / np.sum(weights)
Sigma2 = (weights.reshape(-1,1)*(X1-mu)).T@(X1-mu) / (weights.sum()-1)
print('time taken', time() - start)
Sigma2
# time taken 0.00099945068359375
# array([[ 890.94399402, -85.64453306, 115.59965823, 27.03209622],
# [ -85.64453306, 930.63865593, -219.27393837, -147.81191515],
# [ 115.59965823, -219.27393837, 752.47415657, 92.31686826],
# [ 27.03209622, -147.81191515, 92.31686826, 745.7993326 ]])
with pandas
and numpy
:
import pandas as pd
# assuming that the data is stored as dataframe
X = pd.DataFrame(X1, columns=list('ABCD'))
weights = np.random.randint(0,100,size=100)
start = time()
mean = np.ma.average(X, axis=0, weights=weights) # Computing the weighted sample mean (fast, efficient and precise)
mean = pd.Series(mean, index=list(X.keys())) # Convert to a Pandas' Series (it's just aesthetic and more ergonomic, no differenc in computed values)
xm = X-mean # xm = X diff to mean
xm = xm.fillna(0)
sigma2 = 1./(weights.sum()-1) * xm.mul(weights, axis=0).T.dot(xm);
print('time taken', time() - start)
sigma2
# time taken 0.0059969425201416016
# A B C D
# A 890.943994 -85.644533 115.599658 27.032096
# B -85.644533 930.638656 -219.273938 -147.811915
# C 115.599658 -219.273938 752.474157 92.316868
# D 27.032096 -147.811915 92.316868 745.799333