Skip to contents

Why presets exist

PageRank is a lens, not the deliverable. The same crawl answers different questions depending on how you prepare the graph first: what does the site look like exactly as crawled? What does it look like once its own canonical and nofollow declarations are honored? Which pages feed authority outward rather than collect it? What survives once site chrome stops voting?

pagerank() can express all of those, but only through a long argument list. A preset is a small named bundle of those arguments describing one recurring view, so a view becomes a one-liner:

edges <- data.frame(
  from = c("A", "A", "B", "C"),
  to = c("B", "C", "A", "A")
)

pagerank(edges, preset = "raw")
#>   node_name  pagerank
#> 1         A 0.4864865
#> 2         B 0.2567568
#> 3         C 0.2567568

Presets are opt-in. pagerank() with no preset behaves exactly as it always has — nothing here changes a default.

The four presets

pr_preset("raw")
#> $self_loops
#> [1] "keep"
#> 
#> $drop_isolates_flag
#> [1] FALSE
#> 
#> $nofollow_action
#> [1] "keep"
#> 
#> $out_of_scope_fold
#> [1] "keep"
#> 
#> attr(,"pr_preset_name")
#> [1] "raw"

A preset is a plain named list, so you can always print one to see exactly what it does. Nothing is hidden: the table below is the whole registry.

Preset Expands to The view
"raw" self_loops = "keep", drop_isolates_flag = FALSE, nofollow_action = "keep", out_of_scope_fold = "keep" The graph exactly as crawled. Nothing is applied; rel=nofollow votes like any other link. The faithful baseline.
"declared" self_loops = "drop", drop_isolates_flag = TRUE, nofollow_action = "evaporate", robots_blocked_action = "show", out_of_scope_fold = "relabel" The graph after honoring what the site declares — nofollow, canonicals, redirects, robots. A pure pin of today’s defaults.
"reversed" reverse = TRUE Every edge flipped: a page scores highly when it points at well-connected pages. The feeder view.
"content" placement_weights = c(content = 1, nav = 0.1, header = 0.1, footer = 0.1, aside = 0.1) Links in the main content keep their full vote; links in site chrome are discounted to a tenth.

"raw" — the faithful baseline

The naive view, and deliberately the one the package makes easiest to state. Every other view is a claim about what should count; "raw" is the claim-free comparison point. Reach for it when you want to see how much of a ranking your own preparation choices produced.

"declared" — a pin, not a change

"declared" sets every value to what pagerank() already defaults to. Running with it produces byte-identical scores to running without it:

identical(
  pagerank(edges, preset = "declared")$pagerank,
  pagerank(edges)$pagerank
)
#> [1] TRUE

That is the point. Its value is provenance: a run made with preset = "declared" is a recorded, auditable statement that the declared view was intended, and it stays pinned to the bundle as documented even if a future package default moves.

Note the boundary: a preset sets policy, never data. "declared" turns on honoring canonicals and redirects, but the canonical and redirect tables themselves (canonicals_df, redirects_df, indexability_df) are still yours to supply.

"reversed" — the feeder view

pagerank(edges, preset = "reversed")
#>   node_name  pagerank
#> 1         A 0.4864865
#> 2         B 0.2567568
#> 3         C 0.2567568

topic_feeder_pagerank() already reverses the graph itself, so preset = "reversed" is a harmless no-op there rather than an error.

"content" — stop letting the navigation rank the site

Site chrome — navigation, header, footer, sidebar — is typically the large majority of a crawl’s edges. Left unweighted it does not merely influence the ranking, it manufactures it: every page links to the same handful of chrome targets, so those targets win by sheer repetition regardless of editorial intent.

"content" discounts them. It needs one thing from you: placement_col, the name of a column saying which page region each link was found in. That is data, so the preset cannot supply it.

placed <- data.frame(
  from = c("A", "A", "B", "C"),
  to = c("B", "C", "A", "A"),
  region = c("content", "footer", "content", "content")
)

pagerank(placed, preset = "content", placement_col = "region")
#>   node_name   pagerank
#> 1         A 0.48648649
#> 2         B 0.42592138
#> 3         C 0.08759214

B and C are each linked once from A. Unweighted they tie; weighted, the footer link is worth a tenth of the in-content one and C falls behind.

The accepted region terms are content, nav, header, footer and aside. pagerank_screaming_frog() derives them for you from the crawl, so there preset = "content" works with no extra argument.

