Skip to content

Expose the proximity stage and use a one-step profile comparison #29

Description

@jhollway

The equivalence pipeline runs

census  →  proximity  →  clustering  →  membership

and two problems sit in the middle of it. Both were raised by @Kaladani, the first
in #18 (comment) and the second
in #28 (comment).

The transpose bug reported alongside the first is already fixed on develop and
released in 1.0.1, so this issue covers only what remains.

1. The documented pipeline is not the implemented one

The @section for cluster_hierarchical() says a distance matrix is made "by
subtracting these correlations from 1, and this is given to stats::hclust()".
That describes as.dist(1 - C): one comparison of node profiles. The code inserts
stats::dist(), so the clustering runs on distances between the rows of 1 - C,
a second comparison over the dissimilarity profiles.

The one-step form is the established one. sna::sedist() offers correlation,
euclidean, hamming and gamma as alternative ways to make a single comparison, and
sna::equiv.clust() then calls hclust(as.dist(equiv.dist)). On that reading
correlation is a choice of first comparison, not something a distance is taken over.

It is not cosmetic. k_silhouette() is the default k and reads hc$distances, so
the choice reaches the returned membership rather than only the dendrogram. On
ison_algebra, node_in_structural() returns 4 clusters under the current two-step
form and 7 under the one-step form.

2. The proximity stage has no public accessor

The proximity matrix is built inside the cluster_*() functions and consumed there.
Nothing returns it. attr(out, "hc")$distances belongs to the following step, not
this one.

That middle matrix is often what you want: to inspect it, and to ask whether an
attribute structures it (assortativity, observed/expected mixing, influence and
selection analysis). It matters more once the orbit census of #28 exists, since a
role-similarity matrix computed from it is the natural next step.

Proposed shape

Not decided; this is the starting point for the discussion.

  • A proximity_*() family in R/method_proximity.R, with a method_proximity Rd
    topic: proximity_correlation() and proximity_cosine() taking a motif matrix and
    returning the node-by-node matrix. This follows the family rule in CONTRIBUTING,
    which names a family for what it returns, and gives the pipeline its missing exit.
  • A proximity = argument on the five node_in_*() equivalence functions,
    defaulting to "correlation".
  • cluster narrows to the algorithm, c("hierarchical", "concor").
    cluster = "cosine" ships in 1.0.0, so it cannot be removed: accept it with a
    deprecation warning mapping it to proximity = "cosine", following the
    resolve_max_k() idiom in R/class_metrics.R.
  • cluster_hierarchical() becomes hclust(as.dist(1 - P)) on the proximity matrix.
  • distance then has no work left. Either deprecate it, or keep it for proximities
    that are already distances rather than similarities. This is the loosest end.

Points to settle

  • distance. Deprecate, or repurpose? See above.
  • CONTRIBUTING contradicts this. The "Method helper naming" section rejected a
    similarity family, on the grounds that generic similarities belong to {manynet}
    and "are consumed here through distance = and cluster_*(), so they would never
    live in this family anyway". That paragraph weighed naming, not the missing
    accessor. If a proximity_*() family lands, that reasoning needs rewriting rather
    than leaving the two in contradiction.
  • How much belongs upstream. See the note below.
  • Behaviour change. Every node_in_*() returns different memberships under the
    one-step form. The position tutorial prints cluster counts throughout and will need
    rebuilding and rereading.

Note on the kernels

@Kaladani suggested implementing the missing kernels inside netrics for now. They do
not need writing. manynet::to_proximity() already offers 24 measures, including
jaccard, overlap, cosine, pearson, euclidean, hamming and spearman
(manynet:::.proj_measures).

The block is elsewhere. to_proximity() aborts on a rectangular matrix, because it
reads one as a two-mode network: "compares the nodes of a one-mode network on their
ties to one another". to_correlation() and to_cosine() reach the same kernels
through .project() and so bypass that guard, which is why only those two work on a
census.

So the upstream ask on {manynet} is to let to_proximity() accept a plain
node-by-feature profile matrix. After that a proximity_*() family here is thin
dispatch over existing kernels rather than new numerical code.

Related: #28, #18

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions