salsa: Master SALSA hub/authority calculation wrapper

View source: R/salsa.R

salsaR Documentation

Master SALSA hub/authority calculation wrapper

Description

Computes Lempel & Moran's SALSA hub and authority scores over the same cleaned, redirect/canonical-folded, domain-filtered, deduplicated link graph that [pagerank()] builds, so node identities line up across the centrality measures. SALSA is a stochastic variant of HITS: it runs HITS-style mutual reinforcement as PageRank-style random walks on the bipartite hub/authority graph, yielding stationary-distribution scores instead of dominant eigenvectors.

Usage

salsa(
  edge_list_df,
  redirects_df = NULL,
  clean_edge_urls = TRUE,
  clean_redirect_urls = TRUE,
  rurl_params = list(),
  self_loops = c("drop", "keep"),
  drop_isolates_flag = TRUE,
  duplicate_edge_policy = c("collapse", "aggregate", "count_instances"),
  edge_from_col = "from",
  edge_to_col = "to",
  redirect_from_col = "from",
  redirect_to_col = "to",
  duplicate_from_policy = c("strict", "first_wins", "last_wins", "most_frequent",
    "prune_source", "resolve_if_consistent"),
  loop_handling = c("error", "prune_loop", "break_arrow"),
  canonicals_df = NULL,
  canonical_from_col = "from",
  canonical_to_col = "to",
  clean_canonical_urls = TRUE,
  canonical_duplicate_from_policy = c("strict", "first_wins", "last_wins",
    "most_frequent", "prune_source", "resolve_if_consistent"),
  canonical_loop_handling = c("error", "prune_loop", "break_arrow"),
  canonical_conflict_policy = c("redirect_wins", "error", "canonical_wins"),
  keep_domains = NULL,
  exclude_domains = NULL,
  keep_hosts = NULL,
  exclude_hosts = NULL,
  ...
)

Arguments

edge_list_df

A data frame representing the edge list, typically with columns like "from" and "to". Edges are expected to be page-to-page hyperlinks: 'pagerank()' is graph-agnostic and treats every endpoint as a node, so resource links (images, CSS, JS, and other non-HTML references) must be filtered upstream or they collect authority as ordinary vertices. 'pagerank_screaming_frog()' does this at the crawl boundary via [sf_graph_eligible()] ('Hyperlink' only); a hand-built or non-SF edge list should apply the same hyperlink-only filter before scoring.

redirects_df

An optional data frame for redirect rules, typically with "from" and "to" columns. Defaults to NULL.

clean_edge_urls

Logical, whether to clean URLs in the edge list. Defaults to TRUE.

clean_redirect_urls

Logical, whether to clean URLs in the redirect list. Defaults to TRUE. Only effective if 'redirects_df' is provided.

rurl_params

A list of parameters to pass to 'rurl::clean_url'. Defaults to an empty list. 'protocol_handling' defaults to '"keep"' and 'case_handling' to '"lower_host"' for cross-project canonicalization consistency. If you set 'host_encoding' ('"idna"' or '"unicode"') to fold internationalized (IDN) hosts, that same value is also passed to the domain-filtering step so its comparisons stay consistent with the cleaned node keys. (Registrable-domain matching is encoding-independent, so this only matters if host-level filtering is involved.)

self_loops

A character string specifying how to handle self-loops. Either "drop" (default) or "keep".

drop_isolates_flag

Logical, whether to drop isolated nodes before PageRank computation. Defaults to TRUE.

duplicate_edge_policy

How repeated 'from -> to' rows are represented after URL cleaning, redirect/canonical folding, and domain filtering. One of:

'"collapse"'

(default) Destination-level surfer: repeated rows collapse to one unweighted destination edge, preserving legacy 'get_unique_edges()' behavior and the common binary PageRank convention.

'"aggregate"'

Collapse each 'from -> to' pair with [aggregate_edges()] semantics. Numeric columns, including 'weight_col', are summed; logical columns such as 'nofollow' use the default '"any"' conflict policy.

'"count_instances"'

Link-slot / edge-level surfer: repeated rows increase transition probability. With no 'weight_col', each surviving 'from -> to' pair receives an internal weight equal to its duplicate-row count. With 'weight_col', weights are summed and an 'instance_count' audit column is retained.

edge_from_col, edge_to_col

Names of from/to columns in 'edge_list_df'.

redirect_from_col, redirect_to_col

Names of from/to columns in 'redirects_df'.

duplicate_from_policy

How to handle conflicting redirects in 'redirects_df'. Passed through to [resolve_redirects()]. Default '"strict"' (error on conflicts). See [resolve_redirects()] for all available policies.

loop_handling

How to handle redirect cycles. Passed through to [resolve_redirects()]. Default '"error"'. See [resolve_redirects()] for all available policies.

canonicals_df

An optional data frame of declared 'rel=canonical' links, with 'from'/'to' columns (or as set by 'canonical_from_col' / 'canonical_to_col') pairing a source URL with the canonical it declares. Default 'NULL' (opt-in; the default preserves current behavior). Canonicals are a **distinct, advisory** signal from enforced 3xx 'redirects_df': they are tracked separately and audited via [audit_canonicals()] / [audit_fold()], then folded into the same composed map as redirects (see [build_fold_map()]). Self-canonicals drop as no-ops.

