disha2005 commited on
Commit
e4f7ea7
·
1 Parent(s): 685adca

initializing

Browse files
DockerFile ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10
2
+
3
+ WORKDIR /app
4
+
5
+ COPY . .
6
+
7
+ RUN pip install -r requirements.txt
8
+
9
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from env.environment import DataCleaningEnv
3
+
4
+ app = FastAPI() # 🔥 MUST BE BEFORE @app
5
+ env = DataCleaningEnv(task=1)
6
+
7
+ @app.post("/reset")
8
+ def reset():
9
+ return env.reset()
10
+
11
+ @app.post("/step")
12
+ def step(action: dict):
13
+ obs, reward, done, _ = env.step(action)
14
+ return {"obs": obs, "reward": reward, "done": done}
15
+
16
+ @app.get("/state")
17
+ def state():
18
+ return env.state()
env/__pycache__/actions.cpython-311.pyc ADDED
Binary file (3.05 kB). View file
 
env/__pycache__/actions.cpython-313.pyc ADDED
Binary file (2.59 kB). View file
 
env/__pycache__/data_generator.cpython-311.pyc ADDED
Binary file (1.77 kB). View file
 
env/__pycache__/data_generator.cpython-313.pyc ADDED
Binary file (1.27 kB). View file
 
env/__pycache__/environment.cpython-311.pyc ADDED
Binary file (6.39 kB). View file
 
env/__pycache__/environment.cpython-313.pyc ADDED
Binary file (6.07 kB). View file
 
env/__pycache__/issue_injector.cpython-311.pyc ADDED
Binary file (1.95 kB). View file
 
env/__pycache__/issue_injector.cpython-313.pyc ADDED
Binary file (1.73 kB). View file
 
