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)
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 -- | Document : a piece of Text linked to a Date
65 data Document = Document
69 -- | Corpus : a list of Documents
70 type Corpus = [Document]
74 type PeriodeSize = Int
75 -- data Periodes b a = Map (b,b) a
76 type Occurrences = Int
78 data LinkLvlLabel = Link_m1_0 | Link_0_m1 | Link_1_0 | Link_0_1 | Link_x_y
79 deriving (Show, Eq, Enum, Bounded)
81 data LinkLvl = LinkLvl
82 { linkLvlLabel :: LinkLvlLabel
88 data PhyloError = LevelDoesNotExist
93 --------------------------------------------------------------------
94 phyloExampleFinal :: Phylo
95 phyloExampleFinal = undefined
97 --------------------------------------------------------------------
98 appariement :: Map (Date, Date) (Map (Set Ngrams) Int)
99 appariement = undefined
101 ------------------------------------------------------------------------
102 -- | STEP 10 | -- Incrementaly cluster the PhyloGroups n times, link them through the Periods and build level n of the Phylo
105 ------------------------------------------------------------------------
106 -- | STEP 8 | -- Cluster the Fis
108 ------------------------------------------------------------------------
109 -- | STEP 9 | -- Link the PhyloGroups of level 1 through the Periods
111 shouldPair :: PhyloGroup -> PhyloGroup -> Bool
112 shouldPair g g' = (not . null) $ intersect (getGroupNgrams g) (getGroupNgrams g')
115 getKeyPair :: (Int,Int) -> Map (Int,Int) a -> (Int,Int)
116 getKeyPair (x,y) m = case findPair (x,y) m of
117 Nothing -> panic "[ERR][Viz.Phylo.Example.getKeyPair] Nothing"
120 --------------------------------------
121 findPair :: (Int,Int) -> Map (Int,Int) a -> Maybe (Int,Int)
123 | member (x,y) m = Just (x,y)
124 | member (y,x) m = Just (y,x)
125 | otherwise = Nothing
126 --------------------------------------
129 listToCombi :: forall a b. (a -> b) -> [a] -> [(b,b)]
130 listToCombi f l = [ (f x, f y) | (x:rest) <- tails l
134 fisToCooc :: Map (Date, Date) Fis -> Phylo -> Map (Int, Int) Double
135 fisToCooc m p = map (/docs)
136 $ foldl (\mem x -> adjust (+1) (getKeyPair x mem) mem) cooc
138 $ map (\x -> listToCombi (\x -> ngramsToIdx x p) $ (Set.toList . fst) x) fis
140 --------------------------------------
141 fis :: [(Clique,Support)]
142 fis = concat $ map (\x -> Map.toList x) (elems m)
143 --------------------------------------
144 fisNgrams :: [Ngrams]
145 fisNgrams = foldl (\mem x -> union mem $ (Set.toList . fst) x) [] fis
146 --------------------------------------
148 docs = fromIntegral $ foldl (\mem x -> mem + (snd x)) 0 fis
149 --------------------------------------
150 cooc :: Map (Int, Int) (Double)
151 cooc = Map.fromList $ map (\x -> (x,0)) (listToCombi (\x -> ngramsToIdx x p) fisNgrams)
152 --------------------------------------
156 phyloWithAppariement1 :: Phylo
157 phyloWithAppariement1 = phyloLinked_0_1
159 ------------------------------------------------------------------------
160 -- | STEP 8 | -- Find the Fis out of Documents and Ngrams and build level 1 of the Phylo
162 phyloLinked_0_1 :: Phylo
163 phyloLinked_0_1 = phyloToLinks lvl_0_1 phyloLinked_1_0
166 lvl_0_1 = (LinkLvl Link_0_1 0 1)
168 phyloLinked_1_0 :: Phylo
169 phyloLinked_1_0 = phyloToLinks lvl_1_0 phyloWithGroups1
172 lvl_1_0 = (LinkLvl Link_1_0 1 0)
174 phyloWithGroups1 :: Phylo
175 phyloWithGroups1 = updatePhyloByLevel (initLevel 1 Level_1) phyloLinked_m1_0
177 cliqueToGroup :: PhyloPeriodId -> Int -> Int -> Ngrams -> (Clique,Support) -> Phylo -> PhyloGroup
178 cliqueToGroup period lvl idx label fis p = PhyloGroup ((period, lvl), idx)
180 (sort $ map (\x -> ngramsToIdx x p)
184 (singleton "support" (fromIntegral $ snd fis))
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 (PhyloLevel (periodId, 1) groups) : levels
199 -- | To preserve nonempty periods from filtering, please use : filterFisBySupport False ...
200 phyloFisFiltered :: Map (Date, Date) Fis
201 phyloFisFiltered = filterFisBySupport True 1 (filterFisByNested phyloFis)
203 filterFisBySupport :: Bool -> Int -> Map (Date, Date) Fis -> Map (Date, Date) Fis
204 filterFisBySupport empty min m = case empty of
205 True -> Map.map (\fis -> filterMinorFis min fis) m
206 False -> Map.map (\fis -> filterMinorFisNonEmpty min fis) m
208 filterMinorFis :: Int -> Fis -> Fis
209 filterMinorFis min fis = Map.filter (\s -> s > min) fis
211 filterMinorFisNonEmpty :: Int -> Fis -> Fis
212 filterMinorFisNonEmpty min fis = if (Map.null fis') && (not $ Map.null fis)
213 then filterMinorFisNonEmpty (min - 1) fis
216 fis' = filterMinorFis min fis
218 doesContains :: [Ngrams] -> [Ngrams] -> Bool
221 | length l' > length l = False
222 | elem (head l') l = doesContains l (tail l')
225 doesAnyContains :: Set Ngrams -> [Set Ngrams] -> [Set Ngrams] -> Bool
226 doesAnyContains h l l' = any (\c -> doesContains (Set.toList c) (Set.toList h)) (l' ++ l)
228 filterNestedCliques :: Set Ngrams -> [Set Ngrams] -> [Set Ngrams] -> [Set Ngrams]
229 filterNestedCliques h l l'
230 | null l = if doesAnyContains h l l'
233 | doesAnyContains h l l' = filterNestedCliques (head l) (tail l) l'
234 | otherwise = filterNestedCliques (head l) (tail l) (h : l')
237 filterFisByNested :: Map (Date, Date) Fis -> Map (Date, Date) Fis
238 filterFisByNested = map (\fis -> restrictKeys fis
240 $ filterNestedCliques (head (keys fis)) (keys fis) []
243 phyloFis :: Map (Date, Date) Fis
244 phyloFis = termsToFis phyloPeriods
246 termsToFis :: Map (Date, Date) [Document]
247 -> Map (Date, Date) Fis
248 termsToFis = corpusToFis (words . text)
250 -- | TODO: parameters has to be checked
251 -- | TODO FIS on monotexts
252 corpusToFis :: (Document -> [Ngrams])
253 -> Map (Date, Date) [Document]
254 -> Map (Date, Date) (Map (Set Ngrams) Int)
255 corpusToFis f = map (\d -> fisWithSizePolyMap (Segment 1 20) 1 (map f d))
258 ------------------------------------------------------------------------
259 -- | STEP 7 | -- Link level -1 to level 0
262 phyloLinked_m1_0 :: Phylo
263 phyloLinked_m1_0 = phyloToLinks lvl_m1_0 phyloLinked_0_m1
266 lvl_m1_0 = (LinkLvl Link_m1_0 (-1) 0)
269 ------------------------------------------------------------------------
270 -- | STEP 6 | -- Link level 0 to level -1
273 addPointer :: Semigroup field
274 => ASetter current target identity (field -> field)
275 -> field -> current -> target
276 addPointer field targetPointer current =
277 set field (<> targetPointer) current
279 containsIdx :: [Int] -> [Int] -> Bool
282 | last l < head l' = False
283 | head l' `elem` l = True
284 | otherwise = containsIdx l (tail l')
286 shouldLink :: LinkLvl -> [Int] -> [Int] -> Bool
287 shouldLink lvl current target = case linkLvlLabel lvl of
288 Link_0_m1 -> containsIdx target current
289 Link_m1_0 -> containsIdx target current
290 Link_0_1 -> containsIdx target current
291 Link_1_0 -> containsIdx target current
292 Link_x_y -> undefined
293 _ -> panic ("error link level to be defined")
295 linkGroupToGroups :: LinkLvl -> PhyloGroup -> [PhyloGroup] -> PhyloGroup
296 linkGroupToGroups lvl current targets
297 | linkLvlFrom lvl < linkLvlTo lvl = setLevelParents current
298 | linkLvlFrom lvl > linkLvlTo lvl = setLevelChilds current
299 | otherwise = current
301 setLevelChilds :: PhyloGroup -> PhyloGroup
302 setLevelChilds = over (phylo_groupLevelChilds) addPointers
304 setLevelParents :: PhyloGroup -> PhyloGroup
305 setLevelParents = over (phylo_groupLevelParents) addPointers
307 addPointers :: [Pointer] -> [Pointer]
308 addPointers lp = lp ++ Maybe.mapMaybe (\target -> if shouldLink lvl (_phylo_groupNgrams current)
309 (_phylo_groupNgrams target )
310 then Just ((getGroupId target),1)
314 addPointers' :: [Pointer] -> [Pointer]
315 addPointers' lp = lp ++ map (\target -> ((getGroupId target),1)) targets
317 linkGroupsByLevel :: LinkLvl -> Phylo -> [PhyloGroup] -> [PhyloGroup]
318 linkGroupsByLevel lvl p groups = map (\group ->
319 if getGroupLevel group == linkLvlFrom lvl
320 then linkGroupToGroups lvl group (getGroupsWithFilters (linkLvlTo lvl) (getGroupPeriod group) p)
323 phyloToLinks :: LinkLvl -> Phylo -> Phylo
324 phyloToLinks lvl p = over ( phylo_periods
330 (linkGroupsByLevel lvl p) p
332 phyloLinked_0_m1 :: Phylo
333 phyloLinked_0_m1 = phyloToLinks lvl_0_m1 phyloWithGroups0
336 lvl_0_m1 = (LinkLvl Link_0_m1 0 (-1))
339 ------------------------------------------------------------------------
340 -- | STEP 5 | -- Build level 0 as a copy of level -1
343 -- | To clone the last PhyloLevel of each PhyloPeriod and update it with a new LevelValue
344 clonePhyloLevel :: Int -> Phylo -> Phylo
345 clonePhyloLevel lvl p = alterPhyloLevels (\l -> addPhyloLevel
346 (setPhyloLevelId lvl $ head l)
349 phyloWithGroups0 :: Phylo
350 phyloWithGroups0 = updatePhyloByLevel (initLevel 0 Level_0) phyloWithGroupsm1
353 ------------------------------------------------------------------------
354 -- | STEP 4 | -- Build level -1
357 -- | To transform a list of Documents into a PhyloLevel
358 docsToPhyloLevel :: Int ->(Date, Date) -> [Document] -> Phylo -> PhyloLevel
359 docsToPhyloLevel lvl (d, d') docs p = initPhyloLevel
361 (map (\(f,s) -> initGroup [s] s f lvl d d' p)
364 $ map (words . text) docs)
366 -- | To transform a Map of Periods and Documents into a list of [PhyloPeriod]
367 docsToPhyloPeriods :: Int -> Map (Date,Date) [Document] -> Phylo -> [PhyloPeriod]
368 docsToPhyloPeriods lvl docs p = map (\(id,l) -> initPhyloPeriod id l)
371 --------------------------------------
372 levels :: Map (Date,Date) [PhyloLevel]
373 levels = mapWithKey (\k v -> [docsToPhyloLevel lvl k v p]) docs
374 --------------------------------------
376 -- | To update a Phylo for a given Levels
377 updatePhyloByLevel :: Level -> Phylo -> Phylo
378 updatePhyloByLevel lvl p
379 = case getLevelLabel lvl of
381 Level_m1 -> appendPhyloPeriods (docsToPhyloPeriods (getLevelValue lvl) lvlData p) p
383 --------------------------------------
384 lvlData :: Map (Date,Date) [Document]
385 lvlData = phyloPeriods
386 --------------------------------------
388 Level_0 -> clonePhyloLevel (getLevelValue lvl) p
390 Level_1 -> fisToPhyloLevel phyloFisFiltered p
392 _ -> panic ("[ERR][Viz.Phylo.Example.updatePhyloByLevel] Level not defined")
394 phyloWithGroupsm1 :: Phylo
395 phyloWithGroupsm1 = updatePhyloByLevel (initLevel (-1) Level_m1) phylo
398 ------------------------------------------------------------------------
399 -- | STEP 3 | -- Parse the Documents and group them by Periods
402 -- | To init a set of periods out of a given Grain and Step
403 docsToPeriods :: (Ord date, Enum date) => (doc -> date)
404 -> Grain -> Step -> [doc] -> Map (date, date) [doc]
405 docsToPeriods _ _ _ [] = panic "[ERR][Viz.Phylo.Example.docsToPeriods] Empty [Documents] can not have any periods"
406 docsToPeriods f g s es = Map.fromList $ zip hs $ map (inPeriode f es) hs
408 --------------------------------------
409 hs = steps g s $ both f (head es, last es)
410 --------------------------------------
411 inPeriode :: Ord b => (t -> b) -> [t] -> (b, b) -> [t]
412 inPeriode f' h (start,end) =
413 fst $ List.partition (\d -> f' d >= start && f' d <= end) h
414 --------------------------------------
415 steps :: (Eq date, Enum date) => Grain -> Step -> (date, date) -> [(date, date)]
416 steps s' o' (start,end) = map (\l -> (head l, last l))
417 $ chunkAlong s' o' [start .. end]
418 --------------------------------------
420 -- | To parse a list of Documents by filtering on a Vector of Ngrams
421 parseDocs :: PhyloNgrams -> [Document] -> [Document]
422 parseDocs l docs = map (\(Document d t) -> Document d (unwords
423 $ filter (\x -> Vector.elem x l)
426 -- | To group a list of Documents by fixed periods
427 groupDocsByPeriod :: Grain -> Step -> [Document] -> Phylo -> Map (Date, Date) [Document]
428 groupDocsByPeriod g s docs p = docsToPeriods date g s
429 $ parseDocs (getPhyloNgrams p) docs
431 phyloPeriods :: Map (Date, Date) [Document]
432 phyloPeriods = groupDocsByPeriod 5 3 phyloDocs phylo
435 ------------------------------------------------------------------------
436 -- | STEP 2 | -- Init an initial list of Ngrams and a Phylo
439 -- | To init a Phylomemy
440 initPhylo :: [Document] -> PhyloNgrams -> Phylo
441 initPhylo docs ngrams = Phylo (both date $ (last &&& head) docs) ngrams []
443 -- | To init a PhyloNgrams as a Vector of Ngrams
444 initNgrams :: [Ngrams] -> PhyloNgrams
445 initNgrams l = Vector.fromList $ map toLower l
448 phylo = initPhylo phyloDocs (initNgrams actants)
451 ------------------------------------------------------------------------
452 -- | STEP 1 | -- Get a list of Document
455 -- | To transform a corpus of texts into a structured list of Documents
456 corpusToDocs :: [(Date, Text)] -> [Document]
457 corpusToDocs l = map (\(d,t) -> Document d t) l
459 phyloDocs :: [Document]
460 phyloDocs = corpusToDocs corpus
463 ------------------------------------------------------------------------
464 -- | STEP 0 | -- Let's start with an example
468 actants = [ "Cleopatre" , "Ptolemee", "Ptolemee-XIII", "Ptolemee-XIV"
469 , "Marc-Antoine", "Cesar" , "Antoine" , "Octave" , "Rome"
470 , "Alexandrie" , "Auguste" , "Pompee" , "Cassius" , "Brutus"]
472 corpus :: [(Date, Text)]
473 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")]