Spaces:
Sleeping
Sleeping
Download server/environment.py from 01ammu/data-cleaning-openenv: direct link, hf CLI and curl.
- Browser
- Download file 6.57 kB
-
https://huggingface.co/spaces/01ammu/data-cleaning-openenv/resolve/main/server/environment.py
- Command line
-
hf download hf://spaces/01ammu/data-cleaning-openenv/server/environment.py
-
curl -L -o environment.py https://huggingface.co/spaces/01ammu/data-cleaning-openenv/resolve/main/server/environment.py
6.57 kB
| import uuid | |
| import sys | |
| import os | |
| from typing import Any, Dict, List | |
| from copy import deepcopy | |
| # Fix path so 'tasks' module can be found | |
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) | |
| from tasks.graders import TASKS | |
| class DataCleaningEnvironment: | |
| """ | |
| Data Cleaning RL Environment. | |
| Agent receives a messy dataset row by row and must clean it. | |
| """ | |
| def __init__(self): | |
| self.episode_id = None | |
| self.task_name = None | |
| self.dataset = [] | |
| self.cleaned_data = [] | |
| self.current_row_index = 0 | |
| self.step_count = 0 | |
| self.total_reward = 0.0 | |
| self.done = False | |
| self.grader = None | |
| def reset(self, task_name: str = "easy") -> Dict[str, Any]: | |
| """Start a new episode with the given task.""" | |
| if task_name not in TASKS: | |
| raise ValueError(f"Unknown task: {task_name}. Choose from {list(TASKS.keys())}") | |
| task = TASKS[task_name] | |
| self.episode_id = str(uuid.uuid4())[:8] | |
| self.task_name = task_name | |
| self.dataset = task["generate"]() | |
| self.cleaned_data = [] | |
| self.current_row_index = 0 | |
| self.step_count = 0 | |
| self.total_reward = 0.0 | |
| self.done = False | |
| self.grader = task["grade"] | |
| return self._make_observation(reward=0.0) | |
| def step(self, action_type: str, column: str, value: Any = None) -> Dict[str, Any]: | |
| """Apply a cleaning action to the current row.""" | |
| if self.done: | |
| raise ValueError("Episode done. Call reset() first.") | |
| current_row = deepcopy(self.dataset[self.current_row_index]) | |
| reward = 0.0 | |
| issues = self._detect_issues(current_row) | |
| if action_type == "fill_missing": | |
| if current_row.get(column) is None: | |
| current_row[column] = value | |
| reward += 0.3 | |
| else: | |
| reward -= 0.1 | |
| elif action_type == "fix_type": | |
| try: | |
| current_row[column] = float(current_row[column]) | |
| reward += 0.2 | |
| except (ValueError, TypeError): | |
| reward -= 0.1 | |
| elif action_type == "remove_duplicate": | |
| is_dup = any( | |
| r.get("email") == current_row.get("email") | |
| and r.get("name") == current_row.get("name") | |
| for r in self.cleaned_data | |
| ) | |
| if is_dup: | |
| self.current_row_index += 1 | |
| reward += 0.4 | |
| self.step_count += 1 | |
| self.total_reward += reward | |
| if self.current_row_index >= len(self.dataset): | |
| self.done = True | |
| return self._make_observation(reward=reward) | |
| else: | |
| reward -= 0.2 | |
| elif action_type == "fix_category": | |
| category_map = { | |
| "eng": "Engineering", "engineering": "Engineering", | |
| "ENGINEERING": "Engineering", | |
| "hr": "HR", "h.r.": "HR", "H.R.": "HR", | |
| "finance": "Finance", "FINANCE": "Finance", | |
| } | |
| raw = str(current_row.get(column, "")) | |
| if raw in category_map: | |
| current_row[column] = category_map[raw] | |
| reward += 0.3 | |
| else: | |
| reward -= 0.1 | |
| elif action_type == "remove_outlier": | |
| val = current_row.get(column) | |
| if val is not None and (val > 200000 or val < 0): | |
| current_row[column] = 70000 | |
| reward += 0.4 | |
| else: | |
| reward -= 0.1 | |
| elif action_type == "fix_formatting": | |
| val = current_row.get(column) | |
| if isinstance(val, str): | |
| current_row[column] = val.lower().strip() | |
| reward += 0.2 | |
| else: | |
| reward -= 0.1 | |
| elif action_type == "skip": | |
| reward -= 0.2 | |
| else: | |
| reward -= 0.3 | |
| self.cleaned_data.append(current_row) | |
| self.current_row_index += 1 | |
| self.step_count += 1 | |
| self.total_reward += reward | |
| if self.current_row_index >= len(self.dataset): | |
| self.done = True | |
| return self._make_observation(reward=reward) | |
| def get_state(self) -> Dict[str, Any]: | |
| score = 0.0 | |
| if self.done and self.grader: | |
| score, _ = self.grader(self.cleaned_data) | |
| return { | |
| "episode_id": self.episode_id, | |
| "task_name": self.task_name, | |
| "step_count": self.step_count, | |
| "total_reward": round(self.total_reward, 3), | |
| "rows_cleaned": len(self.cleaned_data), | |
| "score": score, | |
| } | |
| def _detect_issues(self, row: Dict[str, Any]) -> List[str]: | |
| issues = [] | |
| for col, val in row.items(): | |
| if val is None: | |
| issues.append(f"missing:{col}") | |
| if isinstance(val, str): | |
| try: | |
| float(val) | |
| issues.append(f"wrong_type:{col}") | |
| except ValueError: | |
| pass | |
| if val != val.lower(): | |
| issues.append(f"formatting:{col}") | |
| return issues | |
| def _make_observation(self, reward: float) -> Dict[str, Any]: | |
| if self.done or self.current_row_index >= len(self.dataset): | |
| current_data = {} | |
| issues = [] | |
| legal_actions = [] | |
| else: | |
| current_data = self.dataset[self.current_row_index] | |
| issues = self._detect_issues(current_data) | |
| legal_actions = self._get_legal_actions(current_data, issues) | |
| return { | |
| "current_row": self.current_row_index, | |
| "total_rows": len(self.dataset), | |
| "current_data": current_data, | |
| "issues_detected": issues, | |
| "legal_actions": legal_actions, | |
| "progress": round(self.current_row_index / max(len(self.dataset), 1), 2), | |
| "reward": round(reward, 3), | |
| "done": self.done, | |
| } | |
| def _get_legal_actions(self, row: Dict[str, Any], issues: List[str]) -> List[str]: | |
| actions = ["skip"] | |
| issue_types = [i.split(":")[0] for i in issues] | |
| if "missing" in issue_types: | |
| actions.append("fill_missing") | |
| if "wrong_type" in issue_types: | |
| actions.append("fix_type") | |
| if "formatting" in issue_types: | |
| actions.append("fix_formatting") | |
| actions.extend(["remove_duplicate", "fix_category", "remove_outlier"]) | |
| return list(set(actions)) |