Ray1ee01 commited on
Commit
51fcbfd
·
verified ·
1 Parent(s): 2cf467c

Upload folder using huggingface_hub

Browse files
modules/preprocess/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ """
2
+ Preprocess package initialization.
3
+ """
modules/preprocess/preprocess.py ADDED
@@ -0,0 +1,316 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import json
3
+ import logging
4
+ from typing import Dict, Any
5
+ from pathlib import Path
6
+
7
+ # Configure logging
8
+ logging.basicConfig(
9
+ level=logging.INFO,
10
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
11
+ )
12
+ logger = logging.getLogger("DataFormatUpdater")
13
+
14
+ # Standard attributes to add to all files
15
+ STANDARD_ADDITIONS = {
16
+ "secondary_data": [],
17
+ "variables": {
18
+ "width": 600,
19
+ "height": 600,
20
+ "has_rounded_corners": False,
21
+ "has_shadow": False,
22
+ "has_spacing": False,
23
+ "has_gradient": False,
24
+ "has_stroke": False
25
+ },
26
+ "typography": {
27
+ "title": {
28
+ "font_family": "Arial",
29
+ "font_size": "28px",
30
+ "font_weight": 700
31
+ },
32
+ "description": {
33
+ "font_family": "Arial",
34
+ "font_size": "16px",
35
+ "font_weight": 500
36
+ },
37
+ "label": {
38
+ "font_family": "Arial",
39
+ "font_size": "16px",
40
+ "font_weight": 500
41
+ },
42
+ "annotation": {
43
+ "font_family": "Arial",
44
+ "font_size": "12px",
45
+ "font_weight": 400
46
+ }
47
+ }
48
+ }
49
+ from typing import Dict, List, Tuple
50
+ import re
51
+ from datetime import datetime
52
+ import logging
53
+
54
+ logger = logging.getLogger(__name__)
55
+
56
+ def process_temporal_data(data: Dict) -> None:
57
+ """处理时间类型的数据"""
58
+ for column in data["data"]["columns"]:
59
+ if column["data_type"] == "temporal":
60
+ has_valid_temporal = False
61
+ for row in data["data"]["data"]:
62
+ value = str(row.get(column["name"], ""))
63
+
64
+ try:
65
+ if value.isdigit():
66
+ if len(value) == 4:
67
+ has_valid_temporal = True
68
+ continue
69
+ else:
70
+ has_valid_temporal = False
71
+ break
72
+
73
+ if "." in value:
74
+ parts = value.split(".")
75
+ if len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
76
+ year, month = parts
77
+ month = month.zfill(2)
78
+ row[column["name"]] = f"{year}-{month}"
79
+ has_valid_temporal = True
80
+ elif len(parts) == 3 and all(part.isdigit() for part in parts):
81
+ year, month, day = parts
82
+ month = month.zfill(2)
83
+ day = day.zfill(2)
84
+ row[column["name"]] = f"{year}-{month}-{day}"
85
+ has_valid_temporal = True
86
+ else:
87
+ continue
88
+ continue
89
+
90
+ if " " in value:
91
+ try:
92
+ # 尝试解析完整的月份名称
93
+ date_obj = datetime.strptime(value, "%B %Y")
94
+ except ValueError:
95
+ try:
96
+ # 尝试解析缩写的月份名称
97
+ date_obj = datetime.strptime(value, "%b %Y")
98
+ except ValueError:
99
+ # 尝试其他常见格式
100
+ try:
101
+ # 处理 "YYYY-MM" 或 "YYYY/MM" 格式
102
+ if "-" in value or "/" in value:
103
+ separator = "-" if "-" in value else "/"
104
+ parts = value.split(separator)
105
+ if len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
106
+ year = parts[0]
107
+ month = parts[1].zfill(2)
108
+ row[column["name"]] = f"{year}-{month}"
109
+ has_valid_temporal = True
110
+ continue
111
+ except Exception:
112
+ continue
113
+ continue
114
+
115
+ # 转换为 "YYYY-MM" 格式
116
+ row[column["name"]] = date_obj.strftime("%Y-%m")
117
+ has_valid_temporal = True
118
+ continue
119
+
120
+ except Exception as e:
121
+ logger.warning(f"Failed to parse temporal value '{value}': {str(e)}")
122
+ continue
123
+
124
+ # 如果没有找到任何有效的时间数据,将类型改为categorical
125
+ if not has_valid_temporal:
126
+ column["data_type"] = "categorical"
127
+ data["data"]["type_combination"] = " + ".join([col["data_type"] for col in data["data"]["columns"]])
128
+ logger.info(f"Changed column '{column['name']}' from temporal to categorical due to invalid temporal data")
129
+
130
+ def process_numerical_data(data: Dict) -> None:
131
+ """处理数值类型的数据"""
132
+ for column in data["data"]["columns"]:
133
+ if column["data_type"] == "numerical":
134
+ for row in data["data"]["data"]:
135
+ value = row.get(column["name"])
136
+
137
+ # 处理 null 或 None
138
+ if value is None or value == "null" or value == "":
139
+ row[column["name"]] = 0
140
+ continue
141
+
142
+ # 转换为字符串以进行处理
143
+ value_str = str(value)
144
+
145
+ # 提取数字(包括负号和小数点)
146
+ numeric_chars = re.findall(r'-?\d*\.?\d+', value_str)
147
+ if numeric_chars:
148
+ # 使用第一个匹配的数字
149
+ try:
150
+ row[column["name"]] = float(numeric_chars[0])
151
+ except ValueError:
152
+ row[column["name"]] = 0
153
+ else:
154
+ row[column["name"]] = 0
155
+
156
+ def deduplicate_combinations(data: Dict) -> None:
157
+ """检查并去重temporal和categorical属性的组合
158
+
159
+ Args:
160
+ data: 包含数据的字典,格式为 {"data": {"columns": [...], "data": [...]}}
161
+ """
162
+ # 找出所有temporal和categorical列
163
+ temporal_categorical_cols = [
164
+ col["name"] for col in data["data"]["columns"]
165
+ if col["data_type"] in ["temporal", "categorical"]
166
+ ]
167
+
168
+ if not temporal_categorical_cols:
169
+ return
170
+
171
+ # 用于存储已见过的组合
172
+ seen_combinations = set()
173
+ # 用于存储要保留的行索引
174
+ rows_to_keep = []
175
+
176
+ # 检查每一行
177
+ for idx, row in enumerate(data["data"]["data"]):
178
+ # 获取当前行的temporal和categorical值组合
179
+ combination = tuple(str(row.get(col, "")) for col in temporal_categorical_cols)
180
+
181
+ # 如果这个组合还没见过,就保留这行
182
+ if combination not in seen_combinations:
183
+ seen_combinations.add(combination)
184
+ rows_to_keep.append(idx)
185
+
186
+ # 只保留不重复的行
187
+ data["data"]["data"] = [data["data"]["data"][i] for i in rows_to_keep]
188
+
189
+ # 记录去重信息
190
+ removed_count = len(data["data"]["data"]) - len(rows_to_keep)
191
+ #if removed_count > 0:
192
+ # logger.info(f"Removed {removed_count} duplicate combinations of temporal/categorical attributes")
193
+ def remove_unnecessary_fields(data: Any) -> Any:
194
+ """
195
+ Recursively remove unnecessary fields from any level of the data structure
196
+ """
197
+ unnecessary_fields = {
198
+ "discarded_data_points",
199
+ "missing_percentage",
200
+ "zero_percentage",
201
+ "transformed_columns"
202
+ }
203
+
204
+ if isinstance(data, dict):
205
+ return {
206
+ k: remove_unnecessary_fields(v)
207
+ for k, v in data.items()
208
+ if k not in unnecessary_fields
209
+ }
210
+ elif isinstance(data, list):
211
+ return [remove_unnecessary_fields(item) for item in data]
212
+ else:
213
+ return data
214
+
215
+ def update_data_format(data: Dict[str, Any]) -> Dict[str, Any]:
216
+ """
217
+ Update the data format to match the new requirements
218
+ """
219
+ # First, remove unnecessary fields at all levels
220
+ updated_data = remove_unnecessary_fields(data.copy())
221
+
222
+ # Extract columns and data from the nested structure
223
+ if "data" in updated_data and "data" in updated_data["data"] and "columns" in updated_data["data"]:
224
+ pass
225
+ else:
226
+ columns = updated_data["columns"]
227
+ data = updated_data["data"]
228
+ updated_data["data"] = {
229
+ "data": data,
230
+ "columns": columns
231
+ }
232
+ del updated_data["columns"]
233
+
234
+ try:
235
+ if "title" in updated_data and "description" in updated_data and "main_insight" in updated_data:
236
+ title = updated_data["title"]
237
+ description = updated_data["description"]
238
+ main_insight = updated_data["main_insight"]
239
+ updated_data["metadata"] = {
240
+ "title": title,
241
+ "description": description,
242
+ "main_insight": main_insight
243
+ }
244
+ elif "description" in updated_data and "titles" in updated_data and "main_title" in updated_data["titles"]:
245
+ description = updated_data["description"]
246
+ main_title = updated_data["titles"]["main_title"]
247
+ main_insight = updated_data["metadata"]["main_insight"]
248
+ datafact = updated_data["metadata"]["datafact"]
249
+ updated_data["metadata"] = {
250
+ "title": main_title,
251
+ "description": description,
252
+ "main_insight": main_insight,
253
+ "datafact": datafact
254
+ }
255
+ except Exception as e:
256
+ pass
257
+
258
+ if "data" in updated_data and "type_combinations" in updated_data["data"]:
259
+ updated_data["data"]["type_combination"] = updated_data["data"]["type_combinations"]
260
+ del updated_data["data"]["type_combinations"]
261
+ # Add standard attributes
262
+ for key, value in STANDARD_ADDITIONS.items():
263
+ if key not in updated_data:
264
+ updated_data[key] = value
265
+
266
+ return updated_data
267
+
268
+ def process(input: str, output: str = None) -> None:
269
+ """
270
+ Pipeline入口函数,处理单个文件的数据预处理
271
+
272
+ Args:
273
+ input (str): 输入JSON文件路径
274
+ output (str): 输出JSON文件路径,如果为None则原地修改输入文件
275
+ """
276
+ try:
277
+ # 如果没有指定输出路径,则原地修改
278
+ if output is None:
279
+ output = input
280
+
281
+ logger.info(f"处理文件: {input}")
282
+
283
+ # 检查是否需要处理
284
+ if Path(output).exists():
285
+ with open(output) as f:
286
+ data = json.load(f)
287
+ #if "metadata" in data and "data" in data and "variables" in data and "processed" in data:
288
+ # logger.info(f"跳过处理: {output} 已包含必要字段")
289
+ # return
290
+
291
+ # 读取输入数据
292
+ with open(input, 'r', encoding='utf-8') as f:
293
+ data = json.load(f)
294
+
295
+ # 更新数据格式
296
+ updated_data = update_data_format(data)
297
+
298
+ # 处理时间类型数据
299
+ process_temporal_data(updated_data)
300
+
301
+ # 处理数值类型数据
302
+ process_numerical_data(updated_data)
303
+
304
+ # 去重temporal和categorical属性的组合
305
+ deduplicate_combinations(updated_data)
306
+ updated_data["processed"] = True
307
+
308
+ # 保存更新后的数据
309
+ with open(output, 'w', encoding='utf-8') as f:
310
+ json.dump(updated_data, f, indent=2, ensure_ascii=False)
311
+
312
+ logger.info(f"处理完成: {output}")
313
+
314
+ except Exception as e:
315
+ logger.error(f"处理失败: {str(e)}")
316
+ raise