TiMBA-Trade 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.
File without changes
@@ -0,0 +1,111 @@
1
+ from src.timba_trade.core.InputManager import InputManager
2
+ from src.timba_trade.paramters.hscodes import timba_commodity_list, timba_comcode
3
+ import src.timba_trade.paramters.paramters as p
4
+ from src.timba_trade.utils.helper import hs_to_fao_dict
5
+ from src.timba_trade.utils.logger import setup_logging
6
+
7
+ import logging
8
+ import pandas as pd
9
+
10
+ setup_logging()
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class BACIProcessor:
15
+ def __init__(self, df:pd.DataFrame, year: int = 2023,):
16
+ self.im = InputManager()
17
+ self.commodity_list = timba_commodity_list
18
+ self.year = year
19
+ self.df = df
20
+
21
+ @staticmethod
22
+ def _groupby_sum(df: pd.DataFrame, id_list: list, value_list: list) -> pd.DataFrame:
23
+ return df.groupby(id_list)[value_list].sum().reset_index()
24
+
25
+ def prepare_baci_data(self):
26
+ hs_to_fao_map = hs_to_fao_dict(self.commodity_list)
27
+
28
+ df = self.df.copy()
29
+ df["Commodity"] = df[p.COM_COLUMN].map(hs_to_fao_map)
30
+
31
+ return df[df[p.TIME_COLUMN] == self.year].dropna(subset=["Commodity"]).reset_index(drop=True)
32
+
33
+ def create_bilateral_trade(self,df:pd.DataFrame)->pd.DataFrame:
34
+ counterflow = df.rename(
35
+ columns={
36
+ p.REPORTER_COLUMN: p.PARTNER_COLUMN,
37
+ p.PARTNER_COLUMN: p.REPORTER_COLUMN,
38
+ p.QUANTITY_COLUMN: "Import",
39
+ }
40
+ )
41
+ result = df.merge(counterflow,on=p.BACIID,how="outer")
42
+ result = result.rename(columns={p.QUANTITY_COLUMN: "Export"})
43
+
44
+ return self._groupby_sum(df=result,id_list=p.BACIID,value_list=p.X_M_NAMES)
45
+
46
+ def _merge_trade_totals(self,df)->pd.DataFrame:
47
+ exporter_totals = self._groupby_sum(df=df,id_list=p.TOTALSID,value_list=p.X_M_NAMES)
48
+ importer_totals = exporter_totals.rename(columns={p.REPORTER_COLUMN: p.PARTNER_COLUMN})
49
+
50
+ total = df.merge(
51
+ right=exporter_totals,
52
+ how="outer",
53
+ on=p.TOTALSID,
54
+ suffixes=(
55
+ "_bilateral",
56
+ "_total_exporter",
57
+ ),
58
+ )
59
+
60
+ total = total.merge(
61
+ right=importer_totals,
62
+ how="outer",
63
+ on=p.TOTALSIMPORTID,
64
+ suffixes=("", "_total_importer"),
65
+ )
66
+
67
+ return total
68
+
69
+ def add_trade_totals(self,df:pd.DataFrame,map:dict)->pd.DataFrame:
70
+ total = self._merge_trade_totals(df)
71
+
72
+ total = total[
73
+ [
74
+ p.TIME_COLUMN,
75
+ p.REPORTER_COLUMN,
76
+ p.PARTNER_COLUMN,
77
+ "Commodity",
78
+ "Export_bilateral",
79
+ "Import_bilateral",
80
+ "Export_total_exporter",
81
+ "Import_total_exporter",
82
+ "Export",
83
+ "Import",
84
+ ]
85
+ ].rename(
86
+ columns={
87
+ p.REPORTER_COLUMN: "Reporter",
88
+ p.PARTNER_COLUMN: "Partner",
89
+ "Export": "Export_total_importer",
90
+ "Import": "Import_total_importer",
91
+ }
92
+ )
93
+
94
+ total["Period"] = 0
95
+ total["Commodity"] = total["Commodity"].map(map)
96
+
97
+ return total
98
+
99
+ def main(self)->pd.DataFrame:
100
+ baci_data = self.prepare_baci_data()
101
+ baci_data = self._groupby_sum(
102
+ df=baci_data,
103
+ id_list=p.BACIID,
104
+ value_list=["Quantity"]
105
+ )
106
+ bilateral_trade = self.add_trade_totals(
107
+ df=self.create_bilateral_trade(df=baci_data),
108
+ map=timba_comcode,
109
+ )
110
+
111
+ return bilateral_trade
@@ -0,0 +1,35 @@
1
+ from src.timba_trade.paramters.paths import INPUT_PATH, BITRADEPATH
2
+
3
+ import pandas as pd
4
+ import pickle
5
+ import gzip
6
+ import logging
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class InputManager:
12
+ def __init__(self):
13
+ pass
14
+
15
+ @staticmethod
16
+ def load_timba_results():
17
+ path = INPUT_PATH
18
+ pkl_files = list(path.glob("*.pkl"))
19
+
20
+ if not pkl_files:
21
+ raise FileNotFoundError(
22
+ f" No .pkl file found at: {path.resolve()}"
23
+ )
24
+
25
+ file = max(pkl_files, key=lambda f: f.stat().st_mtime)
26
+
27
+ with gzip.open(file, "rb") as pkl_file:
28
+ obj = pickle.load(pkl_file)
29
+
30
+ return obj["data_periods"]
31
+
32
+ @staticmethod
33
+ def load_baci_data():
34
+ return pd.read_parquet(BITRADEPATH)
35
+
@@ -0,0 +1,30 @@
1
+ import pandas as pd
2
+ from pathlib import Path
3
+ import pyarrow
4
+
5
+ from src.timba_trade.paramters.paths import OUTPUT_PATH
6
+
7
+ import logging
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class OutputManager:
12
+ def __init__(self):
13
+ pass
14
+
15
+ def save_df(self, df, file_name, max_attempts=10):
16
+ for i in range(max_attempts):
17
+ file = (file_name + ".csv") if i == 0 else f"{file_name}_({i}).csv"
18
+ try:
19
+ df.to_csv(OUTPUT_PATH / Path(file), index=False)
20
+ logger.info(f"CSV file saved at: {OUTPUT_PATH / Path(file)}")
21
+ return file
22
+ except PermissionError:
23
+ if i == max_attempts - 1:
24
+ raise
25
+ continue
26
+
27
+ def save_parquet(self, df, file_name):
28
+ file = Path(file_name + ".parquet")
29
+ df.to_parquet(OUTPUT_PATH / file)
30
+ logger.info(f"Parquet saved at: {OUTPUT_PATH / Path(file)}")
@@ -0,0 +1,107 @@
1
+ from src.timba_trade.core.TiMBADataProcessor import TiMBADataProcessor
2
+ from src.timba_trade.core.BACIDataProcessor import BACIProcessor
3
+ from src.timba_trade.core.InputManager import InputManager
4
+ from src.timba_trade.logic.CalculationManager import CalcManager
5
+ from src.timba_trade.logic.RAS import RAS
6
+ from src.timba_trade.core.OutputManager import OutputManager
7
+
8
+ import time
9
+ import pandas as pd
10
+ import logging
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class ProcessManager:
15
+
16
+ def __init__(self):
17
+ self.data = None
18
+ self.country_data = None
19
+ self.partner_data = None
20
+ self.countries = None
21
+ self.commodities = None
22
+ self.periods = None
23
+ self.dataset = None
24
+ self.result = None
25
+ self.start_time = None
26
+ self.im = InputManager()
27
+ self.om = OutputManager()
28
+
29
+ def start_process(self, process_description:str):
30
+ self.start_time = time.time()
31
+ logger.info(process_description)
32
+
33
+
34
+ def end_process(self, process_description):
35
+ if self.start_time is None:
36
+ raise ValueError("The process has not been started. Use `start_process` first.")
37
+
38
+ end_time = time.time()
39
+ elapsed_time = round(end_time - self.start_time, 2)
40
+ logger.info(
41
+ f"{process_description} in {elapsed_time:.1f} seconds"
42
+ )
43
+
44
+ def load_timba_process(self, data:pd.DataFrame|None)->pd.DataFrame:
45
+ self.start_process("Beginn to load TiMBA data")
46
+ if data is None:
47
+ data = self.im.load_timba_results()
48
+ tdp = TiMBADataProcessor(data)
49
+ timba_data = tdp.main()
50
+ self.end_process("TiMBA data loaded")
51
+
52
+ return timba_data
53
+
54
+ def load_baci_process(self)->pd.DataFrame:
55
+ self.start_process("Beginn to load BACI data")
56
+ baci_data = self.im.load_baci_data()
57
+ bdp = BACIProcessor(df=baci_data)
58
+ baci_data = bdp.main()
59
+ self.end_process("BACI data loaded")
60
+
61
+ return baci_data
62
+
63
+ def main_process(self, td:pd.DataFrame|None)->pd.DataFrame:
64
+ timba_data = self.load_timba_process(data=td)
65
+ fao_df = CalcManager(data=timba_data).main().copy()
66
+ fao_df = fao_df[["Reporter","Partner","Commodity","Period","X_q_o","M_q_p","P_share_growth"]]
67
+
68
+ bt_df = self.load_baci_process()[["Reporter","Partner","Commodity","Period","Export_bilateral"]]
69
+ bt_df["Export_bilateral"] /= 1000
70
+
71
+ self.start_process(f"Beginn the RAS process")
72
+
73
+ fao_df = fao_df.loc[~fao_df["Commodity"].isin([89, 90, 93])]
74
+ bt_df = bt_df.loc[~bt_df["Commodity"].isin([89, 90, 93])]
75
+
76
+ merge_keys = ["Reporter","Partner", "Commodity", "Period"]
77
+ bt_result = pd.DataFrame()
78
+ for period in fao_df.Period.unique():
79
+ logger.info(f" Period: {period}")
80
+ fp = fao_df[fao_df.Period==period]
81
+
82
+ bt_df = bt_df[["Reporter","Partner","Commodity","Export_bilateral"]]
83
+ bt_df["Period"]=period
84
+ bt_df = bt_df.drop_duplicates()
85
+ bt_df = bt_df.dropna().reset_index(drop=True)
86
+ bt_df = bt_df[bt_df['Reporter'] != 'RoW']
87
+ bt_df = bt_df[bt_df['Partner'] != 'RoW']
88
+
89
+ ras_df = fp.merge(
90
+ bt_df,
91
+ on=merge_keys,
92
+ how="left"
93
+ )
94
+ ras_df["Export_bilateral"] *= ras_df["P_share_growth"]
95
+
96
+ bt_df = RAS().fit(ras_df)
97
+ bt_result = pd.concat([bt_result,bt_df]).reset_index(drop=True)
98
+
99
+ self.om.save_parquet(
100
+ df=bt_result,
101
+ file_name="TiMBA_bilateral_results"
102
+ )
103
+
104
+ self.end_process(f"End of RAS process")
105
+
106
+ return bt_result
107
+
@@ -0,0 +1,217 @@
1
+ from src.timba_trade.paramters.country_info import country_dict
2
+ import src.timba_trade.paramters.paramters as params
3
+
4
+ import pandas as pd
5
+ import numpy as np
6
+ import xarray as xr
7
+ import logging
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class TiMBADataProcessor:
12
+ def __init__(self, data):
13
+ self.data = data
14
+
15
+ def _rename_columns(self):
16
+ self.data = self.data.rename(columns=params.COLUMN_MAPPING)
17
+
18
+ def _add_iso_codes(self):
19
+ self.data["Country"] = self.data["RegionCode"].map(country_dict)
20
+
21
+ self.data = (
22
+ self.data
23
+ .dropna(subset=["Country"])
24
+ .reset_index(drop=True)
25
+ )
26
+
27
+ self.data = self.data[
28
+ [
29
+ "Country",
30
+ "Commodity",
31
+ "domain",
32
+ "Period",
33
+ "p",
34
+ "q",
35
+ ]
36
+ ]
37
+
38
+ def _aggregate_domains(self):
39
+ self.data = self.data[
40
+ ~self.data["domain"].isin(params.DOMAINS_TO_REMOVE)
41
+ ].copy()
42
+
43
+ self.data["domain"] = self.data["domain"].replace(
44
+ params.DOMAIN_MAPPING
45
+ )
46
+
47
+ self.data = (
48
+ self.data
49
+ .groupby(
50
+ params.GROUP_COLUMNS,
51
+ as_index=False,
52
+ observed=True
53
+ )
54
+ .agg(
55
+ p=("p", "max"),
56
+ q=("q", "max"),
57
+ )
58
+ )
59
+
60
+ def _downcasting(self):
61
+ self.data["Country"] = self.data["Country"].astype("category")
62
+ self.data["Commodity"] = self.data["Commodity"].astype("category")
63
+ self.data["domain"] = self.data["domain"].astype("category")
64
+ self.data["p"] = self.data["p"].astype("float32")
65
+ self.data["q"] = self.data["q"].astype("float32")
66
+ self.data["Period"] = self.data["Period"].astype("int16")
67
+
68
+ def _pivot_domains(self):
69
+ self.data = self.data.pivot(
70
+ index=params.INDEX_COLUMNS,
71
+ columns="domain",
72
+ values=params.VALUE_COLUMNS,
73
+ )
74
+
75
+ self.data.columns = [
76
+ f"{domain}_{variable}"
77
+ for variable, domain in self.data.columns
78
+ ]
79
+
80
+ self.data = self.data.reset_index()
81
+
82
+ def _sort_data(self):
83
+ self.data = (
84
+ self.data
85
+ .sort_values(params.GROUP_COLUMNS)
86
+ .reset_index(drop=True)
87
+ )
88
+
89
+ def _prepare_timba_for_biltrade(self):
90
+ self.country_data = self.data[params.COUNTRY_COLUMNS].copy()
91
+
92
+ self.partner_data = self.data[params.PARTNER_COLUMNS].rename(
93
+ columns={"Country": "Partner"}
94
+ )
95
+
96
+ self.countries = np.sort(self.data["Country"].unique())
97
+ self.commodities = np.sort(self.data["Commodity"].unique())
98
+ self.periods = np.sort(self.data["Period"].unique())
99
+
100
+ def _to_dense_array(self, df: pd.DataFrame, dim_name: str, value_col: str) -> xr.DataArray:
101
+ series = (
102
+ df[[dim_name, "Commodity", "Period", value_col]]
103
+ .set_index([dim_name, "Commodity", "Period"])[value_col]
104
+ .astype(np.float32)
105
+ )
106
+
107
+ array = xr.DataArray.from_series(series)
108
+ array = array.reindex(
109
+ {
110
+ dim_name: self.countries,
111
+ "Commodity": self.commodities,
112
+ "Period": self.periods,
113
+ }
114
+ )
115
+
116
+ return array
117
+
118
+ def _to_dense_array(self, df: pd.DataFrame, dim_name: str, value_col: str) -> xr.DataArray:
119
+ series = (
120
+ df[[dim_name, "Commodity", "Period", value_col]]
121
+ .set_index([dim_name, "Commodity", "Period"])[value_col]
122
+ .astype(np.float32)
123
+ )
124
+ array = xr.DataArray.from_series(series)
125
+ array = array.reindex(
126
+ {
127
+ dim_name: self.countries,
128
+ "Commodity": self.commodities,
129
+ "Period": self.periods,
130
+ }
131
+ )
132
+ return array
133
+
134
+ @staticmethod
135
+ def _log_of_price_share(data):
136
+ with np.errstate(divide="ignore"):
137
+ data["P_share"] = np.log(data["P_p_o"] / data["P_p_p"])
138
+ return data
139
+
140
+ def _build_dataset(self):
141
+ self.dataset = xr.Dataset(
142
+ {
143
+ "P_p_o": self._to_dense_array(self.country_data, "Country", "P_p"),
144
+ "P_q_o": self._to_dense_array(self.country_data, "Country", "P_q"),
145
+ "M_p_o": self._to_dense_array(self.country_data, "Country", "M_p"),
146
+ "M_q_o": self._to_dense_array(self.country_data, "Country", "M_q"),
147
+ "X_p_o": self._to_dense_array(self.country_data, "Country", "X_p"),
148
+ "X_q_o": self._to_dense_array(self.country_data, "Country", "X_q"),
149
+ "P_p_p": self._to_dense_array(self.partner_data, "Partner", "P_p"),
150
+ "P_q_p": self._to_dense_array(self.partner_data, "Partner", "P_q"),
151
+ "X_p_p": self._to_dense_array(self.partner_data, "Partner", "X_p"),
152
+ "X_q_p": self._to_dense_array(self.partner_data, "Partner", "X_q"),
153
+ "M_q_p": self._to_dense_array(self.partner_data, "Partner", "M_q"),
154
+ }
155
+ )
156
+
157
+ self._log_of_price_share(data=self.dataset)
158
+ self._add_lags()
159
+
160
+ def _add_lags(self):
161
+ lagged = self.dataset.shift(Period=1)
162
+ lagged = lagged.rename(
163
+ {var: f"{var}_lag" for var in lagged.data_vars}
164
+ )
165
+
166
+ self.dataset = xr.merge([self.dataset, lagged])
167
+
168
+ def _process_all(self):
169
+ chunk_size = params.PROCESS_CHUNK_SIZE
170
+ n_countries = len(self.countries)
171
+
172
+ chunks = []
173
+
174
+ for start in range(0, n_countries, chunk_size):
175
+ end = min(start + chunk_size, n_countries)
176
+
177
+ chunk_ds = self.dataset.isel(Country=slice(start, end))
178
+
179
+ chunk_df = (
180
+ chunk_ds.to_dataframe()
181
+ .reset_index()
182
+ )
183
+
184
+ chunks.append(chunk_df)
185
+
186
+ self.result = pd.concat(chunks, ignore_index=True)
187
+
188
+ self.result = self.result[
189
+ [
190
+ "Country", "Partner", "Commodity", "Period",
191
+ "P_p_o", "P_p_o_lag",
192
+ "P_q_o", "P_q_o_lag",
193
+ "P_p_p", "P_p_p_lag",
194
+ "P_q_p", "P_q_p_lag",
195
+ "M_p_o", "M_p_o_lag",
196
+ "M_q_o", "M_q_o_lag",
197
+ "M_q_p", "M_q_p_lag",
198
+ "X_p_o", "X_p_o_lag",
199
+ "X_q_o", "X_q_o_lag",
200
+ "X_p_p", "X_p_p_lag",
201
+ "X_q_p", "X_q_p_lag",
202
+ "P_share", "P_share_lag",
203
+ ]
204
+ ]
205
+
206
+ def main(self) -> pd.DataFrame:
207
+ self._rename_columns()
208
+ self._add_iso_codes()
209
+ self._aggregate_domains()
210
+ self._sort_data()
211
+ self._downcasting()
212
+ self._pivot_domains()
213
+ self._prepare_timba_for_biltrade()
214
+ self._build_dataset()
215
+ self._process_all()
216
+
217
+ return self.result
File without changes
@@ -0,0 +1,52 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+
5
+ class CalcManager:
6
+ def __init__(self, data:pd.DataFrame):
7
+ self.AE = 1
8
+ self.data = data
9
+
10
+ def _calc_price_share(self):
11
+ df = self.data
12
+ df["P_share_growth"] = df.P_share - df.P_share_lag + 1
13
+ df["LD"] = df.P_q_o - df.X_q_o + df.M_q_o
14
+ df["LD_lag"] = df.P_q_o_lag - df.X_q_o_lag + df.M_q_o_lag
15
+ df["LD_growth"] = df.LD / df.LD_lag
16
+
17
+ df.rename(
18
+ columns={
19
+ "Country":"Reporter"
20
+ },
21
+ inplace=True
22
+ )
23
+
24
+ self.results = df[[
25
+ "Reporter", "Partner", "Commodity", "Period",
26
+ "X_q_o", "M_q_o", "X_q_p", "M_q_p",
27
+ "P_share_growth","LD_growth"
28
+ ]]
29
+
30
+ def clip_shares(self,cols:list,lb_fix:float=None,ub_fix:float=None):
31
+ df = self.results
32
+ for col in cols:
33
+ df[col] = df[col].replace([np.inf, -np.inf, np.nan], 1)
34
+ q1 = df[col].quantile(0.25)
35
+ q3 = df[col].quantile(0.75)
36
+ iqr = (q3 - q1)
37
+
38
+ lb = lb_fix if lb_fix is not None else q1 - iqr * 5
39
+ ub = ub_fix if ub_fix is not None else q3 + iqr * 5
40
+
41
+ df[col] = df[col].clip(lower=lb, upper=ub)
42
+
43
+ def _combined_effect(self):
44
+ self.results["M_growth"] = self.results["LD_growth"] * self.results["P_share_growth"] ** (self.AE)
45
+
46
+ def main(self)->pd.DataFrame:
47
+ self._calc_price_share()
48
+ self.clip_shares(cols=["P_share_growth"],lb_fix=0.5,ub_fix=2)
49
+ self.clip_shares(cols=["LD_growth"])
50
+ self._combined_effect()
51
+ #self.clip_shares(cols=["M_growth"])
52
+ return self.results