mrio-toolbox 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mrio-toolbox might be problematic. Click here for more details.
- mrio_toolbox/__init__.py +5 -0
- mrio_toolbox/_parts/_Axe.py +481 -0
- mrio_toolbox/_parts/_Part.py +1504 -0
- mrio_toolbox/_parts/__init__.py +3 -0
- mrio_toolbox/_parts/part_operations.py +50 -0
- mrio_toolbox/mrio.py +739 -0
- mrio_toolbox/utils/__init__.py +0 -0
- mrio_toolbox/utils/converters/__init__.py +2 -0
- mrio_toolbox/utils/converters/pandas.py +245 -0
- mrio_toolbox/utils/converters/xarray.py +141 -0
- mrio_toolbox/utils/loaders/__init__.py +3 -0
- mrio_toolbox/utils/loaders/_loader.py +256 -0
- mrio_toolbox/utils/loaders/_loader_factory.py +75 -0
- mrio_toolbox/utils/loaders/_nc_loader.py +148 -0
- mrio_toolbox/utils/loaders/_np_loader.py +112 -0
- mrio_toolbox/utils/loaders/_pandas_loader.py +102 -0
- mrio_toolbox/utils/loaders/_parameter_loader.py +341 -0
- mrio_toolbox/utils/savers/__init__.py +8 -0
- mrio_toolbox/utils/savers/_path_checker.py +19 -0
- mrio_toolbox/utils/savers/_to_folder.py +160 -0
- mrio_toolbox/utils/savers/_to_nc.py +52 -0
- mrio_toolbox-1.0.0.dist-info/LICENSE +674 -0
- mrio_toolbox-1.0.0.dist-info/METADATA +28 -0
- mrio_toolbox-1.0.0.dist-info/RECORD +26 -0
- mrio_toolbox-1.0.0.dist-info/WHEEL +5 -0
- mrio_toolbox-1.0.0.dist-info/top_level.txt +1 -0
mrio_toolbox/mrio.py
ADDED
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Thu Mar 30 10:42:23 2023
|
|
4
|
+
|
|
5
|
+
Representation of economic MRIO tables
|
|
6
|
+
|
|
7
|
+
@author: beaufils
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import numpy as np
|
|
12
|
+
import logging
|
|
13
|
+
import xarray as xr
|
|
14
|
+
import pandas as pd
|
|
15
|
+
from mrio_toolbox._parts import Part
|
|
16
|
+
from mrio_toolbox.utils import converters
|
|
17
|
+
from mrio_toolbox.utils.loaders import make_loader
|
|
18
|
+
from mrio_toolbox.utils.savers import save_mrio_to_folder,save_to_nc
|
|
19
|
+
|
|
20
|
+
log = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
class MRIO:
|
|
23
|
+
"""
|
|
24
|
+
Representation of an MRIO table
|
|
25
|
+
|
|
26
|
+
An MRIO table holds a collection of Parts, each representing a different
|
|
27
|
+
aspect of the table (inter-industry matrix, final demand, etc.)
|
|
28
|
+
The MRIO instance allows to perform basic operations on the table.
|
|
29
|
+
|
|
30
|
+
Instance variables
|
|
31
|
+
-------------------
|
|
32
|
+
metadata : dict
|
|
33
|
+
Dictionnary storing the metadata
|
|
34
|
+
labels : dict
|
|
35
|
+
Labels of the table parts:
|
|
36
|
+
list of countries
|
|
37
|
+
list of sectors
|
|
38
|
+
groupings : dict
|
|
39
|
+
Groupings of the MRIO table.
|
|
40
|
+
Groupings are used to group labels into larger categories
|
|
41
|
+
(e.g countries into zones, sectors in aggregate sectors).
|
|
42
|
+
Groupings have in principle no impact on the resolution of the table
|
|
43
|
+
but can be used for visualization or aggregation purposes.
|
|
44
|
+
c : int
|
|
45
|
+
Number of countries in the table
|
|
46
|
+
s : int
|
|
47
|
+
Number of sectors in the table
|
|
48
|
+
parts : dict of Part objects
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
Methods
|
|
52
|
+
-----------
|
|
53
|
+
load_parts(parts,year) :
|
|
54
|
+
Load new parts
|
|
55
|
+
load_zones(name) :
|
|
56
|
+
load new zones
|
|
57
|
+
rename_zone(zone,name) :
|
|
58
|
+
rename a zone
|
|
59
|
+
modify_zone()
|
|
60
|
+
load_extension(extension):
|
|
61
|
+
Load a new MRIO extension.
|
|
62
|
+
group_y():
|
|
63
|
+
Aggregated final demand by zone.
|
|
64
|
+
extract_zone(part,zone):
|
|
65
|
+
Extract a given zone from a given part of the MRIO table.
|
|
66
|
+
sel(index):
|
|
67
|
+
Returns the slice containing the indices of all sectors of a country.
|
|
68
|
+
"""
|
|
69
|
+
def __init__(self,**kwargs):
|
|
70
|
+
"""
|
|
71
|
+
Initialize an MRIO instance
|
|
72
|
+
|
|
73
|
+
There are two ways to initialize an MRIO instance:
|
|
74
|
+
- from a .nc file
|
|
75
|
+
- from explicit parameters
|
|
76
|
+
|
|
77
|
+
If created from a .nc file, only the "file" parameter is required, with the path to the file.
|
|
78
|
+
Loading an MRIO from explicit parameters requires at least a path to the data and the labels.
|
|
79
|
+
|
|
80
|
+
You can also provide the path to a .yaml file containing the loading instructions,
|
|
81
|
+
using the "file" parameter.
|
|
82
|
+
|
|
83
|
+
Note that the "data" parameter is reserved for setting an MRIO instance from a dictionary.
|
|
84
|
+
It is intended for internal use only.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
file : str, optional
|
|
89
|
+
Path to the file to load the MRIO table from.
|
|
90
|
+
If a .yaml file is provided, the file is interpreted as loading instructions.
|
|
91
|
+
"""
|
|
92
|
+
if not kwargs:
|
|
93
|
+
#Create an empty MRIO instance
|
|
94
|
+
kwargs = {
|
|
95
|
+
"data" : dict()
|
|
96
|
+
}
|
|
97
|
+
if "data" in kwargs:
|
|
98
|
+
data = kwargs.pop("data")
|
|
99
|
+
if isinstance(data,dict):
|
|
100
|
+
log.info("Create MRIO from dict")
|
|
101
|
+
self.parts,self.labels,self.metadata,self.groupings = dict(),dict(),dict(),dict()
|
|
102
|
+
self.__dict__.update(data)
|
|
103
|
+
|
|
104
|
+
for part in self.parts:
|
|
105
|
+
self._update_labels(
|
|
106
|
+
self.parts[part],
|
|
107
|
+
update_part=False
|
|
108
|
+
)
|
|
109
|
+
self.metadata.update(kwargs)
|
|
110
|
+
self.loader = make_loader()
|
|
111
|
+
return
|
|
112
|
+
if isinstance(data,(xr.DataArray,xr.Dataset)):
|
|
113
|
+
if isinstance(data,xr.DataArray):
|
|
114
|
+
data = data.to_dataset()
|
|
115
|
+
mrio_data,to_load = converters.xarray.make_mrio(data,**kwargs)
|
|
116
|
+
self.__init__(data=mrio_data)
|
|
117
|
+
for part in to_load:
|
|
118
|
+
self.add_part(data[part])
|
|
119
|
+
return
|
|
120
|
+
if isinstance(data,pd.DataFrame):
|
|
121
|
+
self.__init__(data=kwargs)
|
|
122
|
+
self.add_part(data)
|
|
123
|
+
return
|
|
124
|
+
raise ValueError(f"Cannot create an MRIO instance from type: {type(data)}")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
file = kwargs.pop("file",None)
|
|
128
|
+
#Initialize the loader
|
|
129
|
+
self.loader = make_loader(file=file,**kwargs)
|
|
130
|
+
|
|
131
|
+
#Load basic MRIO data
|
|
132
|
+
self.metadata = self.loader.metadata
|
|
133
|
+
self.labels = self.loader.labels
|
|
134
|
+
self.groupings = self.loader.groupings
|
|
135
|
+
|
|
136
|
+
#Initialize the parts
|
|
137
|
+
self.parts = dict()
|
|
138
|
+
available_parts = kwargs.get(
|
|
139
|
+
"parts",
|
|
140
|
+
self.loader.available_parts(
|
|
141
|
+
extension = kwargs.get("extension",None)
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
to_load = {part:part for part in available_parts}
|
|
146
|
+
if "part_settings" in self.loader.__dict__ and bool(self.loader.part_settings):
|
|
147
|
+
to_load = dict()
|
|
148
|
+
for part in self.loader.part_settings.keys():
|
|
149
|
+
to_load[part] = self.loader.part_settings[part].get("file_name",part)
|
|
150
|
+
|
|
151
|
+
for part in to_load.keys():
|
|
152
|
+
if to_load[part] not in available_parts:
|
|
153
|
+
log.warning(f"Part {part} not found in available parts")
|
|
154
|
+
continue
|
|
155
|
+
kwargs["name"] = part
|
|
156
|
+
kwargs["file_name"] = to_load[part]
|
|
157
|
+
self.load_part(**kwargs)
|
|
158
|
+
|
|
159
|
+
if "countries" in self.labels:
|
|
160
|
+
self.c = len(self.labels["countries"])
|
|
161
|
+
if "sectors" in self.labels:
|
|
162
|
+
self.s = len(self.labels["sectors"])
|
|
163
|
+
|
|
164
|
+
def load_part(self,
|
|
165
|
+
update_part = True,
|
|
166
|
+
standalone=False,
|
|
167
|
+
**kwargs):
|
|
168
|
+
"""
|
|
169
|
+
Load a Part object into the MRIO table
|
|
170
|
+
|
|
171
|
+
By default, the Part is loaded using the current loader.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
update_part : bool, optional
|
|
176
|
+
The groupings and labels of the Part are updated based on the MRIO attributes.
|
|
177
|
+
standalone : bool, optional
|
|
178
|
+
Whether to load the Part as a standalone object.
|
|
179
|
+
The default is False.
|
|
180
|
+
kwargs : dict
|
|
181
|
+
Additional arguments to pass to the Part loader.
|
|
182
|
+
"""
|
|
183
|
+
name = kwargs.get("name","new_part")
|
|
184
|
+
try:
|
|
185
|
+
log.debug(f"Try loading part {name} from the current loader")
|
|
186
|
+
part = self.loader.load_part(**kwargs)
|
|
187
|
+
except FileNotFoundError:
|
|
188
|
+
log.info(f"Part {name} not found with the current loader")
|
|
189
|
+
log.debug(f"Try resetting the loader")
|
|
190
|
+
loader = make_loader(**kwargs)
|
|
191
|
+
part = loader.load_part()
|
|
192
|
+
part = Part(**part)
|
|
193
|
+
if standalone:
|
|
194
|
+
return part
|
|
195
|
+
log.info(f"Add part {part.name} to MRIO table")
|
|
196
|
+
self.add_part(part,update_part=update_part)
|
|
197
|
+
|
|
198
|
+
def _update_labels(self,part,update_part=True):
|
|
199
|
+
"""
|
|
200
|
+
Update the labels of the MRIO table with the labels of a Part object
|
|
201
|
+
|
|
202
|
+
If all the labels of the Part are already in the MRIO labels,
|
|
203
|
+
the method does nothing.
|
|
204
|
+
This method is run after adding a new Part to the MRIO table.
|
|
205
|
+
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
part : Part object
|
|
209
|
+
Part object to use for the update.
|
|
210
|
+
update_part : bool, optional
|
|
211
|
+
If True and the Part labels are not properly set,
|
|
212
|
+
tries to update the Part labels based on the MRIO labels.
|
|
213
|
+
"""
|
|
214
|
+
part_labels = part.get_labels()
|
|
215
|
+
log.debug(f"Update labels of MRIO table with {part.name}")
|
|
216
|
+
for labels in part_labels:
|
|
217
|
+
for label in labels.keys():
|
|
218
|
+
if isinstance(label,int):
|
|
219
|
+
log.debug(f"Skip numerical label in {part.name}")
|
|
220
|
+
if update_part:
|
|
221
|
+
log.debug(f"Try to update labels of {part.name}")
|
|
222
|
+
labels[label] = self._get_labels(
|
|
223
|
+
len(labels[label])
|
|
224
|
+
)
|
|
225
|
+
continue #Skip unkwnown labels
|
|
226
|
+
if label not in self.labels.keys():
|
|
227
|
+
self.labels[label] = labels[label]
|
|
228
|
+
log.info(f"Add label {label} to MRIO table")
|
|
229
|
+
if update_part:
|
|
230
|
+
part.set_labels(part_labels)
|
|
231
|
+
|
|
232
|
+
def _get_labels(self,l):
|
|
233
|
+
"""Find the labels fitting an axis with a given shape
|
|
234
|
+
|
|
235
|
+
Available labels:
|
|
236
|
+
countries and sectors
|
|
237
|
+
countries
|
|
238
|
+
zones and sectors
|
|
239
|
+
zones
|
|
240
|
+
sectors
|
|
241
|
+
|
|
242
|
+
If no fitting label is found, data are labelled numerically
|
|
243
|
+
|
|
244
|
+
Parameters
|
|
245
|
+
----------
|
|
246
|
+
l : int
|
|
247
|
+
Length of the data dimension.
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
dict of str:list of str
|
|
252
|
+
Labels of the axis.
|
|
253
|
+
|
|
254
|
+
"""
|
|
255
|
+
if l==1:
|
|
256
|
+
return (["all"])
|
|
257
|
+
log.debug("Try to infer label from axis of length "+str(l))
|
|
258
|
+
for label in self.labels:
|
|
259
|
+
#Look whether a basic label fits the axis
|
|
260
|
+
if l == len(self.labels[label]):
|
|
261
|
+
log.debug(f"Label {label} fits axis of length {l}")
|
|
262
|
+
return {label:self.labels[label]}
|
|
263
|
+
for grouping in self.groupings:
|
|
264
|
+
#Look whether a grouped label fits the axis
|
|
265
|
+
if l == len(self.groupings[grouping]):
|
|
266
|
+
log.debug(f"Label {label} fits axis of length {l}")
|
|
267
|
+
return {grouping:list(self.groupings[grouping]).keys()}
|
|
268
|
+
log.warning("No label found for axis of length "+str(l))
|
|
269
|
+
return {0:[i for i in range(l)]}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def set_groupings(self,groupings=None):
|
|
273
|
+
"""Set the groupings of the MRIO table
|
|
274
|
+
|
|
275
|
+
Groupings are used to group labels into larger categories
|
|
276
|
+
(e.g countries into zones, sectors in aggregate sectors).
|
|
277
|
+
Groupings have in principle no impact on the resolution of the table
|
|
278
|
+
but can be used for visualization or aggregation purposes.
|
|
279
|
+
|
|
280
|
+
Groupings should be disjoint, but this is not enforced.
|
|
281
|
+
Nested groupings are not supported.
|
|
282
|
+
|
|
283
|
+
Unspecified groupings are set to the identity.
|
|
284
|
+
Calling the method without arguments resets the groupings to the identity.
|
|
285
|
+
|
|
286
|
+
Parameters
|
|
287
|
+
----------
|
|
288
|
+
groupings : dict of dict, optional
|
|
289
|
+
Groupings of the MRIO table.
|
|
290
|
+
The default is None.
|
|
291
|
+
If None, the groupings are set to the identity.
|
|
292
|
+
Groupings should be provided as a dict of dict:
|
|
293
|
+
{dimension : {group : [items]}}
|
|
294
|
+
where dimension is the name of the label to group,
|
|
295
|
+
group is the name of the group,
|
|
296
|
+
and items is a list of items to group.
|
|
297
|
+
"""
|
|
298
|
+
if groupings is None:
|
|
299
|
+
groupings = {label:dict() for label in self.labels.keys()}
|
|
300
|
+
self.set_groupings(groupings)
|
|
301
|
+
for key in groupings.keys():
|
|
302
|
+
labels = self.labels[key]
|
|
303
|
+
covered = []
|
|
304
|
+
for group in groupings[key]:
|
|
305
|
+
for item in groupings[key][group]:
|
|
306
|
+
if item not in self.labels[key]:
|
|
307
|
+
log.warning(
|
|
308
|
+
f"Item {item} not found in {key} labels"
|
|
309
|
+
)
|
|
310
|
+
groupings[key][group].remove(item)
|
|
311
|
+
else:
|
|
312
|
+
covered.append(item)
|
|
313
|
+
if len(group) == 0:
|
|
314
|
+
log.warning(f"Group {group} is empty")
|
|
315
|
+
groupings[key].pop(group)
|
|
316
|
+
for item in labels:
|
|
317
|
+
if item not in covered:
|
|
318
|
+
groupings[key][item] = [item]
|
|
319
|
+
self.groupings[key] = groupings[key]
|
|
320
|
+
self._update_groupings()
|
|
321
|
+
self.loader.set_groupings(self.groupings)
|
|
322
|
+
|
|
323
|
+
def _update_groupings(self):
|
|
324
|
+
"""
|
|
325
|
+
Update the groupings of all Parts of the MRIO instance
|
|
326
|
+
"""
|
|
327
|
+
for part in self.parts.keys():
|
|
328
|
+
self.parts[part].update_groupings(self.groupings)
|
|
329
|
+
self.groups = {
|
|
330
|
+
groups : list(self.groupings[groups].keys()) for groups in self.groupings
|
|
331
|
+
} #Save name of groups for each dimension
|
|
332
|
+
|
|
333
|
+
def filter(self,threshold,fill_value=0):
|
|
334
|
+
"""
|
|
335
|
+
Filter the MRIO table by removing values below a threshold
|
|
336
|
+
|
|
337
|
+
Parameters
|
|
338
|
+
----------
|
|
339
|
+
threshold : float
|
|
340
|
+
Value below which the values are set to 0.
|
|
341
|
+
fill_value : float, optional
|
|
342
|
+
Value to use to fill the table if only dimensions are given.
|
|
343
|
+
|
|
344
|
+
Returns
|
|
345
|
+
-------
|
|
346
|
+
None.
|
|
347
|
+
"""
|
|
348
|
+
for part in self.parts.keys():
|
|
349
|
+
self.parts[part] = self.parts[part].filter(threshold,fill_value)
|
|
350
|
+
|
|
351
|
+
def add_part(self,
|
|
352
|
+
part,
|
|
353
|
+
name=None,
|
|
354
|
+
update_part=True):
|
|
355
|
+
"""
|
|
356
|
+
Add a Part object to the MRIO table
|
|
357
|
+
|
|
358
|
+
Parameters
|
|
359
|
+
----------
|
|
360
|
+
part : Part object
|
|
361
|
+
Part object to add to the MRIO table.
|
|
362
|
+
update_part : bool, optional
|
|
363
|
+
Whether to update the labels of the Part object.
|
|
364
|
+
The default is True.
|
|
365
|
+
"""
|
|
366
|
+
if not isinstance(part,Part):
|
|
367
|
+
#Cast the data into a Part object
|
|
368
|
+
part=Part(part)
|
|
369
|
+
if name is None:
|
|
370
|
+
name = part.name
|
|
371
|
+
log.info(f"Add part {name} to MRIO table")
|
|
372
|
+
self._update_labels(part,update_part)
|
|
373
|
+
if update_part:
|
|
374
|
+
part.update_groupings(self.groupings)
|
|
375
|
+
for metadata in self.metadata:
|
|
376
|
+
if metadata not in part.metadata:
|
|
377
|
+
part.metadata[metadata] = self.metadata[metadata]
|
|
378
|
+
self.parts[name] = part
|
|
379
|
+
|
|
380
|
+
def new_part(self,
|
|
381
|
+
data=None,
|
|
382
|
+
name="part",
|
|
383
|
+
dimensions=None,
|
|
384
|
+
fill_value=0.0,
|
|
385
|
+
**kwargs):
|
|
386
|
+
"""Cast part data into the corresponding Part Object
|
|
387
|
+
|
|
388
|
+
Parameters
|
|
389
|
+
----------
|
|
390
|
+
data : np.ndarray, optional
|
|
391
|
+
Data to load in the Part. The default is None.
|
|
392
|
+
If None, the dimensions argument is used to create an empty Part.
|
|
393
|
+
name : str, optional
|
|
394
|
+
Name of the Part. The default is "part".
|
|
395
|
+
dimensions or labels : list of str, list of ints, str, list of dicts, optional
|
|
396
|
+
Labels of the Part.
|
|
397
|
+
Either of these formats are accepted:
|
|
398
|
+
Dictionary of explicit labels for each axis
|
|
399
|
+
List of explicit labels for each axis
|
|
400
|
+
List of existing dimension names
|
|
401
|
+
If None, the labels are inferred from the data shape.
|
|
402
|
+
multiplier : str, optional
|
|
403
|
+
multiplier of the data. The default is None.
|
|
404
|
+
unit : float, optional
|
|
405
|
+
Unit of the data. The default is 1.
|
|
406
|
+
fill_value : float, optional
|
|
407
|
+
Value to use to fill the table if only dimensions are given.
|
|
408
|
+
|
|
409
|
+
Returns
|
|
410
|
+
-------
|
|
411
|
+
Part instance
|
|
412
|
+
"""
|
|
413
|
+
def unpack_dimensions(self,dimensions):
|
|
414
|
+
"""Unpack the dimensions argument"""
|
|
415
|
+
if isinstance(dimensions,dict):
|
|
416
|
+
return dimensions
|
|
417
|
+
if isinstance(dimensions,str):
|
|
418
|
+
#Try to get the dimensions from the current labels or groupings
|
|
419
|
+
if dimensions in self.labels.keys():
|
|
420
|
+
return {dimensions:self.labels[dimensions]}
|
|
421
|
+
if dimensions in self.groupings.keys():
|
|
422
|
+
return {dimensions:list(self.groupings[dimensions])}
|
|
423
|
+
raise ValueError(f"Invalid dimension {dimensions}")
|
|
424
|
+
if isinstance(dimensions,int):
|
|
425
|
+
#Try to infer the dimension from the length of the data
|
|
426
|
+
return self._get_labels(dimensions)
|
|
427
|
+
if isinstance(dimensions,(list,tuple)):
|
|
428
|
+
try:
|
|
429
|
+
#Try to unpack nested dimensions
|
|
430
|
+
output = dict()
|
|
431
|
+
for dim in dimensions:
|
|
432
|
+
output.update(unpack_dimensions(self,dim))
|
|
433
|
+
return output
|
|
434
|
+
except ValueError:
|
|
435
|
+
#Otherwise assume the labels were given as a list
|
|
436
|
+
return {0:dimensions}
|
|
437
|
+
raise TypeError(f"Invalid type for dimensions {type(dimensions)}")
|
|
438
|
+
|
|
439
|
+
dimensions = kwargs.get("dimensions",dimensions)
|
|
440
|
+
if dimensions is None:
|
|
441
|
+
dimensions = kwargs.get("labels",None)
|
|
442
|
+
if data is None:
|
|
443
|
+
#Create a Part from the dimensions only
|
|
444
|
+
if dimensions is None:
|
|
445
|
+
raise ValueError("No data nor dimensions provided")
|
|
446
|
+
|
|
447
|
+
if isinstance(dimensions,(int,str)):
|
|
448
|
+
#Ensure dimensions are iterable
|
|
449
|
+
dimensions = [dimensions]
|
|
450
|
+
labels = []
|
|
451
|
+
for dim in dimensions:
|
|
452
|
+
labels.append(unpack_dimensions(self,dim))
|
|
453
|
+
dims = len(labels)
|
|
454
|
+
shape = []
|
|
455
|
+
for dim in range(dims):
|
|
456
|
+
#Recursively compute the shape of the table
|
|
457
|
+
length = 1
|
|
458
|
+
for label in labels[dim]:
|
|
459
|
+
length *= len(labels[dim][label])
|
|
460
|
+
shape.append(length)
|
|
461
|
+
data = np.full(shape,fill_value=fill_value)
|
|
462
|
+
|
|
463
|
+
elif dimensions is None:
|
|
464
|
+
#Infer the dimensions from the data shape
|
|
465
|
+
labels = []
|
|
466
|
+
for dimension in range(data.ndim):
|
|
467
|
+
labels.append(
|
|
468
|
+
self._get_labels(data.shape[dimension])
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
else:
|
|
472
|
+
#Reformat the dimensions
|
|
473
|
+
labels = []
|
|
474
|
+
if isinstance(dimensions,(int,str)):
|
|
475
|
+
#Ensure dimensions are iterable
|
|
476
|
+
dimensions = [dimensions]
|
|
477
|
+
for dim in dimensions:
|
|
478
|
+
labels.append(unpack_dimensions(self,dim))
|
|
479
|
+
|
|
480
|
+
return Part(data=data,
|
|
481
|
+
name=name,
|
|
482
|
+
groupings = self.groupings,
|
|
483
|
+
labels = labels,
|
|
484
|
+
**kwargs)
|
|
485
|
+
|
|
486
|
+
def add_dimensions(self,dimensions):
|
|
487
|
+
"""
|
|
488
|
+
Add dimensions to the MRIO table
|
|
489
|
+
|
|
490
|
+
Parameters
|
|
491
|
+
----------
|
|
492
|
+
dimensions : dict
|
|
493
|
+
Description of the dimension to add.
|
|
494
|
+
"""
|
|
495
|
+
for dimension in dimensions.keys():
|
|
496
|
+
log.info("Add dimension "+dimension+" to MRIO table")
|
|
497
|
+
self.labels[dimension] = dimensions[dimension]
|
|
498
|
+
|
|
499
|
+
def add_labels(self,new_indices,dimension,
|
|
500
|
+
fill_value=0.0):
|
|
501
|
+
"""
|
|
502
|
+
Add items to a label of the MRIO instance
|
|
503
|
+
|
|
504
|
+
All Parts are updated automatically with the given fill_value
|
|
505
|
+
|
|
506
|
+
Parameters
|
|
507
|
+
----------
|
|
508
|
+
new_indices : list of str
|
|
509
|
+
items to add to the label
|
|
510
|
+
dimension : str
|
|
511
|
+
name of the labels to which the new indices should be added
|
|
512
|
+
fill_value : float, optional
|
|
513
|
+
Value to use to fill the newly created label fields in the tables.
|
|
514
|
+
"""
|
|
515
|
+
log.info(f"Add labels {str(new_indices)} to dimension "+dimension)
|
|
516
|
+
for part in self.parts.keys():
|
|
517
|
+
self.parts[part] = self.parts[part].add_labels(
|
|
518
|
+
new_indices,dimension=dimension,
|
|
519
|
+
fill_value=fill_value
|
|
520
|
+
)
|
|
521
|
+
self.labels[dimension] += new_indices
|
|
522
|
+
if dimension == "countries":
|
|
523
|
+
self.c = len(self.labels[dimension])
|
|
524
|
+
if dimension == "sectors":
|
|
525
|
+
self.s = len(self.labels[dimension])
|
|
526
|
+
if "x" in self.parts.keys():
|
|
527
|
+
x = self.x.data
|
|
528
|
+
x[x==0] = 1
|
|
529
|
+
self.loader.set_labels(self.labels)
|
|
530
|
+
|
|
531
|
+
def replace_labels(self,name,new_labels):
|
|
532
|
+
"""
|
|
533
|
+
Replace labels in all MRIO parts
|
|
534
|
+
|
|
535
|
+
Parameters
|
|
536
|
+
----------
|
|
537
|
+
names : str
|
|
538
|
+
Name of the labels
|
|
539
|
+
new_labels : dict of str:list of str
|
|
540
|
+
Description of the new labels to use.
|
|
541
|
+
"""
|
|
542
|
+
log.info(f"Replace labels for {name}")
|
|
543
|
+
for part in self.parts.keys():
|
|
544
|
+
self.parts[part] = self.parts[part].replace_labels(name,new_labels)
|
|
545
|
+
if name != new_labels.keys():
|
|
546
|
+
self.labels.update(new_labels)
|
|
547
|
+
self.labels.pop(name)
|
|
548
|
+
if name in self.groupings.keys():
|
|
549
|
+
self.groupings.pop(name)
|
|
550
|
+
self.loader.set_labels(self.labels)
|
|
551
|
+
|
|
552
|
+
def rename_labels(self,old_names,new_names):
|
|
553
|
+
"""
|
|
554
|
+
Rename labels for the MRIO instance
|
|
555
|
+
|
|
556
|
+
Parameters
|
|
557
|
+
----------
|
|
558
|
+
old_names : str or list of str
|
|
559
|
+
Labels to rename.
|
|
560
|
+
new_names : str or list of str
|
|
561
|
+
New names for the labels.
|
|
562
|
+
"""
|
|
563
|
+
if isinstance(old_names,str):
|
|
564
|
+
old_names = [old_names]
|
|
565
|
+
if isinstance(new_names,str):
|
|
566
|
+
new_names = [new_names]
|
|
567
|
+
for old,new in zip(old_names,new_names):
|
|
568
|
+
log.info(f"Rename label {old} to {new}")
|
|
569
|
+
if old not in self.labels.keys():
|
|
570
|
+
log.warning(f"Label {old} is not in MRIO labels and cannot be renamed.")
|
|
571
|
+
continue
|
|
572
|
+
self.labels[new] = self.labels.pop(old)
|
|
573
|
+
if old in self.groupings.keys():
|
|
574
|
+
self.groupings[new] = self.groupings.pop(old)
|
|
575
|
+
for part in self.parts.keys():
|
|
576
|
+
self.parts[part].rename_labels(old,new)
|
|
577
|
+
if old in self.labels.keys():
|
|
578
|
+
self.labels[new] = self.labels.pop(old)
|
|
579
|
+
|
|
580
|
+
def aggregate(self,on="sectors"):
|
|
581
|
+
"""Aggregate the MRIO table on a given dimension
|
|
582
|
+
|
|
583
|
+
The aggregation is performed by summing the values of the table
|
|
584
|
+
for the items that are grouped together.
|
|
585
|
+
|
|
586
|
+
Parameters
|
|
587
|
+
----------
|
|
588
|
+
on : str, optional
|
|
589
|
+
Name of the dimension to aggregate on.
|
|
590
|
+
The default is "sectors".
|
|
591
|
+
|
|
592
|
+
Returns
|
|
593
|
+
-------
|
|
594
|
+
None.
|
|
595
|
+
|
|
596
|
+
"""
|
|
597
|
+
if on == "all":
|
|
598
|
+
on = list(self.groupings.keys())
|
|
599
|
+
if isinstance(on,list):
|
|
600
|
+
for item in on:
|
|
601
|
+
self.aggregate(item)
|
|
602
|
+
return
|
|
603
|
+
log.info(f"Aggregate MRIO on {on}")
|
|
604
|
+
if on not in self.labels.keys():
|
|
605
|
+
raise ValueError(f"Invalid dimension {on}")
|
|
606
|
+
if on not in self.groupings.keys():
|
|
607
|
+
raise ValueError(f"No groupings defined for dimensions {on}")
|
|
608
|
+
|
|
609
|
+
new_groupings = {
|
|
610
|
+
item : [item] for item in self.groupings[on]
|
|
611
|
+
}
|
|
612
|
+
new_labels = [item for item in self.groupings[on]]
|
|
613
|
+
|
|
614
|
+
for part in self.parts.keys():
|
|
615
|
+
self.parts[part] = self.parts[part].aggregate(on)
|
|
616
|
+
|
|
617
|
+
self.groupings[on] = new_groupings
|
|
618
|
+
self.labels[on] = new_labels
|
|
619
|
+
|
|
620
|
+
if on == "countries":
|
|
621
|
+
self.c = len(self.labels[on])
|
|
622
|
+
if on == "sectors":
|
|
623
|
+
self.s = len(self.labels[on])
|
|
624
|
+
|
|
625
|
+
self.loader.set_groupings(self.groupings)
|
|
626
|
+
self.loader.set_labels(self.labels)
|
|
627
|
+
|
|
628
|
+
def __getattr__(self,name):
|
|
629
|
+
selset = [
|
|
630
|
+
self.parts,
|
|
631
|
+
self.labels,
|
|
632
|
+
self.metadata
|
|
633
|
+
]
|
|
634
|
+
for sel in selset:
|
|
635
|
+
try:
|
|
636
|
+
return sel[name]
|
|
637
|
+
except:
|
|
638
|
+
pass
|
|
639
|
+
raise AttributeError(f"Attribute {name} not found")
|
|
640
|
+
|
|
641
|
+
def __setattr__(self,name,value):
|
|
642
|
+
if isinstance(value,Part):
|
|
643
|
+
self.add_part(value,name=name)
|
|
644
|
+
elif isinstance(value,np.ndarray):
|
|
645
|
+
self.add_part(self.new_part(value,name))
|
|
646
|
+
else:
|
|
647
|
+
super().__setattr__(name,value)
|
|
648
|
+
|
|
649
|
+
def __str__(self):
|
|
650
|
+
s = ["MRIO object: "+ ' '.join(self.metadata.values())]
|
|
651
|
+
s.append("")
|
|
652
|
+
s.append("Parts currently loaded:")
|
|
653
|
+
s.append(" " + ", ".join(self.parts.keys()))
|
|
654
|
+
return "\n".join(s)
|
|
655
|
+
|
|
656
|
+
def has_neg(self,parts=None):
|
|
657
|
+
"""Check whether some Parts have negative values
|
|
658
|
+
|
|
659
|
+
Parameters
|
|
660
|
+
----------
|
|
661
|
+
parts : str, list of str or None, optional
|
|
662
|
+
List of parts to inspect.
|
|
663
|
+
If left empty, all parts are inspected.
|
|
664
|
+
|
|
665
|
+
Returns
|
|
666
|
+
-------
|
|
667
|
+
bool
|
|
668
|
+
"""
|
|
669
|
+
if parts is None:
|
|
670
|
+
parts = "all"
|
|
671
|
+
elif isinstance(parts,str):
|
|
672
|
+
parts = [parts]
|
|
673
|
+
for part in parts:
|
|
674
|
+
if self.parts[part].hasneg():
|
|
675
|
+
return True
|
|
676
|
+
return False
|
|
677
|
+
|
|
678
|
+
def copy(self):
|
|
679
|
+
"""Create a copy of the MRIO object"""
|
|
680
|
+
return MRIO(data=self.__dict__)
|
|
681
|
+
|
|
682
|
+
def save(self,
|
|
683
|
+
file,
|
|
684
|
+
name=None,
|
|
685
|
+
extension = "npy",
|
|
686
|
+
overwrite=False,
|
|
687
|
+
**kwargs):
|
|
688
|
+
"""
|
|
689
|
+
Save the current MRIO instance
|
|
690
|
+
|
|
691
|
+
If the path points to a folder, the MRIO parts can be saved as:
|
|
692
|
+
- .npy
|
|
693
|
+
- .csv
|
|
694
|
+
- .txt
|
|
695
|
+
- .xlsx
|
|
696
|
+
Labels are saved as .txt files and metadata as a .yaml file.
|
|
697
|
+
|
|
698
|
+
Otherwise the MRIO instance is saved as a .nc file.
|
|
699
|
+
|
|
700
|
+
Parameters
|
|
701
|
+
----------
|
|
702
|
+
file : str
|
|
703
|
+
Full path to the file or folder to save the MRIO instance into.
|
|
704
|
+
If a file is provided, the extension is used to determine the format.
|
|
705
|
+
extension : str, optional
|
|
706
|
+
Extension of the file to save the MRIO instance into.
|
|
707
|
+
This is only used if the file is a folder.
|
|
708
|
+
overwrite : bool, optional
|
|
709
|
+
Whether to overwrite the existing file. The default is False.
|
|
710
|
+
If False, the version name is iterated until a non-existing
|
|
711
|
+
file name is found.
|
|
712
|
+
kwargs : dict
|
|
713
|
+
Additional arguments to pass to the saver.
|
|
714
|
+
"""
|
|
715
|
+
file_extension = os.path.splitext(file)[1]
|
|
716
|
+
if file_extension == "" and extension == ".nc":
|
|
717
|
+
file = file+".nc"
|
|
718
|
+
file_extension = ".nc"
|
|
719
|
+
if file_extension == "":
|
|
720
|
+
#If the file is a folder, save in folder
|
|
721
|
+
save_mrio_to_folder(
|
|
722
|
+
self,
|
|
723
|
+
file,
|
|
724
|
+
name=name,
|
|
725
|
+
extension=extension,
|
|
726
|
+
overwrite=overwrite,
|
|
727
|
+
**kwargs
|
|
728
|
+
)
|
|
729
|
+
elif file_extension == ".nc":
|
|
730
|
+
#If the file is a .nc, save the tables
|
|
731
|
+
save_to_nc(self,file,name,overwrite)
|
|
732
|
+
else:
|
|
733
|
+
raise NotImplementedError(f"Cannot save MRIO in {file_extension} format")
|
|
734
|
+
|
|
735
|
+
def to_xarray(self):
|
|
736
|
+
"""
|
|
737
|
+
Convert the MRIO instance to an xarray Dataset
|
|
738
|
+
"""
|
|
739
|
+
return converters.xarray.to_DataSet(self)
|