Skip to contents

Compares PageRank before and after proposed changes to the link graph, at both the edge level (adding/removing links) and the URL level (retiring a page behind a redirect, or repointing an existing redirect). The whole graph is recomputed and a before/after table is returned; interpretation is left to the caller. This is a faithful recompute primitive, not a ranking or target-optimization engine.

Usage

simulate_changes(
  edge_list_df,
  add_links_df = NULL,
  remove_links_df = NULL,
  redirect_urls_df = NULL,
  remove_urls = NULL,
  redirects_df = NULL,
  on_unknown_target = c("warn", "error", "allow"),
  ...,
  edge_from_col = "from",
  edge_to_col = "to",
  redirect_from_col = "from",
  redirect_to_col = "to",
  label_baseline = "baseline",
  label_proposed = "proposed"
)

Arguments

edge_list_df

A data frame representing the current link edge list.

Optional data frame of links to add. Must have the same from/to column names as edge_list_df. Columns present in edge_list_df but absent here are padded with NA on the added rows, so weighted / annotated edge lists keep their schema. Default NULL.

Optional data frame of links to remove. Matching is by exact from+to pair. Must have the same from/to column names as edge_list_df. Default NULL.

redirect_urls_df

Optional two-column from/to data frame of URL-level redirects to model. Each row retires the from URL and sends its inbound authority to to at 100% pass-through. Retire semantics: the live source's own outbound links are stripped before folding (an honest 301 has no body), so the target inherits the source's inbound authority only, never its outlinks. A row for source A overrides any prior redirect for A – whether from an earlier row or from the baseline crawl's real 3xx – so "change A into a redirect to C" is a single override. A duplicate source mapping to two distinct targets in one changeset is an error (strict). Default NULL.

remove_urls

Optional character vector of URLs to model as removed (turned into HTTP 404s). Each removed URL keeps its inbound links – other pages still point at it – but now they flow into a dead page: authority arrives and evaporates to the shared waste sink rather than redistributing across the site (dangle) or self-amplifying (self-loop). The page's own outbound links are dropped. The node stays in the output holding the mass it absorbed once, flagged "removed-dead" in node_status so its residual score is never misread as earned authority. Under the hood this forces a status_df entry (HTTP 404) into the proposed model only; 4xx and 5xx are one class (no split). A URL appearing in both remove_urls and redirect_urls_df is an error (a page cannot be both a 301 and a 404). To also model cleaning up the inbound links, compose with remove_links_df. Default NULL.

redirects_df

Optional data frame of existing redirects (baseline). Default NULL.

on_unknown_target

How to treat a redirect or link target that is not a node in the current graph (it may be a legitimate new page, modeled as a new node that carries inbound authority with no outlinks yet). One of "warn" (default, warn and proceed), "error", or "allow" (proceed silently).

...

Additional arguments passed to both pagerank() calls (e.g., clean_edge_urls, damping, nofollow_col, indexability_df, etc.).

edge_from_col

Name of the from column in edge list data frames. Default "from".

edge_to_col

Name of the to column in edge list data frames. Default "to".

redirect_from_col

Name of the source column in redirect_urls_df and redirects_df. Default "from".

redirect_to_col

Name of the target column in redirect_urls_df and redirects_df. Default "to".

label_baseline

Label for the baseline model in the comparison output. Default "baseline".

label_proposed

Label for the proposed model in the comparison output. Default "proposed".

Value

The output of compare_pagerank (per-node deltas, percentage changes, and rank changes between baseline and proposed) with an added node_status column: "normal" for a node present and live in both models, "new-target" for a node introduced by the changeset (present in the proposed model, absent from the baseline), or "removed-dead" for a node retired via remove_urls (its proposed score is residual absorbed mass on the way to the waste sink, not earned authority).

node_status describes a node's role in this before/after comparison, which is a different axis from the page_state column pagerank attaches to describe a page's health/index state (live / noindex / robots_blocked / response_dead). The two are deliberately not merged into one vocabulary: new-target has no health analogue, and removed-dead is the single value bridging both axes — a node whose comparison role is "removed" because its proposed health state is response_dead (a forced 404). Attributes:

summary

Aggregate statistics from compare_pagerank().

proposed

The full proposed pagerank() result, including its transition_audit attribute, so the evaporated-mass cost of a removal is surfaced by default.

manifest

A named list describing the changeset: redirects applied, which sources overrode a prior redirect, URLs removed, link add/remove counts, and any unknown targets.

See also

simulate_changes_screaming_frog for the Screaming Frog bundle entry point.

Examples

# Current site links
edges <- data.frame(
  from = c("Home", "Home", "About", "Blog"),
  to = c("About", "Blog", "Home", "Home")
)

# Propose adding a link from Blog to About
new_links <- data.frame(
  from = "Blog", to = "About"
)
result <- simulate_changes(edges,
  add_links_df = new_links,
  clean_edge_urls = FALSE
)
print(result)
#>   node_name pagerank_baseline pagerank_proposed       delta pct_change
#> 1     About         0.2567568         0.3333333  0.07657658  29.824561
#> 2      Home         0.4864865         0.4327485 -0.05373795 -11.046134
#> 3      Blog         0.2567568         0.2339181 -0.02283863  -8.895045
#>   rank_baseline rank_proposed rank_delta node_status
#> 1             2             2          0      normal
#> 2             1             1          0      normal
#> 3             2             3         -1      normal
attr(result, "summary")
#> $spearman_rho
#> [1] 0.8660254
#> 
#> $mean_abs_delta
#> [1] 0.05105105
#> 
#> $nodes_gained
#> [1] 0
#> 
#> $nodes_lost
#> [1] 0
#> 

# Retire the About page behind a redirect to Home
retire <- data.frame(from = "About", to = "Home")
simulate_changes(edges, redirect_urls_df = retire, clean_edge_urls = FALSE)
#>   node_name pagerank_baseline pagerank_proposed      delta pct_change
#> 1      Blog         0.2567568               0.5 0.24324324  94.736842
#> 2      Home         0.4864865               0.5 0.01351351   2.777778
#> 3     About         0.2567568                NA         NA         NA
#>   rank_baseline rank_proposed rank_delta node_status
#> 1             2             1          1      normal
#> 2             1             1          0      normal
#> 3             2            NA         NA      normal

# Model the About page 404-ing: inbound authority flows in and evaporates
simulate_changes(edges, remove_urls = "About", clean_edge_urls = FALSE)
#>   node_name pagerank_baseline pagerank_proposed       delta pct_change
#> 1      Home         0.4864865        0.21722114 -0.26926535  -55.34899
#> 2     About         0.2567568        0.09231898 -0.16443777  -64.04419
#> 3      Blog         0.2567568        0.16731898 -0.08943777  -34.83366
#>   rank_baseline rank_proposed rank_delta  node_status
#> 1             1             1          0       normal
#> 2             2             3         -1 removed-dead
#> 3             2             2          0       normal