In a surprise discovery, we found that query itself is the biggest predictor of how much CTR will deviate from the site's position average.
This lead to clearer correlations, better feature interpretability and the discovery of search query as the dominant factor in CTR delta prediction. This finding highlights the importance of query intent comprehension with significant implications on traffic modelling and projection tasks in search.
The rest of this article outlines our process and concludes with practical application in the context of SEO analysis and strategy design.
Read on or skip to the end for TLDR.


Example:
LABEL_0: 'Commercial' LABEL_1: 'Non-Commercial' LABEL_2: 'Branded' LABEL_3: 'Non-Branded' LABEL_4: 'Informational' LABEL_5: 'Navigational' LABEL_6: 'Transactional' LABEL_7: 'Commercial Investigation' LABEL_8: 'Local' LABEL_9: 'Entertainment'
This step is key because it allows for intent-specific CTR average calculations. A query "seo by dejan" on #1 will have a 30% CTR while query "seo testimonials" on #1 will have a 1.8% CTR. If you treat them equally you'll end up with noisy CTR averages. Splitting calculations into branded and non-branded queries addresses the biases associated with brand-related query intent.
With clean CTR statistics it's possible to do three levels of traffic projections:
Important: When projecting clicks for rank increase scenarios it's advisable to scale the projection proportionately to the current performance of the query. In other words if a query underperforms by 10% on rank 2, it will likely continue to underperform by 10% on rank 1. Similarly if a query performs better than average, moving it up in rank and factoring in site-average would indeed reduce its calculated click gains.
The next step is to scrape and extract meta data for each page and store it in a database. We use a combination of trafilatura and beautiful soup for the task. One for full page content and another for more precise extraction of titles and descriptions. Later down the track, the above mentioned metadata fits into the following structure:
Query [SEP] Title [SEP] Description [SEP] URL [SEP] CTR Delta
With a reliable CTR Delta metric we can calculate the click loss and flag that query for investigation.
Is the cause for bad CTR in the snippet itself or is there something else?
Here's the full setup:
├── 2024-08-17T14-24_export.csv
├── albert-ctr-description-model
│ ├── config.json
│ ├── model.safetensors
│ ├── special_tokens_map.json
│ ├── spiece.model
│ └── tokenizer_config.json
├── albert-ctr-query-model
│ ├── checkpoints
│ │ ├── checkpoint-1553
│ │ │ ├── config.json
│ │ │ ├── model.safetensors
│ │ │ ├── optimizer.pt
│ │ │ ├── rng_state.pth
│ │ │ ├── scheduler.pt
│ │ │ ├── trainer_state.json
│ │ │ └── training_args.bin
│ │ ├── checkpoint-1582
│ │ │ ├── config.json
│ │ │ ├── model.safetensors
│ │ │ ├── optimizer.pt
│ │ │ ├── rng_state.pth
│ │ │ ├── scheduler.pt
│ │ │ ├── trainer_state.json
│ │ │ └── training_args.bin
│ │ ├── checkpoint-3106
│ │ │ ├── config.json
│ │ │ ├── model.safetensors
│ │ │ ├── optimizer.pt
│ │ │ ├── rng_state.pth
│ │ │ ├── scheduler.pt
│ │ │ ├── trainer_state.json
│ │ │ └── training_args.bin
│ │ ├── checkpoint-3164
│ │ │ ├── config.json
│ │ │ ├── model.safetensors
│ │ │ ├── optimizer.pt
│ │ │ ├── rng_state.pth
│ │ │ ├── scheduler.pt
│ │ │ ├── trainer_state.json
│ │ │ └── training_args.bin
│ │ ├── checkpoint-4659
│ │ │ ├── config.json
│ │ │ ├── model.safetensors
│ │ │ ├── optimizer.pt
│ │ │ ├── rng_state.pth
│ │ │ ├── scheduler.pt
│ │ │ ├── trainer_state.json
│ │ │ └── training_args.bin
│ │ └── checkpoint-4746
│ │ ├── config.json
│ │ ├── model.safetensors
│ │ ├── optimizer.pt
│ │ ├── rng_state.pth
│ │ ├── scheduler.pt
│ │ ├── trainer_state.json
│ │ └── training_args.bin
│ ├── config.json
│ ├── model.safetensors
│ ├── special_tokens_map.json
│ ├── spiece.model
│ └── tokenizer_config.json
├── albert-ctr-title-model
│ ├── checkpoints
│ │ ├── checkpoint-1582
│ │ │ ├── config.json
│ │ │ ├── model.safetensors
│ │ │ ├── optimizer.pt
│ │ │ ├── rng_state.pth
│ │ │ ├── scheduler.pt
│ │ │ ├── trainer_state.json
│ │ │ └── training_args.bin
│ │ ├── checkpoint-3164
│ │ │ ├── config.json
│ │ │ ├── model.safetensors
│ │ │ ├── optimizer.pt
│ │ │ ├── rng_state.pth
│ │ │ ├── scheduler.pt
│ │ │ ├── trainer_state.json
│ │ │ └── training_args.bin
│ │ └── checkpoint-4746
│ │ ├── config.json
│ │ ├── model.safetensors
│ │ ├── optimizer.pt
│ │ ├── rng_state.pth
│ │ ├── scheduler.pt
│ │ ├── trainer_state.json
│ │ └── training_args.bin
│ ├── config.json
│ ├── model.safetensors
│ ├── special_tokens_map.json
│ ├── spiece.model
│ └── tokenizer_config.json
├── albert-ctr-url-model
│ ├── checkpoints
│ │ ├── checkpoint-1582
│ │ │ ├── config.json
│ │ │ ├── model.safetensors
│ │ │ ├── optimizer.pt
│ │ │ ├── rng_state.pth
│ │ │ ├── scheduler.pt
│ │ │ ├── trainer_state.json
│ │ │ └── training_args.bin
│ │ ├── checkpoint-3164
│ │ │ ├── config.json
│ │ │ ├── model.safetensors
│ │ │ ├── optimizer.pt
│ │ │ ├── rng_state.pth
│ │ │ ├── scheduler.pt
│ │ │ ├── trainer_state.json
│ │ │ └── training_args.bin
│ │ └── checkpoint-4746
│ │ ├── config.json
│ │ ├── model.safetensors
│ │ ├── optimizer.pt
│ │ ├── rng_state.pth
│ │ ├── scheduler.pt
│ │ ├── trainer_state.json
│ │ └── training_args.bin
│ ├── config.json
│ ├── model.safetensors
│ ├── special_tokens_map.json
│ ├── spiece.model
│ └── tokenizer_config.json
├── raw_training_data.csv
├── test.py
├── train_description.py
├── train_query.py
├── train_title.py
└── train_url.py

Overfitting is when model is trained to the point where its weights are adjusted to best fit training data features. This makes it perform exceptionally well on training data but less likely to generalise on new and never-before-seen data.
Our approach is to test the limit of the model and see at which point it overfits and select the checkpoint saved just before it happens.
The framework predicts whether CTR for a page query pair will have a better or worse CTR than expected. We input a query, the models return scores, we combine the scores and return a prediction. We do this in a batch-processing pipeline.
Output of all models is combined and balanced in proportion to their softmax values. This allows more confident predictions to have a stronger vote.
It predicts CTR delta correctly 8 out of 10 times. This is most likely as good as it gets without adding new biases such as special SERP features.
Its input will be the classifier quartet output combined with the snippet features and its output will be a percentage of how much the CTR will deviate from the norm.
Stay tuned.