serp-ctr 0.1.3

Search-console arithmetic: click-through rate, impression-weighted average position, and click projection against a caller-supplied CTR-by-position curve.
Documentation

serp-ctr

Search-console arithmetic in safe, dependency-free Rust: click-through rate, impression-weighted average position, SERP page bucketing, pooled totals, and click projection against a CTR-by-position curve.

Why there is no built-in CTR table

Published "average CTR by position" tables are third-party estimates that disagree with each other and vary by query intent and SERP layout. Baking one in would turn somebody else's sample into this crate's constant. CtrCurve is therefore built from your observations, and it returns an error for a position it has never seen rather than interpolating one.

use serp_ctr::{ctr_percent, CtrCurve, PositionRow, weighted_average_position};

// 7 clicks on 1,000 impressions
assert_eq!(ctr_percent(7, 1_000, 2).unwrap(), 0.7);

// Impression-weighted, not a mean of the position column
let rows = [
    PositionRow { position: 2.0, impressions: 9_000 },
    PositionRow { position: 90.0, impressions: 1_000 },
];
assert!((weighted_average_position(&rows).unwrap() - 10.8).abs() < 1e-9);

// Curve built from measured rows, then used to restate them at a new volume
let curve = CtrCurve::from_observations([(3.0, 60, 1_000), (9.0, 10, 1_000)]).unwrap();
assert_eq!(curve.project_clicks(3, 5_000).unwrap(), 300);
assert!(curve.ctr_at(5).is_err()); // never observed, so never guessed

Install

[dependencies]
serp-ctr = "0.1"

#![forbid(unsafe_code)], no dependencies, MSRV 1.63.

Licence

MIT OR Apache-2.0.

Written for the SEO pipeline at https://toolsthatrank.com/.