From eed3ebd8eac390ce7da3ab51b2d33b0ddabf0b2f Mon Sep 17 00:00:00 2001 From: Felix Beckebanze Date: Wed, 14 Aug 2024 14:39:45 +0200 Subject: [PATCH] Changed matrix As into B, defined it as transpose of previous implementation, and also changed the implementation of G to its transpose (an all definitions of G in terms of L etc likewise). --- pymrio/core/mriosystem.py | 12 ++-- pymrio/tools/iomath.py | 48 +++++++------- tests/test_math.py | 135 ++++++++++++++++++++++++-------------- 3 files changed, 113 insertions(+), 82 deletions(-) diff --git a/pymrio/core/mriosystem.py b/pymrio/core/mriosystem.py index 8c397fed..cd5c9080 100644 --- a/pymrio/core/mriosystem.py +++ b/pymrio/core/mriosystem.py @@ -32,7 +32,7 @@ from pymrio.tools.iomath import ( calc_A, calc_accounts, - calc_As, + calc_B, calc_F, calc_F_Y, calc_G, @@ -1720,7 +1720,7 @@ def __init__( Z=None, Y=None, A=None, - As=None, + B=None, x=None, L=None, G=None, @@ -1740,7 +1740,7 @@ def __init__( self.Y = Y self.x = x self.A = A - self.As = As + self.B = B self.L = L self.G = G self.unit = unit @@ -1867,8 +1867,8 @@ def calc_system(self): self.A = calc_A(self.Z, self.x) self.meta._add_modify("Coefficient matrix A calculated") - if self.As is None: - self.As = calc_As(self.Z, self.x) + if self.B is None: + self.B = calc_B(self.Z, self.x) self.meta._add_modify("Coefficient matrix As calculated") if self.L is None: @@ -1876,7 +1876,7 @@ def calc_system(self): self.meta._add_modify("Leontief matrix L calculated") if self.G is None: - self.G = calc_G(self.As) + self.G = calc_G(self.B) self.meta._add_modify("Ghosh matrix G calculated") return self diff --git a/pymrio/tools/iomath.py b/pymrio/tools/iomath.py index 323b0fd8..c1f02ef6 100644 --- a/pymrio/tools/iomath.py +++ b/pymrio/tools/iomath.py @@ -152,10 +152,10 @@ def calc_A(Z, x): return Z * recix -def calc_As(Z, x): - """Calculate the As matrix (coefficients) from Z and x +def calc_B(Z, x): + """Calculate the B matrix (coefficients) from Z and x - As is a normalized version of the industrial flows of the transpose of Z, which quantifies the input to downstream + B is a normalized version of the industrial flows of the transpose of Z, which quantifies the input to downstream sectors. Parameters @@ -187,13 +187,13 @@ def calc_As(Z, x): recix = recix.reshape((1, -1)) # use numpy broadcasting - factor ten faster # Mathematical form - slow - # return Z.dot(np.diagflat(recix)) + # return np.diagflat(recix).dot(Z) if type(Z) is pd.DataFrame: return pd.DataFrame( - np.transpose(Z.values) * recix, index=Z.index, columns=Z.columns + np.transpose(np.transpose(Z.values) * recix), index=Z.index, columns=Z.columns ) else: - return np.transpose(Z) * recix + return np.transpose(np.transpose(Z) * recix) def calc_L(A): @@ -232,30 +232,26 @@ def calc_L(A): return np.linalg.inv(I - A) -def calc_G(As, L=None, x=None): - """Calculate the Ghosh inverse matrix G either from As (high computation effor) or from Leontief matrix L and x +def calc_G(B, L=None, x=None): + """Calculate the Ghosh inverse matrix G either from B (high computation effort) or from Leontief matrix L and x (low computation effort) - G = inverse matrix of (I - As) = hat(x)^{-1} * L^T * hat(x) + G = inverse matrix of (I - B) = hat(x) * L * hat(x)^{-1} - where I is an identity matrix of same shape as As, and hat(x) is the diagonal matrix with values of x on the + where I is an identity matrix of same shape as B, and hat(x) is the diagonal matrix with values of x on the diagonal. - Note that we define G as the transpose of the Ghosh inverse matrix, so that we can apply the factors of - production intensities from the left-hand-side for both Leontief and Ghosh attribution. In this way the - multipliers have the same (vector) dimensions and can be added. - Parameters ---------- - As : pandas.DataFrame or numpy.array + B : pandas.DataFrame or numpy.array Symmetric input output table (coefficients) Returns ------- pandas.DataFrame or numpy.array Ghosh input output table G - The type is determined by the type of As. - If DataFrame index/columns as As + The type is determined by the type of B. + If DataFrame index/columns as B """ # if L has already been calculated, then G can be derived from it with low computational cost. @@ -275,20 +271,20 @@ def calc_G(As, L=None, x=None): if type(L) is pd.DataFrame: return pd.DataFrame( - recix * np.transpose(L.values * x), index=Z.index, columns=Z.columns + np.transpose(recix * np.transpose(L.values * x)), index=Z.index, columns=Z.columns ) else: - # G = hat(x)^{-1} * L^T * hat(x) in mathematical form np.linalg.inv(hatx).dot(L.transpose()).dot(hatx). + # G = hat(x) * L * hat(x)^{-1} in mathematical form hatx.dot(L.transpose()).dot(np.linalg.inv(hatx)). # it is computationally much faster to multiply element-wise because hatx is a diagonal matrix. - return recix * np.transpose(L * x) + return np.transpose(recix * np.transpose(L * x)) else: # calculation of the inverse of I-As has a high computational cost. - I = np.eye(As.shape[0]) # noqa - if type(As) is pd.DataFrame: + I = np.eye(B.shape[0]) # noqa + if type(B) is pd.DataFrame: return pd.DataFrame( - np.linalg.inv(I - As), index=As.index, columns=As.columns + np.linalg.inv(I - B), index=B.index, columns=B.columns ) else: - return np.linalg.inv(I - As) # G = inverse matrix of (I - As) + return np.linalg.inv(I - B) # G = inverse matrix of (I - B) def calc_S(F, x): @@ -421,7 +417,7 @@ def calc_M(S, L): def calc_M_down(S, G): """Calculate downstream multipliers of the extensions - M_down = S * ( G - I ) + M_down = S * ( G^T - I ) Where I is an identity matrix of same shape as G @@ -440,7 +436,7 @@ def calc_M_down(S, G): If DataFrame index/columns as S """ - return S.dot(G - np.eye(G.shape[0])) + return S.dot(np.transpose(G) - np.eye(G.shape[0])) def calc_e(M, Y): diff --git a/tests/test_math.py b/tests/test_math.py index 3b54cdbe..8f9a9a55 100644 --- a/tests/test_math.py +++ b/tests/test_math.py @@ -16,7 +16,7 @@ # the function which should be tested here from pymrio.tools.iomath import calc_A # noqa from pymrio.tools.iomath import calc_accounts # noqa -from pymrio.tools.iomath import calc_As # noqa +from pymrio.tools.iomath import calc_B # noqa from pymrio.tools.iomath import calc_e # noqa from pymrio.tools.iomath import calc_F # noqa from pymrio.tools.iomath import calc_F_Y # noqa @@ -235,21 +235,56 @@ class IO_Data: columns=_Z_multiindex, ) - As = pd.DataFrame( + B = pd.DataFrame( data=[ - [0.19607843, 0.0, 0.17241379, 0.1, 0.0, 0.12820513], # noqa - [0.09803922, 0.13333333, 0.05172414, 0.0, 0.19230769, 0.0], # noqa - [0.01960784, 0.0, 0.34482759, 0.0, 0.01923077, 0.0], # noqa - [0.11764706, 0.0, 0.06896552, 0.02, 0.0, 0.02564103], # noqa [ + 0.19607843, 0.09803922, + 0.01960784, + 0.11764706, + 0.09803922, + 0.1372549 + ], # noqa + [ + 0.0, + 0.13333333, + 0.0, + 0.0, 0.33333333, + 0.2 + ], # noqa + [ + 0.17241379, + 0.05172414, + 0.34482759, + 0.06896552, 0.03448276, + 0. + ], # noqa + [ + 0.1, + 0.0, + 0.0, + 0.02, 0.2, + 0.18 + ], # noqa + [ + 0.0, + 0.19230769, + 0.01923077, + 0.0, 0.38461538, - 0.25641026, + 0.01923077 ], # noqa - [0.1372549, 0.2, 0.0, 0.18, 0.01923077, 0.25641026], # noqa + [ + 0.12820513, + 0.0, + 0.0, + 0.02564103, + 0.25641026, + 0.25641026 + ] ], index=_Z_multiindex, columns=_Z_multiindex, @@ -314,52 +349,52 @@ class IO_Data: data=[ [ 1.33871463, - 0.07482651, - 0.38065717, - 0.19175489, - 0.04316348, - 0.25230906, - ], # noqa - [ 0.28499301, - 1.37164729, - 0.22311379, - 0.15732254, - 0.44208094, - 0.20700334, - ], # noqa - [ 0.05727924, + 0.1747153 , + 0.58648041, + 0.38121958 + ], # noqa + [ + 0.07482651, + 1.37164729, 0.02969614, - 1.54929428, - 0.02349465, - 0.05866155, - 0.03091401, - ], # noqa - [ - 0.1747153, 0.02185805, - 0.15941084, + 0.93542302, + 0.41222074 + ], # noqa + [ + 0.38065717, + 0.22311379, + 1.54929428, + 0.15941084, + 0.39473223, + 0.17907022 + ], # noqa + [ + 0.19175489, + 0.15732254, + 0.02349465, 1.05420074, - 0.01404088, - 0.07131676, + 0.60492361, + 0.34854312 ], # noqa [ - 0.58648041, - 0.93542302, - 0.39473223, - 0.60492361, + 0.04316348, + 0.44208094, + 0.05866155, + 0.01404088, 1.95452858, - 0.79595212, + 0.18081884 ], # noqa [ - 0.38121958, - 0.41222074, - 0.17907022, - 0.34854312, - 0.18081884, - 1.48492515, - ], # noqa + 0.25230906, + 0.20700334, + 0.03091401, + 0.07131676, + 0.79595212, + 1.48492515 + ] ], index=_Z_multiindex, columns=_Z_multiindex, @@ -637,15 +672,15 @@ def test_calc_A_MRIO(td_small_MRIO): pdt.assert_frame_equal(td_small_MRIO.A, calc_A(td_small_MRIO.Z, x_Tvalues)) -def test_calc_As_MRIO(td_small_MRIO): - pdt.assert_frame_equal(td_small_MRIO.As, calc_As(td_small_MRIO.Z, td_small_MRIO.x)) +def test_calc_B_MRIO(td_small_MRIO): + pdt.assert_frame_equal(td_small_MRIO.B, calc_B(td_small_MRIO.Z, td_small_MRIO.x)) # we also test the different methods to provide x: x_values = td_small_MRIO.x.values x_Tvalues = td_small_MRIO.x.T.values x_series = pd.Series(td_small_MRIO.x.iloc[:, 0]) - pdt.assert_frame_equal(td_small_MRIO.As, calc_As(td_small_MRIO.Z, x_series)) - pdt.assert_frame_equal(td_small_MRIO.As, calc_As(td_small_MRIO.Z, x_values)) - pdt.assert_frame_equal(td_small_MRIO.As, calc_As(td_small_MRIO.Z, x_Tvalues)) + pdt.assert_frame_equal(td_small_MRIO.B, calc_B(td_small_MRIO.Z, x_series)) + pdt.assert_frame_equal(td_small_MRIO.B, calc_B(td_small_MRIO.Z, x_values)) + pdt.assert_frame_equal(td_small_MRIO.B, calc_B(td_small_MRIO.Z, x_Tvalues)) def test_calc_Z_MRIO(td_small_MRIO): @@ -664,7 +699,7 @@ def test_calc_L_MRIO(td_small_MRIO): def test_calc_G_MRIO(td_small_MRIO): - pdt.assert_frame_equal(td_small_MRIO.G, calc_G(td_small_MRIO.As)) + pdt.assert_frame_equal(td_small_MRIO.G, calc_G(td_small_MRIO.B)) def test_calc_S_MRIO(td_small_MRIO):