2 Module : Gargantext.Viz.Phylo.Example
3 Description : Phylomemy example based on history of Cleopatre.
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
10 -- | Cesar et Cleôpatre
11 -- Exemple de phylomemie
12 -- French without accents
16 - split the functions : RAW -> Document -> Ngrams
18 -- reverse history: antechronologique
24 {-# LANGUAGE NoImplicitPrelude #-}
25 {-# LANGUAGE FlexibleContexts #-}
26 {-# LANGUAGE OverloadedStrings #-}
28 module Gargantext.Viz.Phylo.Example where
30 import Control.Lens hiding (makeLenses, both, Level)
32 import Data.Bool (Bool, not)
33 import Data.List (concat, union, intersect, tails, tail, head, last, null, zip, sort, length, any, (++), (!!), nub, sortOn, reverse)
34 import Data.Map (Map, elems, member, adjust, singleton, (!), keys, restrictKeys, mapWithKey)
35 import Data.Semigroup (Semigroup)
37 import Data.Text (Text, unwords, toLower, words)
38 import Data.Tuple (fst, snd)
39 import Data.Tuple.Extra
40 import Data.Vector (Vector, fromList, elemIndex)
42 import Gargantext.Prelude hiding (head)
43 import Gargantext.Text.Metrics.FrequentItemSet (fisWithSizePolyMap, Size(..))
44 import Gargantext.Text.Terms.Mono (monoTexts)
45 import Gargantext.Viz.Phylo
46 import Gargantext.Viz.Phylo.Tools
48 import qualified Data.Bool as Bool
49 import qualified Data.List as List
50 import qualified Data.Map as Map
51 import qualified Data.Maybe as Maybe
52 import qualified Data.Set as Set
53 import qualified Data.Tuple as Tuple
54 import qualified Data.Vector as Vector
57 ------------------------------------------------------------------------
58 -- | STEP 12 | -- Incrementaly cluster the PhyloGroups n times, link them through the Periods and build level n of the Phylo
61 ------------------------------------------------------------------------
62 -- | STEP 11 | -- Cluster the Fis
65 ------------------------------------------------------------------------
66 -- | STEP 10 | -- Link the PhyloGroups of level 1 through the Periods
69 -- | Are two PhyloGroups sharing at leats one Ngrams
70 shareNgrams :: PhyloGroup -> PhyloGroup -> Bool
71 shareNgrams g g' = (not . null) $ intersect (getGroupNgrams g) (getGroupNgrams g')
74 -- | To get the good pair of keys (x,y) or (y,x) in a given Map (a,b) c
75 getKeyPair :: (Int,Int) -> Map (Int,Int) a -> (Int,Int)
76 getKeyPair (x,y) m = case findPair (x,y) m of
77 Nothing -> panic "[ERR][Viz.Phylo.Example.getKeyPair] Nothing"
80 --------------------------------------
81 findPair :: (Int,Int) -> Map (Int,Int) a -> Maybe (Int,Int)
83 | member (x,y) m = Just (x,y)
84 | member (y,x) m = Just (y,x)
86 --------------------------------------
89 -- | To get all combinations of a list with no repetition and apply a function to the resulting list of pairs
90 listToCombi :: forall a b. (a -> b) -> [a] -> [(b,b)]
91 listToCombi f l = [ (f x, f y) | (x:rest) <- tails l, y <- rest ]
94 -- | To transform the Fis into a coocurency Matrix in a Phylo
95 fisToCooc :: Map (Date, Date) Fis -> Phylo -> Map (Int, Int) Double
96 fisToCooc m p = map (/docs)
97 $ foldl (\mem x -> adjust (+1) (getKeyPair x mem) mem) cooc
99 $ map (\x -> listToCombi (\x -> ngramsToIdx x p) $ (Set.toList . fst) x) fis
101 --------------------------------------
102 fis :: [(Clique,Support)]
103 fis = concat $ map (\x -> Map.toList x) (elems m)
104 --------------------------------------
105 fisNgrams :: [Ngrams]
106 fisNgrams = foldl (\mem x -> union mem $ (Set.toList . fst) x) [] fis
107 --------------------------------------
109 docs = fromIntegral $ foldl (\mem x -> mem + (snd x)) 0 fis
110 --------------------------------------
111 cooc :: Map (Int, Int) (Double)
112 cooc = Map.fromList $ map (\x -> (x,0)) (listToCombi (\x -> ngramsToIdx x p) fisNgrams)
113 --------------------------------------
115 data Proximity = WeightedLogJaccard | Other
117 data Candidates = Childs | Parents
119 weightedLogJaccard :: PhyloGroup -> PhyloGroup -> (PhyloGroupId, Double)
120 weightedLogJaccard group group' = (getGroupId group', 1)
122 getProximity :: Proximity -> PhyloGroup -> PhyloGroup -> (PhyloGroupId, Double)
123 getProximity p group group' = case p of
124 WeightedLogJaccard -> weightedLogJaccard group group'
126 _ -> panic ("[ERR][Viz.Phylo.Example.getProximity] Proximity function not defined")
128 getPhyloPeriods :: Phylo -> [PhyloPeriodId]
129 getPhyloPeriods p = map _phylo_periodId
130 $ view (phylo_periods) p
132 -- | Trouver un moyen de naviguer dans la liste des périodes next or prévious depuis getCandidates
133 -- | lié getCandidates à pair group
134 -- | faire rentrer de la récurence de profondeur 5 au plus dans pair group
135 -- | faire le jaccard
136 -- | faire les pointeurs
137 -- | faire l'amont de pair group
138 -- | faire le double sens
140 getCandidates :: Candidates -> PhyloGroup -> Phylo -> [PhyloGroup]
141 getCandidates c group p = getGroupsWithFilters (getGroupLevel group) prd p
143 --------------------------------------
145 Childs -> getGroupPeriod group
146 Parents -> getGroupPeriod group
147 _ -> panic ("[ERR][Viz.Phylo.Example.getCandidates] Candidates type not defined")
149 pairGroupToGroups :: Double -> PhyloGroup -> [PhyloGroup] -> PhyloGroup
150 pairGroupToGroups thr group l = if (not . null) $ keepBest thr scores
154 --------------------------------------
155 scores :: [(PhyloGroupId, Double)]
156 scores = map (\group' -> getProximity WeightedLogJaccard group group') l
157 --------------------------------------
158 keepBest :: Double -> [(PhyloGroupId, Double)] -> [(PhyloGroupId, Double)]
159 keepBest thr l = reverse
161 $ filter (\(id,s) -> s >= thr) l
162 --------------------------------------
166 phyloWithAppariement1 :: Phylo
167 phyloWithAppariement1 = phyloLinked_0_1
170 ------------------------------------------------------------------------
171 -- | STEP 9 | -- Build level 1 of the Phylo
174 -- | To Cliques into Groups
175 cliqueToGroup :: PhyloPeriodId -> Int -> Int -> Ngrams -> (Clique,Support) -> Phylo -> PhyloGroup
176 cliqueToGroup period lvl idx label fis p = PhyloGroup ((period, lvl), idx)
178 (sort $ map (\x -> ngramsToIdx x p)
182 (singleton "support" (fromIntegral $ snd fis))
186 -- | To transform Fis into PhyloLevels
187 fisToPhyloLevel :: Map (Date, Date) Fis -> Phylo -> Phylo
188 fisToPhyloLevel m p = over (phylo_periods . traverse)
190 let periodId = _phylo_periodId period
191 fisList = zip [1..] (Map.toList (m ! periodId))
192 in over (phylo_periodLevels)
194 let groups = map (\fis -> cliqueToGroup periodId 1 (fst fis) "" (snd fis) p) fisList
195 in levels ++ [PhyloLevel (periodId, 1) groups]
199 phyloLinked_0_1 :: Phylo
200 phyloLinked_0_1 = alterLevelLinks lvl_0_1 phyloLinked_1_0
204 lvl_0_1 = initLevelLink (initLevel 0 Level_0) (initLevel 1 Level_1)
207 phyloLinked_1_0 :: Phylo
208 phyloLinked_1_0 = alterLevelLinks lvl_1_0 phyloWithGroups1
212 lvl_1_0 = initLevelLink (initLevel 1 Level_1) (initLevel 0 Level_0)
215 phyloWithGroups1 :: Phylo
216 phyloWithGroups1 = updatePhyloByLevel (initLevel 1 Level_1) phyloLinked_m1_0
219 ------------------------------------------------------------------------
220 -- | STEP 8 | -- Create Frequent Items Sets by Period and filter them
223 -- | To Filter Fis by support
224 filterFisBySupport :: Bool -> Int -> Map (Date, Date) Fis -> Map (Date, Date) Fis
225 filterFisBySupport empty min m = case empty of
226 True -> Map.map (\fis -> filterMinorFis min fis) m
227 False -> Map.map (\fis -> filterMinorFisNonEmpty min fis) m
230 -- | To filter Fis with small Support, to preserve nonempty periods please use : filterFisBySupport False
231 filterMinorFis :: Int -> Fis -> Fis
232 filterMinorFis min fis = Map.filter (\s -> s > min) fis
235 -- | To filter Fis with small Support but by keeping non empty Periods
236 filterMinorFisNonEmpty :: Int -> Fis -> Fis
237 filterMinorFisNonEmpty min fis = if (Map.null fis') && (not $ Map.null fis)
238 then filterMinorFisNonEmpty (min - 1) fis
241 --------------------------------------
243 fis' = filterMinorFis min fis
244 --------------------------------------
247 -- | To filter nested Fis
248 filterFisByNested :: Map (Date, Date) Fis -> Map (Date, Date) Fis
249 filterFisByNested = map (\fis -> restrictKeys fis
251 $ filterNestedSets (head (keys fis)) (keys fis) []
255 -- | To transform a list of Documents into a Frequent Items Set
256 docsToFis :: Map (Date, Date) [Document] -> Map (Date, Date) Fis
257 docsToFis docs = map (\d -> fisWithSizePolyMap
260 (map (words . text) d)) docs
263 phyloFisFiltered :: Map (Date, Date) Fis
264 phyloFisFiltered = filterFisBySupport True 1 (filterFisByNested phyloFis)
267 phyloFis :: Map (Date, Date) Fis
268 phyloFis = docsToFis phyloPeriods
271 ------------------------------------------------------------------------
272 -- | STEP 7 | -- Link level -1 to level 0
275 phyloLinked_m1_0 :: Phylo
276 phyloLinked_m1_0 = alterLevelLinks lvl_m1_0 phyloLinked_0_m1
279 lvl_m1_0 :: LevelLink
280 lvl_m1_0 = initLevelLink (initLevel (-1) Level_m1) (initLevel 0 Level_0)
283 ------------------------------------------------------------------------
284 -- | STEP 6 | -- Link level 0 to level -1
287 -- | To set the LevelLinks between a given PhyloGroup and a list of childs/parents PhyloGroups
288 linkGroupToGroups :: LevelLink -> PhyloGroup -> [PhyloGroup] -> PhyloGroup
289 linkGroupToGroups lvl current targets
290 | getLevelLinkValue From lvl < getLevelLinkValue To lvl = setLevelParents current
291 | getLevelLinkValue From lvl > getLevelLinkValue To lvl = setLevelChilds current
292 | otherwise = current
294 --------------------------------------
295 setLevelChilds :: PhyloGroup -> PhyloGroup
296 setLevelChilds = over (phylo_groupLevelChilds) addPointers
297 --------------------------------------
298 setLevelParents :: PhyloGroup -> PhyloGroup
299 setLevelParents = over (phylo_groupLevelParents) addPointers
300 --------------------------------------
301 addPointers :: [Pointer] -> [Pointer]
302 addPointers lp = lp ++ Maybe.mapMaybe (\target ->
303 if shouldLink lvl (_phylo_groupNgrams current)
304 (_phylo_groupNgrams target )
305 then Just ((getGroupId target),1)
306 else Nothing) targets
307 --------------------------------------
310 -- | To set the LevelLinks between two lists of PhyloGroups
311 linkGroupsByLevel :: LevelLink -> Phylo -> [PhyloGroup] -> [PhyloGroup]
312 linkGroupsByLevel lvl p groups = map (\group ->
313 if getGroupLevel group == getLevelLinkValue From lvl
314 then linkGroupToGroups lvl group (getGroupsWithFilters (getLevelLinkValue To lvl) (getGroupPeriod group) p)
318 -- | To set the LevelLink of all the PhyloGroups of a Phylo
319 alterLevelLinks :: LevelLink -> Phylo -> Phylo
320 alterLevelLinks lvl p = alterPhyloGroups (linkGroupsByLevel lvl p) p
323 phyloLinked_0_m1 :: Phylo
324 phyloLinked_0_m1 = alterLevelLinks lvl_0_m1 phyloWithGroups0
327 lvl_0_m1 :: LevelLink
328 lvl_0_m1 = initLevelLink (initLevel 0 Level_0) (initLevel (-1) Level_m1)
331 ------------------------------------------------------------------------
332 -- | STEP 5 | -- Build level 0 as a copy of level -1
335 -- | To clone the last PhyloLevel of each PhyloPeriod and update it with a new LevelValue
336 clonePhyloLevel :: Int -> Phylo -> Phylo
337 clonePhyloLevel lvl p = alterPhyloLevels (\l -> addPhyloLevel
338 (setPhyloLevelId lvl $ head l)
342 phyloWithGroups0 :: Phylo
343 phyloWithGroups0 = updatePhyloByLevel (initLevel 0 Level_0) phyloWithGroupsm1
346 ------------------------------------------------------------------------
347 -- | STEP 4 | -- Build level -1
350 -- | To transform a list of Documents into a PhyloLevel
351 docsToPhyloLevel :: Int ->(Date, Date) -> [Document] -> Phylo -> PhyloLevel
352 docsToPhyloLevel lvl (d, d') docs p = initPhyloLevel
354 (map (\(f,s) -> initGroup [s] s f lvl d d' p)
357 $ map (words . text) docs)
360 -- | To transform a Map of Periods and Documents into a list of PhyloPeriods
361 docsToPhyloPeriods :: Int -> Map (Date,Date) [Document] -> Phylo -> [PhyloPeriod]
362 docsToPhyloPeriods lvl docs p = map (\(id,l) -> initPhyloPeriod id l)
365 --------------------------------------
366 levels :: Map (Date,Date) [PhyloLevel]
367 levels = mapWithKey (\k v -> [docsToPhyloLevel lvl k v p]) docs
368 --------------------------------------
371 -- | To update a Phylo for a given Levels
372 updatePhyloByLevel :: Level -> Phylo -> Phylo
373 updatePhyloByLevel lvl p
374 = case getLevelLabel lvl of
376 Level_m1 -> appendPhyloPeriods (docsToPhyloPeriods (getLevelValue lvl) lvlData p) p
378 --------------------------------------
379 lvlData :: Map (Date,Date) [Document]
380 lvlData = phyloPeriods
381 --------------------------------------
383 Level_0 -> clonePhyloLevel (getLevelValue lvl) p
385 Level_1 -> fisToPhyloLevel lvlData p
387 --------------------------------------
388 lvlData :: Map (Date, Date) Fis
389 lvlData = phyloFisFiltered
390 --------------------------------------
392 _ -> panic ("[ERR][Viz.Phylo.Example.updatePhyloByLevel] Level not defined")
395 phyloWithGroupsm1 :: Phylo
396 phyloWithGroupsm1 = updatePhyloByLevel (initLevel (-1) Level_m1) phylo
399 ------------------------------------------------------------------------
400 -- | STEP 3 | -- Parse the Documents and group them by Periods
403 -- | To init a set of periods out of a given Grain and Step
404 docsToPeriods :: (Ord date, Enum date) => (doc -> date)
405 -> Grain -> Step -> [doc] -> Map (date, date) [doc]
406 docsToPeriods _ _ _ [] = panic "[ERR][Viz.Phylo.Example.docsToPeriods] Empty [Documents] can not have any periods"
407 docsToPeriods f g s es = Map.fromList $ zip hs $ map (inPeriode f es) hs
409 --------------------------------------
410 hs = steps g s $ both f (head es, last es)
411 --------------------------------------
412 inPeriode :: Ord b => (t -> b) -> [t] -> (b, b) -> [t]
413 inPeriode f' h (start,end) =
414 fst $ List.partition (\d -> f' d >= start && f' d <= end) h
415 --------------------------------------
416 steps :: (Eq date, Enum date) => Grain -> Step -> (date, date) -> [(date, date)]
417 steps s' o' (start,end) = map (\l -> (head l, last l))
418 $ chunkAlong s' o' [start .. end]
419 --------------------------------------
422 -- | To parse a list of Documents by filtering on a Vector of Ngrams
423 parseDocs :: PhyloNgrams -> [Document] -> [Document]
424 parseDocs l docs = map (\(Document d t) -> Document d (unwords
425 $ filter (\x -> Vector.elem x l)
429 -- | To group a list of Documents by fixed periods
430 groupDocsByPeriod :: Grain -> Step -> [Document] -> Phylo -> Map (Date, Date) [Document]
431 groupDocsByPeriod g s docs p = docsToPeriods date g s
432 $ parseDocs (getPhyloNgrams p) docs
435 phyloPeriods :: Map (Date, Date) [Document]
436 phyloPeriods = groupDocsByPeriod 5 3 phyloDocs phylo
439 ------------------------------------------------------------------------
440 -- | STEP 2 | -- Init an initial list of Ngrams and a Phylo
443 -- | To init a Phylomemy
444 initPhylo :: [Document] -> PhyloNgrams -> Phylo
445 initPhylo docs ngrams = Phylo (both date $ (last &&& head) docs) ngrams []
448 -- | To init a PhyloNgrams as a Vector of Ngrams
449 initNgrams :: [Ngrams] -> PhyloNgrams
450 initNgrams l = Vector.fromList $ map toLower l
454 phylo = initPhylo phyloDocs (initNgrams actants)
457 ------------------------------------------------------------------------
458 -- | STEP 1 | -- Get a list of Document
461 -- | To transform a corpus of texts into a structured list of Documents
462 corpusToDocs :: [(Date, Text)] -> [Document]
463 corpusToDocs l = map (\(d,t) -> Document d t) l
466 phyloDocs :: [Document]
467 phyloDocs = corpusToDocs corpus
470 ------------------------------------------------------------------------
471 -- | STEP 0 | -- Let's start with an example
475 actants = [ "Cleopatre" , "Ptolemee", "Ptolemee-XIII", "Ptolemee-XIV"
476 , "Marc-Antoine", "Cesar" , "Antoine" , "Octave" , "Rome"
477 , "Alexandrie" , "Auguste" , "Pompee" , "Cassius" , "Brutus"]
480 corpus :: [(Date, Text)]
481 corpus = List.sortOn fst [ (-51,"Cleopatre règne sur l’egypte entre 51 et 30 av. J.-C. avec ses frères-epoux Ptolemee-XIII et Ptolemee-XIV, puis aux côtes du general romain Marc-Antoine. Elle est celèbre pour avoir ete la compagne de Jules Cesar puis d'Antoine, avec lesquels elle a eu plusieurs enfants. Partie prenante dans la guerre civile opposant Antoine à Octave, elle est vaincue à la bataille d'Actium en 31 av. J.-C. Sa defaite va permettre aux Romains de mener à bien la conquête de l’egypte, evenement qui marquera la fin de l'epoque hellenistique."), (-40,"Il existe relativement peu d'informations sur son sejour à Rome, au lendemain de l'assassinat de Cesar, ou sur la periode passee à Alexandrie durant l'absence d'Antoine, entre -40 et -37."), (-48,"L'historiographie antique lui est globalement defavorable car inspiree par son vainqueur, l'empereur Auguste, et par son entourage, dont l'interêt est de la noircir, afin d'en faire l'adversaire malfaisant de Rome et le mauvais genie d'Antoine. On observe par ailleurs que Cesar ne fait aucune mention de sa liaison avec elle dans les Commentaires sur la Guerre civile"), (-69,"Cleopatre est nee au cours de l'hiver -69/-686 probablement à Alexandrie."), (-48,"Pompee a en effet ete le protecteur de Ptolemee XII, le père de Cleopatre et de Ptolemee-XIII dont il se considère comme le tuteur."), (-48,"Ptolemee-XIII et Cleopatre auraient d'ailleurs aide Pompee par l'envoi d'une flotte de soixante navires."), (-48,"Mais le jeune roi Ptolemee-XIII et ses conseillers jugent sa cause perdue et pensent s'attirer les bonnes graces du vainqueur en le faisant assassiner à peine a-t-il pose le pied sur le sol egyptien, près de Peluse, le 30 juillet 48 av. J.-C., sous les yeux de son entourage."), (-48,"Cesar fait enterrer la tête de Pompee dans le bosquet de Nemesis en bordure du mur est de l'enceinte d'Alexandrie. Pour autant la mort de Pompee est une aubaine pour Cesar qui tente par ailleurs de profiter des querelles dynastiques pour annexer l’egypte."), (-48,"Il est difficile de se prononcer clairement sur les raisons qui ont pousse Cesar à s'attarder à Alexandrie. Il y a des raisons politiques, mais aussi des raisons plus sentimentales (Cleopatre ?). Il tente d'abord d'obtenir le remboursement de dettes que Ptolemee XII"), (-46,"Les deux souverains sont convoques par Cesar au palais royal d'Alexandrie. Ptolemee-XIII s'y rend après diverses tergiversations ainsi que Cleopatre."), (-47,"A Rome, Cleopatre epouse alors un autre de ses frères cadets, à Alexandrie, Ptolemee-XIV, sur l'injonction de Jules Cesar"), (-46,"Cesar a-t-il comme objectif de montrer ce qu'il en coûte de se revolter contre Rome en faisant figurer dans son triomphe la sœur de Cleopatre et de Ptolemee-XIV, Arsinoe, qui s'est fait reconnaître reine par les troupes de Ptolemee-XIII ?"), (-44,"Au debut de l'annee -44, Cesar est assassine par Brutus. Profitant de la situation confuse qui s'ensuit, Cleopatre quitte alors Rome à la mi-avril, faisant escale en Grèce. Elle parvient à Alexandrie en juillet -44."), (-44,"La guerre que se livrent les assassins de Cesar, Cassius et Brutus et ses heritiers, Octave et Marc-Antoine, oblige Cleopatre à des contorsions diplomatiques."), (-41,"Nous ignorons depuis quand Cleopatre, agee de 29 ans en -41, et Marc-Antoine, qui a une quarantaine d'annees, se connaissent. Marc-Antoine est l'un des officiers qui ont participe au retablissement de Ptolemee XII. Il est plus vraisemblable qu'ils se soient frequentes lors du sejour à Rome de Cleopatre."), (-42,"Brutus tient la Grèce tandis que Cassius s'installe en Syrie. Le gouverneur de Cleopatre à Chypre, Serapion, vient en aide à Cassius."), (-42,"Cassius aurait envisage de s'emparer d'Alexandrie quand le 'debarquement' en Grèce d'Antoine et d'Octave l'oblige à renoncer à ses projets")]