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 (both)
31 import Data.List (concat, union, intersect, tails, tail, head, last, null, zip, sort, length, any, (++), nub)
32 import qualified Data.List as List
33 import Data.Text (Text, unwords, toLower, words)
34 import Data.Tuple.Extra
35 import Data.Semigroup (Semigroup)
37 import Data.Map (Map, elems, member, adjust, singleton, (!), keys, restrictKeys, mapWithKey)
38 import qualified Data.Map as Map
40 import Data.Vector (Vector, fromList, elemIndex)
41 import qualified Data.Vector as Vector
42 import qualified Data.Maybe as Maybe
44 import Data.Tuple (fst, snd)
45 import qualified Data.Tuple as Tuple
47 import Data.Bool (Bool, not)
48 import qualified Data.Bool as Bool
51 import qualified Data.Set as Set
52 import qualified Data.Matrix as DM'
54 import Gargantext.Text.Metrics.FrequentItemSet (fisWithSizePolyMap, Size(..))
55 import Gargantext.Text.Terms.Mono (monoTexts)
56 import Gargantext.Prelude hiding (head)
57 import Gargantext.Viz.Phylo
58 import Gargantext.Viz.Phylo.Tools
60 ------------------------------------------------------------------------
64 -- | Date : a simple Integer
66 -- | Document : a piece of Text linked to a Date
67 data Document = Document
71 -- | Corpus : a list of Documents
72 type Corpus = [Document]
75 type MapList = [Ngrams]
76 type PeriodeSize = Int
77 -- data Periodes b a = Map (b,b) a
78 type Occurrences = Int
80 data Levels = Level_m1 | Level_0 | Level_1 | Level_2 | Level_N
81 deriving (Show, Eq, Enum, Bounded)
86 data LinkLvlLabel = Link_m1_0 | Link_0_m1 | Link_1_0 | Link_0_1 | Link_x_y
87 deriving (Show, Eq, Enum, Bounded)
89 data LinkLvl = LinkLvl
90 { linkLvlLabel :: LinkLvlLabel
96 data PhyloError = LevelDoesNotExist
101 --------------------------------------------------------------------
102 phyloExampleFinal :: Phylo
103 phyloExampleFinal = undefined
105 --------------------------------------------------------------------
106 appariement :: Map (Date, Date) (Map (Set Ngrams) Int)
107 appariement = undefined
109 ------------------------------------------------------------------------
110 -- | STEP 10 | -- Incrementaly cluster the PhyloGroups n times, link them through the Periods and build level n of the Phylo
113 ------------------------------------------------------------------------
114 -- | STEP 8 | -- Cluster the Fis
116 ------------------------------------------------------------------------
117 -- | STEP 9 | -- Link the PhyloGroups of level 1 through the Periods
119 shouldPair :: PhyloGroup -> PhyloGroup -> Bool
120 shouldPair g g' = (not . null) $ intersect (getNgrams g) (getNgrams g')
123 getKeyPair :: (Int,Int) -> Map (Int,Int) a -> (Int,Int)
124 getKeyPair (x,y) m = case findPair (x,y) m of
125 Nothing -> panic "[ERR][Viz.Phylo.Example.getKeyPair] Nothing"
128 --------------------------------------
129 findPair :: (Int,Int) -> Map (Int,Int) a -> Maybe (Int,Int)
131 | member (x,y) m = Just (x,y)
132 | member (y,x) m = Just (y,x)
133 | otherwise = Nothing
134 --------------------------------------
136 listToCombi :: (a -> b) -> [a] -> [(b,b)]
137 listToCombi f l = [(f x, f y) | (x:rest) <- tails l, y <- rest]
139 fisToCooc :: Map (Date, Date) Fis -> Map (Int, Int) Double
140 fisToCooc m = map (/docs)
141 $ foldl (\mem x -> adjust (+1) (getKeyPair x mem) mem) cooc
143 $ map (\x -> listToCombi findIdx $ (Set.toList . fst) x) fis
145 --------------------------------------
146 fis :: [(Clique,Support)]
147 fis = concat $ map (\x -> Map.toList x) (elems m)
148 --------------------------------------
149 fisNgrams :: [Ngrams]
150 fisNgrams = foldl (\mem x -> union mem $ (Set.toList . fst) x) [] fis
151 --------------------------------------
153 docs = fromIntegral $ foldl (\mem x -> mem + (snd x)) 0 fis
154 --------------------------------------
155 cooc :: Map (Int, Int) (Double)
156 cooc = Map.fromList $ map (\x -> (x,0)) (listToCombi findIdx fisNgrams)
157 --------------------------------------
161 phyloWithAppariement1 :: Phylo
162 phyloWithAppariement1 = phyloLinked_0_1
164 ------------------------------------------------------------------------
165 -- | STEP 8 | -- Find the Fis out of Documents and Ngrams and build level 1 of the Phylo
167 phyloLinked_0_1 :: Phylo
168 phyloLinked_0_1 = phyloToLinks lvl_0_1 phyloLinked_1_0
171 lvl_0_1 = (LinkLvl Link_0_1 0 1)
173 phyloLinked_1_0 :: Phylo
174 phyloLinked_1_0 = phyloToLinks lvl_1_0 phyloWithGroups1
177 lvl_1_0 = (LinkLvl Link_1_0 1 0)
179 phyloWithGroups1 :: Phylo
180 phyloWithGroups1 = updatePhyloByLevel Level_1 phyloLinked_m1_0
182 cliqueToGroup :: PhyloPeriodId -> Int -> Int -> Ngrams -> (Clique,Support) -> PhyloGroup
183 cliqueToGroup period lvl idx label fis = PhyloGroup ((period, lvl), idx)
185 (sort $ map (\x -> findIdx x) $ Set.toList $ fst fis)
186 (singleton "support" (fromIntegral $ snd fis))
189 fisToPhyloLevel :: Map (Date, Date) Fis -> Phylo -> Phylo
190 fisToPhyloLevel m p = over (phylo_periods . traverse)
192 let periodId = _phylo_periodId period
193 fisList = zip [1..] (Map.toList (m ! periodId))
194 in over (phylo_periodLevels)
196 let groups = map (\fis -> cliqueToGroup periodId 1 (fst fis) "" (snd fis)) fisList
197 in (PhyloLevel (periodId, 1) groups) : levels
201 -- | To preserve nonempty periods from filtering, please use : filterFisBySupport False ...
202 phyloFisFiltered :: Map (Date, Date) Fis
203 phyloFisFiltered = filterFisBySupport True 1 (filterFisByNested phyloFis)
205 filterFisBySupport :: Bool -> Int -> Map (Date, Date) Fis -> Map (Date, Date) Fis
206 filterFisBySupport empty min m = case empty of
207 True -> Map.map (\fis -> filterMinorFis min fis) m
208 False -> Map.map (\fis -> filterMinorFisNonEmpty min fis) m
210 filterMinorFis :: Int -> Fis -> Fis
211 filterMinorFis min fis = Map.filter (\s -> s > min) fis
213 filterMinorFisNonEmpty :: Int -> Fis -> Fis
214 filterMinorFisNonEmpty min fis = if (Map.null fis') && (not $ Map.null fis)
215 then filterMinorFisNonEmpty (min - 1) fis
218 fis' = filterMinorFis min fis
220 doesContains :: [Ngrams] -> [Ngrams] -> Bool
223 | length l' > length l = False
224 | elem (head l') l = doesContains l (tail l')
227 doesAnyContains :: Set Ngrams -> [Set Ngrams] -> [Set Ngrams] -> Bool
228 doesAnyContains h l l' = any (\c -> doesContains (Set.toList c) (Set.toList h)) (l' ++ l)
230 filterNestedCliques :: Set Ngrams -> [Set Ngrams] -> [Set Ngrams] -> [Set Ngrams]
231 filterNestedCliques h l l'
232 | null l = if doesAnyContains h l l'
235 | doesAnyContains h l l' = filterNestedCliques (head l) (tail l) l'
236 | otherwise = filterNestedCliques (head l) (tail l) (h : l')
239 filterFisByNested :: Map (Date, Date) Fis -> Map (Date, Date) Fis
240 filterFisByNested = map (\fis -> restrictKeys fis
242 $ filterNestedCliques (head (keys fis)) (keys fis) []
245 phyloFis :: Map (Date, Date) Fis
246 phyloFis = termsToFis phyloTerms
248 termsToFis :: Map (Date, Date) [Document]
249 -> Map (Date, Date) Fis
250 termsToFis = corpusToFis (words . text)
252 -- | TODO: parameters has to be checked
253 -- | TODO FIS on monotexts
254 corpusToFis :: (Document -> [Ngrams])
255 -> Map (Date, Date) [Document]
256 -> Map (Date, Date) (Map (Set Ngrams) Int)
257 corpusToFis f = map (\d -> fisWithSizePolyMap (Segment 1 20) 1 (map f d))
260 ------------------------------------------------------------------------
261 -- | STEP 7 | -- Link level -1 to level 0
264 phyloLinked_m1_0 :: Phylo
265 phyloLinked_m1_0 = phyloToLinks lvl_m1_0 phyloLinked_0_m1
268 lvl_m1_0 = (LinkLvl Link_m1_0 (-1) 0)
271 ------------------------------------------------------------------------
272 -- | STEP 6 | -- Link level 0 to level -1
275 addPointer :: Semigroup field
276 => ASetter current target identity (field -> field)
277 -> field -> current -> target
278 addPointer field targetPointer current =
279 set field (<> targetPointer) current
281 getNgrams :: PhyloGroup -> [Int]
282 getNgrams = _phylo_groupNgrams
284 getGroups :: Phylo -> [PhyloGroup]
285 getGroups = view (phylo_periods . traverse . phylo_periodLevels . traverse . phylo_levelGroups)
287 getGroupId :: PhyloGroup -> PhyloGroupId
288 getGroupId = view (phylo_groupId)
290 getGroupLvl :: PhyloGroup -> Int
291 getGroupLvl = snd . fst . getGroupId
293 getGroupPeriod :: PhyloGroup -> (Date,Date)
294 getGroupPeriod = fst . fst . getGroupId
296 getGroupsByLevelAndPeriod :: Int -> (Date,Date) -> Phylo -> [PhyloGroup]
297 getGroupsByLevelAndPeriod lvl period p = List.filter testGroup (getGroups p)
299 testGroup group = (getGroupLvl group == lvl )
300 && (getGroupPeriod group == period)
302 containsIdx :: [Int] -> [Int] -> Bool
305 | last l < head l' = False
306 | head l' `elem` l = True
307 | otherwise = containsIdx l (tail l')
309 shouldLink :: LinkLvl -> [Int] -> [Int] -> Bool
310 shouldLink lvl current target = case linkLvlLabel lvl of
311 Link_0_m1 -> containsIdx target current
312 Link_m1_0 -> containsIdx target current
313 Link_0_1 -> containsIdx target current
314 Link_1_0 -> containsIdx target current
315 Link_x_y -> undefined
316 _ -> panic ("error link level to be defined")
318 linkGroupToGroups :: LinkLvl -> PhyloGroup -> [PhyloGroup] -> PhyloGroup
319 linkGroupToGroups lvl current targets
320 | linkLvlFrom lvl < linkLvlTo lvl = setLevelParents current
321 | linkLvlFrom lvl > linkLvlTo lvl = setLevelChilds current
322 | otherwise = current
324 setLevelChilds :: PhyloGroup -> PhyloGroup
325 setLevelChilds = over (phylo_groupLevelChilds) addPointers
327 setLevelParents :: PhyloGroup -> PhyloGroup
328 setLevelParents = over (phylo_groupLevelParents) addPointers
330 addPointers :: [Pointer] -> [Pointer]
331 addPointers lp = lp ++ Maybe.mapMaybe (\target -> if shouldLink lvl (_phylo_groupNgrams current)
332 (_phylo_groupNgrams target )
333 then Just ((getGroupId target),1)
337 addPointers' :: [Pointer] -> [Pointer]
338 addPointers' lp = lp ++ map (\target -> ((getGroupId target),1)) targets
340 linkGroupsByLevel :: LinkLvl -> Phylo -> [PhyloGroup] -> [PhyloGroup]
341 linkGroupsByLevel lvl p groups = map (\group ->
342 if getGroupLvl group == linkLvlFrom lvl
343 then linkGroupToGroups lvl group (getGroupsByLevelAndPeriod (linkLvlTo lvl) (getGroupPeriod group) p)
346 phyloToLinks :: LinkLvl -> Phylo -> Phylo
347 phyloToLinks lvl p = over ( phylo_periods
353 (linkGroupsByLevel lvl p) p
355 phyloLinked_0_m1 :: Phylo
356 phyloLinked_0_m1 = phyloToLinks lvl_0_m1 phyloWithGroups0
359 lvl_0_m1 = (LinkLvl Link_0_m1 0 (-1))
362 ------------------------------------------------------------------------
363 -- | STEP 5 | -- Build level 0 (for the moment it's just a durty copy of level -1)
366 setGroupIdLvl :: Int -> PhyloGroup -> PhyloGroup
367 setGroupIdLvl lvl (PhyloGroup ((period, lvl'), idx) gLabel gNgrams gQ gPP gPC gLP gLC)
368 = PhyloGroup ((period, lvl), idx) gLabel gNgrams gQ gPP gPC gLP gLC
370 setPhyloLevel :: Int -> PhyloLevel -> PhyloLevel
371 setPhyloLevel lvl (PhyloLevel (periodId, lvl') lvlGroups)
372 = PhyloLevel (periodId, lvl) lvlGroups'
374 lvlGroups' = map (\g -> setGroupIdLvl lvl g) lvlGroups
376 copyPhyloLevel :: Int -> [PhyloLevel] -> [PhyloLevel]
377 copyPhyloLevel lvl l = (setPhyloLevel lvl (head l)) : l
379 alterLvl :: Int -> [PhyloPeriod] -> [PhyloPeriod]
380 alterLvl lvl l = map (\p -> PhyloPeriod (_phylo_periodId p) (copyPhyloLevel lvl $ _phylo_periodLevels p)) l
382 phyloWithGroups0 :: Phylo
383 phyloWithGroups0 = updatePhyloByLevel Level_0 phyloWithGroupsm1
386 ------------------------------------------------------------------------
387 -- | STEP 4 | -- Build level -1
390 findIdx :: Ngrams -> Int
391 findIdx n = case (elemIndex n (_phylo_ngrams phylo)) of
392 Nothing -> panic "PhyloError"
395 ngramsToGroup :: [Ngrams] -> Text -> Int -> Int -> Int -> Int -> PhyloGroup
396 ngramsToGroup terms label idx lvl from to = PhyloGroup (((from, to), lvl), idx)
398 (sort (map (\x -> findIdx x) terms))
402 docsToLevel :: (Date, Date) -> Corpus -> PhyloLevel
403 docsToLevel k v = PhyloLevel (k,(-1)) (map (\x ->
404 ngramsToGroup [snd x] (snd x) (fst x) (-1) (fst k) (snd k)
405 ) $ zip [1..] $ (nub . concat) $ map (words . text) v)
407 corpusToPhyloPeriod :: Map (Date,Date) Corpus -> [PhyloPeriod]
408 corpusToPhyloPeriod corpus = map (\x -> PhyloPeriod (fst x) [(snd x)]) $ zip (keys mapLvl) (elems mapLvl)
410 mapLvl :: Map (Date,Date) PhyloLevel
411 mapLvl = mapWithKey docsToLevel corpus
413 updatePhyloByLevel :: Levels -> Phylo -> Phylo
414 updatePhyloByLevel lvl (Phylo pDuration pNgrams pPeriods)
417 Level_m1 -> Phylo pDuration pNgrams pPeriods'
418 where pPeriods' = (corpusToPhyloPeriod phyloTerms) ++ pPeriods
420 Level_0 -> Phylo pDuration pNgrams pPeriods'
421 where pPeriods' = alterLvl 0 pPeriods
423 Level_1 -> fisToPhyloLevel phyloFisFiltered (Phylo pDuration pNgrams pPeriods)
425 _ -> panic ("error level to be defined")
427 phyloWithGroupsm1 :: Phylo
428 phyloWithGroupsm1 = updatePhyloByLevel Level_m1 phylo
431 ------------------------------------------------------------------------
432 -- | STEP 3 | -- Split the Corpus into Periods and reduce each Document as a list of Ngrams
435 phyloTerms :: Map (Date, Date) [Document]
436 phyloTerms = toPeriodes date 5 3 $ cleanCorpus cleanedActants phyloCorpus
438 toPeriodes :: (Ord date, Enum date) => (doc -> date)
439 -> Grain -> Step -> [doc] -> Map (date, date) [doc]
440 toPeriodes _ _ _ [] = panic "Empty corpus can not have any periods"
441 toPeriodes f g s es = Map.fromList $ zip hs $ map (inPeriode f es) hs
443 hs = steps g s $ both f (head es, last es)
444 --------------------------------------------------------------------
445 -- | Define overlapping periods of time by following regular steps
446 inPeriode :: Ord b => (t -> b) -> [t] -> (b, b) -> [t]
447 inPeriode f' h (start,end) =
448 fst $ List.partition (\d -> f' d >= start && f' d <= end) h
449 --------------------------------------------------------------------
450 -- | Find steps of linear and homogenous time of integer
451 steps :: (Eq date, Enum date) => Grain -> Step -> (date, date) -> [(date, date)]
452 steps s' o' (start,end) = map (\l -> (head l, last l))
453 $ chunkAlong s' o' [start .. end]
455 cleanCorpus :: MapList -> Corpus -> Corpus
456 cleanCorpus ml = map (\(Document d t) -> Document d (unwords $ filter (\x -> elem x ml) $ monoTexts t))
459 ------------------------------------------------------------------------
460 -- | STEP 2 | -- Find some Ngrams (ie: phyloGroup of level -1) out of the Corpus & init the phylo
463 phylo = Phylo (both date $ (last &&& head) phyloCorpus) phyloNgrams []
465 phyloNgrams :: PhyloNgrams
466 phyloNgrams = Vector.fromList cleanedActants
468 cleanedActants :: [Ngrams]
469 cleanedActants = map toLower actants
472 actants = [ "Cleopatre" , "Ptolemee", "Ptolemee-XIII", "Ptolemee-XIV"
473 , "Marc-Antoine", "Cesar" , "Antoine" , "Octave" , "Rome"
474 , "Alexandrie" , "Auguste" , "Pompee" , "Cassius" , "Brutus"]
477 ------------------------------------------------------------------------
478 -- | STEP 1 | -- Get a corpus of Documents
481 phyloCorpus :: Corpus
482 phyloCorpus = map (\(d,t) -> Document d t) exampleDocuments
484 exampleDocuments :: [(Date, Text)]
485 exampleDocuments = 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")]