packages feed

hierarchical-clustering 0.1 → 0.3

raw patch · 2 files changed

+129/−23 lines, 2 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Data.Clustering.Hierarchical: completeDendrogram :: (Fractional d, Ord d) => Linkage -> [a] -> (a -> a -> d) -> Dendrogram d a
+ Data.Clustering.Hierarchical: completeLinkage :: (Ord d) => [a] -> (a -> a -> d) -> Dendrogram d a
+ Data.Clustering.Hierarchical: cutAt :: (Ord d) => Dendrogram d a -> d -> [Dendrogram d a]
+ Data.Clustering.Hierarchical: dendrogram :: (Ord d, Fractional d) => Linkage -> [a] -> (a -> a -> d) -> Dendrogram d a
+ Data.Clustering.Hierarchical: elements :: Dendrogram d a -> [a]
+ Data.Clustering.Hierarchical: fakeAverageLinkage :: (Fractional d, Ord d) => [a] -> (a -> a -> d) -> Dendrogram d a
+ Data.Clustering.Hierarchical: singleLinkage :: (Ord d) => [a] -> (a -> a -> d) -> Dendrogram d a
+ Data.Clustering.Hierarchical: upgma :: (Fractional d, Ord d) => [a] -> (a -> a -> d) -> Dendrogram d a

Files

