Solution author: Cowile
import warnings
warnings.filterwarnings('ignore')
Step 1: Load The Data
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
trn = pd.read_csv("/kaggle/input/mortys-time-paradox-aicc-round-3/train.csv")
tst = pd.read_csv("/kaggle/input/mortys-time-paradox-aicc-round-3/test.csv")
X = trn.iloc[:, 1:9].values
cand = trn.iloc[:, 9:14].values
X_test = tst.iloc[:, 1:9].values
X_all = np.vstack([X, X_test]) # Combine features from the training and test sets
Step 2: Determine Cyclic Periods
The problem states features are cyclic. We need to determine the period T for each feature by examining their ranges. The period should be approximately ceil(max) for each feature.
mins = X_all.min(axis=0)
maxs = X_all.max(axis=0)
T = np.rint(maxs).astype(int)
T = np.maximum(T, 1)
ranges = pd.DataFrame({"min": mins, "max": maxs, "T": T})
ranges.index = [f"x{i}" for i in range(X.shape[1])]
ranges
min max T
x0 0.008416 10.999272 11
x1 0.010393 12.997244 13
x2 0.006611 16.997881 17
x3 0.013319 18.993695 19
x4 0.001282 10.994939 11
x5 0.009497 12.998363 13
x6 0.006156 16.987903 17
x7 0.011533 18.986551 19
Step 3: Cyclic Encoding
For cyclic features, we apply the standard sin/cos transformation:
This transforms each feature into 2 components, preserving the cyclic nature of the data.
import math
def enc(X, T):
n, d = X.shape
Z = np.empty((n, 2 * d), dtype=np.float64)
for j in range(d):
a = 2 * math.pi * X[:, j] / float(T[j])
Z[:, 2 * j] = np.sin(a)
Z[:, 2 * j + 1] = np.cos(a)
return Z
X_encoded = enc(X_all, T)
Step 4. Visualizing the data
from sklearn.preprocessing import StandardScaler
X_scaled = StandardScaler().fit_transform(X_encoded)
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
tsne = TSNE(n_components=2, perplexity=50, random_state=2026)
emb = tsne.fit_transform(X_scaled)
n_train = len(X)
plt.scatter(*emb[:n_train].T, s=5, alpha=0.5, label='Train')
plt.scatter(*emb[n_train:].T, s=5, alpha=0.5, label='Test')
plt.title('t-SNE')
plt.legend()
_ = plt.axis('off')
<Figure size 640x480 with 1 Axes>
Step 5. Clustering
from sklearn.cluster import SpectralClustering
cl = SpectralClustering(n_clusters=5, random_state=2026, assign_labels="cluster_qr", n_jobs=-1)
lab = cl.fit_predict(X_scaled)
plt.scatter(*emb.T, c=lab, s=5)
plt.title("Clusters visualized in 2D")
plt.axis('off')
plt.show()
<Figure size 640x480 with 1 Axes>
Step 6. Majority Voting for Label Assignment
cluster_label = np.array([cand[:n_train][lab[:n_train] == k].sum(axis=0).argmax() for k in range(5)])
cluster_label
array([0, 3, 1, 2, 4])
pd.DataFrame(lab).value_counts()
0
1 502
3 502
0 499
4 499
2 498
Name: count, dtype: int64
test_labels = cluster_label[lab[n_train:]]
Step 7: Generate Predictions
sub = pd.DataFrame({"row_id": tst.iloc[:, 0], "label": test_labels.astype(int)})
sub.to_csv("submission.csv", index=False)
sub.head()
row_id label
0 0 4
1 1 2
2 2 2
3 3 4
4 4 0