Source code for bekk.param_spatial

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Spatial parameter class
-----------------------

"""
from __future__ import print_function, division

import itertools

import numpy as np
import scipy.linalg as scl
from scipy.optimize import minimize

from .param_generic import ParamGeneric

__all__ = ['ParamSpatial']


[docs]class ParamSpatial(ParamGeneric): """Class to hold parameters of the BEKK model. Attributes ---------- amat, bmat, cmat, avecs, bvecs, dvecs Matrix representations of BEKK parameters groups List of related items weights Spatial relation matrices Methods ------- from_theta Initialize from theta vector get_theta Convert parameter matrices to 1-dimensional array get_weight Generate weighting matrices given groups """ def __init__(self, nstocks=2): """Class constructor. Parameters ---------- nstocks : int Number os stocks in the model """ super(ParamSpatial, self).__init__(nstocks) self.avecs = np.vstack((np.diag(self.amat), np.zeros(nstocks))) self.bvecs = np.vstack((np.diag(self.bmat), np.zeros(nstocks))) self.dvecs = np.vstack((np.diag(self.cmat), np.zeros(nstocks))) self.weights = np.zeros((1, nstocks, nstocks)) @staticmethod def get_model(): """Return model name. """ return 'spatial' @classmethod def from_groups(cls, groups=None, target=None, abstart=(.1, .5)): """Initialize from groups. Parameters ---------- groups : list of lists of tuples Encoded groups of items target : (nstocks, nstocks) array Unconditional variance matrix Returns ------- param : BEKKParams instance BEKK parameters """ weights = cls.get_weight(groups=groups) ncat, nstocks = weights.shape[:2] avecs = np.vstack([np.ones((1, nstocks)) * abstart[0], np.zeros((ncat, nstocks))]) bvecs = np.vstack([np.ones((1, nstocks)) * abstart[1], np.zeros((ncat, nstocks))]) dvecs = np.zeros((1 + ncat, nstocks)) if target is None: dvecs[0] = np.ones(nstocks) else: dvecs[0] = np.diag(target) return cls.from_abdv(avecs=avecs, bvecs=bvecs, dvecs=dvecs, groups=groups) @classmethod def from_abdv(cls, avecs=None, bvecs=None, dvecs=None, groups=None): """Initialize from spatial representation. Parameters ---------- avecs : (ncat+1, nstocks) array Parameter matrix bvecs : (ncat+1, nstocks) array Parameter matrix dvecs : (ncat+1, nstocks) array Parameter matrix groups : list of lists of tuples Encoded groups of items Returns ------- param : BEKKParams instance BEKK parameters """ weights = cls.get_weight(groups=groups) amat, bmat, dmat = cls.from_vecs_to_mat(avecs=avecs, bvecs=bvecs, dvecs=dvecs, weights=weights) try: dmat_inv = scl.inv(dmat) except scl.LinAlgError: dmat_inv = np.eye(dmat.shape[0]) ccmat = dmat_inv.dot(np.diag(dvecs[0]**2)).dot(dmat_inv.T) try: cmat = scl.cholesky(ccmat, 1) except scl.LinAlgError: # warnings.warn('Matrix C is singular!') cmat = np.diag(np.diag(ccmat)**.5) param = cls.from_abc(amat=amat, bmat=bmat, cmat=cmat) param.avecs = avecs param.bvecs = bvecs param.dvecs = dvecs param.weights = weights param.groups = groups return param @classmethod def from_abcmat(cls, avecs=None, bvecs=None, cmat=None, groups=None): """Initialize from spatial representation. Parameters ---------- avecs : (ncat+1, nstocks) array Parameter matrix bvecs : (ncat+1, nstocks) array Parameter matrix cmat : (nstocks, nstocks) array Lower triangular matrix for teh intercept CC' groups : list of lists of tuples Encoded groups of items Returns ------- param : BEKKParams instance BEKK parameters """ weights = cls.get_weight(groups=groups) amat, bmat, dmat = cls.from_vecs_to_mat(avecs=avecs, bvecs=bvecs, weights=weights) param = cls.from_abc(amat=amat, bmat=bmat, cmat=cmat) param.avecs = avecs param.bvecs = bvecs param.dvecs = None param.weights = weights param.groups = groups return param @classmethod def from_abt(cls, avecs=None, bvecs=None, target=None, groups=None, restriction=None, solve_dvecs=False): """Initialize from spatial representation. Parameters ---------- avecs : (ncat+1, nstocks) array Parameter matrix bvecs : (ncat+1, nstocks) array Parameter matrix target : (nstocks, nstocks) array Variance target matrix groups : list of lists of tuples Encoded groups of items Returns ------- param : BEKKParams instance BEKK parameters """ weights = cls.get_weight(groups=groups) amat, bmat, dmat = cls.from_vecs_to_mat(avecs=avecs, bvecs=bvecs, weights=weights) ccmat = cls.find_ccmat(amat=amat, bmat=bmat, target=target) cmat = cls.find_cmat(amat=amat, bmat=bmat, ccmat=ccmat) param = cls.from_abc(amat=amat, bmat=bmat, cmat=cmat) param.avecs = avecs param.bvecs = bvecs param.dvecs = None if solve_dvecs: param.dvecs = ParamSpatial.find_approx_target(weights, groups, ccmat, restriction) param.weights = weights param.groups = groups return param @staticmethod def cmat_approx(theta, weights, groups): """Approximation of CC' using spatial representation. Parameters ---------- theta : array Parameters stacked together weights : (ncat, nstocks, nstocks) array Weight matrices groups : list of lists of tuples Encoded groups of items Returns ------- (nstocks, nstocks) array """ ncat, nstocks = weights.shape[:2] dvecs = ParamSpatial.vecs_from_theta(theta, groups)[0] dmat = np.eye(nstocks) for i in range(ncat): dmat -= np.diag(dvecs[i + 1]).dot(weights[i]) return scl.solve(dmat, np.diag(np.abs(dvecs[0])**.5)) @staticmethod def target_deviation(theta, weights, groups, target): """Deviation from target matrix. Parameters ---------- theta : array Parameters stacked together weights : (ncat, nstocks, nstocks) array Weight matrices groups : list of lists of tuples Encoded groups of items Returns ------- float """ out = ParamSpatial.cmat_approx(theta, weights, groups) return scl.norm(target - out.dot(out.T)) @staticmethod def find_approx_target(weights, groups, target, restriction): """Find target approximation. """ ncat, nstocks = weights.shape[:2] args = (weights, groups, target) if restriction == 'hetero': theta = np.arange((ncat + 1) * nstocks) elif restriction == 'ghomo': theta = np.arange(nstocks + sum([len(group) for group in groups])) elif restriction in ('homo', 'shomo'): theta = np.arange(ncat + nstocks + 1) else: raise NotImplementedError('Restriction is not implemented!') out = minimize(ParamSpatial.target_deviation, theta, args=args) return ParamSpatial.vecs_from_theta(out.x, groups)[0] @staticmethod def from_vecs_to_mat(avecs=None, bvecs=None, dvecs=None, weights=None): """Initialize amat, bmat, and dmat from spatial representation. Parameters ---------- avecs : (ncat+1, nstocks) array Parameter matrix bvecs : (ncat+1, nstocks) array Parameter matrix dvecs : (ncat+1, nstocks) array Parameter matrix weights : (ncat, nstocks, nstocks) array Weight matrices Returns ------- amat, bmat, dmat : (nstocks, nstocks) arrays BEKK parameter matrices """ ncat, nstocks = weights.shape[:2] amat, bmat = np.diag(avecs[0]), np.diag(bvecs[0]) dmat = np.eye(nstocks) for i in range(ncat): amat += np.diag(avecs[i+1]).dot(weights[i]) bmat += np.diag(bvecs[i+1]).dot(weights[i]) if dvecs is not None: dmat -= np.diag(dvecs[i+1]).dot(weights[i]) return amat, bmat, dmat @staticmethod def vecs_from_theta(theta=None, groups=None): """Convert theta to vecs. Parameters ---------- theta : 1d array Parameter vector groups : list of lists of tuples Encoded groups of items Returns ------- vecs : (ncat+1, nstocks) array Spatial representation of parameters """ weights = ParamSpatial.get_weight(groups) ncat, nstocks = weights.shape[:2] vecs = np.zeros((ncat+1, nstocks)) vecs[0, :] = theta[:nstocks] j = nstocks for cat in range(ncat): for group in groups[cat]: for item in group: vecs[cat+1, item] = theta[j] j += 1 return vecs, theta[j:] def theta_from_vecs(self, vecs=None): """Convert vecs to theta. Parameters ---------- vecs : (ncat+1, nstocks) array Spatial representation of parameters Returns ------- theta : 1d array Parameter vector """ ncat, nstocks = self.weights.shape[:2] theta = [vecs[0, :]] for cat in range(ncat): for group in self.groups[cat]: theta.append([vecs[cat+1, group[0]]]) return np.concatenate(theta) @staticmethod def ab_from_theta(theta=None, restriction='shomo', groups=None): """Initialize A and B spatial from theta vector. Parameters ---------- theta : 1d array Parameter vector groups : list of lists of tuples Encoded groups of items restriction : str Can be - 'hetero' (heterogeneous) - 'ghomo' (group homogeneous) - 'homo' (homogeneous) - 'shomo' (scalar homogeneous) Returns ------- avecs : (ncat+1, nstocks) array Parameter matrix bvecs : (ncat+1, nstocks) array Parameter matrix theta : 1d array Parameter vector. What's left after cutting off avecs and bvecs. """ weights = ParamSpatial.get_weight(groups) ncat, nstocks = weights.shape[:2] if restriction == 'hetero': abvecs_size = (ncat+1)*nstocks avecs = theta[:abvecs_size].reshape((ncat+1, nstocks)) theta = theta[abvecs_size:] bvecs = theta[:abvecs_size].reshape((ncat+1, nstocks)) theta = theta[abvecs_size:] elif restriction == 'ghomo': avecs, theta = ParamSpatial.vecs_from_theta(theta, groups) bvecs, theta = ParamSpatial.vecs_from_theta(theta, groups) elif restriction == 'homo': avecs = np.zeros((ncat+1, nstocks)) bvecs = np.zeros((ncat+1, nstocks)) avecs[0] = theta[:nstocks] theta = theta[nstocks:] avecs[1:] = np.tile(theta[:ncat, np.newaxis], nstocks) theta = theta[ncat:] bvecs[0] = theta[:nstocks] theta = theta[nstocks:] bvecs[1:] = np.tile(theta[:ncat, np.newaxis], nstocks) theta = theta[ncat:] elif restriction == 'shomo': avecs = np.tile(theta[:ncat+1, np.newaxis], nstocks) theta = theta[ncat+1:] bvecs = np.tile(theta[:ncat+1, np.newaxis], nstocks) theta = theta[ncat+1:] else: raise NotImplementedError('Restriction is not implemented!') return avecs, bvecs, theta @staticmethod def d_from_theta(theta=None, restriction='shomo', groups=None): """Initialize D and V spatial from theta vector. Parameters ---------- theta : 1d array Parameter vector groups : list of lists of tuples Encoded groups of items restriction : str Can be - 'hetero' (heterogeneous) - 'ghomo' (group homogeneous) - 'homo' (homogeneous) - 'shomo' (scalar homogeneous) Returns ------- dvecs : (ncat+1, nstocks) array Parameter matrix """ weights = ParamSpatial.get_weight(groups) ncat, nstocks = weights.shape[:2] if restriction == 'hetero': dvecs_size = (ncat+1)*nstocks dvecs = theta[:dvecs_size] dvecs = dvecs.reshape((ncat+1, nstocks)) elif restriction == 'ghomo': dvecs = ParamSpatial.vecs_from_theta(theta, groups)[0] elif restriction in ('homo', 'shomo'): dvecs = np.zeros((ncat+1, nstocks)) dvecs[0] = theta[:nstocks] theta = theta[nstocks:] dvecs[1:] = np.tile(theta[:ncat, np.newaxis], nstocks) else: raise NotImplementedError('Restriction is not implemented!') return dvecs
[docs] @classmethod def from_theta(cls, theta=None, groups=None, cfree=False, restriction='shomo', target=None, solve_dvecs=False): """Initialize from theta vector. Parameters ---------- theta : 1d array Length depends on the model restrictions and variance targeting weights : (ncat, nstocks, nstocks) array Weight matrices groups : list of lists of tuples Encoded groups of items cfree : bool Whether to leave C matrix free (True) or not (False) target : (nstocks, nstocks) array Variance target matrix restriction : str Can be - 'hetero' (heterogeneous) - 'ghomo' (group homogeneous) - 'homo' (homogeneous) - 'shomo' (scalar homogeneous) Returns ------- param : BEKKParams instance BEKK parameters """ weights = cls.get_weight(groups) ncat, nstocks = weights.shape[:2] avecs, bvecs, theta = cls.ab_from_theta(theta=theta, restriction=restriction, groups=groups) if (target is None) and (not cfree): cmat = None dvecs = cls.d_from_theta(theta=theta, groups=groups, restriction=restriction) return cls.from_abdv(avecs=avecs, bvecs=bvecs, dvecs=dvecs, groups=groups) elif (target is None) and cfree: dvecs = None cmat = np.zeros((nstocks, nstocks)) cmat[np.tril_indices(nstocks)] = theta return cls.from_abcmat(avecs=avecs, bvecs=bvecs, cmat=cmat, groups=groups) else: return cls.from_abt(avecs=avecs, bvecs=bvecs, target=target, groups=groups, restriction=restriction, solve_dvecs=solve_dvecs)
def get_theta_from_ab(self, restriction='shomo'): """Convert parameter matrices A and B to 1-dimensional array. Parameters ---------- restriction : str Can be - 'hetero' (heterogeneous) - 'ghomo' (group homogeneous) - 'homo' (homogeneous) - 'shomo' (scalar homogeneous) Returns ------- theta : 1d array Length depends on the model restrictions and variance targeting If use_target is True: - 'hetero' - 2*n*(m+1) - 'ghomo' - 2*(n+m*k) - 'homo' - 2*(n+m) - 'shomo' - 2*(m+1) If use_target is False and cfree is False: - 'hetero' - +n*(m+1) - 'ghomo' - +(n+m*k) - 'homo' - +(n+m) - 'shomo' - +(n+m) If use_target is False and cfree is True: - +n*(n+1)/2 """ if restriction == 'hetero': theta = [self.avecs.flatten(), self.bvecs.flatten()] elif restriction == 'ghomo': theta = [self.theta_from_vecs(self.avecs), self.theta_from_vecs(self.bvecs)] elif restriction == 'homo': theta = [self.avecs[0], self.avecs[1:, 0], self.bvecs[0], self.bvecs[1:, 0]] elif restriction == 'shomo': theta = [self.avecs[:, 0].flatten(), self.bvecs[:, 0].flatten()] else: raise NotImplementedError('Restriction is not implemented!') return np.concatenate(theta)
[docs] def get_theta(self, restriction='shomo', use_target=False, cfree=False): """Convert parameter matrices to 1-dimensional array. Parameters ---------- restriction : str Can be - 'hetero' (heterogeneous) - 'ghomo' (group homogeneous) - 'homo' (homogeneous) - 'shomo' (scalar homogeneous) use_target : bool Whether to estimate only A and B (True) or C as well (False) cfree : bool Whether to leave C matrix free (True) or not (False) Returns ------- theta : 1d array Length depends on the model restrictions and variance targeting If use_target is True: - 'hetero' - 2*n*(m+1) - 'ghomo' - 2*(n+m*k) - 'homo' - 2*(n+m) - 'shomo' - 2*(m+1) If use_target is False and cfree is False: - 'hetero' - +n*(m+1) - 'ghomo' - +(n+m*k) - 'homo' - +(n+m) - 'shomo' - +(n+m) If use_target is False and cfree is True: - +n*(n+1)/2 """ theta = self.get_theta_from_ab(restriction) if cfree and (not use_target): index = np.tril_indices(self.cmat.shape[0]) theta = np.concatenate([theta, self.cmat[index]]) elif (not cfree) and (not use_target): if self.dvecs is None: self.dvecs = np.vstack((np.diag(self.cmat), np.zeros(self.cmat.shape[0]))) if restriction == 'hetero': theta = np.concatenate([theta, self.dvecs.flatten()]) elif restriction == 'ghomo': theta = np.concatenate([theta, self.theta_from_vecs(self.dvecs)]) elif restriction in ('homo', 'shomo'): theta = np.concatenate([theta, self.dvecs[0], self.dvecs[1:, 0]]) else: raise NotImplementedError('Restriction is not implemented!') return theta
[docs] @staticmethod def get_weight(groups=None): """Generate weighting matrices given groups. Parameters ---------- groups : list of lists of tuples Encoded groups of items Returns ------- (ngroups, nitems, nitems) array Spatial weights Examples -------- >>> print(ParamSpatial.get_weight(groups=[[(0, 1)]])) [[[ 0. 1.] [ 1. 0.]]] >>> print(ParamSpatial.get_weight(groups=[[(0, 1, 2)]])) [[[ 0. 0.5 0.5] [ 0.5 0. 0.5] [ 0.5 0.5 0. ]]] >>> print(ParamSpatial.get_weight(groups=[[(0, 1), (2, 3)]])) [[[ 0. 1. 0. 0.] [ 1. 0. 0. 0.] [ 0. 0. 0. 1.] [ 0. 0. 1. 0.]]] >>> print(ParamSpatial.get_weight(groups=[[(0, 1), (2, 3, 4)]])) [[[ 0. 1. 0. 0. 0. ] [ 1. 0. 0. 0. 0. ] [ 0. 0. 0. 0.5 0.5] [ 0. 0. 0.5 0. 0.5] [ 0. 0. 0.5 0.5 0. ]]] """ ncat = len(groups) nitems = 0 for group in groups: for items in group: temp = np.max(items) if temp > nitems: nitems = temp weight = np.zeros((ncat, nitems+1, nitems+1)) for i in range(ncat): for group in groups[i]: for id1, id2 in itertools.product(group, group): if id1 != id2: weight[i, id1, id2] = 1 norm = weight[i].sum(0)[:, np.newaxis] weight[i] /= norm return weight