pr-imports 0.2.0__tar.gz
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.
- pr_imports-0.2.0/PKG-INFO +11 -0
- pr_imports-0.2.0/README.md +0 -0
- pr_imports-0.2.0/pyproject.toml +19 -0
- pr_imports-0.2.0/src/pr_imports/__init__.py +7 -0
- pr_imports-0.2.0/src/pr_imports/graphs.py +0 -0
- pr_imports-0.2.0/src/pr_imports/jp_imports.py +359 -0
- pr_imports-0.2.0/src/pr_imports/resources/code_agr.json +131 -0
- pr_imports-0.2.0/src/pr_imports/utils.py +411 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: pr-imports
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Dist: censusforge>=1.2.0
|
|
6
|
+
Requires-Dist: duckdb>=1.4.5
|
|
7
|
+
Requires-Dist: jp-tools>=0.5.8
|
|
8
|
+
Requires-Dist: polars>=1.36.1
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pr-imports"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"censusforge>=1.2.0",
|
|
9
|
+
"duckdb>=1.4.5",
|
|
10
|
+
"jp-tools>=0.5.8",
|
|
11
|
+
"polars>=1.36.1",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
pr-imports = "pr_imports:main"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["uv_build>=0.11.30,<0.12.0"]
|
|
19
|
+
build-backend = "uv_build"
|
|
File without changes
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import importlib.resources as resources
|
|
2
|
+
from datetime import datetime as dt
|
|
3
|
+
|
|
4
|
+
import polars as pl
|
|
5
|
+
|
|
6
|
+
from .utils import TradeUtils
|
|
7
|
+
|
|
8
|
+
LEVEL_GROUPS = {
|
|
9
|
+
"total": [],
|
|
10
|
+
"naics": ["naics"],
|
|
11
|
+
"hts": ["hts_code"],
|
|
12
|
+
"country": ["country"],
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
TIME_GROUPS = {
|
|
16
|
+
"yearly": ["year"],
|
|
17
|
+
"fiscal": ["fiscal_year"],
|
|
18
|
+
"qtr": ["year", "qtr"],
|
|
19
|
+
"monthly": ["year", "month"],
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class JPTrade(TradeUtils):
|
|
24
|
+
"""
|
|
25
|
+
Data processing class for the various data sources in DataPull.
|
|
26
|
+
Optimized to dynamically aggregate metrics using a configuration-driven design.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
saving_dir: str = "data/",
|
|
32
|
+
log_file: str = "data.log",
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Initialize the DataProcess class.
|
|
36
|
+
"""
|
|
37
|
+
super().__init__(saving_dir, log_file)
|
|
38
|
+
self.agr_file = str(
|
|
39
|
+
resources.files("jp_imports").joinpath("resources/code_agr.json")
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def process_int_jp(
|
|
43
|
+
self,
|
|
44
|
+
level: str,
|
|
45
|
+
time_frame: str,
|
|
46
|
+
datetime: str = "",
|
|
47
|
+
agriculture_filter: bool = False,
|
|
48
|
+
level_filter: str = "",
|
|
49
|
+
) -> pl.DataFrame:
|
|
50
|
+
"""
|
|
51
|
+
Process the data for Puerto Rico Statistics Institute provided to JP.
|
|
52
|
+
"""
|
|
53
|
+
df = self.pull_int_jp()
|
|
54
|
+
|
|
55
|
+
if agriculture_filter:
|
|
56
|
+
df = df.filter(pl.col("agri_prod") == 1)
|
|
57
|
+
|
|
58
|
+
# Unified taxonomy filtering
|
|
59
|
+
if level in ["hts", "naics", "country"]:
|
|
60
|
+
level_map = {"hts": "hts_code", "naics": "naics", "country": "country"}
|
|
61
|
+
filter_col = level_map[level]
|
|
62
|
+
|
|
63
|
+
df = df.filter(pl.col(filter_col).str.starts_with(level_filter))
|
|
64
|
+
if df.is_empty():
|
|
65
|
+
raise ValueError(f"Invalid {level.upper()} code: {level_filter}")
|
|
66
|
+
|
|
67
|
+
# Streamlined date routing
|
|
68
|
+
if datetime:
|
|
69
|
+
times = datetime.split("+")
|
|
70
|
+
if len(times) == 2:
|
|
71
|
+
start_date = dt.strptime(times[0], "%Y-%m-%d")
|
|
72
|
+
end_date = dt.strptime(times[1], "%Y-%m-%d")
|
|
73
|
+
df = df.filter(pl.col("date").is_between(start_date, end_date))
|
|
74
|
+
elif len(times) == 1:
|
|
75
|
+
df = df.filter(pl.col("date").dt.year() == int(datetime))
|
|
76
|
+
else:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
'Invalid time format. Use "date" or "start_date+end_date"'
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
df = self.conversion(df)
|
|
82
|
+
|
|
83
|
+
return self.process_data(time_frame=time_frame, level=level, base=df)
|
|
84
|
+
|
|
85
|
+
def process_data(
|
|
86
|
+
self, time_frame: str, level: str, base: pl.DataFrame
|
|
87
|
+
) -> pl.DataFrame:
|
|
88
|
+
"""
|
|
89
|
+
Process the data based on dynamic groupings without logic duplication.
|
|
90
|
+
"""
|
|
91
|
+
if time_frame not in TIME_GROUPS or level not in LEVEL_GROUPS:
|
|
92
|
+
raise ValueError(
|
|
93
|
+
f"Invalid combination layout requested: {time_frame=}, {level=}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
group_by_keys = TIME_GROUPS[time_frame] + LEVEL_GROUPS[level]
|
|
97
|
+
|
|
98
|
+
df = self.filter_data(base, group_by_keys)
|
|
99
|
+
|
|
100
|
+
coalesce_exprs = []
|
|
101
|
+
for col in group_by_keys:
|
|
102
|
+
right_col = f"{col}_right"
|
|
103
|
+
if right_col in df.columns:
|
|
104
|
+
coalesce_exprs.append(
|
|
105
|
+
pl.when(pl.col(col).is_null())
|
|
106
|
+
.then(pl.col(right_col))
|
|
107
|
+
.otherwise(pl.col(col))
|
|
108
|
+
.alias(col)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if coalesce_exprs:
|
|
112
|
+
df = df.with_columns(coalesce_exprs)
|
|
113
|
+
|
|
114
|
+
df = df.drop([c for c in df.columns if c.endswith("_right")])
|
|
115
|
+
|
|
116
|
+
target_metrics = ["imports", "exports", "imports_qty", "exports_qty"]
|
|
117
|
+
df = (
|
|
118
|
+
df.with_columns(pl.col(target_metrics).fill_null(0))
|
|
119
|
+
.sort(group_by_keys)
|
|
120
|
+
.with_columns(
|
|
121
|
+
net_exports=pl.col("exports") - pl.col("imports"),
|
|
122
|
+
net_qty=pl.col("exports_qty") - pl.col("imports_qty"),
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return df
|
|
127
|
+
|
|
128
|
+
def process_price(self, agriculture_filter: bool = False) -> pl.DataFrame:
|
|
129
|
+
"""
|
|
130
|
+
Calculate rolling price statistics on top of monthly item structures.
|
|
131
|
+
"""
|
|
132
|
+
df = self.process_int_jp(
|
|
133
|
+
time_frame="monthly", level="hts", agriculture_filter=agriculture_filter
|
|
134
|
+
)
|
|
135
|
+
df = df.with_columns(pl.col("imports_qty", "exports_qty").replace(0, 1))
|
|
136
|
+
df = df.with_columns(hs4=pl.col("hts_code").str.slice(0, 4))
|
|
137
|
+
|
|
138
|
+
df = df.group_by(pl.col("hs4", "month", "year")).agg(
|
|
139
|
+
pl.col("imports").sum().alias("imports"),
|
|
140
|
+
pl.col("exports").sum().alias("exports"),
|
|
141
|
+
pl.col("imports_qty").sum().alias("imports_qty"),
|
|
142
|
+
pl.col("exports_qty").sum().alias("exports_qty"),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
df = df.with_columns(
|
|
146
|
+
price_imports=pl.col("imports") / pl.col("imports_qty"),
|
|
147
|
+
price_exports=pl.col("exports") / pl.col("exports_qty"),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
df = df.with_columns(date=pl.datetime(pl.col("year"), pl.col("month"), 1)).sort(
|
|
151
|
+
"date"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Rolling Statistical Engine
|
|
155
|
+
results = df.with_columns(
|
|
156
|
+
pl.col("price_imports")
|
|
157
|
+
.rolling_mean(window_size=3, min_samples=1)
|
|
158
|
+
.over("hs4")
|
|
159
|
+
.alias("moving_price_imports"),
|
|
160
|
+
pl.col("price_exports")
|
|
161
|
+
.rolling_mean(window_size=3, min_samples=1)
|
|
162
|
+
.over("hs4")
|
|
163
|
+
.alias("moving_price_exports"),
|
|
164
|
+
pl.col("price_imports")
|
|
165
|
+
.rolling_std(window_size=3, min_samples=1)
|
|
166
|
+
.over("hs4")
|
|
167
|
+
.alias("moving_price_imports_std"),
|
|
168
|
+
pl.col("price_exports")
|
|
169
|
+
.rolling_std(window_size=3, min_samples=1)
|
|
170
|
+
.over("hs4")
|
|
171
|
+
.alias("moving_price_exports_std"),
|
|
172
|
+
).with_columns(
|
|
173
|
+
pl.col("moving_price_imports")
|
|
174
|
+
.rank("ordinal")
|
|
175
|
+
.over("date")
|
|
176
|
+
.alias("rank_imports")
|
|
177
|
+
.cast(pl.Int64),
|
|
178
|
+
pl.col("moving_price_exports")
|
|
179
|
+
.rank("ordinal")
|
|
180
|
+
.over("date")
|
|
181
|
+
.alias("rank_exports")
|
|
182
|
+
.cast(pl.Int64),
|
|
183
|
+
upper_band_imports=pl.col("moving_price_imports")
|
|
184
|
+
+ 2 * pl.col("moving_price_imports_std"),
|
|
185
|
+
lower_band_imports=pl.col("moving_price_imports")
|
|
186
|
+
- 2 * pl.col("moving_price_imports_std"),
|
|
187
|
+
upper_band_exports=pl.col("moving_price_exports")
|
|
188
|
+
+ 2 * pl.col("moving_price_exports_std"),
|
|
189
|
+
lower_band_exports=pl.col("moving_price_exports")
|
|
190
|
+
- 2 * pl.col("moving_price_exports_std"),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
results = df.join(results, on=["date", "hs4"], how="left", validate="1:1")
|
|
194
|
+
|
|
195
|
+
# Clean up overlap column names after explicit join validation
|
|
196
|
+
results = results.with_columns(
|
|
197
|
+
year=pl.when(pl.col("year").is_null())
|
|
198
|
+
.then(pl.col("year_right"))
|
|
199
|
+
.otherwise(pl.col("year")),
|
|
200
|
+
month=pl.when(pl.col("month").is_null())
|
|
201
|
+
.then(pl.col("month_right"))
|
|
202
|
+
.otherwise(pl.col("month")),
|
|
203
|
+
imports=pl.when(pl.col("imports").is_null())
|
|
204
|
+
.then(pl.col("imports_right"))
|
|
205
|
+
.otherwise(pl.col("imports")),
|
|
206
|
+
exports=pl.when(pl.col("exports").is_null())
|
|
207
|
+
.then(pl.col("exports_right"))
|
|
208
|
+
.otherwise(pl.col("exports")),
|
|
209
|
+
price_imports=pl.when(pl.col("price_imports").is_null())
|
|
210
|
+
.then(pl.col("price_imports_right"))
|
|
211
|
+
.otherwise(pl.col("price_imports")),
|
|
212
|
+
price_exports=pl.when(pl.col("price_exports").is_null())
|
|
213
|
+
.then(pl.col("price_exports_right"))
|
|
214
|
+
.otherwise(pl.col("price_exports")),
|
|
215
|
+
imports_qty=pl.when(pl.col("imports_qty").is_null())
|
|
216
|
+
.then(pl.col("exports_qty_right"))
|
|
217
|
+
.otherwise(pl.col("imports_qty")),
|
|
218
|
+
exports_qty=pl.when(pl.col("exports_qty").is_null())
|
|
219
|
+
.then(pl.col("exports_qty"))
|
|
220
|
+
.otherwise(pl.col("exports_qty")),
|
|
221
|
+
).drop([c for c in results.columns if c.endswith("_right")])
|
|
222
|
+
|
|
223
|
+
# Track month shifts for sequential year-over-year deltas
|
|
224
|
+
results = results.with_columns(
|
|
225
|
+
pl.col("moving_price_imports")
|
|
226
|
+
.pct_change()
|
|
227
|
+
.over("date", "hs4")
|
|
228
|
+
.alias("pct_change_imports")
|
|
229
|
+
).sort(by=["date", "hs4"])
|
|
230
|
+
|
|
231
|
+
results = results.with_columns(
|
|
232
|
+
pl.when(pl.col("date").dt.year() > 1)
|
|
233
|
+
.then(pl.col("moving_price_imports").shift(12))
|
|
234
|
+
.otherwise(None)
|
|
235
|
+
.alias("prev_year_imports"),
|
|
236
|
+
pl.when(pl.col("date").dt.year() > 1)
|
|
237
|
+
.then(pl.col("moving_price_exports").shift(12))
|
|
238
|
+
.otherwise(None)
|
|
239
|
+
.alias("prev_year_exports"),
|
|
240
|
+
pl.when(pl.col("date").dt.year() > 1)
|
|
241
|
+
.then(pl.col("rank_imports").shift(12))
|
|
242
|
+
.otherwise(None)
|
|
243
|
+
.alias("prev_year_rank_imports"),
|
|
244
|
+
pl.when(pl.col("date").dt.year() > 1)
|
|
245
|
+
.then(pl.col("rank_exports").shift(12))
|
|
246
|
+
.otherwise(None)
|
|
247
|
+
.alias("prev_year_rank_exports"),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
results = results.with_columns(
|
|
251
|
+
(
|
|
252
|
+
(pl.col("moving_price_imports") - pl.col("prev_year_imports"))
|
|
253
|
+
/ pl.col("prev_year_imports")
|
|
254
|
+
).alias("pct_change_imports_year_over_year"),
|
|
255
|
+
(
|
|
256
|
+
(pl.col("moving_price_exports") - pl.col("prev_year_exports"))
|
|
257
|
+
/ pl.col("prev_year_exports")
|
|
258
|
+
).alias("pct_change_exports_year_over_year"),
|
|
259
|
+
(pl.col("rank_imports") - pl.col("prev_year_rank_imports")).alias(
|
|
260
|
+
"rank_imports_change_year_over_year"
|
|
261
|
+
),
|
|
262
|
+
(
|
|
263
|
+
pl.col("rank_exports").cast(pl.Int64)
|
|
264
|
+
- pl.col("prev_year_rank_exports").cast(pl.Int64)
|
|
265
|
+
).alias("rank_exports_change_year_over_year"),
|
|
266
|
+
).sort(by=["date", "hs4"])
|
|
267
|
+
|
|
268
|
+
return results
|
|
269
|
+
|
|
270
|
+
def conversion(self, df: pl.DataFrame) -> pl.DataFrame:
|
|
271
|
+
"""
|
|
272
|
+
Convert the data to the correct units (kg).
|
|
273
|
+
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
df: pl.LazyFrame
|
|
277
|
+
Data to convert.
|
|
278
|
+
|
|
279
|
+
Returns
|
|
280
|
+
-------
|
|
281
|
+
pl.LazyFrame
|
|
282
|
+
Converted data.
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
df = df.with_columns(pl.col("qty_1", "qty_2").fill_null(strategy="zero"))
|
|
286
|
+
df = df.with_columns(
|
|
287
|
+
conv_1=pl.when(pl.col("unit_1").str.to_lowercase() == "kg")
|
|
288
|
+
.then(pl.col("qty_1") * 1)
|
|
289
|
+
.when(pl.col("unit_1").str.to_lowercase() == "l")
|
|
290
|
+
.then(pl.col("qty_1") * 1)
|
|
291
|
+
.when(pl.col("unit_1").str.to_lowercase() == "doz")
|
|
292
|
+
.then(pl.col("qty_1") / 0.756)
|
|
293
|
+
.when(pl.col("unit_1").str.to_lowercase() == "m3")
|
|
294
|
+
.then(pl.col("qty_1") * 1560)
|
|
295
|
+
.when(pl.col("unit_1").str.to_lowercase() == "t")
|
|
296
|
+
.then(pl.col("qty_1") * 907.185)
|
|
297
|
+
.when(pl.col("unit_1").str.to_lowercase() == "kts")
|
|
298
|
+
.then(pl.col("qty_1") * 1)
|
|
299
|
+
.when(pl.col("unit_1").str.to_lowercase() == "pfl")
|
|
300
|
+
.then(pl.col("qty_1") * 0.789)
|
|
301
|
+
.when(pl.col("unit_1").str.to_lowercase() == "gm")
|
|
302
|
+
.then(pl.col("qty_1") * 1000)
|
|
303
|
+
.otherwise(pl.col("qty_1")),
|
|
304
|
+
conv_2=pl.when(pl.col("unit_2").str.to_lowercase() == "kg")
|
|
305
|
+
.then(pl.col("qty_2") * 1)
|
|
306
|
+
.when(pl.col("unit_2").str.to_lowercase() == "l")
|
|
307
|
+
.then(pl.col("qty_2") * 1)
|
|
308
|
+
.when(pl.col("unit_2").str.to_lowercase() == "doz")
|
|
309
|
+
.then(pl.col("qty_2") / 0.756)
|
|
310
|
+
.when(pl.col("unit_2").str.to_lowercase() == "m3")
|
|
311
|
+
.then(pl.col("qty_2") * 1560)
|
|
312
|
+
.when(pl.col("unit_2").str.to_lowercase() == "t")
|
|
313
|
+
.then(pl.col("qty_2") * 907.185)
|
|
314
|
+
.when(pl.col("unit_2").str.to_lowercase() == "kts")
|
|
315
|
+
.then(pl.col("qty_2") * 1)
|
|
316
|
+
.when(pl.col("unit_2").str.to_lowercase() == "pfl")
|
|
317
|
+
.then(pl.col("qty_2") * 0.789)
|
|
318
|
+
.when(pl.col("unit_2").str.to_lowercase() == "gm")
|
|
319
|
+
.then(pl.col("qty_2") * 1000)
|
|
320
|
+
.otherwise(pl.col("qty_2")),
|
|
321
|
+
qtr=pl.when(
|
|
322
|
+
(pl.col("date").dt.month() >= 1) & (pl.col("date").dt.month() <= 3)
|
|
323
|
+
)
|
|
324
|
+
.then(1)
|
|
325
|
+
.when((pl.col("date").dt.month() >= 4) & (pl.col("date").dt.month() <= 6))
|
|
326
|
+
.then(2)
|
|
327
|
+
.when((pl.col("date").dt.month() >= 7) & (pl.col("date").dt.month() <= 9))
|
|
328
|
+
.then(3)
|
|
329
|
+
.when((pl.col("date").dt.month() >= 10) & (pl.col("date").dt.month() <= 12))
|
|
330
|
+
.then(4),
|
|
331
|
+
fiscal_year=pl.when(pl.col("date").dt.month() > 6)
|
|
332
|
+
.then(pl.col("date").dt.year() + 1)
|
|
333
|
+
.otherwise(pl.col("date").dt.year())
|
|
334
|
+
.alias("fiscal_year"),
|
|
335
|
+
month=pl.col("date").dt.month(),
|
|
336
|
+
year=pl.col("date").dt.year(),
|
|
337
|
+
).with_columns(qty=pl.col("conv_1") + pl.col("conv_2"))
|
|
338
|
+
return df
|
|
339
|
+
|
|
340
|
+
def filter_data(self, df: pl.DataFrame, filter: list) -> pl.DataFrame:
|
|
341
|
+
"""
|
|
342
|
+
Filter the data based on the filter list.
|
|
343
|
+
"""
|
|
344
|
+
df = df.filter(pl.col("hts_code").is_not_null())
|
|
345
|
+
imports = (
|
|
346
|
+
df.filter(pl.col("trade_id") == 1)
|
|
347
|
+
.group_by(filter)
|
|
348
|
+
.agg(pl.sum("data", "qty"))
|
|
349
|
+
.sort(filter)
|
|
350
|
+
.rename({"data": "imports", "qty": "imports_qty"})
|
|
351
|
+
)
|
|
352
|
+
exports = (
|
|
353
|
+
df.filter(pl.col("trade_id") == 2)
|
|
354
|
+
.group_by(filter)
|
|
355
|
+
.agg(pl.sum("data", "qty"))
|
|
356
|
+
.sort(filter)
|
|
357
|
+
.rename({"data": "exports", "qty": "exports_qty"})
|
|
358
|
+
)
|
|
359
|
+
return imports.join(exports, on=filter, how="full")
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
{
|
|
2
|
+
"0": 405,
|
|
3
|
+
"1": 1514,
|
|
4
|
+
"2": 1206,
|
|
5
|
+
"3": 2105,
|
|
6
|
+
"4": 208,
|
|
7
|
+
"5": 302,
|
|
8
|
+
"6": 2104,
|
|
9
|
+
"7": 802,
|
|
10
|
+
"8": 301,
|
|
11
|
+
"9": 1504,
|
|
12
|
+
"10": 1508,
|
|
13
|
+
"11": 804,
|
|
14
|
+
"12": 307,
|
|
15
|
+
"13": 807,
|
|
16
|
+
"14": 708,
|
|
17
|
+
"15": 1105,
|
|
18
|
+
"16": 404,
|
|
19
|
+
"17": 1103,
|
|
20
|
+
"18": 106,
|
|
21
|
+
"19": 2102,
|
|
22
|
+
"20": 908,
|
|
23
|
+
"21": 906,
|
|
24
|
+
"22": 1511,
|
|
25
|
+
"23": 410,
|
|
26
|
+
"24": 2006,
|
|
27
|
+
"25": 1106,
|
|
28
|
+
"26": 703,
|
|
29
|
+
"27": 209,
|
|
30
|
+
"28": 704,
|
|
31
|
+
"29": 2009,
|
|
32
|
+
"30": 713,
|
|
33
|
+
"31": 201,
|
|
34
|
+
"32": 910,
|
|
35
|
+
"33": 1701,
|
|
36
|
+
"34": 1517,
|
|
37
|
+
"35": 710,
|
|
38
|
+
"36": 408,
|
|
39
|
+
"37": 210,
|
|
40
|
+
"38": 1104,
|
|
41
|
+
"39": 1209,
|
|
42
|
+
"40": 206,
|
|
43
|
+
"41": 403,
|
|
44
|
+
"42": 1516,
|
|
45
|
+
"43": 1101,
|
|
46
|
+
"44": 1605,
|
|
47
|
+
"45": 803,
|
|
48
|
+
"46": 706,
|
|
49
|
+
"47": 801,
|
|
50
|
+
"48": 1509,
|
|
51
|
+
"49": 701,
|
|
52
|
+
"50": 1902,
|
|
53
|
+
"51": 907,
|
|
54
|
+
"52": 1806,
|
|
55
|
+
"53": 714,
|
|
56
|
+
"54": 2103,
|
|
57
|
+
"55": 2008,
|
|
58
|
+
"56": 1005,
|
|
59
|
+
"57": 1302,
|
|
60
|
+
"58": 2007,
|
|
61
|
+
"59": 909,
|
|
62
|
+
"60": 1207,
|
|
63
|
+
"61": 810,
|
|
64
|
+
"62": 1602,
|
|
65
|
+
"63": 1202,
|
|
66
|
+
"64": 811,
|
|
67
|
+
"65": 1502,
|
|
68
|
+
"66": 2004,
|
|
69
|
+
"67": 1901,
|
|
70
|
+
"68": 306,
|
|
71
|
+
"69": 202,
|
|
72
|
+
"70": 1601,
|
|
73
|
+
"71": 1518,
|
|
74
|
+
"72": 1109,
|
|
75
|
+
"73": 709,
|
|
76
|
+
"74": 303,
|
|
77
|
+
"75": 712,
|
|
78
|
+
"76": 1805,
|
|
79
|
+
"77": 707,
|
|
80
|
+
"78": 2005,
|
|
81
|
+
"79": 1905,
|
|
82
|
+
"80": 406,
|
|
83
|
+
"81": 904,
|
|
84
|
+
"82": 1214,
|
|
85
|
+
"83": 806,
|
|
86
|
+
"84": 207,
|
|
87
|
+
"85": 808,
|
|
88
|
+
"86": 1507,
|
|
89
|
+
"87": 2001,
|
|
90
|
+
"88": 705,
|
|
91
|
+
"89": 2101,
|
|
92
|
+
"90": 1513,
|
|
93
|
+
"91": 304,
|
|
94
|
+
"92": 1704,
|
|
95
|
+
"93": 203,
|
|
96
|
+
"94": 1212,
|
|
97
|
+
"95": 407,
|
|
98
|
+
"96": 401,
|
|
99
|
+
"97": 1804,
|
|
100
|
+
"98": 813,
|
|
101
|
+
"99": 1102,
|
|
102
|
+
"100": 1211,
|
|
103
|
+
"101": 1702,
|
|
104
|
+
"102": 1904,
|
|
105
|
+
"103": 1008,
|
|
106
|
+
"104": 1604,
|
|
107
|
+
"105": 402,
|
|
108
|
+
"106": 204,
|
|
109
|
+
"107": 1107,
|
|
110
|
+
"108": 305,
|
|
111
|
+
"109": 1108,
|
|
112
|
+
"110": 2002,
|
|
113
|
+
"111": 409,
|
|
114
|
+
"112": 805,
|
|
115
|
+
"113": 809,
|
|
116
|
+
"114": 2106,
|
|
117
|
+
"115": 901,
|
|
118
|
+
"116": 1510,
|
|
119
|
+
"117": 1512,
|
|
120
|
+
"118": 2003,
|
|
121
|
+
"119": 1515,
|
|
122
|
+
"120": 902,
|
|
123
|
+
"121": 1301,
|
|
124
|
+
"122": 1006,
|
|
125
|
+
"123": 1501,
|
|
126
|
+
"124": 1004,
|
|
127
|
+
"125": 711,
|
|
128
|
+
"126": 702,
|
|
129
|
+
"127": 905,
|
|
130
|
+
"128": 1001
|
|
131
|
+
}
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import importlib.resources as resources
|
|
3
|
+
import logging
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
import zipfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import duckdb
|
|
11
|
+
import polars as pl
|
|
12
|
+
from CensusForge import CensusAPI
|
|
13
|
+
from jp_tools import download
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TradeUtils:
|
|
17
|
+
"""
|
|
18
|
+
This class pulls data from the CENSUS and the Puerto Rico Institute of Statistics
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
saving_dir: str = "data/",
|
|
25
|
+
log_file: str = "data.log",
|
|
26
|
+
):
|
|
27
|
+
self.saving_dir = Path(saving_dir)
|
|
28
|
+
self.conn = duckdb.connect()
|
|
29
|
+
|
|
30
|
+
logging.basicConfig(
|
|
31
|
+
level=logging.INFO,
|
|
32
|
+
format="%(asctime)s - %(levelname)s - %(message)s",
|
|
33
|
+
datefmt="%d-%b-%y %H:%M:%S",
|
|
34
|
+
filename=log_file,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def pull_int_jp(self, update: bool = False) -> pl.DataFrame:
|
|
38
|
+
"""
|
|
39
|
+
Pulls data from the Puerto Rico Institute of Statistics used by the JP.
|
|
40
|
+
Saved them in the raw directory as parquet files.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
None
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
None
|
|
49
|
+
"""
|
|
50
|
+
output_dir = (
|
|
51
|
+
self.saving_dir
|
|
52
|
+
/ "raw"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
file_path = output_dir / "jp_data"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
file_path = self.saving_dir / "raw" / "jp_data.parquet"
|
|
60
|
+
name_hash = hashlib.md5(str(file_path).encode()).hexdigest()
|
|
61
|
+
temp_csv = Path(tempfile.gettempdir()) / f"{name_hash}.csv"
|
|
62
|
+
|
|
63
|
+
if not file_path.exists() or update:
|
|
64
|
+
|
|
65
|
+
headers = {
|
|
66
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
67
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
download(
|
|
71
|
+
url="https://datos.estadisticas.pr/dataset/027ddbe1-c51c-46bf-aec3-a62d5d7e8539/resource/b8367825-a3de-41cf-8794-e42c10987b6f/download/ftrade_all_iepr.csv",
|
|
72
|
+
filename=str(temp_csv),
|
|
73
|
+
verify=False,
|
|
74
|
+
timeout=120,
|
|
75
|
+
headers=headers,
|
|
76
|
+
)
|
|
77
|
+
df = pl.read_csv(temp_csv, ignore_errors=True)
|
|
78
|
+
|
|
79
|
+
agri_prod = pl.read_json(
|
|
80
|
+
str(resources.files("pr_imports").joinpath("resources/code_agr.json"))
|
|
81
|
+
).transpose()
|
|
82
|
+
agri_prod = (
|
|
83
|
+
agri_prod.with_columns(pl.nth(0).cast(pl.String).str.zfill(4))
|
|
84
|
+
.to_series()
|
|
85
|
+
.to_list()
|
|
86
|
+
)
|
|
87
|
+
df = df.rename({col: col.lower() for col in df.collect_schema().names()})
|
|
88
|
+
df = df.with_columns(
|
|
89
|
+
date=pl.col("year").cast(pl.String)
|
|
90
|
+
+ "-"
|
|
91
|
+
+ pl.col("month").cast(pl.String)
|
|
92
|
+
+ "-01",
|
|
93
|
+
unit_1=pl.col("unit_1").str.to_lowercase(),
|
|
94
|
+
unit_2=pl.col("unit_2").str.to_lowercase(),
|
|
95
|
+
hts_code=pl.col("commodity_code")
|
|
96
|
+
.cast(pl.String)
|
|
97
|
+
.str.zfill(10)
|
|
98
|
+
.str.replace("'", ""),
|
|
99
|
+
trade_id=pl.when(pl.col("trade") == "i").then(1).otherwise(2),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
df = df.with_columns(pl.col("date").cast(pl.Date))
|
|
103
|
+
|
|
104
|
+
df = df.with_columns(
|
|
105
|
+
agri_prod=pl.when(pl.col("hts_code").is_in(agri_prod))
|
|
106
|
+
.then(1)
|
|
107
|
+
.otherwise(0)
|
|
108
|
+
)
|
|
109
|
+
df = df.with_columns(
|
|
110
|
+
sitc=pl.when(pl.col("sitc_short_desc").str.starts_with("Civilian"))
|
|
111
|
+
.then(9998)
|
|
112
|
+
.when(pl.col("sitc_short_desc").str.starts_with("-"))
|
|
113
|
+
.then(9999)
|
|
114
|
+
.otherwise(pl.col("sitc"))
|
|
115
|
+
)
|
|
116
|
+
df = df.filter(pl.col("hts_code").is_not_null())
|
|
117
|
+
df = df.select(
|
|
118
|
+
pl.col(
|
|
119
|
+
"date",
|
|
120
|
+
"country",
|
|
121
|
+
"trade_id",
|
|
122
|
+
"agri_prod",
|
|
123
|
+
"hts_code",
|
|
124
|
+
"hts_desc",
|
|
125
|
+
"data",
|
|
126
|
+
"qty_1",
|
|
127
|
+
"unit_1",
|
|
128
|
+
"qty_2",
|
|
129
|
+
"unit_2",
|
|
130
|
+
"sitc",
|
|
131
|
+
"naics",
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
df.write_parquet(file_path)
|
|
136
|
+
|
|
137
|
+
return pl.read_parquet(file_path)
|
|
138
|
+
|
|
139
|
+
def pull_int_org(self, update: bool = False, export: bool = True) -> pl.DataFrame:
|
|
140
|
+
"""
|
|
141
|
+
Downloads, extracts, and unifies organizational data from the Puerto Rico
|
|
142
|
+
Institute of Statistics.
|
|
143
|
+
|
|
144
|
+
The process involves a nested extraction: a parent ZIP contains multiple
|
|
145
|
+
inner ZIPs, which in turn contain the target CSV files. Due to known
|
|
146
|
+
formatting issues with the source ZIP files, the method employs a fallback
|
|
147
|
+
to the '7z' system utility if the standard zipfile library fails.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
update (bool): If True, ignores existing local parquet files and
|
|
151
|
+
re-downloads/processes the data. Defaults to False.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
pl.DataFrame: A Polars DataFrame containing the unified data from all
|
|
155
|
+
extracted CSVs.
|
|
156
|
+
|
|
157
|
+
Note:
|
|
158
|
+
Requires '7z' to be installed in the system PATH as a fallback for
|
|
159
|
+
malformed archive handling.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
# Define Output file and hash
|
|
163
|
+
parquet_export_path = self.saving_dir / "raw" / "jp_org_exports.parquet"
|
|
164
|
+
parquet_import_path = self.saving_dir / "raw" / "jp_org_imports.parquet"
|
|
165
|
+
name_hash = hashlib.md5(str(parquet_export_path).encode()).hexdigest()
|
|
166
|
+
|
|
167
|
+
# Define Temporary directories
|
|
168
|
+
temp_zip = Path(tempfile.gettempdir()) / f"jp_org_zip_{name_hash}.zip"
|
|
169
|
+
stage1_dir = Path(tempfile.gettempdir()) / f"stage1_{name_hash}"
|
|
170
|
+
stage2_dir = Path(tempfile.gettempdir()) / f"stage2_{name_hash}"
|
|
171
|
+
|
|
172
|
+
if not parquet_export_path.exists() or update:
|
|
173
|
+
|
|
174
|
+
download(
|
|
175
|
+
url="http://apps.estadisticas.pr/iepr/LinkClick.aspx?fileticket=JVyYmIHqbqc%3d&tabid=284&mid=244930",
|
|
176
|
+
filename=(str(temp_zip)),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Stage 1: Extract all Parent zip file
|
|
180
|
+
# WARNING: Added the use of subprocess due to mal formated zip from estadisticas.pr.gov
|
|
181
|
+
|
|
182
|
+
stage1_dir.mkdir(parents=True, exist_ok=True)
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
with zipfile.ZipFile(temp_zip, "r") as zip_ref:
|
|
186
|
+
zip_ref.extractall(stage1_dir)
|
|
187
|
+
|
|
188
|
+
except zipfile.BadZipFile:
|
|
189
|
+
print("Warning: Used 7z subprocess due to mall formated file")
|
|
190
|
+
subprocess.run(
|
|
191
|
+
["7z", "x", str(temp_zip), f"-o{stage1_dir}", "-y"],
|
|
192
|
+
capture_output=True,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
stage2_dir.mkdir(parents=True, exist_ok=True)
|
|
196
|
+
inner_zips = list(stage1_dir.rglob("*.zip"))
|
|
197
|
+
|
|
198
|
+
for izip in inner_zips:
|
|
199
|
+
try:
|
|
200
|
+
with zipfile.ZipFile(izip, "r") as zip_ref:
|
|
201
|
+
# Extracting to our second temp directory
|
|
202
|
+
zip_ref.extractall(stage2_dir)
|
|
203
|
+
except zipfile.BadZipFile:
|
|
204
|
+
print(
|
|
205
|
+
f"Warning: {izip.name} was also malformed. Switching back to 7-zip for this file."
|
|
206
|
+
)
|
|
207
|
+
subprocess.run(
|
|
208
|
+
["7z", "x", str(izip), f"-o{stage2_dir}", "-y"],
|
|
209
|
+
capture_output=True,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Stage 3: unify data into a single file
|
|
213
|
+
csv_files = list(stage2_dir.rglob("*.csv"))
|
|
214
|
+
if not csv_files:
|
|
215
|
+
raise FileNotFoundError(
|
|
216
|
+
"Extraction successful, but no CSV files were found to process."
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
df_list = [pl.read_csv(f, ignore_errors=True) for f in csv_files]
|
|
220
|
+
df = pl.concat(df_list)
|
|
221
|
+
df = df.with_columns(
|
|
222
|
+
country=pl.col("country").str.to_lowercase(),
|
|
223
|
+
hts=pl.col("HTS").str.replace("'", ""),
|
|
224
|
+
date=(
|
|
225
|
+
pl.col("year").cast(pl.String)
|
|
226
|
+
+ "-"
|
|
227
|
+
+ pl.col("month").cast(pl.String).str.zfill(2)
|
|
228
|
+
+ "-01"
|
|
229
|
+
).str.to_datetime("%Y-%m-%d"),
|
|
230
|
+
)
|
|
231
|
+
df = df.select(
|
|
232
|
+
pl.col(
|
|
233
|
+
"date",
|
|
234
|
+
"country",
|
|
235
|
+
"hts",
|
|
236
|
+
"unit_1",
|
|
237
|
+
"qty_1",
|
|
238
|
+
"unit_2",
|
|
239
|
+
"qty_2",
|
|
240
|
+
"value",
|
|
241
|
+
"import_export",
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Save Export Data
|
|
246
|
+
df_exports = df.filter(pl.col("import_export") == "e").drop("import_export")
|
|
247
|
+
df_exports.write_parquet(parquet_export_path)
|
|
248
|
+
|
|
249
|
+
# Save import data
|
|
250
|
+
df_imports = df.filter(pl.col("import_export") == "i").drop("import_export")
|
|
251
|
+
df_imports.write_parquet(parquet_import_path)
|
|
252
|
+
|
|
253
|
+
# Stage 4: Cleanup
|
|
254
|
+
shutil.rmtree(stage1_dir)
|
|
255
|
+
shutil.rmtree(stage2_dir)
|
|
256
|
+
temp_zip.unlink(missing_ok=True)
|
|
257
|
+
|
|
258
|
+
if export:
|
|
259
|
+
return pl.read_parquet(parquet_export_path)
|
|
260
|
+
else:
|
|
261
|
+
return pl.read_parquet(parquet_import_path)
|
|
262
|
+
|
|
263
|
+
def pull_census_hts(self, exports: bool, state: str) -> pl.DataFrame:
|
|
264
|
+
"""
|
|
265
|
+
Pulls HTS data from the Census and saves them in a parquet file.
|
|
266
|
+
|
|
267
|
+
Parameters
|
|
268
|
+
----------
|
|
269
|
+
end_year: int
|
|
270
|
+
The last year to pull data from.
|
|
271
|
+
start_year: int
|
|
272
|
+
The first year to pull data from.
|
|
273
|
+
exports: bool
|
|
274
|
+
If True, pulls exports data. If False, pulls imports data.
|
|
275
|
+
state: str
|
|
276
|
+
The state to pull data from (e.g. "PR" for Puerto Rico).
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
None
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
for _year in range(2010, 2023):
|
|
284
|
+
exports_path = Path(
|
|
285
|
+
f"{self.saving_dir}raw/census-hts-exports-{state}-{_year}.parquet"
|
|
286
|
+
)
|
|
287
|
+
imports_path = Path(
|
|
288
|
+
f"{self.saving_dir}raw/census-hts-imports-{state}-{_year}.parquet"
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
if exports_path.exists() and imports_path.exists():
|
|
292
|
+
continue
|
|
293
|
+
|
|
294
|
+
req_exports = CensusAPI().timeseries_query(
|
|
295
|
+
dataset="timeseries-intltrade-exports-statehs",
|
|
296
|
+
params_list=[
|
|
297
|
+
"CTY_CODE",
|
|
298
|
+
"CTY_NAME",
|
|
299
|
+
"ALL_VAL_MO",
|
|
300
|
+
"COMM_LVL",
|
|
301
|
+
"E_COMMODITY",
|
|
302
|
+
],
|
|
303
|
+
year=_year,
|
|
304
|
+
extra=f"STATE={state}",
|
|
305
|
+
skip_checks=True,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
req_imports = CensusAPI().timeseries_query(
|
|
309
|
+
dataset="timeseries-intltrade-imports-statehs",
|
|
310
|
+
params_list=[
|
|
311
|
+
"CTY_CODE",
|
|
312
|
+
"CTY_NAME",
|
|
313
|
+
"GEN_VAL_MO",
|
|
314
|
+
"COMM_LVL",
|
|
315
|
+
"I_COMMODITY",
|
|
316
|
+
],
|
|
317
|
+
year=_year,
|
|
318
|
+
extra=f"STATE={state}",
|
|
319
|
+
skip_checks=True,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
df_exports = pl.DataFrame(req_exports)
|
|
323
|
+
df_exports.write_parquet(exports_path)
|
|
324
|
+
|
|
325
|
+
df_imports = pl.DataFrame(req_imports)
|
|
326
|
+
df_imports.write_parquet(imports_path)
|
|
327
|
+
if exports:
|
|
328
|
+
return self.conn.execute(
|
|
329
|
+
f"SELECT * FROM '{self.saving_dir}raw/census-hts-exports-{state}-*.parquet';"
|
|
330
|
+
).pl()
|
|
331
|
+
else:
|
|
332
|
+
return self.conn.execute(
|
|
333
|
+
f"SELECT * FROM '{self.saving_dir}raw/census-hts-imports-{state}-*.parquet';"
|
|
334
|
+
).pl()
|
|
335
|
+
|
|
336
|
+
def pull_census_naics(self, exports: bool, state: str) -> pl.DataFrame:
|
|
337
|
+
"""
|
|
338
|
+
Pulls NAICS data from the Census and saves them in a parquet file.
|
|
339
|
+
|
|
340
|
+
Parameters
|
|
341
|
+
----------
|
|
342
|
+
end_year: int
|
|
343
|
+
The last year to pull data from.
|
|
344
|
+
start_year: int
|
|
345
|
+
The first year to pull data from.
|
|
346
|
+
exports: bool
|
|
347
|
+
If True, pulls exports data. If False, pulls imports data.
|
|
348
|
+
state: str
|
|
349
|
+
The state to pull data from (e.g. "PR" for Puerto Rico).
|
|
350
|
+
|
|
351
|
+
Returns
|
|
352
|
+
-------
|
|
353
|
+
None
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
for _year in range(2010, 2023):
|
|
357
|
+
exports_path = Path(
|
|
358
|
+
f"{self.saving_dir}raw/census-naics-exports-{state}-{_year}.parquet"
|
|
359
|
+
)
|
|
360
|
+
imports_path = Path(
|
|
361
|
+
f"{self.saving_dir}raw/census-naics-imports-{state}-{_year}.parquet"
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
if exports_path.exists() and imports_path.exists():
|
|
365
|
+
continue
|
|
366
|
+
|
|
367
|
+
req_exports = CensusAPI().timeseries_query(
|
|
368
|
+
dataset="timeseries-intltrade-exports-statenaics",
|
|
369
|
+
params_list=[
|
|
370
|
+
"CTY_CODE",
|
|
371
|
+
"CTY_NAME",
|
|
372
|
+
"ALL_VAL_MO",
|
|
373
|
+
"COMM_LVL",
|
|
374
|
+
"NAICS",
|
|
375
|
+
],
|
|
376
|
+
year=_year,
|
|
377
|
+
extra=f"STATE={state}",
|
|
378
|
+
skip_checks=True,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
req_imports = CensusAPI().timeseries_query(
|
|
382
|
+
dataset="timeseries-intltrade-imports-statenaics",
|
|
383
|
+
params_list=[
|
|
384
|
+
"CTY_CODE",
|
|
385
|
+
"CTY_NAME",
|
|
386
|
+
"GEN_VAL_MO",
|
|
387
|
+
"COMM_LVL",
|
|
388
|
+
"NAICS",
|
|
389
|
+
],
|
|
390
|
+
year=_year,
|
|
391
|
+
extra=f"STATE={state}",
|
|
392
|
+
skip_checks=True,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
df_exports = pl.DataFrame(req_exports)
|
|
396
|
+
df_exports = df_exports.rename(df_exports.row(0, named=True))
|
|
397
|
+
df_exports = df_exports.slice(1)
|
|
398
|
+
df_exports.write_parquet(exports_path)
|
|
399
|
+
|
|
400
|
+
df_imports = pl.DataFrame(req_imports)
|
|
401
|
+
df_imports = df_imports.rename(df_imports.row(0, named=True))
|
|
402
|
+
df_imports = df_imports.slice(1)
|
|
403
|
+
df_imports.write_parquet(imports_path)
|
|
404
|
+
if exports:
|
|
405
|
+
return self.conn.execute(
|
|
406
|
+
f"SELECT * FROM '{self.saving_dir}raw/census-naics-exports-{state}-*.parquet';"
|
|
407
|
+
).pl()
|
|
408
|
+
else:
|
|
409
|
+
return self.conn.execute(
|
|
410
|
+
f"SELECT * FROM '{self.saving_dir}raw/census-naics-imports-{state}-*.parquet';"
|
|
411
|
+
).pl()
|