Impression share loss report
read-only
Separates impression share lost to budget from impression share lost to ad rank, and lists each campaign under the cause that applies, because the two shortfalls have opposite fixes.
- requires
- Search or Shopping campaigns. Impression share is not reported for every campaign type.
- writes
- Nothing. Read-only.
- schedule
- Weekly
- change first
- EMAIL, MIN_COST and the two thresholds at the top.
// Impression share loss report
// Separates the two reasons a campaign is not showing: budget and ad rank.
// They need opposite responses, and the account summary does not distinguish them.
// Read-only.
var DAYS = 7;
var MIN_COST = 20; // ignore campaigns too small to act on
var BUDGET_AT = 0.10; // flag when 10% or more of impression share is lost to budget
var RANK_AT = 0.40; // flag when 40% or more is lost to rank
var EMAIL = '';
function main() {
var rows = AdsApp.search(
'SELECT campaign.name, campaign.advertising_channel_type, ' +
' metrics.cost_micros, metrics.conversions, ' +
' metrics.search_impression_share, ' +
' metrics.search_budget_lost_impression_share, ' +
' metrics.search_rank_lost_impression_share ' +
'FROM campaign ' +
'WHERE segments.date DURING LAST_' + DAYS + '_DAYS ' +
' AND campaign.status = "ENABLED"'
);
var budgetBound = [];
var rankBound = [];
while (rows.hasNext()) {
var r = rows.next();
var cost = Number(r.metrics.costMicros) / 1000000;
if (cost < MIN_COST) { continue; }
var is = num(r.metrics.searchImpressionShare);
var lostBud = num(r.metrics.searchBudgetLostImpressionShare);
var lostRank = num(r.metrics.searchRankLostImpressionShare);
// Impression share metrics are not reported for every channel type.
if (is === null && lostBud === null && lostRank === null) { continue; }
var row = {
name: String(r.campaign.name),
type: String(r.campaign.advertisingChannelType),
cost: cost,
conv: Number(r.metrics.conversions),
is: is,
bud: lostBud,
rank: lostRank
};
if (lostBud !== null && lostBud >= BUDGET_AT) { budgetBound.push(row); }
if (lostRank !== null && lostRank >= RANK_AT) { rankBound.push(row); }
}
if (!budgetBound.length && !rankBound.length) {
Logger.log('No campaign over the thresholds in the last ' + DAYS + ' days.');
return;
}
budgetBound.sort(function (a, b) { return b.cost - a.cost; });
rankBound.sort(function (a, b) { return b.cost - a.cost; });
var lines = [AdsApp.currentAccount().getName(), 'Last ' + DAYS + ' days', ''];
if (budgetBound.length) {
lines.push('LOSING SHARE TO BUDGET (raise the budget, or accept the ceiling)');
lines.push('campaign\ttype\tcost\tconv\tIS\tlost to budget');
for (var i = 0; i < budgetBound.length; i++) { lines.push(fmt(budgetBound[i], 'bud')); }
lines.push('');
}
if (rankBound.length) {
lines.push('LOSING SHARE TO RANK (relevance, bids or landing page, not budget)');
lines.push('campaign\ttype\tcost\tconv\tIS\tlost to rank');
for (var j = 0; j < rankBound.length; j++) { lines.push(fmt(rankBound[j], 'rank')); }
}
var body = lines.join('\n') +
'\n\nShares above 90% are reported as 0.9 and below 10% as 0.1. ' +
'Impression share is not reported for every campaign type.';
Logger.log(body);
if (EMAIL) {
MailApp.sendEmail(EMAIL, 'Impression share loss - ' + AdsApp.currentAccount().getName(), body);
}
}
function fmt(r, which) {
return [
r.name,
r.type,
r.cost.toFixed(2),
r.conv.toFixed(1),
pct(r.is),
pct(r[which])
].join('\t');
}
function num(v) {
if (v === null || v === undefined || v === '') { return null; }
var n = Number(v);
return isNaN(n) ? null : n;
}
function pct(v) {
return v === null ? '-' : (v * 100).toFixed(0) + '%';
}Why the split matters
Impression share lost to budget and impression share lost to rank are the same shortfall with opposite fixes. Budget loss means the campaign wanted to show and ran out of money — the answer is more money or a narrower target. Rank loss means the campaign entered auctions and lost them — the answer is relevance, bid strategy or the landing page, and adding budget makes it worse by buying more of the same losses.
The account summary shows one impression share figure. This report shows the two causes separately and lists each campaign under the one that applies.
Reading the two lists
Budget bound. A campaign losing ten per cent or more to budget has demand you are declining to buy. Whether that is a problem depends entirely on its cost per conversion — which is why the cost and conversion columns sit next to the share, rather than in a separate report.
Rank bound. Forty per cent or more lost to rank is the default flag. Below that, most Search campaigns lose some share to rank permanently and it is not news. Above it, something structural is losing auctions.
A campaign can appear in both lists. That combination usually means a broad campaign fighting for queries it should not be entering at all, and the fix is narrower targeting rather than either more budget or higher bids.
Limits of the metric
Google reports anything above ninety per cent as 0.9 and anything below ten per cent as 0.1. Small movements at the top and bottom of the range are invisible, so do not build trend logic on changes inside those bands.
Impression share is also not reported for every campaign type. The script skips rows where all three fields are absent rather than treating them as zero, which is why a Performance Max campaign will not appear here even when it is the largest spender in the account.
Finally, impression share is share of an estimate. It is a directional signal about competitive position, not a measured count, and it is at its least reliable in low-volume campaigns — which is what MIN_COST is for.
Notes
- Weekly over seven days. Daily impression share in a small account swings for reasons that have nothing to do with the account.
- The thresholds are the two lines worth tuning. Start where they are and tighten once you have seen a month.
- Pair it with the pacing monitor: budget-bound campaigns in an account pacing under budget are the clearest opportunity an account gives you.