Checklist Bulgaria selection 2025 IOAI Team Selection · Task 4
Salary Prediction
Bulgarian title: Прогнозиране на заплата
Beat a linear-regression baseline for Glassdoor salary prediction through feature engineering and a stronger model.
The task
As a junior data scientist, the contestant must improve a salary-prediction model trained on data collected from Glassdoor. The provided script loads and minimally cleans the data, trains a baseline linear regression on a few basic features and prints its mean absolute error.
Working in the marked contestants' zone (the student_solution function), the contestant creates more informative features, selects and prepares them, and trains a stronger model to obtain a lower MAE than the baseline.
Abridged and translated by SOTA from the official Bulgarian materials. The official statement has the exact rules, and it wins wherever this summary differs.
In English
This task was published in Bulgarian. SOTA translated its 2 files into English on 17 September 2026. Only the words changed in the notebooks: markdown, code comments, messages and printed output. The code, file names and paths are the original's, so a translated notebook runs with the original data.
- Task notebook Bulgarian original of Task notebook
- Solution by Mario Petkov Bulgarian original of Solution by Mario Petkov
Read the task notebook in English
Task: Salary Prediction
Introduction:
You are a junior data scientist at a company and are given the task of improving an existing model for predicting salaries. The current model is a simple linear regression that uses only a few basic features of the data collected from Glassdoor. Your goal is to use your skills in feature engineering and modelling to create a significantly more accurate model.
1. The problem
You will be given a Python script that:
- Loads the data and performs minimal cleaning on it.
- Trains a baseline model (Linear Regression) on a small set of features.
- Computes and prints its mean absolute error (Mean Absolute Error - MAE).
Your task is to work in the designated "Contestants' zone" in order to:
- Create new, more informative features from the existing data.
- Choose and train a more powerful model.
- Achieve a lower MAE than the baseline model.
2. Scoring
The main scoring metric is the Mean Absolute Error (MAE). A lower value means a better model - your goal is to minimise it. MAE is easy to interpret, since it shows the average prediction error in the same units as the salary (e.g. an MAE of 15 means that the model is off by $15,000 on average).
3. Submission
Submit your generated predictions, predictions.csv, on the data from test_features.csv, together with the notebook.
Importing the required libraries
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
import xgboost as xgb
from sklearn.metrics import mean_absolute_error
import warnings
from sklearn.model_selection import GridSearchCV, cross_val_score
warnings.filterwarnings('ignore')
BASELINE_FEATURES = ['Rating', 'Size', 'Type of ownership', 'Industry', 'Sector', 'Revenue']
Y_COLUMN = ['Salary Estimate']
!pip install gdown
!gdown 1atCcqIxgKfvmtRUQkqz--wF2FTlEAHal
!gdown 1fA6iChCXW2e6v9a3G68r6b6L36_Em1ki
Helper functions and baseline model
# ==============================================================================
# === (HELPER FUNCTIONS AND BASELINE MODEL - DO NOT CHANGE THIS CELL) ===
# ==============================================================================
def title_simplifier(title):
if 'data scientist' in title.lower():
return 'data scientist'
elif 'data engineer' in title.lower():
return 'data engineer'
elif 'analyst' in title.lower():
return 'analyst'
elif 'machine learning' in title.lower():
return 'mle'
elif 'manager' in title.lower():
return 'manager'
elif 'director' in title.lower():
return 'director'
else:
return 'na'
def seniority(title):
if 'sr' in title.lower() or 'senior' in title.lower() or 'lead' in title.lower() or 'principal' in title.lower():
return 'senior'
elif 'jr' in title.lower() or 'jr.' in title.lower():
return 'jr'
else:
return 'na'
def load_data(path: str) -> pd.DataFrame:
"""
Loads the data and performs a robust initial cleaning,
inspired by the detailed analysis.
"""
df = pd.read_csv(path)
return df
def clean_train_data(df: pd.DataFrame) -> pd.DataFrame:
df = df[df['Salary Estimate']!= '-1']
# Cleaning the salary string
df['Salary Estimate'] = df['Salary Estimate'].apply(lambda x: x.split('(')[0])
df['Salary Estimate'] = df['Salary Estimate'].apply(lambda x: x.replace('K','').replace('$',''))
df['Salary Estimate'] = df['Salary Estimate'].apply(lambda x: x.lower().replace('per hour', ''))
df['Salary Estimate'] = df['Salary Estimate'].apply(lambda x: x.lower().replace('employer provided salary:', ''))
df['Rating'] = df['Rating'].apply(lambda x: x if x > 0 else np.nan)
df['State'] = df.Location.apply(lambda x: x.split(',')[1])
# Handling both ranges (e.g. "50-100") and single values ("75")
df['Min_Salary'] = df['Salary Estimate'].apply(lambda x: int(x.split('-')[0]))
df['Max_Salary'] = df['Salary Estimate'].apply(lambda x: int(x.split('-')[1]))
df['Salary Estimate']= (df['Min_Salary'] + df['Max_Salary'])/2
df.drop(['Min_Salary', 'Max_Salary'], axis=1, inplace=True)
return df
def clean_data_features(df: pd.DataFrame) -> pd.DataFrame:
df['Rating'] = df['Rating'].apply(lambda x: x if x > 0 else np.nan)
df['State'] = df.Location.apply(lambda x: x.split(',')[1])
return df
def train_baseline_model(df: pd.DataFrame) -> float:
"""Trains a baseline model and returns it, together with its MAE and ."""
# We use several categorical and numerical features
base_features = BASELINE_FEATURES + Y_COLUMN
df_base = df[base_features].dropna()
# Creating dummy variables for the baseline model
df_base_dum = pd.get_dummies(df_base)
X = df_base_dum.drop('Salary Estimate', axis=1)
y = df_base_dum['Salary Estimate'].values
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)
model = LinearRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_val)
mae = mean_absolute_error(y_val, y_pred)
print(f"Baseline Model MAE: {mae:.3f}")
return model, mae, X, y
Contestants' zone
Goal:
Your task is to implement the student_solution function below. The goal is to create a model that has a lower mean absolute error (MAE) than the baseline model.
Steps:
- Feature Engineering: Create new, more useful features.
- Data preparation: Choose the features you will use and prepare them for the model in a suitable way.
- Model training: Choose and train your model.
# ==============================================================================
# === (MAIN SCRIPT - DO NOT CHANGE THIS CELL) ===
# ==============================================================================
# Loading the data
df_1 = load_data("train.csv")
df = clean_train_data(df_1)
# Training and scoring the baseline model
baseline_model, baseline_mae, X_train_baseline, y_train_baseline = train_baseline_model(df.copy())
df_test = load_data("../../../test_features.csv")
df_test_cleaned = clean_data_features(df_test)
df_test_processed = df_test_cleaned[BASELINE_FEATURES].dropna()
baseline_model_columns = X_train_baseline.columns
# Creating dummy variables for the baseline model
df_test_dum = pd.get_dummies(df_test_processed)
df_test_aligned = df_test_dum.reindex(
columns=baseline_model_columns, fill_value=0
)
y_pred = baseline_model.predict(df_test_aligned)
y_pred_df = pd.DataFrame(y_pred, columns=Y_COLUMN)
# y_pred_df.to_csv('predictions.csv', index=False)
# ==============================================================================
# ZONE FOR YOUR CODE
# =============================================================================
# You can try to:
# - clean/rework the data further (e.g. )
# - add other features (e.g. the age of the company)
# - experiment with different models
# - combine several models
# - run a grid search to find optimal parameters
# ==============================================================================
student_model = baseline_model
df[['Age','Salary Estimate', 'Rating', 'desc_len']].corr()
_, X_val, _, y_val = train_test_split(X_train_baseline, y_train_baseline, test_size=0.2, random_state=42)
student_predictions = student_model.predict(X_val)
student_mae = mean_absolute_error(y_val, student_predictions)
print("\n--- Final results ---")
print(f"The baseline model has MAE: {baseline_mae:.3f}")
print(f"Your model has MAE: {student_mae:.3f}")
improvement = baseline_mae - student_mae
if improvement > 0:
print(f"\nImprovement over the baseline model: {improvement:.3f}!")
else:
print("\nTry again! Your model is not better than the baseline.")
Translated by SOTA. The Bulgarian original is the official version and wins wherever the two differ. The solution is a sample solution by a member of the 2025 national team. The task notebook reads the test features from ../../../test_features.csv, while the original folder has test_features.csv next to the notebook. If you organise this olympiad and would like the translation removed, email [email protected] and we will take it down.
At a glance
- You get
train.csv,test_features.csv(andtest_labels.csv, released in the repository).- You submit
predictions.csvfortest_features.csv, submitted together with the notebook.- Scoring
- Mean absolute error (lower is better; an MAE of 15 means an average error of $15,000).
- Rules
- Work only in the contestants' zone.
- Format
- Bulgarian IOAI 2025 team selection, Day 2, Task 4. Dates and format are not published in the repository.