ClusteringResult.java 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. package de.jplag.clustering;
  2. import java.util.*;
  3. import java.util.function.BiFunction;
  4. import java.util.stream.DoubleStream;
  5. import org.apache.commons.math3.linear.Array2DRowRealMatrix;
  6. import org.apache.commons.math3.linear.RealMatrix;
  7. /**
  8. * Set of clusters dividing a set of entities.
  9. * @param <T> type of the clustered entities (e.g. Submission)
  10. */
  11. public class ClusteringResult<T> {
  12. private final List<Cluster<T>> clusters;
  13. private final float communityStrength;
  14. public ClusteringResult(Collection<Cluster<T>> clusters, float communityStrength) {
  15. this.clusters = List.copyOf(clusters);
  16. this.communityStrength = communityStrength;
  17. for (Cluster<T> cluster : clusters) {
  18. cluster.setClusteringResult(this);
  19. }
  20. }
  21. public Collection<Cluster<T>> getClusters() {
  22. return Collections.unmodifiableList(clusters);
  23. }
  24. /**
  25. * Community strength of the clustering. The expectation of community strength in a random graph is zero and it can not
  26. * exceed one. It's the sum of it's clusters {@link Cluster#getCommunityStrength}. If the underlying network is not
  27. * changed, a higher community strength denotes a better clustering. See: Finding and evaluating community structure in
  28. * networks, M. E. J. Newman and M. Girvan, Phys. Rev. E 69, 026113 – Published 26 February 2004, Doi:
  29. * 10.1103/PhysRevE.69.026113 It's called modularity in that paper.
  30. * @return community strength
  31. */
  32. public float getCommunityStrength() {
  33. return communityStrength;
  34. }
  35. /**
  36. * How much this clustering result is worth during optimization.
  37. * @param similarity TODO DF: JAVADOC
  38. * @return worth
  39. */
  40. public float getWorth(BiFunction<T, T, Float> similarity) {
  41. return (float) getClusters().stream().mapToDouble(c -> c.getWorth(similarity)).map(worth -> Double.isFinite(worth) ? worth : 0).average()
  42. .getAsDouble();
  43. }
  44. /**
  45. * Responsible for calculating the {@link ClusteringResult#getCommunityStrength} of a new clustering on integers and
  46. * it's clusters.
  47. */
  48. public static ClusteringResult<Integer> fromIntegerCollections(List<Collection<Integer>> clustering, RealMatrix similarity) {
  49. int numberOfSubmissions = similarity.getRowDimension();
  50. Map<Integer, Integer> clusterIndicesOfSubmissionIndices = new HashMap<>();
  51. int clusterIdx = 0;
  52. for (Collection<Integer> cluster : clustering) {
  53. for (Integer submissionIdx : cluster) {
  54. clusterIndicesOfSubmissionIndices.put(submissionIdx, clusterIdx);
  55. }
  56. clusterIdx++;
  57. }
  58. List<Cluster<Integer>> clusters = new ArrayList<>(clustering.size());
  59. float communityStrength = 0;
  60. if (!clustering.isEmpty()) {
  61. RealMatrix percentagesOfSimilaritySums = new Array2DRowRealMatrix(clustering.size(), clustering.size());
  62. percentagesOfSimilaritySums = percentagesOfSimilaritySums.scalarMultiply(0);
  63. for (int i = 0; i < numberOfSubmissions; i++) {
  64. if (!clusterIndicesOfSubmissionIndices.containsKey(i))
  65. continue;
  66. int clusterA = clusterIndicesOfSubmissionIndices.get(i);
  67. for (int j = i + 1; j < numberOfSubmissions; j++) {
  68. if (!clusterIndicesOfSubmissionIndices.containsKey(j))
  69. continue;
  70. int clusterB = clusterIndicesOfSubmissionIndices.get(j);
  71. percentagesOfSimilaritySums.addToEntry(clusterA, clusterB, similarity.getEntry(i, j));
  72. percentagesOfSimilaritySums.addToEntry(clusterB, clusterA, similarity.getEntry(i, j));
  73. }
  74. }
  75. percentagesOfSimilaritySums = percentagesOfSimilaritySums
  76. .scalarMultiply(1 / Arrays.stream(similarity.getData()).flatMapToDouble(DoubleStream::of).sum());
  77. for (int i = 0; i < clustering.size(); i++) {
  78. double outWeightSum = percentagesOfSimilaritySums.getRowVector(i).getL1Norm();
  79. double clusterCommunityStrength = percentagesOfSimilaritySums.getEntry(i, i) - outWeightSum * outWeightSum;
  80. float averageSimilarity = calculateAverageSimilarityFor(clustering.get(i), similarity);
  81. clusters.add(new Cluster<>(clustering.get(i), (float) clusterCommunityStrength, averageSimilarity));
  82. communityStrength += clusterCommunityStrength;
  83. }
  84. }
  85. return new ClusteringResult<>(clusters, communityStrength);
  86. }
  87. private static float calculateAverageSimilarityFor(Collection<Integer> cluster, RealMatrix similarityMatrix) {
  88. var sumOfSimilarities = 0f;
  89. List<Integer> indices = List.copyOf(cluster);
  90. for (int i = 1; i < cluster.size(); i++) {
  91. int indexOfSubmission1 = indices.get(i);
  92. for (int j = 0; j < i; j++) { // as the similarity matrix is symmetrical we need only iterate over one half of it
  93. int indexOfSubmission2 = indices.get(j);
  94. sumOfSimilarities += similarityMatrix.getEntry(indexOfSubmission1, indexOfSubmission2);
  95. }
  96. }
  97. int nMinusOne = cluster.size() - 1;
  98. float numberOfComparisons = (nMinusOne * (nMinusOne + 1))
  99. / 2f; /*
  100. * Use Gauss sum to calculate number of comparisons in cluster: Given cluster of size n we need Gauss sum of n-1
  101. * comparisons: compare first element of cluster to all other except itself: n-1 comparisons. compare second element to
  102. * all other except itself and first element (as these two were already compared when we processed the first element),
  103. * n-2 comparisons. compare third element to all other but itself and all previously compared: n-3 comparisons and so
  104. * on. when we reach the second to last element we have n-(n-1)=1 comparisons left. when we reach the last element it
  105. * has already been compared to all other. adding up all comparisons we get: (n-1) + (n-2) + (n-3) + ... + (n-(n-1)) =
  106. * Gauss sum of (n-1)
  107. */
  108. return sumOfSimilarities / numberOfComparisons;
  109. }
  110. }