canonical_from_col, canonical_to_col

From/to columns in 'canonicals_df'. Default '"from"' / '"to"'.

clean_canonical_urls

Logical, whether to clean URLs in 'canonicals_df' using the same resolved 'rurl_params' profile as edge and redirect cleaning. Default 'TRUE'. Only effective when 'canonicals_df' is provided.

canonical_duplicate_from_policy

How to handle a canonical source that declares multiple distinct canonicals. Reuses the 'duplicate_from_policy' enum (see [resolve_redirects()]). Default '"strict"'.

canonical_loop_handling

How to handle cycles among declared canonicals. Reuses the 'loop_handling' enum. Default '"error"'.

canonical_conflict_policy

How to resolve a redirect-vs-canonical disagreement on the **same source** URL. One of '"redirect_wins"' (default; the 3xx wins and the canonical on a redirecting source is ignored and flagged), '"error"' (error on genuine disagreement), or '"canonical_wins"' (the declared canonical wins for that source, still flagged). See [build_fold_map()].

keep_domains

Optional character vector of domains to keep. When provided, edges are filtered via [filter_links_by_domain()] so that only links where both endpoints belong to one of the specified domains are included. Useful for restricting to internal links. Default 'NULL' (no domain filtering).

**Ordering:** filtering runs *after* redirect/canonical folding, so it scopes the post-fold (canonical) namespace, not the crawled input. If an out-of-scope canonical/redirect rewrites the crawled domain onto a different one, filtering on the crawled domain matches nothing (an empty graph). To scope the INPUT you crawled, run [filter_links_by_domain()] on the edge list *before* calling 'pagerank()'.

exclude_domains

Optional character vector of domains to exclude. Edges where either endpoint belongs to one of these domains are removed. Like 'keep_domains', this filters the post-fold namespace (see the ordering note above). Default 'NULL' (no exclusion).

keep_hosts

Optional character vector of exact hosts to keep (e.g. '"www.example.com"'), as opposed to registrable domains. Matched on the exact host using the same canonicalization profile as cleaning, so IDN folding ('host_encoding' in 'rurl_params') applies consistently. Default 'NULL'.

exclude_hosts

Optional character vector of exact hosts to exclude. Edges where either endpoint matches one of these hosts are removed. Ignore rules override keep rules. Default 'NULL'.

...

Additional arguments forwarded to [compute_salsa()].

Details

## Relationship to the PageRank pipeline

'salsa()' reuses the exact identity-forming steps of [pagerank()] — URL canonicalization (the same resolved 'rurl' profile), the same composed redirect + canonical fold map, the same domain/host filtering, the same 'duplicate_edge_policy' deduplication, and the same self-loop / isolate handling. The resulting vertex set therefore matches 'pagerank()' run with the same arguments, so hub, authority, and PageRank can be joined on 'node_name' without re-canonicalizing.

The PageRank-specific, *forward-flow* modeling devices have **no SALSA analog and are intentionally not exposed**: nofollow evaporation, the indexability (noindex / robots.txt) transforms, the TIPR teleport prior, and the 'reverse' flag. SALSA already computes both directions of authority flow (hub is the outflow-oriented score, authority the inflow-oriented one).

## SALSA versus HITS, and the pagerankr adaptation

Where [hits()] takes the dominant eigenvectors of A^\top A and A A^\top, SALSA replaces the mutual-reinforcement iteration with two *stochastic* Markov chains on the bipartite hub/authority graph; their stationary distributions are the scores, which on a connected graph reduce to a simple in-/out-degree closed form (see [compute_salsa()]). Because the chains are stochastic, SALSA is far less prone than HITS to the "tightly-knit community" effect, where a dense cluster of mutually linking pages dominates the top scores.

Lempel & Moran's original SALSA ran on a query-focused base set. 'salsa()' instead runs on the **full (or user-filtered) site graph** that 'pagerankr' assembles — a documented site-graph adaptation of the focused-subgraph algorithm. Treat the scores as site-wide structural centralities, not query-relevance scores. Coverage differs from PageRank: a node's 'hub' is 'NA' when it has no outlinks and its 'authority' is 'NA' when it has no inlinks (see [compute_salsa()]).

Value

A data frame with one row per node and columns 'node_name', 'hub', and 'authority' (column names configurable via '...'). Hub and authority each sum to '1' over their non-'NA' entries.

References

Lempel, R. & Moran, S. (2001). SALSA: The Stochastic Approach for Link-Structure Analysis. *ACM Transactions on Information Systems*, 19(2), 131-160.

See Also

[compute_salsa()] for the computational core, [hits()] for the HITS analog, and [pagerank()] for the PageRank analog sharing this identity pipeline.

Examples

edges <- data.frame(
  from = c("http://A.com/", "http://A.com/", "B.com"),
  to = c("B.com", "C.com", "C.com")
)
salsa(edges)

# Hub vs authority: a pure outflow page tops hub, a pure inflow page tops
# authority.
s <- salsa(edges)
s[which.max(s$hub), ]
s[which.max(s$authority), ]

pagerankr documentation built on Oct. 1, 2026, 5:09 p.m.