Data/Clustering/Hierarchical.hs view
@@ -1,7 +1,17 @@ module Data.Clustering.Hierarchical-    (Dendrogram(..)+    (-- * Dendrogram data type+     Dendrogram(..)+    ,elements+    ,cutAt+     -- * Linkage data type     ,Linkage(..)-    ,completeDendrogram+     -- * Generic clustering function+    ,dendrogram+     -- * Functions for specific linkages+    ,singleLinkage+    ,completeLinkage+    ,upgma+    ,fakeAverageLinkage     ) where  import qualified Data.IntMap as IM@@ -15,7 +25,11 @@  import Data.Clustering.Hierarchical.Internal.DistanceMatrix --- | Data structure for storing hierarchical clusters.+-- | Data structure for storing hierarchical clusters.  The+-- distance between clusters is stored on the branches.+-- Distances between leafs are the distances between the elements+-- on those leafs, while distances between branches are defined+-- by the linkage used (see 'Linkage'). data Dendrogram d a =     Leaf a     -- ^ The leaf contains the item @a@ itself.@@ -24,6 +38,45 @@     -- @d@ distance apart.     deriving (Eq, Ord, Show) +-- | List of elements in a dendrogram.+elements :: Dendrogram d a -> [a]+elements = go []+    where+      go acc (Leaf x)       = x : acc+      go acc (Branch _ l r) = go (go acc r) l++-- | @dendro \`cutAt\` threshold@ cuts the dendrogram @dendro@ at+-- all branches which have distances strictly greater than+-- @threshold@.+--+-- For example, suppose we have+--+-- @+-- dendro = Branch 0.8+--            (Branch 0.5+--              (Branch 0.2+--                (Leaf \'A\')+--                (Leaf \'B\'))+--              (Leaf \'C\'))+--            (Leaf \'D\')+-- @+--+-- Then:+--+-- @+-- dendro \`cutAt\` 0.9 == dendro \`cutAt\` 0.8 == [dendro] -- no changes+-- dendro \`cutAt\` 0.7 == dendro \`cutAt\` 0.5 == [Branch 0.5 (Branch 0.2 (Leaf \'A\') (Leaf \'B\')) (Leaf \'C\'), Leaf \'D\']+-- dendro \`cutAt\` 0.4 == dendro \`cutAt\` 0.2 == [Branch 0.2 (Leaf \'A\') (Leaf \'B\'), Leaf \'C\', Leaf \'D\']+-- dendro \`cutAt\` 0.1 == [Leaf \'A\', Leaf \'B\', Leaf \'C\', Leaf \'D\'] -- no branches at all+-- @+cutAt :: Ord d => Dendrogram d a -> d -> [Dendrogram d a]+cutAt dendro threshold = go [] dendro+    where+      go acc x@(Leaf _)                        = x : acc+      go acc x@(Branch d l r) | d <= threshold = x : acc+                              | otherwise      = go (go acc r) l  -- cut!++ -- | Does not recalculate the distances! instance Functor (Dendrogram d) where     fmap f (Leaf d)         = Leaf (f d)@@ -39,7 +92,8 @@   -- | The linkage type determines how the distance between--- clusters will be calculated.+-- clusters will be calculated.  These are the linkage types+-- currently available on this library. data Linkage =     SingleLinkage   -- ^ The distance between two clusters @a@ and @b@ is the@@ -77,27 +131,73 @@     deriving (Eq, Ord, Show, Enum)  --- | Calculates distances between clusters according to the--- chosen linkage.-clusterDistance :: (Fractional d, Ord d) => Linkage -> ClusterDistance d-clusterDistance SingleLinkage      = \_ (_, d1) (_, d2) _ -> d1 `min` d2-clusterDistance CompleteLinkage    = \_ (_, d1) (_, d2) _ -> d1 `max` d2-clusterDistance FakeAverageLinkage = \_ (_, d1) (_, d2) _ -> (d1 + d2) / 2-clusterDistance UPGMA              = \_ (b1,d1) (b2,d2) _ ->-                                       let n1 = fromIntegral (size b1)-                                           n2 = fromIntegral (size b2)-                                       in (n1 * d1 + n2 * d2) / (n1 + n2)+-- Some cluster distances+cdistSingleLinkage      :: Ord d => ClusterDistance d+cdistSingleLinkage      = \_ (_, d1) (_, d2) _ -> d1 `min` d2 +cdistCompleteLinkage    :: Ord d => ClusterDistance d+cdistCompleteLinkage    = \_ (_, d1) (_, d2) _ -> d1 `max` d2 --- | /O(n^2)/ Calculates a complete, rooted dendrogram for a list--- of items and a distance function.-completeDendrogram :: (Fractional d, Ord d) => Linkage ->-                      [a] -> (a -> a -> d) -> Dendrogram d a-completeDendrogram linkage items dist = runST (act ())+cdistUPGMA              :: Fractional d => ClusterDistance d+cdistUPGMA              = \_ (b1,d1) (b2,d2) _ ->+                            let n1 = fromIntegral (size b1)+                                n2 = fromIntegral (size b2)+                            in (n1 * d1 + n2 * d2) / (n1 + n2)++cdistFakeAverageLinkage :: Fractional d => ClusterDistance d+cdistFakeAverageLinkage = \_ (_, d1) (_, d2) _ -> (d1 + d2) / 2+++-- | /O(n^3)/ Calculates a complete, rooted dendrogram for a list+-- of items and a linkage type.  If your distance type has an+-- 'Ord' instance but not a 'Fractional' one, then please use+-- specific functions 'singleLinkage' or 'completeLinkage' that+-- have less restrictive types.+dendrogram :: (Ord d, Fractional d)+           => Linkage        -- ^ Linkage type to be used.+           -> [a]            -- ^ Items to be clustered.+           -> (a -> a -> d)  -- ^ Distance function between items.+           -> Dendrogram d a -- ^ Complete dendrogram.+dendrogram linkage = dendrogram' cdist     where-      n     = length items-      cdist = clusterDistance linkage-      act _ = do+      cdist = case linkage of+                SingleLinkage      -> cdistSingleLinkage+                CompleteLinkage    -> cdistCompleteLinkage+                FakeAverageLinkage -> cdistFakeAverageLinkage+                UPGMA              -> cdistUPGMA++-- | /O(n^3)/ Like 'dendrogram', but specialized to single+-- linkage (see 'SingleLinkage') which does not require+-- 'Fractional'.+singleLinkage :: Ord d => [a] -> (a -> a -> d) -> Dendrogram d a+singleLinkage = dendrogram' cdistSingleLinkage++-- | /O(n^3)/ Like 'dendrogram', but specialized to complete+-- linkage (see 'CompleteLinkage') which does not require+-- 'Fractional'.+completeLinkage :: Ord d => [a] -> (a -> a -> d) -> Dendrogram d a+completeLinkage = dendrogram' cdistCompleteLinkage++-- | /O(n^3)/ Like 'dendrogram', but specialized to 'UPGMA'.+upgma :: (Fractional d, Ord d) => [a] -> (a -> a -> d) -> Dendrogram d a+upgma = dendrogram' cdistUPGMA++-- | /O(n^3)/ Like 'dendrogram', but specialized to fake average+-- linkage (see 'FakeAverageLinkage').+fakeAverageLinkage :: (Fractional d, Ord d) => [a]+                   -> (a -> a -> d) -> Dendrogram d a+fakeAverageLinkage = dendrogram' cdistFakeAverageLinkage++++-- | Worker function to create dendrograms based on a+-- 'ClusterDistance' (and not a 'Linkage').+dendrogram' :: Ord d => ClusterDistance d+            -> [a] -> (a -> a -> d) -> Dendrogram d a+dendrogram' cdist items dist = runST (act ())+    where+      n = length items+      act _noMonomorphismRestrictionPlease = do         let xs = listArray (1, n) items         fromDistance (dist `on` (xs !)) n >>= go xs (n-1) IM.empty       go xs i ds dm = do
hierarchical-clustering.cabal view
@@ -1,5 +1,5 @@ Name:                hierarchical-clustering-Version:             0.1+Version:             0.3 Synopsis:            Algorithms for single, average/UPGMA and complete linkage clustering. License:             BSD3 License-file:        LICENSE@@ -24,6 +24,12 @@   time improvements (e.g. using a finger tree to avoid traversing   the whole matrix on every iteration just to see what the   minimum is).+  .+  Changes in version 0.3:+  .+  * Added function @cutAt@.+  .+  * Fixed complexity in Haddock comments.  Library   Exposed-modules: