├── .gitignore ├── HACluster ├── __init__.py ├── api.py ├── cluster.py ├── corpus.py ├── dendrogram.py ├── distance.py └── linkage.py ├── README.md ├── VNC.ipynb └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | *.egg* 3 | *.pyc 4 | __pycache__ -------------------------------------------------------------------------------- /HACluster/__init__.py: -------------------------------------------------------------------------------- 1 | # Hierarchical Agglomerative Cluster Analysis 2 | # 3 | # Copyright (C) 2013 Folgert Karsdorp 4 | # Author: Folgert Karsdorp 5 | # URL: 6 | # For licence information, see LICENCE.TXT 7 | 8 | """ 9 | Hierarchical Agglomerative Cluster Analysis. 10 | 11 | This module consists of an implementation of Hierarchical Agglomerative 12 | Cluster analysis (HAC), a method of cluster analysis in which items are grouped 13 | together based on their similarity or dissimilarity in a bottom-up procedure. 14 | Besides the classical HAC analysis, this module gives a class of 15 | Variabilty-based Neighbor Clustering, a cluster method described in e.g. Hilpert 16 | & Gries (2006) in which the clustering procedure is temporarily constricted. 17 | In this cluster procedure, only clusters that are chronological neighbors are 18 | allowed to be clustered. 19 | 20 | Both Clusterer and VNClusterer extend the AbstractClusterer interface which 21 | defines some common clustering operations, such as: 22 | 1. cluster (cluster a sequence of vectors) 23 | 2. iterate clusters (iterate the vectors in a specific order (for VNC)) 24 | 25 | Usage example (compare demo()): 26 | >>> from cluster.cluster import DistanceMatrix, Clusterer 27 | >>> from cluster.distance import euclidean_distance 28 | >>> vectors = [[2,4], [0,1], [1,1], [3,2], [4,0], [2,2]] 29 | >>> # compute the distance matrix on the basis of the vectors 30 | >>> dist_matrix = DistanceMatrix(vectors, euclidean_distance) 31 | >>> # plot the distance matrix 32 | >>> dist_matrix.draw() 33 | >>> # initialize a clusterer, with default linkage methode (Ward) 34 | >>> clusterer = Clusterer(dist_matrix) 35 | >>> # start the clustering procedure 36 | >>> clusterer.cluster(verbose=2) 37 | >>> # plot the result as a dendrogram 38 | >>> clusterer.dendrogram().draw(title=clusterer.linkage.__name__) 39 | 40 | @author: Folgert Karsdorp 41 | @requires: Python 2.6+ 42 | @version: 0.1 43 | @license: GPL 44 | @copyright: (c) 2011, Folgert Karsdorp 45 | """ 46 | 47 | from __future__ import absolute_import 48 | 49 | from . cluster import * 50 | from . dendrogram import * 51 | from . distance import * 52 | from . linkage import * 53 | 54 | __all__ = ['Dendrogram', 'Clusterer', 'VNClusterer', 55 | 'CooccurrenceMatrix', 'DistanceMatrix'] 56 | -------------------------------------------------------------------------------- /HACluster/api.py: -------------------------------------------------------------------------------- 1 | # Hierarchical Agglomerative Cluster Analysis 2 | # 3 | # Copyright (C) 2013 Folgert Karsdorp 4 | # Author: Folgert Karsdorp 5 | # URL: 6 | # For licence information, see LICENCE.TXT 7 | 8 | 9 | class AbstractClusterer(object): 10 | """ 11 | Abstract interface covering basic clustering functionality. 12 | """ 13 | def __init__(self, data, linkage, num_clusters): 14 | """ 15 | @param data: A DistanceMatrix or list of feature value pairs from which 16 | a DistanceMatrix can be constructed. 17 | @type data: L{DistanceMatrix} or C{list} 18 | 19 | @param linkage: a clustering of linkage method. The following methods 20 | are implemented: 21 | 1. Single Linkage (L{single_link}) 22 | 2. Complete Linkage (L{complete_link}) 23 | 3. Average Linkage (L{average_link}) 24 | 4. Median Linkage (L{median_link}) 25 | 5. Centroid Linkage (L{centroid_link}) 26 | 6. Ward Linkage or Minimum Variance Linkage (L{ward_link}) 27 | @type linkage: C{function} 28 | 29 | """ 30 | raise AssertionError('AbstractClusterer is an abstract interface') 31 | 32 | def iterate_clusters(self): 33 | """ 34 | Iterate over all unique vector combinations in the matrix. 35 | """ 36 | raise AssertionError('AbstractClusterer is an abstract interface') 37 | 38 | def smallest_distance(self, clusters): 39 | """ 40 | Return the smallest distance in the distance matrix. 41 | The smallest distance depends on the possible connections in the 42 | distance matrix. 43 | """ 44 | raise AssertionError('AbstractClusterer is an abstract interface') 45 | 46 | def cluster(self, verbose=0, sum_ess=False): 47 | """ 48 | Cluster all clusters hierarchically unitl the level of 49 | num_clusters is obtained. 50 | """ 51 | raise AssertionError('AbstractClusterer is an abstract interface') 52 | 53 | def update_distmatrix(self, i, j, clusters): 54 | """ 55 | Update the distance matrix using the specified linkage method, so that 56 | it represents the correct distances to the newly formed cluster. 57 | """ 58 | return self.linkage(clusters, i, j, self._dendrogram) 59 | 60 | def dendrogram(self): 61 | """ 62 | Return the dendrogram object. 63 | """ 64 | return self._dendrogram 65 | 66 | def num_clusters(self): 67 | """ 68 | Return the number of clusters. 69 | """ 70 | return self._num_clusters 71 | 72 | -------------------------------------------------------------------------------- /HACluster/cluster.py: -------------------------------------------------------------------------------- 1 | # Hierarchical Agglomerative Cluster Analysis 2 | # 3 | # Copyright (C) 2013 Folgert Karsdorp 4 | # Author: Folgert Karsdorp 5 | # URL: 6 | # For licence information, see LICENCE.TXT 7 | 8 | from __future__ import division 9 | from __future__ import absolute_import 10 | 11 | import numpy 12 | import copy 13 | import argparse 14 | 15 | from operator import itemgetter 16 | from collections import defaultdict 17 | from itertools import combinations, product 18 | 19 | from . api import AbstractClusterer 20 | from . dendrogram import Dendrogram 21 | from . linkage import linkage_fn 22 | from . distance import * 23 | 24 | from sklearn.metrics.pairwise import pairwise_distances 25 | 26 | 27 | 28 | class CooccurrenceMatrix(numpy.ndarray): 29 | """ Represents a co-occurrence matrix. """ 30 | def __new__(cls, data, dtype=None): 31 | if not isinstance(data, CooccurrenceMatrix): 32 | data, rownames, colnames = CooccurrenceMatrix.convert(data) 33 | else: 34 | rownames, colnames = data.rownames, data.colnames 35 | obj = numpy.asarray(data).view(cls) 36 | obj.rownames = rownames 37 | obj.colnames = colnames 38 | return obj 39 | 40 | def __array_finialize__(self, obj): 41 | if obj is None: return 42 | self.rownames = getattr(obj, 'rownames', None) 43 | self.colnames = getattr(obj, 'colnames', None) 44 | 45 | def row(self, row): 46 | return self[self.rownames.get(row)] 47 | 48 | def col(self, col): 49 | return self[:,self.colnames.get(col)] 50 | 51 | def cell(self, row, col): 52 | return self[self.rownames.get(row), self.colnames.get(col)] 53 | 54 | @classmethod 55 | def convert(cls, data): 56 | matrix = numpy.zeros((len(set(k for k,v in data)), 57 | len(set(v for k,v in data)))) 58 | colnames, rownames = {}, {} 59 | for k,v in sorted(data): 60 | if k not in rownames: 61 | rownames[k] = len(rownames) 62 | if v not in colnames: 63 | colnames[v] = len(colnames) 64 | matrix[rownames[k],colnames[v]] += 1 65 | #rownames = [k for k,v in sorted(rownames.items(), key=itemgetter(1))] 66 | #colnames = [k for k,v in sorted(colnames.items(), key=itemgetter(1))] 67 | return matrix, rownames, colnames 68 | 69 | def tfidf(self): 70 | """ 71 | Returns a matrix in which for all entries in the co-occurence matrix 72 | the 'term frequency-inverse document frequency' is calculated. 73 | """ 74 | matrix = numpy.zeros(self.shape) 75 | # the number of words in a document 76 | words_per_doc = numpy.asarray(self.sum(axis=1), dtype=float) 77 | # the number of documents in which a word is attested. 78 | word_frequencies = numpy.asarray(numpy.sum(self > 0, axis=0), dtype=float) 79 | # calculate the term frequencies 80 | for i in range(self.shape[0]): 81 | tf = self[i] / words_per_doc[i] # array of tf's 82 | matrix[i] = tf * (numpy.log(self.shape[0] / word_frequencies)) 83 | return matrix 84 | 85 | 86 | class DistanceMatrix(numpy.ndarray): 87 | """ 88 | Simple wrapper around numpy.ndarray, to provide some custom 89 | Distance Matrix functionality like plotting the distance matrix 90 | with matplotlib. 91 | """ 92 | def __new__(cls, data, dist_metric=euclidean_distance, lower=True): 93 | if (not isinstance(data, (numpy.ndarray, DistanceMatrix)) 94 | or len(data) != len(data[0]) 95 | or not max(numpy.diag(data)) == 0): 96 | data = DistanceMatrix.convert_to_distmatrix(data, dist_metric, lower=lower) 97 | obj = numpy.asarray(data).view(cls) 98 | obj.distance_metric = dist_metric 99 | return obj 100 | 101 | def __array_finialize__(self, obj): 102 | if obj is None: return 103 | self.distance_metric = getattr(obj, 'distance_metric', None) 104 | 105 | def row(self, row): 106 | return self[self.rownames.get(row)] 107 | 108 | def col(self, col): 109 | return self[:,self.colnames.get(col)] 110 | 111 | def cell(self, row, col): 112 | return self[self.rownames.get(row), self.colnames.get(col)] 113 | 114 | def rows(self): 115 | return [k for k,v in sorted(self.rownames.items(), key=itemgetter(1))] 116 | 117 | @classmethod 118 | def convert_to_distmatrix(cls, data, distance, lower=True): 119 | matrix = numpy.zeros((len(data), len(data))) 120 | for i,j in combinations(range(len(data)), 2): 121 | matrix[i][j] = distance(data[i], data[j]) 122 | if lower == True: 123 | matrix[j][i] = matrix[i][j] 124 | # add a nan-diagonal, useful for further computations. 125 | numpy.fill_diagonal(matrix, numpy.nan) 126 | return matrix 127 | 128 | def diag_is_zero(self): 129 | """Check if the diagonal contains only distances of 0.""" 130 | return max(numpy.diag(self)) == 0 131 | 132 | def remove(self, idx): 133 | """ 134 | Delete a row and column with index IDX. 135 | WARNING this function is NOT destructive! 136 | """ 137 | indices = range(len(self)) 138 | indices.remove(idx) 139 | return self.take(indices, axis=0).take(indices, axis=1) 140 | 141 | def draw(self, save=False, format="pdf"): 142 | """Make a nice colorful plot of the distance matrix.""" 143 | try: 144 | import pylab 145 | except ImportError: 146 | raise ImportError("Install pylab.") 147 | fig = pylab.figure() 148 | axmatrix = fig.add_axes([0.1,0.1,0.8,0.8]) 149 | im = axmatrix.matshow(self, aspect='auto', origin='upper', 150 | cmap=pylab.cm.YlGnBu) 151 | axcolor = fig.add_axes([0.91, 0.1, 0.02, 0.8]) 152 | pylab.colorbar(im, cax=axcolor) 153 | fig.show() 154 | if save: 155 | fig.savefig('distance-matrix.%s' % (format,)) 156 | 157 | def summary(self): 158 | """Return a small summary of the matrix.""" 159 | print('DistanceMatrix (n=%s)' % len(self)) 160 | print('Distance metric = %s' % self.distance_metric.__name__) 161 | print(self) 162 | 163 | 164 | class Clusterer(AbstractClusterer): 165 | """ 166 | The Hierarchical Agglomerative Clusterer starts with each of the N vectors 167 | as singleton clusters. It then iteratively merges pairs of clusters which 168 | have the smallest distance according to function LINKAGE. This continues 169 | until there is only one cluster. 170 | """ 171 | def __init__(self, data, linkage='ward', num_clusters=1): 172 | self._num_clusters = num_clusters 173 | vector_ids = [[i] for i in range(len(data))] 174 | self._dendrogram = Dendrogram(vector_ids) 175 | numpy.fill_diagonal(data, numpy.inf) 176 | self._dist_matrix = data 177 | self.linkage = linkage_fn(linkage) 178 | 179 | def smallest_distance(self, clusters): 180 | """ 181 | Return the smallest distance in the distance matrix. 182 | The smallest distance depends on the possible connections in 183 | the distance matrix. 184 | 185 | @param clusters: an object of the class L{DistanceMatrix} holding the 186 | clusters at a specific state in the clustering procedure. 187 | @type clusters: L{DistanceMatrix} 188 | @return: a tuple containing the smallest distance and the indexes of 189 | the clusters yielding the smallest distance. 190 | """ 191 | i, j = numpy.unravel_index(numpy.argmin(clusters), clusters.shape) 192 | return clusters[i, j], i, j 193 | 194 | def cluster(self, verbose=0, sum_ess=False): 195 | """ 196 | Cluster all clusters hierarchically until the level of 197 | num_clusters is obtained. 198 | 199 | @param verbose: how much output is produced during the clustering (0-2) 200 | @type verbose: C{int} 201 | 202 | @return: None, desctructive method. 203 | """ 204 | ## if sum_ess and self.linkage.__name__ != "ward_link": 205 | ## raise ValueError( 206 | ## "Summing for method other than Ward makes no sense...") 207 | clusters = copy.copy(self._dist_matrix) 208 | #clusters = self._dist_matrix 209 | summed_ess = 0.0 210 | 211 | while len(clusters) > max(self._num_clusters, 1): 212 | if verbose >= 1: 213 | print('k=%s' % len(clusters)) 214 | if verbose == 2: 215 | print(clusters) 216 | 217 | best, i, j = self.smallest_distance(clusters) 218 | # In Ward (1963) ess is summed at each iteration 219 | # in R's hclust and Python's hcluster and some text books it is not. 220 | # Here it is optional... 221 | if sum_ess: 222 | summed_ess += best 223 | else: 224 | summed_ess = best 225 | clusters = self.update_distmatrix(i, j, clusters) 226 | self._dendrogram.merge(i,j) 227 | self._dendrogram[i].distance = summed_ess 228 | indices = numpy.arange(clusters.shape[0]) 229 | indices = indices[indices!=j] 230 | clusters = clusters.take(indices, axis=0).take(indices, axis=1) 231 | 232 | def update_distmatrix(self, i, j, clusters): 233 | """ 234 | Update the distance matrix using the specified linkage method so that 235 | it represents the correct distances to the newly formed cluster. 236 | """ 237 | return self.linkage(clusters, i, j, self._dendrogram) 238 | 239 | @property 240 | def dendrogram(self): 241 | """Return the dendrogram object.""" 242 | return self._dendrogram 243 | 244 | def num_clusters(self): 245 | return self._num_clusters 246 | 247 | def __repr__(self): 248 | return """""" % (self.linkage.__name__, self._num_clusters) 250 | 251 | 252 | class VNClusterer(Clusterer): 253 | """ 254 | Variability Neighbor Clustering Class. A subclass of the regular Clusterer 255 | where the order of clustering can be predetermined. In the normal clustering 256 | procedure, all clusters can be clustered with all other clusters. In this 257 | class, the clusters that are allowed to be clustered follow a specific order. 258 | """ 259 | def __init__(self, data, linkage='ward', num_clusters=1): 260 | Clusterer.__init__(self, data, linkage, num_clusters=num_clusters) 261 | 262 | def iterate_clusters(self, clusters): 263 | for i in range(1, len(clusters)): 264 | yield i-1,i 265 | 266 | def smallest_distance(self, clusters): 267 | best = None 268 | for i, j in self.iterate_clusters(clusters): 269 | if best is None or clusters[i][j] <= best[0]: 270 | best = (clusters[i][j], i, j) 271 | return best 272 | 273 | def cluster(self, verbose=False): 274 | # we must sum the error sum of squares in order not to obtain 275 | # singleton clustering. 276 | Clusterer.cluster(self, verbose=verbose, sum_ess=True) 277 | 278 | 279 | class EuclideanNeighborClusterer(VNClusterer): 280 | 281 | def iterate_clusters(self, x, y): 282 | n_features, n_samples = x, y 283 | offset = (0, -1, 1) 284 | indices = ((i, j) for i in range(n_features) for j in range(n_samples)) 285 | for i, j in indices: 286 | all_neigh = ((i + x, j + y) for x in offset for y in offset) 287 | valid = ((i*n_features + j) for i, j in all_neigh if (0 <= i < n_features) and (0 <= j < n_samples)) 288 | target = valid.next() 289 | for neighbor in list(valid): 290 | yield target, neighbor 291 | 292 | def demo(): 293 | """ 294 | Demo to show some basic functionality. 295 | """ 296 | # declare dummy input vector with two dimensions: 297 | vectors = numpy.array([[2,4], [0,1], [1,1], [3,2], [4,0], [2,2], [8, 9], [8, 11]]) 298 | 299 | # compute the distance matrix on the basis of the vectors via sklearn: 300 | dist_matrix = pairwise_distances(vectors, metric='cityblock') 301 | 302 | # plot the distance matrix: 303 | # dist_matrix.draw() this doesn't work anymore 304 | 305 | # initialize a temporal VNC clusterer, here with the Ward linkage method: 306 | clusterer = VNClusterer(dist_matrix, linkage='ward') # could also be a plain Clusterer() 307 | 308 | # start the clustering procedure: 309 | clusterer.cluster(verbose=1) 310 | 311 | labels = ['n'+str(i+1) for i in range(len(vectors))] 312 | # plot the result as a dendrogram 313 | clusterer.dendrogram.draw(save=True, 314 | labels=labels, 315 | title="VNC Analysis (Ward's Linkage)") 316 | 317 | 318 | if __name__ == '__main__': 319 | demo() 320 | -------------------------------------------------------------------------------- /HACluster/corpus.py: -------------------------------------------------------------------------------- 1 | # Hierarchical Agglomerative Cluster Analysis 2 | # 3 | # Copyright (C) 2013 Folgert Karsdorp 4 | # Author: Folgert Karsdorp 5 | # URL: 6 | # For licence information, see LICENCE.TXT 7 | 8 | import string 9 | 10 | class Text(object): 11 | def __init__(self, id, title, author, year, text, kind=None): 12 | self._id = id 13 | self._text = text 14 | self._title = title 15 | self._author = author 16 | self._year = int(year) 17 | self._kind = kind 18 | # remove all punctuation, transform to lowercase and split on spaces 19 | self._tokens = text.translate(None, string.punctuation).lower().split() 20 | 21 | def __hash__(self): # IS THIS ALLOWED? 22 | return self._id 23 | 24 | def title(self): return self._title 25 | def author(self): return self._author 26 | def year(self): return self._year 27 | def kind(self): return self._kind 28 | 29 | def __getitem__(self, i): 30 | if isinstance(i, slice): 31 | return self._tokens[i.start:i.stop] 32 | else: 33 | return self._tokens[i] 34 | 35 | def __len__(self): 36 | return len(self._tokens) 37 | 38 | -------------------------------------------------------------------------------- /HACluster/dendrogram.py: -------------------------------------------------------------------------------- 1 | # Hierarchical Agglomerative Cluster Analysis 2 | # 3 | # Copyright (C) 2013 Folgert Karsdorp 4 | # Author: Folgert Karsdorp 5 | # URL: 6 | # For licence information, see LICENCE.TXT 7 | 8 | import copy 9 | import sys 10 | import numpy as np 11 | 12 | import numpy 13 | 14 | from operator import itemgetter 15 | 16 | class DendrogramNode(object): 17 | """Represents a node in a dendrogram.""" 18 | def __init__(self, id, *children): 19 | self.id = id 20 | self.distance = 0.0 21 | self._children = children 22 | 23 | def leaves(self): 24 | """Return the leaves of all children of a given node.""" 25 | if self._children: 26 | leaves = [] 27 | for child in self._children: 28 | leaves.extend(child.leaves()) 29 | return leaves 30 | else: 31 | return [self] 32 | 33 | def adjacency_list(self): 34 | """ 35 | For each merge in the dendrogram, return the direct children of 36 | the cluster, the distance between them and the number of items in the 37 | cluster (the total number of children all the way down the tree). 38 | """ 39 | if self._children: 40 | a_list = [(self.id, self._children[0].id, self._children[1].id, 41 | self.distance, len(self))] 42 | for child in self._children: 43 | a_list.extend(child.adjacency_list()) 44 | return a_list 45 | else: return [] 46 | 47 | def __len__(self): 48 | return len(self.leaves()) 49 | 50 | 51 | class Dendrogram(list): 52 | """ 53 | Class representing a dendrogram. Part is inspired by the Dendrogram class 54 | of NLTK. It is adjusted to work properly and more efficiently with 55 | matplotlib and VNC. 56 | """ 57 | def __init__(self, items): 58 | super(Dendrogram, self).__init__(map(DendrogramNode, range(len(items)))) 59 | self._num_items = len(self) 60 | 61 | def merge(self, *indices): 62 | """ 63 | Merge two or more nodes at the given INDICES in the dendrogram. 64 | The new node will get the index of the first node specified. 65 | """ 66 | assert len(indices) >= 2 67 | node = DendrogramNode( 68 | self._num_items, *[self[i] for i in indices]) 69 | self._num_items += 1 70 | self[indices[0]] = node 71 | for i in indices[1:]: 72 | del self[i] 73 | 74 | def to_linkage_matrix(self): 75 | Z = self[0].adjacency_list() 76 | Z.sort() 77 | Z = numpy.array(Z) 78 | return Z[:,1:] 79 | 80 | def draw(self, show=True, save=False, format="pdf", labels=None, title=None, fontsize=None): 81 | """Draw the dendrogram using pylab and matplotlib.""" 82 | try: 83 | from scipy.cluster.hierarchy import dendrogram as scipy_dendrogram 84 | except ImportError: 85 | raise ImportError("Scipy not installed, can't draw dendrogram") 86 | try: 87 | import pylab 88 | except ImportError: 89 | raise ImportError("Pylab not installed, can't draw dendrogram") 90 | try: 91 | import matplotlib.pyplot as plt 92 | except ImportError: 93 | raise ImportError("Matplotlib not installed, can't draw dendrogram") 94 | 95 | fig = plt.figure() 96 | ax = fig.add_subplot(111, axisbg='white') 97 | 98 | plt.rcParams['font.family'] = 'arial' 99 | plt.rcParams['font.size'] = 6 100 | plt.rcParams['lines.linewidth'] = 0.75 101 | 102 | m = self.to_linkage_matrix() 103 | 104 | d = scipy_dendrogram(m, labels=labels, 105 | leaf_font_size=fontsize, 106 | color_threshold=0.7*max(m[:,2]), 107 | leaf_rotation=180) 108 | 109 | ax = plt.gca() 110 | ax_labels = ax.get_xmajorticklabels()+ax.get_ymajorticklabels() 111 | for i in range(len(ax_labels)): 112 | ax_labels[i].set_family('arial') 113 | 114 | ax.get_yaxis().set_ticks([]) 115 | ax.spines['right'].set_visible(False) 116 | ax.spines['top'].set_visible(False) 117 | ax.spines['bottom'].set_visible(False) 118 | ax.spines['left'].set_visible(False) 119 | ax.xaxis.set_ticks_position('bottom') 120 | plt.xticks(rotation=90) 121 | 122 | plt.tick_params(axis='x', which='both', bottom='off', top='off') 123 | plt.tick_params(axis='y', which='both', bottom='off', top='off') 124 | ax.xaxis.grid(False) 125 | ax.yaxis.grid(False) 126 | 127 | plt.rcParams["figure.facecolor"] = "white" 128 | plt.rcParams["axes.facecolor"] = "white" 129 | plt.rcParams["savefig.facecolor"] = "white" 130 | 131 | if title is not None: 132 | fig.suptitle(title, fontsize=12) 133 | if show: 134 | fig.show() 135 | if save: 136 | fig.savefig('dendrogram.%s' % (format,)) 137 | 138 | def ete_tree(self, labels=None): 139 | if sys.version_info[0] == 2: 140 | from ete2 import Tree, NodeStyle, TreeStyle 141 | elif sys.version_info[0] == 3: 142 | from ete3 import Tree, NodeStyle, TreeStyle 143 | else: 144 | raise ValueError('Your version of Python is not supported.') 145 | 146 | from scipy.cluster.hierarchy import to_tree 147 | 148 | T = to_tree(self.to_linkage_matrix()) 149 | root = Tree() 150 | root.dist = 0 151 | root.name = "root" 152 | item2node = {T: root} 153 | to_visit = [T] 154 | while to_visit: 155 | node = to_visit.pop() 156 | cl_dist = node.dist / 2.0 157 | for ch_node in [node.left, node.right]: 158 | if ch_node: 159 | ch = Tree() 160 | ch.dist = cl_dist 161 | ch.name = str(ch_node.id) 162 | item2node[node].add_child(ch) 163 | item2node[ch_node] = ch 164 | to_visit.append(ch_node) 165 | if labels != None: 166 | for leaf in root.get_leaves(): 167 | leaf.name = str(labels[int(leaf.name)]) 168 | 169 | ts = TreeStyle() 170 | ts.show_leaf_name = True 171 | 172 | # Draws nodes as small red spheres of diameter equal to 10 pixels 173 | nstyle = NodeStyle() 174 | nstyle["shape"] = None 175 | nstyle["size"] = 0 176 | 177 | # Gray dashed branch lines 178 | nstyle["hz_line_type"] = 1 179 | nstyle["hz_line_color"] = "#cccccc" 180 | 181 | # Applies the same static style to all nodes in the tree. Note that, 182 | # if "nstyle" is modified, changes will affect to all nodes 183 | for n in root.traverse(): 184 | n.set_style(nstyle) 185 | return root 186 | -------------------------------------------------------------------------------- /HACluster/distance.py: -------------------------------------------------------------------------------- 1 | # Hierarchical Agglomerative Cluster Analysis 2 | # 3 | # Copyright (C) 2013 Folgert Karsdorp 4 | # Author: Folgert Karsdorp 5 | # URL: 6 | # For licence information, see LICENCE.TXT 7 | 8 | import numpy 9 | from numpy import dot, sqrt 10 | 11 | def binarize_vector(u): 12 | return u > 0 13 | 14 | def cosine_distance(u, v, binary=False): 15 | """Return the cosine distance between two vectors.""" 16 | if binary: 17 | return cosine_distance_binary(u, v) 18 | return 1.0 - dot(u, v) / (sqrt(dot(u, u)) * sqrt(dot(v, v))) 19 | 20 | def cosine_distance_binary(u, v): 21 | u = binarize_vector(u) 22 | v = binarize_vector(v) 23 | return (1.0 * (u * v).sum()) / numpy.sqrt((u.sum() * v.sum())) 24 | 25 | def euclidean_distance(u, v): 26 | """Return the euclidean distance between two vectors.""" 27 | diff = u - v 28 | return sqrt(dot(diff, diff)) 29 | 30 | def cityblock_distance(u, v): 31 | """Return the Manhattan/City Block distance between two vectors.""" 32 | return abs(u-v).sum() 33 | 34 | def canberra_distance(u, v): 35 | """Return the canberra distance between two vectors.""" 36 | return numpy.sum(abs(u-v) / abs(u+v)) 37 | 38 | def correlation(u, v): 39 | """Return the correlation distance between two vectors.""" 40 | u_var = u - u.mean() 41 | v_var = v - v.mean() 42 | return 1.0 - dot(u_var, v_var) / (sqrt(dot(u_var, u_var)) * 43 | sqrt(dot(v_var, v_var))) 44 | 45 | def dice(u, v): 46 | """Return the dice coefficient between two vectors.""" 47 | u = u > 0 48 | v = v > 0 49 | return (2.0 * (u * v).sum()) / (u.sum() + v.sum()) 50 | 51 | def jaccard_distance(u, v): 52 | """return jaccard distance""" 53 | u = numpy.asarray(u) 54 | v = numpy.asarray(v) 55 | return (numpy.double(numpy.bitwise_and((u != v), 56 | numpy.bitwise_or(u != 0, v != 0)).sum()) 57 | / numpy.double(numpy.bitwise_or(u != 0, v != 0).sum())) 58 | 59 | def jaccard(u, v): 60 | """Return the Jaccard coefficient between two vectors.""" 61 | u = u > 0 62 | v = v > 0 63 | return (1.0 * (u * v).sum()) / (u + v).sum() 64 | -------------------------------------------------------------------------------- /HACluster/linkage.py: -------------------------------------------------------------------------------- 1 | # Hierarchical Agglomerative Cluster Analysis 2 | # 3 | # Copyright (C) 2013 Folgert Karsdorp 4 | # Author: Folgert Karsdorp 5 | # URL: 6 | # For licence information, see LICENCE.TXT 7 | 8 | from __future__ import division 9 | import numpy as np 10 | 11 | def _general_link(clusters, i, j, method): 12 | """ 13 | This function is used to update the distance matrix in the clustering 14 | procedure. 15 | 16 | Several linkage methods for hierarchical agglomerative clustering 17 | can be used: 18 | - single linkage; 19 | - complete linkage; 20 | - group average linkage; 21 | - median linkage; 22 | - centroid linkage and 23 | - ward linkage. 24 | 25 | All linkage methods use the Lance-Williams update formula: 26 | M{d(ij,k) = S{alpha}(i)*d(i,k) + S{alpha}(j)*d(j,k) + S{beta}*d(i,j) + 27 | S{gamma}*(d(i,k) - d(j,k))} 28 | 29 | In the functions below, the following symbols represent the parameters in 30 | the update formula: 31 | 1. n_x = length cluster 32 | 2. a_x = S{alpha}(x) 33 | 3. b_x = S{beta}(x) 34 | 4. c_x = S{gamma}(x) 35 | 5. d_xy = distance(x,y) = d(x,y) 36 | 37 | @param clusters: an object of the class L{DistanceMatrix} 38 | @type clusters: L{DistanceMatrix} 39 | 40 | @param i: cluster A 41 | @type i: C{int} 42 | 43 | @param j: cluster B 44 | @type j: C{int} 45 | 46 | @param method: the method used for clustering. 47 | @type method: a function 48 | 49 | @return: an updated distance matrix 50 | """ 51 | for k in range(len(clusters)): 52 | if k != i and k != j: 53 | if method.__name__ == "ward_update": 54 | new_distance = method(clusters[i,k], clusters[j,k], k) 55 | else: 56 | new_distance = method(clusters[i,k], clusters[j,k]) 57 | clusters[i,k] = new_distance 58 | clusters[k,i] = new_distance 59 | return clusters 60 | 61 | def single_link(clusters, i, j, dendrogram): 62 | """ 63 | Hierarchical Agglomerative Clustering using single linkage. Cluster j is 64 | clustered with cluster i when the minimum distance between any 65 | of the members of i and j is the smallest distance in the vector space. 66 | 67 | Lance-Williams parameters: 68 | 69 | M{S{alpha}(i) = 0.5; S{beta} = 0; S{gamma} = -0.5} which equals 70 | M{min(d(i,k),d(j,k))} 71 | """ 72 | ks = np.arange(clusters.shape[0]) 73 | ks = ks[(ks!=i) & (ks!=j)] 74 | minima = np.minimum(clusters[i,], clusters[j,])[ks] 75 | clusters[i,ks] = minima 76 | clusters[ks,i] = minima 77 | return clusters 78 | # return _general_link(clusters, i, j, min) 79 | 80 | def complete_link(clusters, i, j, dendrogram): 81 | """ 82 | Hierarchical Agglomerative Clustering using complete linkage. Cluster j is 83 | clustered with cluster i when the maximum distance between any 84 | of the members of i and j is the smallest distance in the vector space. 85 | 86 | Lance-Williams parameters: 87 | 88 | M{S{alpha}(i) = 0.5; S{beta} = 0; S{gamma} = 0.5} which equals 89 | M{max(d(i,k),d(j,k))} 90 | """ 91 | ks = np.arange(clusters.shape[0]) 92 | ks = ks[(ks!=i) & (ks!=j)] 93 | maxima = np.maximum(clusters[i,], clusters[j,])[ks] 94 | clusters[i,ks] = maxima 95 | clusters[ks,i] = maxima 96 | return clusters 97 | # return _general_link(clusters, i, j, max) 98 | 99 | def average_link(clusters, i, j, dendrogram): 100 | """ 101 | Hierarchical Agglomerative Clustering using group average linkage. Cluster j 102 | is clustered with cluster i when the pairwise average of values between the 103 | clusters is the smallest in the vector space. 104 | 105 | Lance-Williams parameters: 106 | 107 | M{S{alpha}(i) = |i|/(|i|+|j|); S{beta} = 0; S{gamma} = 0} 108 | """ 109 | n_i, n_j = len(dendrogram[i]), len(dendrogram[j]) 110 | a_i = n_i / (n_i + n_j) 111 | a_j = n_j / (n_i + n_j) 112 | update_fn = lambda d_ik,d_jk: a_i*d_ik + a_j*d_jk 113 | return _general_link(clusters, i, j, update_fn) 114 | 115 | def median_link(clusters, i, j, dendrogram): 116 | """ 117 | Hierarchical Agglomerative Clustering using median linkage. Cluster j 118 | is clustered with cluster i when the distance between the median values 119 | of the clusters is the smallest in the vector space. 120 | 121 | Lance-Williams parameters: 122 | 123 | M{S{alpha}(i) = 0.5; S{beta} = -0.25; S{gamma} = 0} 124 | """ 125 | update_fn = lambda d_ik,d_jk: 0.5*d_ik + 0.5*d_jk + -0.25*clusters[i,j] 126 | return _general_link(clusters, i, j, update_fn) 127 | 128 | def centroid_link(clusters, i, j, dendrogram): 129 | """ 130 | Hierarchical Agglomerative Clustering using centroid linkage. Cluster j 131 | is clustered with cluster i when the distance between the centroids of the 132 | clusters is the smallest in the vector space. 133 | 134 | Lance-Williams parameters: 135 | 136 | M{S{alpha}(i) = |i| / (|i| + |j|); S{beta} = -|i||j| / (|i|+ |j|)^2; 137 | S{gamma} = 0} 138 | """ 139 | n_i, n_j = len(dendrogram[i]), len(dendrogram[j]) 140 | a_i = n_i / (n_i + n_j) 141 | a_j = n_j / (n_i + n_j) 142 | b = -(n_i * n_j) / (n_i + n_j)**2 143 | update_fn = lambda d_ik,d_jk: a_i*d_ik + a_j*d_jk + b*clusters[i,j] 144 | return _general_link(clusters, i, j, update_fn) 145 | 146 | def ward_link(clusters, i, j, dendrogram): 147 | """ 148 | Hierarchical Agglomerative Clustering using Ward's linkage. Two clusters i 149 | and j are merged when their merge results in the smallest increase in the 150 | sum of error squares in the vector space. 151 | 152 | Lance-Williams parameters: 153 | 154 | M{S{alpha}(i) = (|i| + |k|) / (|i| + |j| + |k|); 155 | S{beta} = -|k|/(|i| + |j| + |k|); S{gamma} = 0} 156 | """ 157 | n_i, n_j = len(dendrogram[i]), len(dendrogram[j]) 158 | def ward_update(d_ik, d_jk, k): 159 | n_k = len(dendrogram[k]) 160 | n_ijk = n_i+n_j+n_k 161 | return ( (n_i+n_k)/(n_ijk)*d_ik + (n_j+n_k)/(n_ijk)*d_jk + 162 | -(n_k/(n_ijk))*clusters[i][j] ) 163 | return _general_link(clusters, i, j, ward_update) 164 | 165 | LINKAGES = {'ward': ward_link, 166 | 'complete': complete_link, 167 | 'single': single_link, 168 | 'centroid': centroid_link, 169 | 'average': average_link, 170 | 'median': median_link} 171 | 172 | def linkage_fn(linkage): 173 | if linkage in LINKAGES: 174 | return LINKAGES[linkage] 175 | raise ValueError("Linkage funtion '%s' is not supported" % linkage) 176 | 177 | __all__ = ['single_link', 'complete_link', 'centroid_link', 'ward_link', 178 | 'median_link', 'average_link'] 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hierarchical Agglomerative Clustering in Python. 2 | 3 | This implementation implements a range of distance metrics and clustering methods, like single-linkage clustering, group-average clustering and Ward or minimum variance clustering. 4 | 5 | The most interesting aspect of this implementation is that it provides functionality to perform Variability-based Neighbor Clustering (e.g. Gries & Hilpert 2006). This is a method of clustering in which the order of allowed clusters is predetermined. 6 | 7 | ## Dependencies 8 | - numpy 9 | - scipy (optional) 10 | - matplotlib (optional) 11 | -------------------------------------------------------------------------------- /VNC.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Variability Neighbor Clustering" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": { 14 | "collapsed": false 15 | }, 16 | "outputs": [], 17 | "source": [ 18 | "%matplotlib inline" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "metadata": { 25 | "collapsed": false 26 | }, 27 | "outputs": [], 28 | "source": [ 29 | "from HACluster import VNClusterer\n", 30 | "from sklearn.metrics import pairwise_distances\n", 31 | "import numpy as np" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 3, 37 | "metadata": { 38 | "collapsed": false 39 | }, 40 | "outputs": [], 41 | "source": [ 42 | "data = np.array([[2,4], [0,1], [1,1], [3,2], [4,0], [2,2], [8, 9], [8, 11]])\n", 43 | "D = pairwise_distances(data)" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 4, 49 | "metadata": { 50 | "collapsed": false 51 | }, 52 | "outputs": [], 53 | "source": [ 54 | "vnc = VNClusterer(D, 'ward')\n", 55 | "vnc.cluster()" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 6, 61 | "metadata": { 62 | "collapsed": false 63 | }, 64 | "outputs": [], 65 | "source": [ 66 | "labels = ['n'+str(i+1) for i in range(len(data))]\n", 67 | "t = vnc.dendrogram.ete_tree(labels=labels)" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": 7, 73 | "metadata": { 74 | "collapsed": false 75 | }, 76 | "outputs": [ 77 | { 78 | "data": { 79 | "image/png": "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\nTCaT1T0EAAAAUHHvvPNOnH322fH2229X9ygAafGYNgAAAACQE8JIAAAAACAnhJEAAAAAQE4IIwEA\nAACAnBBGAgAAAAA5IYwEAAAAAHJCGAkAAAAA5IQwEgAAAADICWEkAAAAAJATwkgAAAAAICeEkQAA\nAABATggjAQAAAICcEEYCAAAAADkhjAQAAAAAckIYCQAAAADkhDASAAAAAMgJYSQAAAAAkBN1q3sA\nAAAAACC3kslkrFq1KpYsWRKrV6+OzZs3RyKRiMaNG0fLli2jY8eO0axZs6xfVxgJAAAAALuAzz//\nPKZMmRJPPPFEzJw5Mz799NPt7u/SpUv06dMnzjrrrOjVq1ckEomMZ0gkk8lkxl0AAACAnHvnnXfi\n7LPPjrfffru6RwF2YmvXro0bbrghbr/99li/fn2lehx44IFx7bXXRr9+/TIKJb0zEgAAAABqqenT\np8cBBxwQN910U6WDyIiIBQsWxBlnnBH9+vWLwsLCSvfxmDYAAOyE3n777XjqqaeqewxgJ7dq1aoo\nKiqq7jGAndRtt90WP/vZzyKbD0ZPnTo1evbsGc8880y0a9euwuc9pg0AADuhfffdN04++eQqeXE8\nUHusXr066tevH3/84x+rexRgJ/PnP/85hgwZst09iUQiDjrooOjYsWMUFBREcXFxrFixIt58880d\n3v3YuXPnmDVrVuyxxx4VmksYCQAAO6EOHTrECy+8EPvuu291jwLsxLwzEkjljTfeiJ49e8bWrVtT\nru+9997xi1/8IgYNGpQyTCwpKYmXX345xo4dG08++WS51znxxBNj+vTpUadO+m+C9M5IAAAAAKgl\nSktL48c//nG5QWT//v3j3XffjeHDh5d7V2NeXl707t07nnjiiZgyZUo0adIk5b4ZM2bEfffdV6H5\nhJEAAAAAUEs88sgjMW/evJRr//Vf/xUPPPBAhV4D069fv5gxY0Y0bNgw5fpvfvOb2LJlS9r9hJEA\nAAAAUEvceuutKeuHH3543H777RV6pPpLPXv2jNtuuy3l2kcffRR//etf0+4ljAQAAACAWmDJkiUx\na9aslGs333xz1KtXr9K9Bw8eHIceemjKtcceeyztPsJIAAAAAKgFZsyYkbJ+wAEHxLe+9a2MeicS\nibjwwgtTrs2cOTPtPsJIAAAAAKgFyrsr8uSTT45EIpFx/169eqWsL1myJIqKitLqUTfjKQAAAACA\n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\n5JCMex977LHRpEmTMvWWLVum3F9aWppyllzabbfd0tonjAQAAACACjr99NPj9NNPr5LeI0eOrND+\npk2bxiOPPFIls2Sbx7QBAAAAgJwQRgIAAAAAOSGMBAAAAAByQhgJAAAAAOSED9gAAABADbZp06Z4\n9NFHq3sM2Cm9/vrrUVxcXN1j8DXCSAAAAKihOnXqFGeddVb85S9/qe5RYKf00UcfxbHHHlvdY/A1\nwkgAAACooerXrx833HBDdY8BO60HH3wwHnvsseoeg6/xzkgAAAAAICeEkQAAAABATggjAQAAAICc\nEEYCAAAAADkhjAQAAAAAckIYCQAAAADkhDASAAAAAMgJYSQAAAAAkBPCSAAAAAAgJ4SRAAAAAEBO\n1K3uAQAAAACA3CspKYnVq1fH+vXrIyKiWbNm0aJFi6hTp+ruXxRGAgAAAMAuoLS0NF566aV47LHH\n4uWXX44FCxbEli1bttlTv379OPjgg+O4446Lvn37xnHHHZfVcFIYCQAAAAC1WDKZjMmTJ8fVV18d\n//rXv7a7t6ioKN54441444034pZbbon9998/fvnLX8agQYMiLy8v41m8MxIAAAAAaqmVK1fGSSed\nFD/60Y92GESm8t5778X5558fPXv2jIULF2Y8jzASAAAAAGqhBQsWxFFHHRXPPPNMxr3mzJkTRx55\nZDz99NMZ9RFGAgAAAEAts3jx4jj++ONj2bJl29231157Rffu3ePII4+MDh06RCKRKHfv+vXr4/vf\n/3688MILlZ5LGAkAAAAAtcjmzZujX79+sWrVqpTrTZs2jZEjR8a///3vWLVqVbzxxhsxe/bsWLRo\nUaxZsybuueee6NKlS8qzW7ZsiR/+8Ifx8ccfV2o2YSQAAAAA1CI33HBDvPnmmynXjj766FiwYEFc\ne+210alTpzLru+22WwwePDjeeuutuPjii1P2WLNmTfz85z+v1GzCSAAAAACoJVauXBmjR49OuXb0\n0UfHs88+G23btt1hn/r168e4ceNi2LBhKdcffPDBmD9/foXnE0YCAAAAQC1x2223xaZNm8rUmzRp\nEg8++GA0btw47V6JRCJuueWW6Ny5c8r1O+64o8LzCSMBAAAAoBYoLS2N++67L+Xa8OHDo3379hXu\nWb9+/fif//mflGuPP/54JJPJCvUTRgIAAABALTBnzpxYvnx5mXqdOnXKff9jOr7//e+nrH/88cc7\n/Fp3mVkqPQUAAAAAsNN4/vnnU9aPOeaYaN26daX77rnnnrHnnnumXFuyZEmFetWt9BQAAAAAQIWs\nWrUqFi1aFIWFhVFaWhqNGzeOtm3bxn777Rd5eXkZ9W7fvn2cfPLJMX/+/G3uWOzVq1emY0fjxo3j\n008/LVNfv359hfoIIwEAAACggmbNmhUzZszYptagQYO44oorUu4dP358PP300+U+1tyoUaM47rjj\n4qyzzor+/ftHo0aNKjzTwIEDY+DAgRERUVhYGG+//XbMnz8/evToUeFe/2nt2rUp6xX5IE5ERCJZ\n0bdMAgAAVa5Dhw7xwgsvxL777lvdowBAjfXggw/GY489FpMnT8567zFjxsQvfvGLbWoFBQXbhHZv\nv/12/OxnPyv38enytGjRIn7961/HT37yk4zvlsyG5cuXR9u2bVOuLV26NNq1a5d2L++MBAAAAIAs\nu/fee+OII46ocBAZEbFmzZoYPnx4nHjiieXekZhLU6dOTVlv165duSFleYSRAAAAAJBFY8eOjcGD\nB0dRUVFGfZ5//vk45ZRTYsuWLVmarOI2bNgQo0ePTrk2YMCASCQSFeonjAQAAACALHn00UdjxIgR\n5a7n5+dH69ato2XLllGnzo6juVmzZsWoUaOyOWLaiouL44ILLkj5xewGDRrEpZdeWuGewkgAAAAA\nyIJNmzbF4MGDy9Q7deoUY8aMiQULFsTGjRtjxYoVsXLlytiwYUM899xzcfbZZ2+376hRo2LNmjVV\nNXZKixYtihNPPDEefvjhlOvXXHNNtGnTpsJ9hZEAAAAAkAVbtmyJzz///Kvf9evXj9///vfx7rvv\nxogRI6Jr167bfJCmQYMGcfzxx8fkyZPjueeei9133z1l3w0bNsT9999f5fMnk8mYP39+XHLJJdG1\na9d44YUXUu47++yzt3v35/bUzWRAAAAAANiZFRYWxiuvvJJxn549e6b1WPWXCgoK4umnn46jjz46\nrf3HH398TJs2LY499tgoLi4us/7YY4/F8OHD075+Ou6+++5Yu3ZtrFu3Lj744IN47bXXYvHixds9\nc+GFF8Ydd9xRoX+LrxNGAgAAAFAr9e7dOyZMmBCXX355Vnpdd911ae8fP3582kHkl3r06BFDhgyJ\n22+/vcza66+/HsXFxVG3bvbivJEjR8aqVavS2tuqVasYO3ZsDBgwIKNrCiMBAAAAqJVatmwZTz31\n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\nAAAAgF3Q+vXrY9CgQVFSUrJN/aijjopf/epXVXLNulXSFQAAAAAo16ZNm6KwsDBKS0ujcePG0bx5\n80gkEjkfGP5cAAAgAElEQVSdYcSIEfHvf/97m1qjRo3i/vvvj7p1qyY2FEYCAAAAQCUsWrQo3nrr\nrW1qderUidNPP73M3s8++yweeuihmD59esyZMyc+/vjjbdYbNWoUBx10UPTu3Tt++MMfxhFHHFGl\n4eQTTzwRd911V5n6zTffHF26dIm1a9dWyXWFkQAAAABQCdOmTYuf/vSn29Tq168fmzdv/ur3559/\nHtddd12MGzcuNm3aVG6vjRs3xpw5c2LOnDkxevToOPLII+P666+PE044Ietzr1y5Mi688MIy9e99\n73sxdOjQrF/v67wzEgAAAACqwOzZs+OQQw6J0aNHbzeITGXOnDlx4oknxkUXXRRbt27N2kzJZDIG\nDx4cq1ev3qbeokWLuOeee6r8UXFhJAAAAABk2TPPPBO9e/eOpUuXZtTnzjvvjEGDBkUymczKXHfc\ncUdMnz69TP1Pf/pTtG7dOivX2B5hJAAAAABk0bx586Jv374VvhuyPA899FD86U9/yrjPu+++GyNG\njChTP/fcc+OMM87IuH86hJEAAAAAkCUlJSVx1llnxcaNG7ep5+fnx4ABA2LixIkxf/78WLlyZXz8\n8cfxj3/8I2655Zb4xje+sd2+I0eOjPXr11d6ri1btsTAgQO3eZ9lRET79u3jD3/4Q6X7VpQwEgAA\nAACypLi4ON5///1tagMHDozFixfHpEmTYuDAgXHwwQdHy5Yto1WrVnH44YfHz372s5g3b17ceeed\nkZ+fn7LvmjVrYtKkSZWe66qrrop58+ZtU0skEjFhwoQoKCiodN+K8jVtAAAAgBpmy5YtWf2oCelp\n3LhxhfbXqVMn7rrrrpRfrk61d+jQodGyZcvo169fyj2PPPJIDBkypEIzRES89NJLMWrUqDL1ESNG\nRK9evSrcLxPCSAAAAIAaZO3atXHSSSfFO++8U92j7HIuueSSlKFeeW644Ya0gsiv69u3b/Tv3z8m\nT55cZu2VV16JkpKSyMvLS7vf2rVr49xzzy3zAZyDDz44rr322grNlg3CSAAAAIAaZN26dbFq1aqM\n3h9I1dt///3jv//7vyt1dujQoSnDyI0bN8by5cujffv2afe65JJLynzRu169ejFx4sRo0KBBpebL\nhHdGAgAAAECWXXzxxVG3buXuA+zZs2e5Z1etWpV2n0mTJqV8z+R1110Xhx56aKVmy5QwEgAAAACy\n7PTTT6/02fr168e+++6bci3dO2KXLl0aF198cZn6cccdFyNGjKj0bJkSRgIAAABAFrVq1arcMLEi\nPVLZtGnTDs+WlJTEueeeG+vWrdum3rRp05gwYUKF3jmZbcJIAAAAAMii/fffP+MeTZs2TVkvLi7e\n4dmbb745XnrppTL1W2+9NeOQNFPCSAAAAADIojZt2mTco169einrpaWl2z03b968GDlyZJl63759\n4/zzz894rkwJIwEAAAAgi5o3b55xj0QiUeEzGzdujIEDB8bWrVu3qe+1115x1113VapntgkjAQAA\nACCLyrursapdfvnl8e6775ap33PPPbHnnntWw0RlCSMBAAAAoIabPn16jBs3rkx9yJAhceqpp1bD\nRKnVre4BAAAAAIDKW7NmTVxwwQVl6nvuuWdcdNFF8f7771e45+eff56yXlJSUm6/5s2bxx577LHd\nvsJIAAAAAKjB3n333Vi1alWZ+qeffhrdunXL6rXWr18fnTt3Trl2+eWXx4033rjd8x7TBgAAAABy\nQhgJAAAAAOSEMBIAAAAAyAlhJAAAAACQEz5gAwAAAAA1WJcuXeL+++/Pas8NGzbEsGHDytQbNmwY\nd911V8ozBx100A77CiMBAAAAoAbba6+9YtCgQVntuXbt2pRhZH5+fkbX8pg2AAAAAJATwkgAAAAA\nICeEkQAAAABATggjAQAAAICcEEYCAAAAADkhjAQAAAAAckIYCQAAAADkhDASAAAAAMgJYSQAAAAA\nkBPCSAAAAAAgJ+pW9wAAAAAAUBMNGTIkzj///DL1evXqZdz7L3/5S5SUlJSpN2jQIOPe6SgoKIgv\nvvgi632FkQAAAABQCfn5+ZGfn18lvRs2bFglfdOVSCSiSZMmWe/rMW0AAAAAICeEkQAAAABATggj\nAQAAAICcEEYCAAAAADnhAzYAAABQQ61evTpOPfXUePPNN6t7FHIomUxG06ZNq3sMqBRhJAAAANRQ\nq1atii+++CIKCwurexRyaOnSpfHAAw9U9xhQKcJIAAAAqMESiUQ0aNCguscgh+rXrx/33Xdf/Pa3\nv63uUaDCvDMSAAAAAMgJYSQAAAAAkBPCSAAAAAAgJ4SRAAAAAEBOCCMBAAAAgJwQRgIAAAAAOSGM\n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\nAACAKfhmJAAAAAAAAABTEEYCAAAAAAAAMAVhJAAAAAAAAABTEEYCAAAAAAAAMAVhJAAAAAAAAABT\nEEYCAAAAAAAAMAVhJAAAAAAAAABT+N/7w/79+3Xy5EnDiS1atFCRIkVMaQoA8oIdO3bowoULhrW2\nbdsqKCjI5I4AAAAAAMheFpvNZvvthyFDhuiTTz4xnLht2zY1bdrUtMYAILfr0aOHvv32W8PaiRMn\nVKlSJZM7AgAAAAAge3FMGwAAAAAAAIApCCMBAAAAAAAAmIIwEgAAAAAAAIApCCMBAAAAAAAAmIIw\nEgAAAAAAAIApCCMBAAAAAAAAmIIwEgAAAAAAAIApCCMBAAAAAAAAmIIwEgAAAAAAAIApCCMBAAAA\nAAAAmIIwEgAAAAAAAIApCCMBAAAAAAAAmIIwEgAAAAAAAIApCCMBAAAAAAAAmIIwEgAAAAAAAIAp\nCCMBAAAAAAAAmIIwEgAAAAAAAIApCCMBAAAAAAAAmIIwEgAAAAAAAIApCCMBAAAAAAAAmIIwEgAA\nAAAAAIApCCMBAAAAAAAAmIIwEgAAAAAAAIApCCMBAAAAAAAAmIIwEgAAAAAAAIApCCMBAAAAAAAA\nmIIwEgAAAAAAAIApCCMBAAAAAAAAmIIwEgAAAAAAAIApCCMBAAAAAAAAmIIwEgAAAAAAAIApCCMB\nAAAAAAAAmML/3h9u3Lhhd+LBgwd93gwA5CXXrl2zW0tNTTWxEwAAAAAAcoY/hJG//PKL3YmDBg3y\neTMA8Fdx+fJlhYeHZ3cbAAAAAACYimPaAAAAAAAAAExBGAkAAAAAAADAFISRAAAAAAAAAExBGAkA\nAAAAAADAFISRAAAAAAAAAEzxh9u0S5curZiYGMOJ3bt3V8mSJU1pCgDygjVr1ujkyZOGtUKFCpnc\nDQAAAAAA2e8PYWSVKlW0fv16w4mjR49W06ZNTWkKAPKCHj162A0jQ0NDTe4GAAAAAIDsxzFtAAAA\nAAAAAKYgjAQAAAAAAABgCsJIAAAAAAAAAKYgjAQAAAAAAABgCsJIAAAAAAAAAKYgjAQAAAAAAABg\nCsJIAAAAAAAAAKYgjAQAAAAAAABgCsJIAAAAAAAAAKYgjAQAAAAAAABgCsJIAAAAAAAAAKYgjAQA\nAAAAAABgCsJIAAAAAAAAAKYgjAQAAAAAAABgCsJIAAAAAAAAAKYgjAQAAAAAAABgCsJIAAAAAAAA\n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\nw+5/ypYtm+WcrG7cdiQqKkpPP/20UlJSnH7m0Ucf1TvvvOPTC3wAAAAAAACQs3k9jLyXxWJRWFiY\nwsLCPF5r165dDusREREe75FXOHMJjNXq3gn9qVOnavTo0U7Pb9iwoaZOneqTb4oCAAAAAAAgd/HK\nNyPNsG3bNof15s2bm9RJzpeRkZHlnEKFCrm87ttvv+10EFmkSBF98cUX2rlzJ0EkAAAAAAAAJPn4\nzUhv2rRpk92axWLJ8tKWv5KEhIQs5xQrVsylNb/44guNHTvWqbmRkZGaPn26SpQo4dIeAAAAAAAA\nyNtyRRh5/fp17d692269cePGCg0NNbGjnO3MmTMO68WLF890QZAj27dv13PPPZflvKCgIH366afq\n16+f02sDAAAAAADgryNXhJGrV692ePS4c+fOJnZjX0xMjMMbvytVqqTGjRv7vI+9e/c6rDtz2/Zv\nbt68qb59++ru3bsO591///1avny5ateu7fTaAAAAAAAA+GtxOow8fPiw5s2bZ7fepEkTdevWzStN\n/dk333zjsO6rfV31448/avjw4Xbr7du316pVq3zag81m08aNGx3OceWynzfffFOnTp1yOCc8PFzr\n16936hZvAAAAAAAA/HU5HUbGxsbqnXfesVuPjIz0SSh49epVrVixwm69Vq1aqlmzptf3dUfFihUd\n1vfs2SObzSaLxeKzHnbs2KFz5845nNOyZUun1rp06ZKmTZvmcE5YWJg2bNig0qVLO9siAAAAAAAA\n/qKcvk27aNGiDuvHjh3zuBkjs2fPVmpqqt16//79fbKvO2rVquWwHh8fr3379vm0hxkzZjisBwYG\nqlWrVk6t9eGHHyolJcVuPSgoSCtWrCCIBAAAAAAAgFOcDiOzeusvOjpaN27c8Lihe92+fdvhm3kB\nAQF66qmnvLqnJ8LCwlSyZEmHc+bOneuz/Y8ePaoFCxY4nPPoo4+qYMGCWa51584dff755w7nTJs2\nzaXvTwIAAAAAAOCvzaU3Ix19EzA9PV3Lly/3SlO/mTp1qi5cuGC33q9fPxUrVsyre3rCYrGoTZs2\nDufMmjXL4e/kLpvNphEjRig9Pd3hvGeffdap9VauXKkrV67YrTdv3lzPPPOMSz0CAAAAAADgr83p\nMFLK+luDH374oWw2myf9/C46OlqTJk2yW7darRozZoxX9vKmnj17OqzfunVL//znP732z+k3s2bN\nyvJynIiICLVo0cKp9aKiohzW33zzTZ9++xIAAAAAAAB5j0thZFYX1OzYsUNff/21Rw1J0vnz59W1\na1fduXPH7pynnnpKVatW9WifWbNmyd/f3+Ffffv2dWnNjh07ZvkNxe+++06vv/66J63/webNm/X8\n889nOe+NN95wKkBMS0vT6tWr7dZr1aqlv/3tby71CAAAAAAAALgURnbu3FklSpRwOOe5557TL7/8\n4nZDp06dUqtWrXT69Gm7c4KDgx2+Neksm82m9PR0h39lZGS4tGb+/Pk1evToLOdNnDhRL7zwgsPL\neZzx/fffq3379g6DW+m//+7atWvn1Jp79uxx+P3PXr168VYkAAAAAAAAXObvyuSAgACNHj3a4fHo\npKQktWrVSl9//bU6dOjgUjPLli3TP/7xD4ffKpSkd955J0ff4DxkyBB9/PHHOnHihMN506ZN048/\n/qipU6eqTZs2LgV8586d06uvvqo5c+ZkOTckJCTLW7bvtXnzZof1KVOmuLSer/z0008KDw/P7jYA\nAAAAAADgJJfCSEkaOnSoPvvsM4dBW0JCgjp27Kg+ffro5ZdfVq1atezOtdls2r59uyZPnqxly5Zl\nuX+nTp00ePBgV9s2VYECBTR37ly1bNkyywtloqOj1bZtW9WqVUu9e/dW69atVbdu3Uw3XmdkZOj0\n6dPaunWrvv/+e/3www9KS0tzqp9PP/1U999/v9P979y502H9xo0bXr853R13797N7hYAAAAAAADg\nApfDyAIFCmjevHlq0aJFlkFbVFSUoqKiVLduXbVq1Urh4eEqWrSoMjIyFBcXp5iYGK1fv17Hjx93\nau8qVaroyy+/zBVHhB966CFNmzZNQ4cOdWr+wYMHdfDgQY0fP16SVKJECd13333y8/NTcnKyLl++\nnOVRbCMvvPCCHn/8cZeeOXDggMv7AAAAAAAAAFlxOYyUpObNm7sUtO3fv1/79+93Z6vflS5dWqtW\nrVKRIkU8WsdMzz//vJKTk/XSSy+5/GxcXJzi4uI82v/JJ5/UlClTXHomLS0ty+PlAAAAAAAAgDtc\nusDmXs8//7xee+01b/ZiV1hYmDZt2qTKlSubsp83/b//9/8UFRWlQoUKmb7vnDlzZLW69q/43Llz\nWb7xCgAAAAAAALjD7TBSkl599VXNnDlTAQEB3uonkzZt2mjnzp2qUqWKz/bwtd69e+vAgQNq06aN\nz/cqU6aMfvjhB7399tsuB5GSdP78eR90BQAAAAD/v707Vq0qiwIwvAZTiCBJIDbpUlkES0ELtbaw\nT2ljIfgOPoONlc9gJWhuqViKEBAEsRYEmyDBQnKnmmEKFUnu/BmY76s366xzyh8OGwBOGSNnZu7d\nuzdv3ryZGzdurGKfv21ubs7jx49nf39/Ll26tNLZZ2FnZ2cWi8U8ffr0lxf6nNTGxsY8fPhw3r9/\nP3fu3DnxnNP+Gg4AAAAAP/PHcrlcrmLQcrmcxWIxjx49mv39/Tk+Pj7RnO3t7bl///48ePBgNjY2\nVrHaTx0cHMxisfjlmcuXL58q7v3IX9/qyZMn8+zZs/n27duJ5qytrc2tW7dmb29v9vb2VvIr+Lt3\n7+b58+ennlO4e/fubG1tnfUaAAAAAPymlcXIf/r8+fO8ePFiXr16NW/fvp0PHz7M4eHhD8+ur6/P\nlStX5vr163P79u25efPmnDt3btUr/WcdHR3Ny5cv5/Xr13NwcDAfP36cT58+zeHh4Xz//n3W1tbm\nwoULs7m5Odvb27OzszO7u7tz9erVuXbt2ly8ePGsXwEAAAAAfsu/EiN/5OvXr/Ply5c5Ojqa4+Pj\nOX/+/Gxtbc36+nrxeAAAAADgjGUxEgAAAAD4fzv1BTYAAAAAAL9DjAQAAAAAEmIkAAAAAJAQIwEA\nAACAhBgJAAAAACTESAAAAAAgIUYCAAAAAAkxEgAAAABIiJEAAAAAQEKMBAAAAAASYiQAAAAAkBAj\nAQAAAICEGAkAAAAAJMRIAAAAACAhRgIAAAAACTESAAAAAEiIkQAAAABAQowEAAAAABJiJAAAAACQ\nECMBAAAAgIQYCQAAAAAkxEgAAAAAICFGAgAAAAAJMRIAAAAASIiRAAAAAEBCjAQAAAAAEmIkAAAA\nAJAQIwEAAACAhBgJAAAAACTESAAAAAAgIUYCAAAAAAkxEgAAAABIiJEAAAAAQEKMBAAAAAASYiQA\nAAAAkBAjAQAAAICEGAkAAAAAJMRIAAAAACAhRgIAAAAACTESAAAAAEiIkQAAAABAQowEAAAAABJi\nJAAAAACQECMBAAAAgIQYCQAAAAAkxEgAAAAAICFGAgAAAAAJMRIAAAAASIiRAAAAAEBCjAQAAAAA\nEmIkAAAAAJAQIwEAAACAhBgJAAAAACTESAAAAAAgIUYCAAAAAAkxEgAAAABIiJEAAAAAQEKMBAAA\nAAASYiQAAAAAkBAjAQAAAICEGAkAAAAAJMRIAAAAACAhRgIAAAAACTESAAAAAEiIkQAAAABAQowE\nAAAAABJiJAAAAACQECMBAAAAgIQYCQAAAAAkxEgAAAAAICFGAgAAAAAJMRIAAAAASIiRAAAAAEBC\njAQAAAAAEmIkAAAAAJAQIwEAAACAhBgJAAAAACTESAAAAAAgIUYCAAAAAAkxEgAAAABIiJEAAAAA\nQEKMBAAAAAASYiQAAAAAkBAjAQAAAICEGAkAAAAAJMRIAAAAACAhRgIAAAAACTESAAAAAEiIkQAA\nAABAQowEAAAAABJiJAAAAACQECMBAAAAgIQYCQAAAAAkxEgAAAAAICFGAgAAAAAJMRIAAAAASIiR\nAAAAAEBCjAQAAAAAEmIkAAAAAJAQIwEAAACAhBgJAAAAACTESAAAAAAgIUYCAAAAAAkxEgAAAABI\niJEAAAAAQEKMBAAAAAASYiQAAAAAkBAjAQAAAICEGAkAAAAAJMRIAAAAACAhRgIAAAAACTESAAAA\nAEiIkQAAAABAQowEAAAAABJiJAAAAACQECMBAAAAgIQYCQAAAAAkxEgAAAAAICFGAgAAAAAJMRIA\nAAAASIiRAAAAAEBCjAQAAAAAEmIkAAAAAJAQIwEAAACAhBgJAAAAACTESAAAAAAgIUYCAAAAAAkx\nEgAAAABIiJEAAAAAQEKMBAAAAAASYiQAAAAAkBAjAQAAAICEGAkAAAAAJMRIAAAAACAhRgIAAAAA\nCTESAAAAAEiIkQAAAABAQowEAAAAABJiJAAAAACQECMBAAAAgIQYCQAAAAAkxEgAAAAAICFGAgAA\nAAAJMRIAAAAASIiRAAAAAEBCjAQAAAAAEmIkAAAAAJAQIwEAAACAhBgJAAAAACTESAAAAAAgIUYC\nAAAAAAkxEgAAAABIiJEAAAAAQEKMBAAAAAASYiQAAAAAkBAjAQAAAICEGAkAAAAAJMRIAAAAACAh\nRgIAAAAACTESAAAAAEiIkQAAAABAQowEAAAAABJiJAAAAACQECMBAAAAgIQYCQAAAAAkxEgAAAAA\nICFGAgAAAAAJMRIAAAAASIiRAAAAAEBCjAQAAAAAEmIkAAAAAJAQIwEAAACAhBgJAAAAACTESAAA\nAAAgIUYCAAAAAAkxEgAAAABIiJEAAAAAQEKMBAAAAAASYiQAAAAAkBAjAQAAAICEGAkAAAAAJMRI\nAAAAACAhRgIAAAAACTESAAAAAEiIkQAAAABAQowEAAAAABJiJAAAAACQECMBAAAAgIQYCQAAAAAk\nxEgAAAAAICFGAgAAAAAJMRIAAAAASIiRAAAAAEBCjAQAAAAAEmIkAAAAAJAQIwEAAACAhBgJAAAA\nACTESAAAAAAgIUYCAAAAAAkxEgAAAABIiJEAAAAAQOJPK7O9nv0bxl0AAAAASUVORK5CYII=\n", 80 | "text/plain": [ 81 | "" 82 | ] 83 | }, 84 | "execution_count": 7, 85 | "metadata": {}, 86 | "output_type": "execute_result" 87 | } 88 | ], 89 | "source": [ 90 | "t.render(\"%%inline\", h=10, units='in')" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": { 97 | "collapsed": true 98 | }, 99 | "outputs": [], 100 | "source": [] 101 | } 102 | ], 103 | "metadata": { 104 | "kernelspec": { 105 | "display_name": "Python 2", 106 | "language": "python", 107 | "name": "python2" 108 | } 109 | }, 110 | "nbformat": 4, 111 | "nbformat_minor": 0 112 | } 113 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import setuptools 4 | from distutils.core import setup 5 | 6 | setup(name = 'HACluster', 7 | version = '0.3', 8 | description = 'Hierarchical Agglomerative Cluster Analysis in Python', 9 | author = 'Folgert Karsdorp', 10 | author_email = 'fbkarsdorp@gmail.com', 11 | requires = ['numpy'], 12 | url = "https://github.com/fbkarsdorp/HAC-python", 13 | packages = ['HACluster'], 14 | platforms = 'Mac OS X, MS Windows, GNU Linux') 15 | --------------------------------------------------------------------------------