Two properties worth stating explicitly:

  • All five terms are named. Regions absent from placement_weights keep weight 1, which is what makes a partial recipe like c(nav = 0.1) mean “discount nav, leave everything else alone”. A preset is a complete recipe, so it names every term — otherwise footer and aside would quietly outweigh nav tenfold.
  • Edges are downweighted, never dropped. Region detection is a heuristic, so a misclassified content link at 0.1 is a small error where a dropped one is a silent deletion. Dropping most of a graph also manufactures isolates and dangling pages, at which point you are measuring your own sink policy rather than the site.

Layers vs. presets: raw, real, and annotated

A preset bundles policy — the graph-hygiene and weighting knobs above. It never touches data: the redirects_df, canonicals_df, indexability_df and status_df tables you supply (or leave NULL). Those tables decide a second, orthogonal thing — how much of the crawl is folded away before scoring — and that is its own view model, layered on top of whichever preset you pick. Three layers, from the crawl as collected to the crawl fully interpreted:

(a) Raw — every crawled URL is its own node. Nothing is folded: a redirecting URL keeps the PageRank it receives instead of passing it on, a canonicalized page stays separate from its canonical, and a 404 scores like any live page. You get this by not supplying the fold tables — they all default NULL, so the raw layer is the absence of data. This is exactly why the "raw" preset alone cannot guarantee it: a preset sets policy, and no policy can un-supply a redirects_df you passed. On the bare pagerank() path the raw layer is the default (you supply no tables), and preset = "raw" additionally relaxes the hygiene policy so nothing at all is applied. On the Screaming Frog path, where the wrapper builds those tables from the bundle for you, preset = "raw" is special-cased to switch the folding back off — so it delivers the raw layer there too.

(b) Real — redirects and canonicals are followed; dead pages collect but cannot pass. Supply the fold tables and the graph reflects the live, canonical corpus a search engine would score: redirect and canonical sources fold into their terminal target (an alias, not a separate page), and the “collects PageRank but cannot pass it” class — noindex, robots-blocked, and 4xx/5xx response-dead pages — routes its throughput to a shared waste sink instead of recirculating it (see ?pagerank and vignette("pagerankr-usage") for why the sink is the only faithful choice for a dead page). This is the view most internal-link audits want; pagerank_screaming_frog() assembles it from the crawl by default.

(c) Annotated — the page_state indicator. Layer (b) hands a folded score to a page that may be a black hole: a 404 can amass PageRank from every page still linking to it, then evaporate it to the sink. That score is real mass but not earned authority, and nothing else on the row distinguishes it from a strong live page. So whenever indexability_df or status_df is supplied, the result gains a page_state column tagging each row live, noindex, robots_blocked, or response_dead (the highest-precedence signal wins). The annotation is the package’s answer to “why is this dead URL ranking?” — and the reason such rows can be kept in the output instead of silently dropped.

Layers and presets compose freely: a layer is which data you fold, a preset is which policy you bundle, and you choose them independently.

Precedence: explicit argument > preset > default

A preset never silently overrides something you typed.

# The raw bundle says nofollow_action = "keep"; the explicit argument wins.
attr(
  pagerank(edges, preset = "raw", nofollow_action = "drop"),
  "transition_audit"
)$config$nofollow_action
#> [1] "drop"

This holds through every wrapper that forwards ... to pagerank()trustrank(), topic_sensitive_pagerank(), topic_feeder_pagerank() and pagerank_screaming_frog() — with one boundary: arguments a wrapper sets itself are wrapper-owned, and a preset cannot change those.

Presets are not composable with each other; preset takes a single bundle. They do not need to be: "content" sets only placement weights and leaves every hygiene knob at its default, and the defaults are the "declared" view.

Inspecting and adapting a bundle

Because a preset is an ordinary list, you can splice it, print it, or use it as a starting point:

# Splice a bundle into a call
do.call(pagerank, c(list(edges), pr_preset("raw")))
#>   node_name  pagerank
#> 1         A 0.4864865
#> 2         B 0.2567568
#> 3         C 0.2567568

# Start from a preset and adjust
my_view <- pr_preset("raw")
my_view$drop_isolates_flag <- TRUE
pagerank(edges, preset = my_view)
#>   node_name  pagerank
#> 1         A 0.4864865
#> 2         B 0.2567568
#> 3         C 0.2567568

Hand-rolled bundles are validated against pagerank()’s formals, so a misspelled argument fails loudly instead of sliding through ....

Provenance in the transition audit

The audit attached to every result records which named view produced it:

attr(pagerank(edges, preset = "raw"), "transition_audit")$config$preset
#> [1] "raw"

It records the preset name for a registered preset (whether passed by name or as a pr_preset() result), "custom" for a hand-rolled bundle, and NULL when no preset was used. Two runs that expand to the same configuration are therefore still distinguishable by the intent behind them — which, for a pure pin like "declared", is the only durable record that intent existed.

See also