iotables 0.1.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.
iotables/__init__.py ADDED
@@ -0,0 +1,43 @@
1
+ import os as _os
2
+ from importlib.metadata import version as _version, PackageNotFoundError as _PackageNotFoundError
3
+ from iotables.globals import DATA_FOLDER as __DATA_FOLDER
4
+ from iotables.globals import IS_WINDOWS as __IS_WINDOWS
5
+ from iotables.globals import FILES_LOG as __FILES_LOG
6
+ from iotables.oecd import OECD
7
+ from iotables.figaro import Figaro
8
+ from iotables.exiobase import ExioBase
9
+ from iotables.utils import remove_downloaded_files
10
+
11
+ try:
12
+ __version__ = _version("iotables")
13
+ except _PackageNotFoundError: # package not installed (e.g. running from source tree)
14
+ __version__ = "0.0.0"
15
+
16
+
17
+ def get_size_data_folder():
18
+ """Get size of the folder where downloaded files are stored
19
+
20
+ Returns:
21
+ str: Size of folder
22
+
23
+ """
24
+ from pathlib import Path
25
+
26
+ def get_size(folder):
27
+ def human(size):
28
+ UNITS = ["B", "KB", "MB", "GB", "TB"]
29
+ HUMANFMT = "{size} {unit}"
30
+ HUMANRADIX = 1024.
31
+ for u in UNITS[:-1]:
32
+ if size < HUMANRADIX:
33
+ return HUMANFMT.format(size=round(size, 2), unit=u)
34
+ size /= HUMANRADIX
35
+ return HUMANFMT.format(size=round(size, 2), unit=UNITS[-1])
36
+
37
+ root_directory = Path(folder)
38
+ tot_size = sum(f.stat().st_size for f in root_directory.glob('**/*') if f.is_file())
39
+ return human(tot_size)
40
+ return get_size(__DATA_FOLDER)
41
+
42
+
43
+ _os.makedirs(__DATA_FOLDER, exist_ok=True)
iotables/base_io.py ADDED
@@ -0,0 +1,339 @@
1
+ """Shared analysis engine: derives the coefficient/inverse matrices and runs shocks."""
2
+ from warnings import warn
3
+ from iotables.matrix import Matrix
4
+ from iotables.utils import assert_is_subset
5
+ import matplotlib.pyplot as plt
6
+ from typing import Union, Iterable, Optional
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+
11
+ class IO:
12
+ """Class for creating standard IO matrices and IO helper functions, meant as a parent
13
+ class for classes that load IO data.
14
+ """
15
+
16
+ def __init__(self):
17
+ necessary_attrs = ['Z',
18
+ 'X',
19
+ 'V',
20
+ 'FD',
21
+ 'FD_GRAN',
22
+ 'ADD',
23
+ 'FD_REGION',
24
+ 'rs',
25
+ 'sector_name_mapping',
26
+ 'regions',
27
+ 'sectors',
28
+ 'unit',
29
+ 'demand_items',
30
+ 'reference',
31
+ 'contact']
32
+ assert_is_subset(necessary_attrs, dir(self))
33
+
34
+ # Coefficients matrix, replace 0 with 1 to allow inversion
35
+ x_filled = self.X.copy()
36
+ x_filled[x_filled == 0] = 1
37
+
38
+ # Leontief demand side
39
+ self.A = Matrix('Technical coefficients',
40
+ self.Z / x_filled.flatten(),
41
+ self.Z.rows,
42
+ self.Z.columns)
43
+
44
+ self.L = Matrix('Leontief inverse',
45
+ (np.eye(self.rs) - self.A).I,
46
+ self.Z.rows,
47
+ self.Z.columns)
48
+
49
+ # Ghosh supply side
50
+ self.B = Matrix('Allocation coefficients',
51
+ self.Z / x_filled,
52
+ self.Z.rows,
53
+ self.Z.columns)
54
+
55
+ self.G = Matrix('Ghosh inverse',
56
+ (np.eye(self.rs) - self.B).I,
57
+ self.Z.rows,
58
+ self.Z.columns)
59
+
60
+ def _shock(self,
61
+ model: str,
62
+ shock: Union[int, float, None] = None,
63
+ regions: Optional[Iterable] = None,
64
+ sectors: Optional[Iterable] = None,
65
+ custom_shock_vector: Optional[Iterable] = None):
66
+ """Calculates new output using Leontief or Ghosh model
67
+
68
+ Args:
69
+ model: leontief or ghosh
70
+ shock: Shock in percentage of original final demand or primary inputs
71
+ regions: List of regions to be shocked
72
+ sectors: List of sectors to be shocked
73
+ custom_shock_vector: Vector of length regions * sectors with percentage shocks, overrides all other shock
74
+ parameters if supplied
75
+
76
+ Returns:
77
+ Matrix: Shocked output
78
+ """
79
+ if custom_shock_vector is not None:
80
+ shock_vector = np.array(custom_shock_vector).reshape(self.rs, 1)
81
+ else:
82
+ if shock is None or regions is None or sectors is None:
83
+ raise ValueError("Must supply parameters: 'shock', 'regions', 'sectors'")
84
+
85
+ assert_is_subset(regions, self.regions)
86
+ assert_is_subset(sectors, self.sectors)
87
+
88
+ shock_vector = np.array([shock if r in regions and s in sectors
89
+ else 0 for r, s in self.X.rows]).reshape(-1, 1)
90
+ shock_vector = shock_vector.astype('float64')
91
+ shock_vector /= 100
92
+ if model == 'leontief':
93
+ x_new = (self.L @ (self.FD * shock_vector)) + self.X
94
+ elif model == 'ghosh':
95
+ x_new = (self.G.T @ (self.V.T * shock_vector)) + self.X
96
+ else:
97
+ raise ValueError('model must be leontief or ghosh')
98
+
99
+ return x_new
100
+
101
+ def _shock_to_df(self,
102
+ x_new: Matrix):
103
+ """Creates a pandas dataframe with columns: region, sector, x and x_new
104
+
105
+ Args:
106
+ x_new: New output
107
+
108
+ Returns:
109
+ pd.DataFrame
110
+ """
111
+
112
+ return pd.DataFrame({'region': [r for r, s in self.X.rows],
113
+ 'sector': [s for r, s in self.X.rows],
114
+ 'x': self.X.flatten(),
115
+ 'x_new': x_new.flatten()})
116
+
117
+ def _shock_and_plot(self,
118
+ model: str,
119
+ shock: Union[int, float, None] = None,
120
+ regions: Optional[Iterable] = None,
121
+ sectors: Optional[Iterable] = None,
122
+ custom_shock_vector: Optional[Iterable] = None,
123
+ plot: bool = False,
124
+ plot_by: str = 'region',
125
+ plot_regions: Optional[Iterable] = None,
126
+ show: bool = True,
127
+ ):
128
+ """Executes a Leontief demand or Ghosh supply shock
129
+
130
+ Args:
131
+ model: leontief or ghosh
132
+ shock: Shock in percentage of original final demand or primary inputs
133
+ regions: List of regions to be shocked
134
+ sectors: List of sectors to be shocked
135
+ custom_shock_vector: Vector of length regions * sectors with percentage shocks, overrides all other shock
136
+ parameters if supplied
137
+ plot: Plot if true
138
+ plot_by: region or sector; if sector, the top 20 is shown
139
+ plot_regions: List of regions to plot the effect for
140
+ show: Show the plot if true
141
+
142
+ Returns:
143
+ pd.DataFrame: df with shocked output vector if plot is False, else matplotlib fig, ax
144
+ """
145
+ x_new = self._shock(model=model, shock=shock, regions=regions, sectors=sectors,
146
+ custom_shock_vector=custom_shock_vector)
147
+
148
+ if plot:
149
+ if plot_regions is None:
150
+ raise ValueError("Please specify 'plot_regions'")
151
+ fig, ax = self._plot_shock(x_new=x_new, model=model, by=plot_by, regions=plot_regions)
152
+ if show:
153
+ plt.show()
154
+ return fig, ax
155
+
156
+ return self._shock_to_df(x_new)
157
+
158
+ def _plot_shock(self,
159
+ x_new: Matrix,
160
+ model: str,
161
+ by: str,
162
+ regions: Iterable):
163
+ """Plot shock from a given new output
164
+
165
+ Args:
166
+ x_new: New output
167
+ model: ghosh or leontief
168
+ by: region or sector
169
+ regions: List of regions to plot the effect for
170
+
171
+ Returns:
172
+ fig, ax
173
+ """
174
+ if by not in {'region', 'sector'}:
175
+ raise ValueError("plot_by must be 'region' or 'sector'")
176
+ assert_is_subset(regions, self.regions)
177
+
178
+ df = self._shock_to_df(x_new)
179
+ df['diff'] = (df.x_new / df.x - 1).fillna(0)
180
+ df['col'] = self.V.flatten() if model == 'leontief' else self.FD.flatten()
181
+ df['newcol'] = df['col'] * (1 + df['diff'])
182
+
183
+ df = df[df.region.isin(regions)].groupby(by)[['col', 'newcol']].sum(0)
184
+ if by == 'sector':
185
+ df.index = df.index.map(self.sector_name_mapping).str[:25]
186
+ df['diff'] = (100 * (df.newcol / df.col - 1)).fillna(0)
187
+ df.sort_values('diff', inplace=True, ascending=False)
188
+ if by == 'sector':
189
+ if df['diff'].mean() < 0:
190
+ df = df.tail(20)
191
+ else:
192
+ df = df.head(20)
193
+
194
+ fig, ax = plt.subplots()
195
+ ax.barh(df.index,
196
+ df['diff']
197
+ )
198
+
199
+ if df['diff'].mean() < 0:
200
+ ax.invert_xaxis()
201
+ else:
202
+ ax.invert_yaxis()
203
+
204
+ plt.xlabel(f"% change in {'GVA' if model == 'leontief' else 'final demand'}")
205
+
206
+ plt.tight_layout()
207
+
208
+ return fig, ax
209
+
210
+ def leontief_demand_shock(self,
211
+ shock: Union[int, float, None] = None,
212
+ regions: Optional[Iterable] = None,
213
+ sectors: Optional[Iterable] = None,
214
+ custom_shock_vector: Optional[Iterable] = None,
215
+ plot: bool = False,
216
+ plot_by: str = 'region',
217
+ plot_regions: Optional[Iterable] = None,
218
+ show: bool = True,
219
+ ):
220
+ """Executes a Leontief demand shock
221
+
222
+ Args:
223
+ shock: Shock in percentage of original demand
224
+ regions: List of regions to be shocked
225
+ sectors: List of sectors to be shocked
226
+ custom_shock_vector: Vector of length regions * sectors with percentage shocks, overrides all other shock
227
+ parameters if supplied
228
+ plot: Plot if true
229
+ plot_by: region or sector; if sector, the top 20 is shown
230
+ plot_regions: List of regions to plot the effect for
231
+ show: Show the plot if true
232
+
233
+ Returns:
234
+ pd.DataFrame: df with shocked output vector if plot is False, else matplotlib fig, ax
235
+ """
236
+ return self._shock_and_plot(model='leontief',
237
+ shock=shock,
238
+ regions=regions,
239
+ sectors=sectors,
240
+ custom_shock_vector=custom_shock_vector,
241
+ plot=plot,
242
+ plot_by=plot_by,
243
+ plot_regions=plot_regions,
244
+ show=show)
245
+
246
+ def ghosh_supply_shock(self,
247
+ shock: Union[int, float, None] = None,
248
+ regions: Optional[Iterable] = None,
249
+ sectors: Optional[Iterable] = None,
250
+ custom_shock_vector: Optional[Iterable] = None,
251
+ plot: bool = False,
252
+ plot_by: str = 'region',
253
+ plot_regions: Optional[Iterable] = None,
254
+ show: bool = True,
255
+ ):
256
+ """Executes a Ghosh supply shock
257
+
258
+ Args:
259
+ shock: Shock in percentage of original primary inputs (value added)
260
+ regions: List of regions to be shocked
261
+ sectors: List of sectors to be shocked
262
+ custom_shock_vector: Vector of length regions * sectors with percentage shocks, overrides all other shock
263
+ parameters if supplied
264
+ plot: Plot if true
265
+ plot_by: region or sector; if sector, the top 20 is shown
266
+ plot_regions: List of regions to plot the effect for
267
+ show: Show the plot if true
268
+
269
+ Returns:
270
+ pd.DataFrame: df with shocked output vector if plot is False, else matplotlib fig, ax
271
+ """
272
+ return self._shock_and_plot(model='ghosh',
273
+ shock=shock,
274
+ regions=regions,
275
+ sectors=sectors,
276
+ custom_shock_vector=custom_shock_vector,
277
+ plot=plot,
278
+ plot_by=plot_by,
279
+ plot_regions=plot_regions,
280
+ show=show)
281
+
282
+ def get_imports_exports(self,
283
+ import_regions: Iterable,
284
+ export_regions: Iterable,
285
+ import_sectors: Optional[Iterable] = None,
286
+ export_sectors: Optional[Iterable] = None,
287
+ use_type: str = 'both'):
288
+ """Get imports and exports between regions and sectors. Use is broken down by intermediate use and final demand,
289
+ specify 'use_type' to get either of them or both summed together.
290
+
291
+ Args:
292
+ import_regions: List of importing regions
293
+ export_regions: List of exporting regions
294
+ import_sectors: List of importing sectors, all sectors by default, only accounts for intermediate import sectors
295
+ export_sectors: List of exporting sectors, all sectors by default
296
+ use_type: 'intermediate', 'final', or 'both'
297
+
298
+ Returns:
299
+ float: Sum of trade flow from exporting region-sectors to importing region-sectors
300
+ """
301
+ if use_type not in {'intermediate', 'final', 'both'}:
302
+ raise ValueError("use_type must be 'intermediate', 'final' or 'both'")
303
+
304
+ if import_sectors is not None and use_type in {'final', 'both'}:
305
+ warn('Note that import_sectors only apply to intermediate use, for final use only import_regions is used')
306
+
307
+ if import_sectors is None:
308
+ import_sectors = self.sectors
309
+ if export_sectors is None:
310
+ export_sectors = self.sectors
311
+
312
+ if isinstance(import_regions, str):
313
+ import_regions = [import_regions]
314
+ if isinstance(export_regions, str):
315
+ export_regions = [export_regions]
316
+ if isinstance(import_sectors, str):
317
+ import_sectors = [import_sectors]
318
+ if isinstance(export_sectors, str):
319
+ export_sectors = [export_sectors]
320
+
321
+ for subset, superset in zip([import_regions, export_regions, import_sectors, export_sectors],
322
+ [self.regions, self.regions, self.sectors, self.sectors]):
323
+ assert_is_subset(subset, superset)
324
+
325
+ if set(import_regions).intersection(export_regions):
326
+ warn(f'There is overlap between import_regions and export_regions: '
327
+ f'{", ".join(set(import_regions).intersection(export_regions))}')
328
+
329
+ intermediate = float(self.Z[np.ix_([r in export_regions and s in export_sectors for r, s in self.Z.rows],
330
+ [r in import_regions and s in import_sectors for r, s in
331
+ self.Z.columns])].sum())
332
+
333
+ final = float(
334
+ self.FD_REGION[np.ix_([r in export_regions and s in export_sectors for r, s in self.FD_REGION.rows],
335
+ [r in import_regions for r in self.FD_REGION.columns])].sum())
336
+
337
+ return {'intermediate': intermediate,
338
+ 'final': final,
339
+ 'both': intermediate + final}[use_type]
iotables/config.py ADDED
@@ -0,0 +1,195 @@
1
+ """Single source of truth for the data that exists: download links and shapes per database."""
2
+
3
+
4
+ def _chunk_links(base, chunks):
5
+ """Expand ``{filename: (start_year, end_year)}`` into ``{year: url}``.
6
+
7
+ OECD distributes each ICIO edition as a handful of multi-year zip archives
8
+ (e.g. ``2016-2022_SML.zip`` holds one CSV per year), so every year in a range
9
+ resolves to the same download URL.
10
+ """
11
+ return {year: base + filename
12
+ for filename, (start, end) in chunks.items()
13
+ for year in range(start, end + 1)}
14
+
15
+
16
+ def _circabc_links(kind_tag, ids):
17
+ """Build CIRCABC anonymous download URLs for Figaro from ``{year: node_id}``.
18
+
19
+ Eurostat distributes the Figaro 2025 edition through CIRCABC; every file has a
20
+ stable, auth-free download URL of the form ``/sd/a/{node_id}/{filename}``.
21
+ """
22
+ return {year: f'https://circabc.europa.eu/sd/a/{nid}/matrix_eu-ic-io_{kind_tag}_25ed_{year}.csv'
23
+ for year, nid in ids.items()}
24
+
25
+
26
+ config = {
27
+ 'oecd':
28
+ {'2021':
29
+ {'links': _chunk_links('https://webfs-sti.oecd.org/files/STI-PIE/ICIO/2021/', {
30
+ 'ICIO2021_1995-1999.zip': (1995, 1999),
31
+ 'ICIO2021_2000-2004.zip': (2000, 2004),
32
+ 'ICIO2021_2005-2009.zip': (2005, 2009),
33
+ 'ICIO2021_2010-2014.zip': (2010, 2014),
34
+ 'ICIO2021_2015-2018.zip': (2015, 2018),
35
+ }),
36
+ 'regex_id': r'ICIO2021_[0-9]{4}-[0-9]{4}',
37
+ 'num_regions': 71,
38
+ 'num_sectors': 45
39
+ },
40
+ '2022-extended':
41
+ {'links': _chunk_links('https://webfs-sti.oecd.org/files/STI-PIE/ICIO/2023/', {
42
+ '1995-2000_EXT.zip': (1995, 2000),
43
+ '2001-2005_EXT.zip': (2001, 2005),
44
+ '2006-2010_EXT.zip': (2006, 2010),
45
+ '2011-2015_EXT.zip': (2011, 2015),
46
+ '2016-2020_EXT.zip': (2016, 2020),
47
+ }),
48
+ 'regex_id': r'2023/[0-9]{4}-[0-9]{4}_EXT',
49
+ 'num_regions': 81,
50
+ 'num_sectors': 45},
51
+ '2022-small':
52
+ {'links': _chunk_links('https://webfs-sti.oecd.org/files/STI-PIE/ICIO/2023/', {
53
+ '1995-2000_SML.zip': (1995, 2000),
54
+ '2001-2005_SML.zip': (2001, 2005),
55
+ '2006-2010_SML.zip': (2006, 2010),
56
+ '2011-2015_SML.zip': (2011, 2015),
57
+ '2016-2020_SML.zip': (2016, 2020),
58
+ }),
59
+ 'regex_id': r'2023/[0-9]{4}-[0-9]{4}_SML',
60
+ 'num_regions': 77,
61
+ 'num_sectors': 45},
62
+ '2025-extended':
63
+ {'links': _chunk_links('https://webfs-sti.oecd.org/files/STI-PIE/ICIO/2025/', {
64
+ '1995-2000_EXT.zip': (1995, 2000),
65
+ '2001-2005_EXT.zip': (2001, 2005),
66
+ '2006-2010_EXT.zip': (2006, 2010),
67
+ '2011-2015_EXT.zip': (2011, 2015),
68
+ '2016-2022_EXT.zip': (2016, 2022),
69
+ }),
70
+ 'regex_id': r'2025/[0-9]{4}-[0-9]{4}_EXT',
71
+ 'num_regions': 85,
72
+ 'num_sectors': 50},
73
+ '2025-regular':
74
+ {'links': _chunk_links('https://webfs-sti.oecd.org/files/STI-PIE/ICIO/2025/', {
75
+ '1995-2000_SML.zip': (1995, 2000),
76
+ '2001-2005_SML.zip': (2001, 2005),
77
+ '2006-2010_SML.zip': (2006, 2010),
78
+ '2011-2015_SML.zip': (2011, 2015),
79
+ '2016-2022_SML.zip': (2016, 2022),
80
+ }),
81
+ 'regex_id': r'2025/[0-9]{4}-[0-9]{4}_SML',
82
+ 'num_regions': 81,
83
+ 'num_sectors': 50}
84
+
85
+ },
86
+
87
+ 'figaro':
88
+ {
89
+ '2025':
90
+ {'links':
91
+ {'product-by-product': _circabc_links('prod-by-prod', {
92
+ 2010: 'f6d2007e-34c9-420a-a106-8c5ea836c49d',
93
+ 2011: 'd27f7d7c-4006-4c36-8820-7d9990679323',
94
+ 2012: '7cea8b73-f166-47ff-9c27-f240794b57af',
95
+ 2013: 'd56c2ed9-0c4f-40a3-9bbc-1aee49f5bd45',
96
+ 2014: 'b5aa0e38-3d1b-496b-b180-bbd9bab3e153',
97
+ 2015: 'd037b772-8bf9-424f-97a4-9274d261e6cf',
98
+ 2016: '66369d21-8262-47d6-ad8a-dc536a2466d0',
99
+ 2017: 'd6848c38-9569-4848-bb68-4144fed38c71',
100
+ 2018: '557b9483-023e-497c-96c5-44ce807bd444',
101
+ 2019: 'fa0e9127-7a2b-4b74-bbad-1bc9e717197b',
102
+ 2020: '46f5665e-d2e1-4270-95a1-970da3d70d32',
103
+ 2021: 'a7e2919d-f084-4c35-8b21-e960a854e2bd',
104
+ 2022: '31484cee-43fe-45d4-a546-209e5898c6dd',
105
+ 2023: 'e213892a-afac-4d34-83e9-45e8a324e7e8',
106
+ }),
107
+ 'industry-by-industry': _circabc_links('ind-by-ind', {
108
+ 2010: 'fc80f855-d144-476e-b4bf-5cfba946819c',
109
+ 2011: '1bcb2624-04ed-43e1-8588-df6680ed352a',
110
+ 2012: '399671ad-cbb3-493e-ad5f-83e989f1eecc',
111
+ 2013: 'a2b4746d-1d11-4a44-ab1c-50ac956f0849',
112
+ 2014: 'beba57b2-2696-497a-b92f-2a5beca724c7',
113
+ 2015: '1a194b8c-6ea1-4bec-9c73-0cd599febcc3',
114
+ 2016: '2cdd74fc-0bce-4ae0-8bf2-34d34546d86d',
115
+ 2017: '4a11c796-4186-4cce-a02f-12d353fc5e59',
116
+ 2018: '7a57a374-2200-498c-bb5f-cee24202b0b8',
117
+ 2019: 'c3467617-8a00-44a0-9b6b-ccad8a2ab58d',
118
+ 2020: '4df668e1-2a8a-4e84-ae57-00309d8bc760',
119
+ 2021: '6736dea8-da14-450f-b212-a791baf238c8',
120
+ 2022: 'b20c339d-984f-413c-a499-54ff76beb90c',
121
+ 2023: '21557f49-1e94-431c-8523-d972fec020b8',
122
+ })},
123
+ 'regex_id': r'matrix_eu-ic-io_[a-z-]+_25ed_[0-9]{4}',
124
+ 'num_regions': 50,
125
+ 'num_sectors': 64},
126
+ },
127
+ 'exiobase':
128
+ {'3.81':
129
+ {'links':
130
+ {'product-by-product': {
131
+ 1995: 'https://zenodo.org/record/5589597/files/IOT_1995_pxp.zip?download=1',
132
+ 1996: 'https://zenodo.org/record/5589597/files/IOT_1996_pxp.zip?download=1',
133
+ 1997: 'https://zenodo.org/record/5589597/files/IOT_1997_pxp.zip?download=1',
134
+ 1998: 'https://zenodo.org/record/5589597/files/IOT_1998_pxp.zip?download=1',
135
+ 1999: 'https://zenodo.org/record/5589597/files/IOT_1999_pxp.zip?download=1',
136
+ 2000: 'https://zenodo.org/record/5589597/files/IOT_2000_pxp.zip?download=1',
137
+ 2001: 'https://zenodo.org/record/5589597/files/IOT_2001_pxp.zip?download=1',
138
+ 2002: 'https://zenodo.org/record/5589597/files/IOT_2002_pxp.zip?download=1',
139
+ 2003: 'https://zenodo.org/record/5589597/files/IOT_2003_pxp.zip?download=1',
140
+ 2004: 'https://zenodo.org/record/5589597/files/IOT_2004_pxp.zip?download=1',
141
+ 2005: 'https://zenodo.org/record/5589597/files/IOT_2005_pxp.zip?download=1',
142
+ 2006: 'https://zenodo.org/record/5589597/files/IOT_2006_pxp.zip?download=1',
143
+ 2007: 'https://zenodo.org/record/5589597/files/IOT_2007_pxp.zip?download=1',
144
+ 2008: 'https://zenodo.org/record/5589597/files/IOT_2008_pxp.zip?download=1',
145
+ 2009: 'https://zenodo.org/record/5589597/files/IOT_2009_pxp.zip?download=1',
146
+ 2010: 'https://zenodo.org/record/5589597/files/IOT_2010_pxp.zip?download=1',
147
+ 2011: 'https://zenodo.org/record/5589597/files/IOT_2011_pxp.zip?download=1',
148
+ 2012: 'https://zenodo.org/record/5589597/files/IOT_2012_pxp.zip?download=1',
149
+ 2013: 'https://zenodo.org/record/5589597/files/IOT_2013_pxp.zip?download=1',
150
+ 2014: 'https://zenodo.org/record/5589597/files/IOT_2014_pxp.zip?download=1',
151
+ 2015: 'https://zenodo.org/record/5589597/files/IOT_2015_pxp.zip?download=1',
152
+ 2016: 'https://zenodo.org/record/5589597/files/IOT_2016_pxp.zip?download=1',
153
+ 2017: 'https://zenodo.org/record/5589597/files/IOT_2017_pxp.zip?download=1',
154
+ 2018: 'https://zenodo.org/record/5589597/files/IOT_2018_pxp.zip?download=1',
155
+ 2019: 'https://zenodo.org/record/5589597/files/IOT_2019_pxp.zip?download=1',
156
+ 2020: 'https://zenodo.org/record/5589597/files/IOT_2020_pxp.zip?download=1',
157
+ 2021: 'https://zenodo.org/record/5589597/files/IOT_2021_pxp.zip?download=1',
158
+ 2022: 'https://zenodo.org/record/5589597/files/IOT_2022_pxp.zip?download=1',
159
+ },
160
+ 'industry-by-industry': {
161
+ 1995: 'https://zenodo.org/record/5589597/files/IOT_1995_ixi.zip?download=1',
162
+ 1996: 'https://zenodo.org/record/5589597/files/IOT_1996_ixi.zip?download=1',
163
+ 1997: 'https://zenodo.org/record/5589597/files/IOT_1997_ixi.zip?download=1',
164
+ 1998: 'https://zenodo.org/record/5589597/files/IOT_1998_ixi.zip?download=1',
165
+ 1999: 'https://zenodo.org/record/5589597/files/IOT_1999_ixi.zip?download=1',
166
+ 2000: 'https://zenodo.org/record/5589597/files/IOT_2000_ixi.zip?download=1',
167
+ 2001: 'https://zenodo.org/record/5589597/files/IOT_2001_ixi.zip?download=1',
168
+ 2002: 'https://zenodo.org/record/5589597/files/IOT_2002_ixi.zip?download=1',
169
+ 2003: 'https://zenodo.org/record/5589597/files/IOT_2003_ixi.zip?download=1',
170
+ 2004: 'https://zenodo.org/record/5589597/files/IOT_2004_ixi.zip?download=1',
171
+ 2005: 'https://zenodo.org/record/5589597/files/IOT_2005_ixi.zip?download=1',
172
+ 2006: 'https://zenodo.org/record/5589597/files/IOT_2006_ixi.zip?download=1',
173
+ 2007: 'https://zenodo.org/record/5589597/files/IOT_2007_ixi.zip?download=1',
174
+ 2008: 'https://zenodo.org/record/5589597/files/IOT_2008_ixi.zip?download=1',
175
+ 2009: 'https://zenodo.org/record/5589597/files/IOT_2009_ixi.zip?download=1',
176
+ 2010: 'https://zenodo.org/record/5589597/files/IOT_2010_ixi.zip?download=1',
177
+ 2011: 'https://zenodo.org/record/5589597/files/IOT_2011_ixi.zip?download=1',
178
+ 2012: 'https://zenodo.org/record/5589597/files/IOT_2012_ixi.zip?download=1',
179
+ 2013: 'https://zenodo.org/record/5589597/files/IOT_2013_ixi.zip?download=1',
180
+ 2014: 'https://zenodo.org/record/5589597/files/IOT_2014_ixi.zip?download=1',
181
+ 2015: 'https://zenodo.org/record/5589597/files/IOT_2015_ixi.zip?download=1',
182
+ 2016: 'https://zenodo.org/record/5589597/files/IOT_2016_ixi.zip?download=1',
183
+ 2017: 'https://zenodo.org/record/5589597/files/IOT_2017_ixi.zip?download=1',
184
+ 2018: 'https://zenodo.org/record/5589597/files/IOT_2018_ixi.zip?download=1',
185
+ 2019: 'https://zenodo.org/record/5589597/files/IOT_2019_ixi.zip?download=1',
186
+ 2020: 'https://zenodo.org/record/5589597/files/IOT_2020_ixi.zip?download=1',
187
+ 2021: 'https://zenodo.org/record/5589597/files/IOT_2021_ixi.zip?download=1',
188
+ 2022: 'https://zenodo.org/record/5589597/files/IOT_2022_ixi.zip?download=1',
189
+ }
190
+ },
191
+ 'regex_id': r'[A-Z]{3}_[0-9]{4}_[a-z]{3}',
192
+ 'num_regions': {'industry-by-industry': 49, 'product-by-product': 49},
193
+ 'num_sectors': {'industry-by-industry': 163, 'product-by-product': 200}}
194
+ }
195
+ }