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
The equivalence pipeline runs
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
developandreleased in 1.0.1, so this issue covers only what remains.
1. The documented pipeline is not the implemented one
The
@sectionforcluster_hierarchical()says a distance matrix is made "bysubtracting these correlations from
1, and this is given tostats::hclust()".That describes
as.dist(1 - C): one comparison of node profiles. The code insertsstats::dist(), so the clustering runs on distances between the rows of1 - 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 callshclust(as.dist(equiv.dist)). On that readingcorrelation is a choice of first comparison, not something a distance is taken over.
It is not cosmetic.
k_silhouette()is the defaultkand readshc$distances, sothe choice reaches the returned membership rather than only the dendrogram. On
ison_algebra,node_in_structural()returns 4 clusters under the current two-stepform 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")$distancesbelongs to the following step, notthis 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.
proximity_*()family inR/method_proximity.R, with amethod_proximityRdtopic:
proximity_correlation()andproximity_cosine()taking a motif matrix andreturning 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.
proximity =argument on the fivenode_in_*()equivalence functions,defaulting to
"correlation".clusternarrows to the algorithm,c("hierarchical", "concor").cluster = "cosine"ships in 1.0.0, so it cannot be removed: accept it with adeprecation warning mapping it to
proximity = "cosine", following theresolve_max_k()idiom inR/class_metrics.R.cluster_hierarchical()becomeshclust(as.dist(1 - P))on the proximity matrix.distancethen has no work left. Either deprecate it, or keep it for proximitiesthat are already distances rather than similarities. This is the loosest end.
Points to settle
distance. Deprecate, or repurpose? See above.similarity family, on the grounds that generic similarities belong to
{manynet}and "are consumed here through
distance =andcluster_*(), so they would neverlive in this family anyway". That paragraph weighed naming, not the missing
accessor. If a
proximity_*()family lands, that reasoning needs rewriting ratherthan leaving the two in contradiction.
node_in_*()returns different memberships under theone-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, includingjaccard,overlap,cosine,pearson,euclidean,hammingandspearman(
manynet:::.proj_measures).The block is elsewhere.
to_proximity()aborts on a rectangular matrix, because itreads one as a two-mode network: "compares the nodes of a one-mode network on their
ties to one another".
to_correlation()andto_cosine()reach the same kernelsthrough
.project()and so bypass that guard, which is why only those two work on acensus.
So the upstream ask on
{manynet}is to letto_proximity()accept a plainnode-by-feature profile matrix. After that a
proximity_*()family here is thindispatch over existing kernels rather than new numerical code.
Related: #28, #18