← Back to the problem

AICC (IOAI Community Contest) 2025 · Solution

Deceptive Points

This page gives the expected approach. Read it once you have searched.

Formatted by IOAI Training from the original (AI-Community-Contest/solutions, licence MIT). The original is authoritative.

Solution author: Asandei Stefan-Alexandru

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import HuberRegressor
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn.pipeline import make_pipeline
seed = 43

root_path = "/home/stefan/ioai-prep/kits/aicc/round-0/deceptive-points"

plt.style.use("dark_background")

Data

def clean_df(df: pd.DataFrame):
    X = df[["feature1","feature2","feature3","feature4"]]

    if "target" in df:
        y = df["target"]
        return X, y
    
    return X
train_df = pd.read_csv(f"{root_path}/train.csv")
X, y = clean_df(train_df)
train_df.head()
   feature1  feature2  feature3   feature4     target
0 -4.973675  7.093568 -0.153496   9.400500 -11.100981
1  0.917252  7.904774  1.189619   1.864921   9.324822
2  8.137991 -3.755653  6.936703  14.174913  -6.015563
3  8.737271 -1.220908 -3.365430   9.303323  10.848881
4  1.772946 -3.452335  5.933342  -6.867038  17.858051

EDA

plt.figure(figsize=(4, 2))
_ = plt.hist(y)
<Figure size 400x200 with 1 Axes>

Model

K, MIN_N, SEED = 4, 6, 0
ALPHA = [0.01, 0.1, 1, 10]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=seed)
pca = PCA(n_components=1, random_state=SEED).fit(X_train)
pc1_train = pca.transform(X_train).ravel()
pc1_test  = pca.transform(X_test).ravel()

# decide sign: choose orientation that gives higher corr(pc1,y)
if np.corrcoef(pc1_train, y_train)[0,1] < 0:
    pc1_train *= -1
    pc1_test  *= -1
Z = np.column_stack([pc1_train, y_train])
Z = StandardScaler().fit_transform(Z)
labels = KMeans(K, random_state=SEED, n_init=20).fit(Z).labels_
# keep clusters having positive slope and >= MIN_N
chosen = []
for c in np.unique(labels):
    mask = labels == c
    if mask.sum() < MIN_N:
        continue
    slope = np.polyfit(pc1_train[mask], y_train[mask], 1)[0]
    if slope > 0:
        chosen.append(c)

if not chosen: # fallback: keep everything
    chosen = np.unique(labels)
def evaluate(model):
    cv = cross_val_score(model, X_train[mask], y_train[mask], cv=3, scoring='neg_mean_squared_error', n_jobs=-1)
    scores = cv.mean() + cv.std()
    
    return -scores.item()
mask = np.isin(labels, chosen)
model = make_pipeline(StandardScaler(), HuberRegressor(alpha=1))

evaluate(model)
0.8964289212560037
model.fit(X_train[mask], y_train[mask])
HuberRegressor(alpha=0)

Submission

test_df = pd.read_csv(f"{root_path}/test.csv")
X_test = clean_df(test_df)
y_pred = model.predict(X_test)
submission_df = pd.DataFrame({
    "ID": test_df["ID"],
    "Target": y_pred
})

submission_df.head()
    ID     Target
0  0.0  17.226919
1  1.0   2.697050
2  2.0  -2.593337
3  3.0   4.204250
4  4.0   6.578691
submission_df.to_csv("submission.csv", index=False)