{- |
Module : Data.Graph.Analysis
Description : A Graph-Theoretic Analysis Library.
Copyright : (c) Ivan Lazar Miljenovic 2009
License : 2-Clause BSD
Maintainer : Ivan.Miljenovic@gmail.com
This is the root module of the /Graphalyze/ library, which aims to
provide a way of analysing the relationships inherent in discrete
data as a graph.
The original version of this library was written as part of my
mathematics honours thesis,
/Graph-Theoretic Analysis of the Relationships in Discrete Data/.
-}moduleData.Graph.Analysis(version,-- * Re-exporting other modulesmoduleData.Graph.Analysis.Types,moduleData.Graph.Analysis.Utils,moduleData.Graph.Analysis.Algorithms,moduleData.Graph.Analysis.Visualisation,moduleData.Graph.Analysis.Reporting,moduleData.Graph.Inductive.Graph,-- * Importing dataImportParams(..),importData,-- * Result analysis-- $analfunctslengthAnalysis,classifyRoots,inaccessibleNodes,interiorChains,collapseAndUpdate,collapseAndUpdate',levelGraphFromRoot)whereimportData.Graph.Analysis.InternalimportData.Graph.Analysis.UtilsimportData.Graph.Analysis.TypesimportData.Graph.Analysis.AlgorithmsimportData.Graph.Analysis.VisualisationimportData.Graph.Analysis.ReportingimportData.Graph.Inductive.GraphimportData.List(find)importData.Maybe(mapMaybe)importqualifiedData.MapasMimportData.Map(Map)importqualifiedData.SetasSimportData.Set(Set)importControl.Arrow(first)importData.Version(showVersion)importqualifiedPaths_GraphalyzeasPaths(version)-- ------------------------------------------------------------------------------- | The library version.version::Stringversion=showVersionPaths.version{- |
This represents the information that's being passed in that we want
to analyse. If the graph is undirected, it is better to list each
edge once rather than both directions.
-}dataImportParamsne=ImpParams{-- | The discrete points.dataPoints::[n]-- | The relationships between the points.,relationships::[Relne]-- | The expected roots of the graph.-- If @'directed' = 'False'@, then this is ignored.,roots::[n]-- | 'False' if relationships are symmetric-- (i.e. an undirected graph).,directed::Bool}{- |
Import data into a format suitable for analysis. This function is
/edge-safe/: if any datums are listed in the edges of
'ImportParams' that aren't listed in the data points, then those
edges are ignored. Thus, no sanitation of the 'relationships' in
@ImportParams@ is necessary. The unused relations are stored in
'unusedRelationships'. Note that it is assumed that all datums in
'roots' are also contained within 'dataPoints'.
-}importData::(Ordn,Orde)=>ImportParamsne->GraphDataneimportDataparams=GraphData{graph=dGraph,wantedRootNodes=rootNodes,directedData=isDir,unusedRelationships=unRs}whereisDir=directedparams-- Adding Node values to each of the data points.lNodes=zip[1..](dataPointsparams)-- The valid edges in the graph along with the unused relationships.(unRs,graphEdges)=relsToEsisDirlNodes(relationshipsparams)-- Creating a lookup map from the label to the @Node@ value.nodeMap=mkNodeMaplNodes-- Validate a nodevalidNodel=M.lookuplnodeMap-- Construct the root nodesrootNodes=ifisDirthenmapMaybevalidNode(rootsparams)else[]-- Construct the graph.dGraph=mkGraphlNodesgraphEdges-- -----------------------------------------------------------------------------{- $analfuncts
Extra functions for data analysis.
-}-- | Returns the mean and standard deviations of the lengths of the sublists,-- as well all those lists more than one standard deviation longer than-- the mean.lengthAnalysis::[[a]]->(Int,Int,[(Int,[a])])lengthAnalysisas=(av,stdDev,as'')whereas'=addLengthsasls=mapfstas'(av,stdDev)=statistics'lsas''=filter(\(l,_)->l>(av+stdDev))as'{- |
Compare the actual roots in the graph with those that are expected
(i.e. those in 'wantedRootNodes'). Returns (in order):
* Those roots that are expected (i.e. elements of 'wantedRootNodes'
that are roots).
* Those roots that are expected but not present (i.e. elements of
'wantedRootNodes' that /aren't/ roots.
* Unexpected roots (i.e. those roots that aren't present in
'wantedRootNodes').
-}classifyRoots::GraphDatane->(SetNode,SetNode,SetNode)classifyRootsgd=(areWanted,notRoots,notWanted)wherewntd=S.fromList$wantedRootNodesgdrts=S.fromList$applyAlgrootsOf'gdareWanted=S.intersectionwntdrtsnotRoots=S.differencewntdrtsnotWanted=S.differencertswntd-- | Find the nodes that are not reachable from the expected roots-- (i.e. those in 'wantedRootNodes').inaccessibleNodes::GraphDatane->SetNodeinaccessibleNodesgd=allNs`S.difference`reachableNswhere-- We can't use accessibleOnlyFrom' on notWanted from-- classifyRoots, as there might be nodes that are roots but not-- detectable (e.g. a loop).allNs=S.fromList$applyAlgnodesgdrs=S.fromList$wantedRootNodesgdreachableNs=applyAlgaccessibleFrom'gdrs-- | Only return those chains (see 'chainsIn') where the non-initial-- nodes are /not/ expected roots.interiorChains::(Eqn,Eqe)=>GraphDatane->[LNGroupn]interiorChainsgd=filter(not.interiorRoot)chainswherechains=applyAlgchainsIngdrts=wantedRootsgdinteriorRoot=any(`elem`rts).tail-- | As with 'collapseAndReplace', but also update the-- 'wantedRootNodes' to contain the possibly compressed nodes.-- Since the datums they refer to may no longer exist (as they are-- compressed), 'unusedRelationships' is set to @[]@.collapseAndUpdate::(Ordn)=>[AGrne->[(NGroup,n)]]->GraphDatane->GraphDatanecollapseAndUpdatefs=fst.collapseAndUpdate'fs-- | As with 'collapseAndUpdate', but also includes a lookup 'Map'-- from the old label to the new.collapseAndUpdate'::(Ordn)=>[AGrne->[(NGroup,n)]]->GraphDatane->(GraphDatane,Mapnn)collapseAndUpdate'fsgd=(gd',repLookup)wheregr=graphgd(gr',reps)=collapseAndReplace'fsgrlns'=mkNodeMap$labNodesgr'reps'=map(firstS.fromList)repsrs=S.fromList$wantedRootNodesgdreplacer=mayber((M.!)lns'.snd)$find(S.memberr.fst)reps'gd'=gd{graph=gr',wantedRootNodes=S.toList$S.mapreplacers,unusedRelationships=[]}nlLookup=M.fromList$labNodesgrgetLs=mapMaybe(flipM.lookupnlLookup)repLookup=M.fromList.spreadOut$map(firstgetLs)reps-- | As with 'levelGraph', but use the expected roots rather than the-- actual roots.levelGraphFromRoot::(Ordn)=>GraphDatane->GraphData(GenClustern)elevelGraphFromRootgd=updateGraph(levelGraphFrom(wantedRootNodesgd))gd