Skip to content

Commit 689ae50

Browse files
Varun NairVarun-nair1997
authored andcommitted
initial ml nodes
sklearn nodes included ML model validation nodes node tests for sklearn nodes
1 parent 4805de2 commit 689ae50

4 files changed

Lines changed: 3024 additions & 0 deletions

File tree

ml_nodes.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
"""
2+
Elementary ML nodes.
3+
4+
This module contains nodes for for machine learning workflows using sk-learn models.
5+
"""
6+
import pandas as pd
7+
import numpy as np
8+
9+
from sklearn.model_selection import train_test_split
10+
from sklearn.ensemble import RandomForestRegressor
11+
from sklearn.linear_model import LinearRegression
12+
from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
13+
14+
from core import as_function_node
15+
16+
17+
@as_function_node
18+
def MLDataSplitter(
19+
df,
20+
y_name: str,
21+
train_fraction: float = 0.70,
22+
validation_fraction: float = 0.15,
23+
test_fraction: float = 0.15,
24+
random_state: int = 42
25+
):
26+
"""
27+
Splits dataframe into train, validation, and test sets. This node prevents data leakage when connected correctly.
28+
using ONLY numeric feature columns.
29+
"""
30+
31+
# -----------------------------
32+
# Validate fractions
33+
# -----------------------------
34+
total = train_fraction + validation_fraction + test_fraction
35+
36+
if not np.isclose(total, 1.0):
37+
raise ValueError("Fractions must sum to 1.0")
38+
39+
# -----------------------------
40+
# Remove missing rows
41+
# -----------------------------
42+
df = df.dropna()
43+
44+
# -----------------------------
45+
# Target column
46+
# -----------------------------
47+
y = df[y_name].copy()
48+
49+
# -----------------------------
50+
# Feature columns
51+
# -----------------------------
52+
X_candidates = df.drop(columns=[y_name])
53+
54+
# Keep ONLY numeric columns
55+
X_numeric = X_candidates.select_dtypes(include=["number"])
56+
57+
# -----------------------------
58+
# FIRST SPLIT
59+
# Train vs Temp
60+
# -----------------------------
61+
temp_fraction = validation_fraction + test_fraction
62+
63+
X_train, X_temp, y_train, y_temp = train_test_split(
64+
X_numeric,
65+
y,
66+
test_size=temp_fraction,
67+
random_state=random_state
68+
)
69+
70+
# -----------------------------
71+
# SECOND SPLIT
72+
# Validation vs Test
73+
# -----------------------------
74+
validation_size_adjusted = validation_fraction / temp_fraction
75+
76+
X_validation, X_test, y_validation, y_test = train_test_split(
77+
X_temp,
78+
y_temp,
79+
test_size=(1 - validation_size_adjusted),
80+
random_state=random_state
81+
)
82+
83+
return X_train, X_validation, X_test, y_train, y_validation, y_test
84+
85+
86+
87+
@as_function_node
88+
def train_regressor(X_train:pd.DataFrame, y_train:pd.DataFrame, r_type: str = None):
89+
"""
90+
trains a regressor
91+
"""
92+
if r_type!= None:
93+
if r_type=="linear":
94+
reg = LinearRegression().fit(X_train, y_train)
95+
if r_type=="tree":
96+
reg = RandomForestRegressor().fit(X_train, y_train)
97+
return reg
98+
99+
100+
101+
102+
103+
# =========================================================
104+
# 2) MODEL EVALUATION FUNCTION
105+
# =========================================================
106+
107+
@as_function_node
108+
def EvaluateRegressionModel(model, X_test, y_test):
109+
"""
110+
Evaluates a regression model.
111+
112+
Returns:
113+
R2
114+
MSE
115+
MAE
116+
"""
117+
118+
# Predictions
119+
y_pred = model.predict(X_test)
120+
121+
# Metrics
122+
r2 = r2_score(y_test, y_pred)
123+
mse = mean_squared_error(y_test, y_pred)
124+
mae = mean_absolute_error(y_test, y_pred)
125+
out = {
126+
"R2": r2,
127+
"MSE": mse,
128+
"MAE": mae
129+
}
130+
return out
131+
132+
133+
# =========================================================
134+
# 3) MODEL COMPARISON FUNCTION
135+
# =========================================================
136+
137+
138+
@as_function_node
139+
def ChooseBestModel(
140+
model_1,
141+
model_2,
142+
X_validation,
143+
y_validation
144+
):
145+
"""
146+
Compares two regression models on VALIDATION DATA.
147+
148+
Selection Priority:
149+
1. Higher R2
150+
2. Lower RMSE
151+
152+
Returns:
153+
best_model
154+
comparison_results
155+
"""
156+
157+
# -----------------------------
158+
# Predictions
159+
# -----------------------------
160+
pred_1 = model_1["model"].predict(X_validation)
161+
pred_2 = model_2["model"].predict(X_validation)
162+
163+
# -----------------------------
164+
# Metrics for model 1
165+
# -----------------------------
166+
r2_1 = r2_score(y_validation, pred_1)
167+
rmse_1 = np.sqrt(mean_squared_error(y_validation, pred_1))
168+
169+
# -----------------------------
170+
# Metrics for model 2
171+
# -----------------------------
172+
r2_2 = r2_score(y_validation, pred_2)
173+
rmse_2 = np.sqrt(mean_squared_error(y_validation, pred_2))
174+
175+
# -----------------------------
176+
# Choose best model
177+
# -----------------------------
178+
if r2_1 > r2_2:
179+
best_model = model_1
180+
181+
elif r2_2 > r2_1:
182+
best_model = model_2
183+
184+
else:
185+
# If R2 tied -> lower RMSE wins
186+
if rmse_1 < rmse_2:
187+
best_model = model_1
188+
else:
189+
best_model = model_2
190+
191+
results = {
192+
"model_1": {
193+
"R2": r2_1,
194+
"RMSE": rmse_1
195+
},
196+
"model_2": {
197+
"R2": r2_2,
198+
"RMSE": rmse_2
199+
}
200+
}
201+
202+
return best_model, results

0 commit comments

Comments
 (0)