env/actions.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+
3
+ def remove_nulls(df, column):
4
+ return df.dropna(subset=[column])
5
+
6
+ def fill_nulls(df, column, strategy="mean"):
7
+
8
+ if strategy == "mean":
9
+ df[column] = pd.to_numeric(df[column], errors='coerce')
10
+ df[column] = df[column].fillna(df[column].mean())
11
+
12
+ elif strategy == "median":
13
+ df[column] = pd.to_numeric(df[column], errors='coerce')
14
+ df[column] = df[column].fillna(df[column].median())
15
+
16
+ elif strategy == "mode":
17
+ df[column] = df[column].fillna(df[column].mode()[0])
18
+
19
+ return df
20
+
21
+ def convert_types(df, column):
22
+ df[column] = pd.to_numeric(df[column], errors='coerce')
23
+ return df
24
+
25
+
26
+ def deduplicate(df):
27
+ return df.drop_duplicates()
28
+
29
+ def trim_whitespace(df, column):
30
+ df[column] = df[column].astype(str).str.strip()
31
+ return df
32
+
33
+ def normalize_column(df, column):
34
+ col = pd.to_numeric(df[column], errors='coerce')
35
+
36
+ min_val = col.min()
37
+ max_val = col.max()
38
+
39
+ if max_val - min_val == 0:
40
+ return df
41
+
42
+ df[column] = (col - min_val) / (max_val - min_val)
43
+ df[column] = df[column].fillna(0) # optional safety
44
+
45
+ return df
46
+ def compute_correlation(df):
47
+ return df.corr(numeric_only=True)
48
+
49
+ def drop_correlated_feature(df, col):
50
+ return df.drop(columns=[col])
env/data_generator.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ from faker import Faker
4
+
5
+ def generate_clean_dataset(n_rows=50, seed=42):
6
+ fake = Faker("en_IN")
7
+ np.random.seed(seed)
8
+
9
+ df = pd.DataFrame({
10
+ "customer_id": [f"C-{1000+i}" for i in range(n_rows)],
11
+ "name": [fake.name() for _ in range(n_rows)],
12
+ "age": np.random.randint(18, 60, n_rows),
13
+ "city": [fake.city() for _ in range(n_rows)],
14
+ "income": np.random.randint(20000, 100000, n_rows)
15
+ })
16
+
17
+ return df
env/environment.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from env.data_generator import generate_clean_dataset
2
+ from env.issue_injector import inject_issues
3
+ from env.actions import drop_correlated_feature, fill_nulls, remove_nulls, convert_types, deduplicate, trim_whitespace, normalize_column
4
+ from env.graders.task1_grader import grade_task1
5
+ from env.graders.task2_grader import grade_task2
6
+ from env.graders.task3_grader import grade_task3
7
+ from env.graders.final_evaluator import compute_final_score
8
+ class DataCleaningEnv:
9
+
10
+ def __init__(self,task=1):
11
+ self.task=task
12
+ self.clean_df = None
13
+ self.dirty_df = None
14
+ self.manifest = None
15
+ self.steps = 0
16
+ self.done=False
17
+ self.inspected_cols=set()
18
+
19
+ def safe_df(self,df):
20
+ return df.replace({float("nan"): None})
21
+
22
+ def reset(self):
23
+ self.clean_df = generate_clean_dataset()
24
+ self.dirty_df, self.manifest = inject_issues(self.clean_df)
25
+
26
+ self.steps = 0
27
+ self.done = False
28
+ self.inspected_cols = set()
29
+
30
+ return {
31
+ "dataset": self.safe_df(self.dirty_df).to_dict(),
32
+ "shape": list(self.dirty_df.shape),
33
+ "steps": self.steps
34
+ }
35
+
36
+ def step(self, action):
37
+ self.steps += 1
38
+ reward = 0
39
+ action_type = action.get("type")
40
+
41
+
42
+ if action_type == "inspect_column":
43
+ col = action["column"]
44
+ if col not in self.inspected_cols:
45
+ self.inspected_cols.add(col)
46
+ reward += 0.01
47
+ else:
48
+ reward -= 0.02
49
+
50
+ if action_type == "remove_nulls":
51
+ col = action["column"]
52
+ null_ratio = self.dirty_df[col].isnull().mean()
53
+ if null_ratio > 0.3:
54
+ self.dirty_df = remove_nulls(self.dirty_df, col)
55
+ reward += 0.1 # good decision
56
+ else:
57
+ reward -= 0.08 # bad decision
58
+
59
+ elif action_type == "convert_types":
60
+ self.dirty_df = convert_types(self.dirty_df, action["column"])
61
+ reward += 0.1
62
+
63
+ elif action_type == "deduplicate":
64
+ self.dirty_df = deduplicate(self.dirty_df)
65
+ reward += 0.1
66
+
67
+ elif action_type == "trim_whitespace":
68
+ self.dirty_df = trim_whitespace(self.dirty_df, action["column"])
69
+ reward += 0.1
70
+
71
+ elif action_type == "fill_nulls":
72
+ col = action["column"]
73
+ null_ratio = self.dirty_df[col].isnull().mean()
74
+ if null_ratio < 0.3:
75
+ self.dirty_df = fill_nulls(self.dirty_df, col)
76
+ reward += 0.12 # good decision
77
+ else:
78
+ reward -= 0.05
79
+
80
+ elif action_type == "normalize":
81
+ col = action["column"]
82
+
83
+ if self.dirty_df[col].dtype != "object":
84
+ self.dirty_df = normalize_column(self.dirty_df, col)
85
+ reward += 0.1
86
+ else:
87
+ reward -= 0.08 # wrong column type
88
+ elif action_type == "drop_correlated":
89
+ self.dirty_df = drop_correlated_feature(self.dirty_df, action["column"])
90
+ reward += 0.1
91
+ else:
92
+ reward -= 0.05
93
+
94
+ return {
95
+ "dataset": self.safe_df(self.dirty_df).to_dict(),
96
+ "shape": list(self.dirty_df.shape),
97
+ "steps": self.steps
98
+ }, reward, self.done, {}
99
+
100
+ def state(self):
101
+ return {
102
+ "steps": self.steps,
103
+ "dataset_shape": self.dirty_df.shape,
104
+ "inspected_columns": list(self.inspected_cols)
105
+ }
106
+
107
+ def submit_cleaned_data(self, agent_df):
108
+
109
+ self.done = True
110
+
111
+ if self.task == 1:
112
+ quality = grade_task1(agent_df, self.clean_df, self.manifest)
113
+
114
+ elif self.task == 2:
115
+ quality = grade_task2(agent_df, self.clean_df)
116
+
117
+ elif self.task == 3:
118
+ quality = grade_task3(agent_df, self.clean_df)
119
+
120
+ final = compute_final_score(quality, self.steps)
121
+
122
+ return {
123
+ "quality_score": quality,
124
+ "steps": self.steps,
125
+ "final_score": final
126
+ }
127
+
128
+ class StateManager:
129
+ def __init__(self):
130
+ self.steps = 0
131
+ self.inspected_cols = set()
env/graders/__pycache__/final_evaluator.cpython-311.pyc ADDED
Binary file (519 Bytes). View file
 
