Skip to content

Fix NaN AUC in robustness_analysis and audit the pattern - #4

Merged
agallinat merged 1 commit into
mainfrom
na-audit
Oct 3, 2026
Merged

agallinat merged 1 commit into
mainfrom
na-audit

Conversation

@agallinat

Copy link
Copy Markdown
Owner
  • Fix five runtime defects and add a testthat suite
  • Prepare DESCRIPTION, LICENSE and .Rbuildignore for CRAN
  • Make all examples executable
  • Replace deprecated ggplot2 and igraph calls
  • Extract shared graph validator and collapse duplicate diffusion kernel
  • Add release infrastructure and fix documentation typos
  • Do not claim untested platforms in cran-comments.md
  • Adopt the .9000 development-version convention
  • Stop setting a future plan inside network_diffusion_with_pvalues()
  • Make the plot return slot uniform across the package
  • Fix silently wrong participation coefficients in calculate_roles()
  • Fix NaN AUC in robustness_analysis() and audit the surrounding pattern

Swept the package for the count/total mismatch behind the calculate_roles()
bug -- a quantity derived one way divided by a total derived another.
Found one more real instance, in robustness_analysis().

Global efficiency divides by n(n-1), which is 0 once a single vertex
remains, so it evaluated to NaN and that NaN propagated through
pracma::trapz() into the AUC. This fired on the *documented default call*:
steps = 50 makes step_size = 1 for any graph of about 51 nodes or fewer,
so the loop does reach one vertex. On a 50-node sample_pa,
auc$efficiency came back NaN while the other two metrics looked fine.
Efficiency is now 0 when fewer than two vertices remain -- a collapsed
network has no paths -- which is also what the robustness literature
reports.

A second divide-by-zero sat behind it: every metric is scaled by
max(metric), once for the AUC and six times inside the plot's aes()
calls. When a metric is zero throughout, which global efficiency is for a
graph with no edges, that made all seven NaN. Replaced with a
normalize_metric() helper (R/utils-metrics.R) that leaves the series
untouched when the maximum is zero or not finite.

Also dropped `fill = "Metric"` from labs(), which made ggplot2 report
"Ignoring unknown labels: fill" on every robustness plot, since no fill
aesthetic is mapped.

Audit conclusions for the sites that are NOT bugs, recorded so they are
not "fixed" later:

- plot_CCDF()/compute_ccdf() divides by sum(deg_tab), i.e. the same table
  it tallied. This is the correct form of the pattern.
- summarize_graph_metrics() divides table(deg) by length(deg); degree()
  cannot return NA, so the two agree by construction.
- The match() calls in calculate_roles(), find_modules() and
  assign_attributes() index with values that are guaranteed present, or
  handle NA explicitly.
- summarize_graph_metrics() returns NaN for Average_path_length,
  Clustering_coefficient, Degree_assortativity, Gini_degree and Modularity
  on an edgeless graph. These are undefined quantities propagated from
  igraph and ineq, not arithmetic errors; substituting zeros would
  fabricate values. A test pins the exact set so a new NaN column shows up
  as a regression.

Corrected summarize_graph_metrics()'s @return, which promised a tibble
while the function returns a data.frame, and now documents the NaN cases.

Tests go from 386 to 411.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@agallinat
agallinat merged commit 4bb7631 into main Oct 3, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant