Discord

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.

  • Tabular
  • Regression
  • Bulgarian original · English translation

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.

Read the task notebook in English 306 words and 9 code cells

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:

  1. Loads the data and performs minimal cleaning on it.
  2. Trains a baseline model (Linear Regression) on a small set of features.
  3. 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:

  1. Feature Engineering: Create new, more useful features.
  2. Data preparation: Choose the features you will use and prepare them for the model in a suitable way.
  3. 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 (and test_labels.csv, released in the repository).
You submit
predictions.csv for test_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.

Details

Year
2025
Round
IOAI Team Selection · Task 4
Language
Bulgarian; English translation by SOTA
License
Not stated by the source