env/graders/__pycache__/final_evaluator.cpython-313.pyc ADDED
Binary file (489 Bytes). View file
 
env/graders/__pycache__/task1_grader.cpython-311.pyc ADDED
Binary file (1.57 kB). View file
 
env/graders/__pycache__/task1_grader.cpython-313.pyc ADDED
Binary file (1.38 kB). View file
 
env/graders/__pycache__/task2_grader.cpython-311.pyc ADDED
Binary file (1.48 kB). View file
 
env/graders/__pycache__/task2_grader.cpython-313.pyc ADDED
Binary file (1.3 kB). View file
 
env/graders/__pycache__/task3_grader.cpython-311.pyc ADDED
Binary file (1.42 kB). View file
 
env/graders/__pycache__/task3_grader.cpython-313.pyc ADDED
Binary file (1.28 kB). View file
 
env/graders/final_evaluator.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ def compute_final_score(quality_score, steps):
2
+
3
+ efficiency_score = max(0, 1 - (steps / 20))
4
+
5
+ return round(0.75 * quality_score + 0.25 * efficiency_score, 4)
env/graders/task1_grader.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def grade_task1(agent_df, clean_df, manifest):
2
+ score = 0
3
+
4
+ # nulls (partial)
5
+ total_nulls = sum(len(v) for v in manifest["nulls"].values())
6
+ remaining_nulls = agent_df.isnull().sum().sum()
7
+
8
+ score += 0.25 * (1 - remaining_nulls / max(total_nulls, 1))
9
+
10
+ # duplicates
11
+ expected = len(clean_df)
12
+ actual = len(agent_df.drop_duplicates())
13
+
14
+ score += 0.25 * (1 - abs(actual - expected) / expected)
15
+
16
+ # dtype
17
+ try:
18
+ agent_df["age"].astype(int)
19
+ score += 0.25
20
+ except:
21
+ pass
22
+
23
+ # whitespace
24
+ score += 0.25
25
+
26
+ return round(min(score, 1.0), 4)
env/graders/task2_grader.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+ def grade_task2(agent_df, clean_df):
4
+
5
+ score = 0
6
+
7
+ numeric_cols = agent_df.select_dtypes(include=np.number).columns
8
+
9
+ # normalization check
10
+ norm_score = 0
11
+ for col in numeric_cols:
12
+ if agent_df[col].min() >= 0 and agent_df[col].max() <= 1:
13
+ norm_score += 1
14
+
15
+ if len(numeric_cols) > 0:
16
+ score += 0.4 * (norm_score / len(numeric_cols))
17
+
18
+ # correlation reduction
19
+ corr = agent_df.corr(numeric_only=True).abs()
20
+ if (corr > 0.8).sum().sum() < len(corr):
21
+ score += 0.3
22
+
23
+ # data preserved
24
+ if len(agent_df) <= len(clean_df):
25
+ score += 0.3
26
+
27
+ return round(min(score, 1.0), 4)
env/graders/task3_grader.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def grade_task3(agent_df, clean_df):
2
+
3
+ score = 0
4
+
5
+ # ✅ 1. Data preservation (VERY IMPORTANT)
6
+ ratio = len(agent_df) / len(clean_df)
7
+
8
+ if ratio > 0.9:
9
+ score += 0.3
10
+ elif ratio > 0.75:
11
+ score += 0.2
12
+ elif ratio > 0.6:
13
+ score += 0.1
14
+
15
+ # ✅ 2. Null removal
16
+ nulls = agent_df.isnull().sum().sum()
17
+ if nulls == 0:
18
+ score += 0.25
19
+
20
+ # ✅ 3. Duplicate removal
21
+ if len(agent_df) == len(agent_df.drop_duplicates()):
22
+ score += 0.2
23
+
24
+ # ✅ 4. Structure preservation (columns)
25
+ col_diff = abs(agent_df.shape[1] - clean_df.shape[1])
26
+ if col_diff == 0:
27
+ score += 0.15
28
+ elif col_diff <= 1:
29
+ score += 0.1
30
+
31
+ # ✅ 5. No excessive cleaning (penalty)
32
+ if ratio < 0.5:
33
+ score -= 0.2 # too much data loss
34
+
35
+ return round(max(min(score, 1.0), 0), 4)
env/issue_injector.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ def inject_issues(df):
5
+ dirty = df.copy()
6
+ manifest = {}
7
+ rng = np.random.default_rng(42)
8
+
9
+
10
+ # nulls
11
+ idx = np.random.choice(len(df), 5, replace=False)
12
+ dirty.loc[idx, "city"] = None
13
+ columns = list(dirty.columns)
14
+ selected_cols = rng.choice(columns, size=2, replace=False)
15
+ null_indices = rng.choice(len(dirty), 20, replace=False).tolist()
16
+ split = len(null_indices) // len(selected_cols)
17
+ manifest["nulls"] = {}
18
+ for i, col in enumerate(selected_cols):
19
+ idxs = null_indices[i * split : (i + 1) * split]
20
+ dirty.loc[idxs, col] = None
21
+ manifest["nulls"][col] = idxs
22
+
23
+ # duplicates
24
+ dirty = pd.concat([dirty, dirty.iloc[:3]], ignore_index=True)
25
+ manifest["duplicates"] = list(range(len(df), len(df)+3))
26
+
27
+ # type issue
28
+ dirty["age"] = dirty["age"].astype(str)
29
+
30
+ return dirty, manifest
env/models.py ADDED
File without changes
env/statae_manager.py ADDED
File without changes
inference.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+ from openai import OpenAI
4
+ from env.environment import DataCleaningEnv
5
+
6
+ # ------------------ ENV VARIABLES ------------------
7
+ API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
8
+ MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
9
+ HF_TOKEN = os.getenv("HF_TOKEN")
10
+
11
+ if HF_TOKEN is None:
12
+ raise ValueError("HF_TOKEN environment variable is required")
13
+
14
+ # ------------------ OPENAI CLIENT ------------------
15
+ client = OpenAI(
16
+ base_url=API_BASE_URL,
17
+ api_key=HF_TOKEN
18
+ )
19
+
20
+ MAX_STEPS = 6
21
+
22
+ # ------------------ LOGGING ------------------
23
+ def log_start(task, env, model):
24
+ print(f"[START] task={task} env={env} model={model}")
25
+
26
+ def log_step(step, action, reward, done, error):
27
+ error_val = error if error else "null"
28
+ print(f"[STEP] step={step} action={action} reward={reward:.2f} done={str(done).lower()} error={error_val}")
29
+
30
+ def log_end(success, steps, score, rewards):
31
+ rewards_str = ",".join(f"{r:.2f}" for r in rewards)
32
+ print(f"[END] success={str(success).lower()} steps={steps} score={score:.3f} rewards={rewards_str}")
33
+
34
+ # ------------------ LLM DECISION ------------------
35
+ def get_action_from_llm(dataset,history):
36
+ prompt = f"""
37
+ You are an intelligent data cleaning agent.
38
+
39
+ Your goal is to clean the dataset completely.
40
+
41
+ You can use the following actions:
42
+ - fill_nulls
43
+ - remove_nulls
44
+ - deduplicate
45
+ - convert_types
46
+ - trim_whitespace
47
+ - normalize
48
+
49
+ Previous actions:
50
+ {history}
51
+
52
+ Rules:
53
+ 1. You can choose ANY action.
54
+ 2. You can choose ANY column.
55
+ 3. You can repeat actions if needed.
56
+ 4. You should decide based on dataset issues.
57
+ 5. Your goal is to maximize data quality.
58
+ 6. Stop only when dataset is clean.
59
+ 7. Prefer fixing critical issues first (nulls, duplicates, types)
60
+ 8. Avoid repeating same action unnecessarily
61
+ 9.Base your decision ONLY on dataset statistics.
62
+ 10.Choose different actions depending on issues.
63
+
64
+ Dataset:
65
+ {dataset}
66
+
67
+ Return ONLY ONE action in this format:
68
+ action_type,column_name
69
+
70
+ Examples:
71
+ fill_nulls,city
72
+ deduplicate,customer_id
73
+ convert_types,age
74
+ normalize,income
75
+
76
+ Do NOT explain anything.
77
+ Only return the action.
78
+ """
79
+
80
+ response = client.chat.completions.create(
81
+ model=MODEL_NAME,
82
+ messages=[{"role": "user", "content": prompt}],
83
+ temperature=0.3,
84
+ max_tokens=50
85
+ )
86
+
87
+ output = response.choices[0].message.content.strip()
88
+
89
+ try:
90
+ action_type, column = output.split(",")
91
+ return {"type": action_type.strip(), "column": column.strip()}
92
+ except:
93
+ return {"type": "fill_nulls", "column": "city"} # fallback
94
+
95
+ # ------------------ MAIN ------------------
96
+ def main():
97
+ env = DataCleaningEnv(task=1)
98
+ obs = env.reset()
99
+
100
+ rewards = []
101
+ steps_taken = 0
102
+ history = []
103
+
104
+ log_start("task1", "data_cleaning", MODEL_NAME)
105
+
106
+ for step in range(1, MAX_STEPS + 1):
107
+
108
+ df = pd.DataFrame(obs["dataset"])
109
+ col_info = {}
110
+
111
+ for col in df.columns:
112
+ col_info[col] = {
113
+ "nulls": float(df[col].isnull().mean()),
114
+ "dtype": str(df[col].dtype),
115
+ "unique": int(df[col].nunique())
116
+ }
117
+
118
+ summary = f"""
119
+ Columns: {list(df.columns)}
120
+
121
+ Column Info:
122
+ {col_info}
123
+
124
+ Duplicates: {df.duplicated().sum()}
125
+
126
+ Sample Data:
127
+ {df.head(3).to_dict()}
128
+ """
129
+ action = get_action_from_llm(summary, history)
130
+
131
+ # check BEFORE adding
132
+ if str(action) in history:
133
+ action = {"type": "deduplicate", "column": "customer_id"}
134
+
135
+ history.append(str(action))
136
+
137
+ obs, reward, done, _ = env.step(action)
138
+
139
+ rewards.append(reward)
140
+ steps_taken = step
141
+
142
+ log_step(step, str(action), reward, done, None)
143
+
144
+ if done:
145
+ break
146
+
147
+ final = env.submit_cleaned_data(env.dirty_df)
148
+ score = final["final_score"]
149
+
150
+ success = score > 0.3
151
+
152
+ log_end(success, steps_taken, score, rewards)
153
+
154
+ if __name__ == "__main__":
155
+ main()
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pandas==2.2.2
2
+ numpy==1.26.4
3
+ pydantic==2.7.1
4
+ faker==25.2.0
5
+ rapidfuzz==3.9.3
6
+ scikit-learn==1.5.0
7
+
8
+ openai==1.30.1
9
+
10
+ python-dotenv==1.0.1
11
+
12
+ uvicorn==0.30.1
13
+ fastapi==0.111.0