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, 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]
73 type MapList = [Ngrams]
74 type PeriodeSize = Int
75 -- data Periodes b a = Map (b,b) a
76 type Occurrences = Int
78 data Level = Level_m1 | Level_0 | Level_1 | Level_2 | Level_N
79 deriving (Show, Eq, Enum, Bounded)
81 data LinkLvlLabel = Link_m1_0 | Link_0_m1 | Link_1_0 | Link_0_1 | Link_x_y
82 deriving (Show, Eq, Enum, Bounded)
84 data LinkLvl = LinkLvl
85 { linkLvlLabel :: LinkLvlLabel
91 data PhyloError = LevelDoesNotExist
96 --------------------------------------------------------------------
97 phyloExampleFinal :: Phylo
98 phyloExampleFinal = undefined
100 --------------------------------------------------------------------
101 appariement :: Map (Date, Date) (Map (Set Ngrams) Int)
102 appariement = undefined
104 ------------------------------------------------------------------------
105 -- | STEP 10 | -- Incrementaly cluster the PhyloGroups n times, link them through the Periods and build level n of the Phylo
108 ------------------------------------------------------------------------
109 -- | STEP 8 | -- Cluster the Fis
111 ------------------------------------------------------------------------
112 -- | STEP 9 | -- Link the PhyloGroups of level 1 through the Periods
114 shouldPair :: PhyloGroup -> PhyloGroup -> Bool
115 shouldPair g g' = (not . null) $ intersect (getNgrams g) (getNgrams g')
118 getKeyPair :: (Int,Int) -> Map (Int,Int) a -> (Int,Int)
119 getKeyPair (x,y) m = case findPair (x,y) m of
120 Nothing -> panic "[ERR][Viz.Phylo.Example.getKeyPair] Nothing"
123 --------------------------------------
124 findPair :: (Int,Int) -> Map (Int,Int) a -> Maybe (Int,Int)
126 | member (x,y) m = Just (x,y)
127 | member (y,x) m = Just (y,x)
128 | otherwise = Nothing
129 --------------------------------------
132 listToCombi :: forall a b. (a -> b) -> [a] -> [(b,b)]
133 listToCombi f l = [ (f x, f y) | (x:rest) <- tails l
137 fisToCooc :: Map (Date, Date) Fis -> Phylo -> Map (Int, Int) Double
138 fisToCooc m p = map (/docs)
139 $ foldl (\mem x -> adjust (+1) (getKeyPair x mem) mem) cooc
141 $ map (\x -> listToCombi (\x -> ngramsToIdx x p) $ (Set.toList . fst) x) fis
143 --------------------------------------
144 fis :: [(Clique,Support)]
145 fis = concat $ map (\x -> Map.toList x) (elems m)
146 --------------------------------------
147 fisNgrams :: [Ngrams]
148 fisNgrams = foldl (\mem x -> union mem $ (Set.toList . fst) x) [] fis
149 --------------------------------------
151 docs = fromIntegral $ foldl (\mem x -> mem + (snd x)) 0 fis
152 --------------------------------------
153 cooc :: Map (Int, Int) (Double)
154 cooc = Map.fromList $ map (\x -> (x,0)) (listToCombi (\x -> ngramsToIdx x p) fisNgrams)
155 --------------------------------------
159 phyloWithAppariement1 :: Phylo
160 phyloWithAppariement1 = phyloLinked_0_1
162 ------------------------------------------------------------------------
163 -- | STEP 8 | -- Find the Fis out of Documents and Ngrams and build level 1 of the Phylo
165 phyloLinked_0_1 :: Phylo
166 phyloLinked_0_1 = phyloToLinks lvl_0_1 phyloLinked_1_0
169 lvl_0_1 = (LinkLvl Link_0_1 0 1)
171 phyloLinked_1_0 :: Phylo
172 phyloLinked_1_0 = phyloToLinks lvl_1_0 phyloWithGroups1
175 lvl_1_0 = (LinkLvl Link_1_0 1 0)
177 phyloWithGroups1 :: Phylo
178 phyloWithGroups1 = updatePhyloByLevel Level_1 phyloLinked_m1_0
180 cliqueToGroup :: PhyloPeriodId -> Int -> Int -> Ngrams -> (Clique,Support) -> Phylo -> PhyloGroup
181 cliqueToGroup period lvl idx label fis p = PhyloGroup ((period, lvl), idx)
183 (sort $ map (\x -> ngramsToIdx x p)
187 (singleton "support" (fromIntegral $ snd fis))
190 fisToPhyloLevel :: Map (Date, Date) Fis -> Phylo -> Phylo
191 fisToPhyloLevel m p = over (phylo_periods . traverse)
193 let periodId = _phylo_periodId period
194 fisList = zip [1..] (Map.toList (m ! periodId))
195 in over (phylo_periodLevels)
197 let groups = map (\fis -> cliqueToGroup periodId 1 (fst fis) "" (snd fis) p) fisList
198 in (PhyloLevel (periodId, 1) groups) : levels
202 -- | To preserve nonempty periods from filtering, please use : filterFisBySupport False ...
203 phyloFisFiltered :: Map (Date, Date) Fis
204 phyloFisFiltered = filterFisBySupport True 1 (filterFisByNested phyloFis)
206 filterFisBySupport :: Bool -> Int -> Map (Date, Date) Fis -> Map (Date, Date) Fis
207 filterFisBySupport empty min m = case empty of
208 True -> Map.map (\fis -> filterMinorFis min fis) m
209 False -> Map.map (\fis -> filterMinorFisNonEmpty min fis) m
211 filterMinorFis :: Int -> Fis -> Fis
212 filterMinorFis min fis = Map.filter (\s -> s > min) fis
214 filterMinorFisNonEmpty :: Int -> Fis -> Fis
215 filterMinorFisNonEmpty min fis = if (Map.null fis') && (not $ Map.null fis)
216 then filterMinorFisNonEmpty (min - 1) fis
219 fis' = filterMinorFis min fis
221 doesContains :: [Ngrams] -> [Ngrams] -> Bool
224 | length l' > length l = False
225 | elem (head l') l = doesContains l (tail l')
228 doesAnyContains :: Set Ngrams -> [Set Ngrams] -> [Set Ngrams] -> Bool
229 doesAnyContains h l l' = any (\c -> doesContains (Set.toList c) (Set.toList h)) (l' ++ l)
231 filterNestedCliques :: Set Ngrams -> [Set Ngrams] -> [Set Ngrams] -> [Set Ngrams]
232 filterNestedCliques h l l'
233 | null l = if doesAnyContains h l l'
236 | doesAnyContains h l l' = filterNestedCliques (head l) (tail l) l'
237 | otherwise = filterNestedCliques (head l) (tail l) (h : l')
240 filterFisByNested :: Map (Date, Date) Fis -> Map (Date, Date) Fis
241 filterFisByNested = map (\fis -> restrictKeys fis
243 $ filterNestedCliques (head (keys fis)) (keys fis) []
246 phyloFis :: Map (Date, Date) Fis
247 phyloFis = termsToFis phyloTerms
249 termsToFis :: Map (Date, Date) [Document]
250 -> Map (Date, Date) Fis
251 termsToFis = corpusToFis (words . text)
253 -- | TODO: parameters has to be checked
254 -- | TODO FIS on monotexts
255 corpusToFis :: (Document -> [Ngrams])
256 -> Map (Date, Date) [Document]
257 -> Map (Date, Date) (Map (Set Ngrams) Int)
258 corpusToFis f = map (\d -> fisWithSizePolyMap (Segment 1 20) 1 (map f d))
261 ------------------------------------------------------------------------
262 -- | STEP 7 | -- Link level -1 to level 0
265 phyloLinked_m1_0 :: Phylo
266 phyloLinked_m1_0 = phyloToLinks lvl_m1_0 phyloLinked_0_m1
269 lvl_m1_0 = (LinkLvl Link_m1_0 (-1) 0)
272 ------------------------------------------------------------------------
273 -- | STEP 6 | -- Link level 0 to level -1
276 addPointer :: Semigroup field
277 => ASetter current target identity (field -> field)
278 -> field -> current -> target
279 addPointer field targetPointer current =
280 set field (<> targetPointer) current
282 containsIdx :: [Int] -> [Int] -> Bool
285 | last l < head l' = False
286 | head l' `elem` l = True
287 | otherwise = containsIdx l (tail l')
289 shouldLink :: LinkLvl -> [Int] -> [Int] -> Bool
290 shouldLink lvl current target = case linkLvlLabel lvl of
291 Link_0_m1 -> containsIdx target current
292 Link_m1_0 -> containsIdx target current
293 Link_0_1 -> containsIdx target current
294 Link_1_0 -> containsIdx target current
295 Link_x_y -> undefined
296 _ -> panic ("error link level to be defined")
298 linkGroupToGroups :: LinkLvl -> PhyloGroup -> [PhyloGroup] -> PhyloGroup
299 linkGroupToGroups lvl current targets
300 | linkLvlFrom lvl < linkLvlTo lvl = setLevelParents current
301 | linkLvlFrom lvl > linkLvlTo lvl = setLevelChilds current
302 | otherwise = current
304 setLevelChilds :: PhyloGroup -> PhyloGroup
305 setLevelChilds = over (phylo_groupLevelChilds) addPointers
307 setLevelParents :: PhyloGroup -> PhyloGroup
308 setLevelParents = over (phylo_groupLevelParents) addPointers
310 addPointers :: [Pointer] -> [Pointer]
311 addPointers lp = lp ++ Maybe.mapMaybe (\target -> if shouldLink lvl (_phylo_groupNgrams current)
312 (_phylo_groupNgrams target )
313 then Just ((getGroupId target),1)
317 addPointers' :: [Pointer] -> [Pointer]
318 addPointers' lp = lp ++ map (\target -> ((getGroupId target),1)) targets
320 linkGroupsByLevel :: LinkLvl -> Phylo -> [PhyloGroup] -> [PhyloGroup]
321 linkGroupsByLevel lvl p groups = map (\group ->
322 if getGroupLevel group == linkLvlFrom lvl
323 then linkGroupToGroups lvl group (getGroupsWithFilters (linkLvlTo lvl) (getGroupPeriod group) p)
326 phyloToLinks :: LinkLvl -> Phylo -> Phylo
327 phyloToLinks lvl p = over ( phylo_periods
333 (linkGroupsByLevel lvl p) p
335 phyloLinked_0_m1 :: Phylo
336 phyloLinked_0_m1 = phyloToLinks lvl_0_m1 phyloWithGroups0
339 lvl_0_m1 = (LinkLvl Link_0_m1 0 (-1))
342 ------------------------------------------------------------------------
343 -- | STEP 5 | -- Build level 0 (for the moment it's just a durty copy of level -1)
346 setGroupIdLvl :: Int -> PhyloGroup -> PhyloGroup
347 setGroupIdLvl lvl (PhyloGroup ((period, lvl'), idx) gLabel gNgrams gQ gPP gPC gLP gLC)
348 = PhyloGroup ((period, lvl), idx) gLabel gNgrams gQ gPP gPC gLP gLC
350 setPhyloLevel :: Int -> PhyloLevel -> PhyloLevel
351 setPhyloLevel lvl (PhyloLevel (periodId, lvl') lvlGroups)
352 = PhyloLevel (periodId, lvl) lvlGroups'
354 lvlGroups' = map (\g -> setGroupIdLvl lvl g) lvlGroups
356 copyPhyloLevel :: Int -> [PhyloLevel] -> [PhyloLevel]
357 copyPhyloLevel lvl l = (setPhyloLevel lvl (head l)) : l
359 alterLvl :: Int -> [PhyloPeriod] -> [PhyloPeriod]
360 alterLvl lvl l = map (\p -> PhyloPeriod (_phylo_periodId p) (copyPhyloLevel lvl $ _phylo_periodLevels p)) l
362 phyloWithGroups0 :: Phylo
363 phyloWithGroups0 = updatePhyloByLevel Level_0 phyloWithGroupsm1
366 ------------------------------------------------------------------------
367 -- | STEP 4 | -- Build level -1
370 docsToLevel :: (Date, Date) -> Corpus -> Phylo -> PhyloLevel
371 docsToLevel k v p = PhyloLevel
373 (map (\x -> initGroup [snd x] (snd x) (fst x) (-1) (fst k) (snd k) p)
376 $ map (words . text) v)
378 corpusToPhyloPeriod :: Map (Date,Date) Corpus -> Phylo -> [PhyloPeriod]
379 corpusToPhyloPeriod corpus p = map (\x -> PhyloPeriod (fst x) [(snd x)]) $ zip (keys mapLvl) (elems mapLvl)
381 mapLvl :: Map (Date,Date) PhyloLevel
382 mapLvl = mapWithKey (\k v -> docsToLevel k v p) corpus
385 -- | To update a Phylo by adding a new PhyloLevel to each PhyloPeriod
386 updatePhyloByLevel :: Level -> Phylo -> Phylo
387 updatePhyloByLevel lvl (Phylo pDuration pNgrams pPeriods)
390 Level_m1 -> Phylo pDuration pNgrams pPeriods'
391 where pPeriods' = (corpusToPhyloPeriod phyloTerms (Phylo pDuration pNgrams pPeriods)) ++ pPeriods
393 Level_0 -> Phylo pDuration pNgrams pPeriods'
394 where pPeriods' = alterLvl 0 pPeriods
396 Level_1 -> fisToPhyloLevel phyloFisFiltered (Phylo pDuration pNgrams pPeriods)
398 Level_N -> alterPhyloPeriods (\x -> x) (Phylo pDuration pNgrams pPeriods)
400 _ -> panic ("error level to be defined")
404 -- | To update a Phylo by adding a new PhyloLevel to each PhyloPeriod
405 updatePhyloByLevel' :: Level -> Phylo -> Phylo
406 updatePhyloByLevel' lvl p
408 Level_m1 -> appendPhyloPeriods (corpusToPhyloPeriod phyloTerms p) p
409 _ -> panic ("error level to be defined")
411 phyloWithGroupsm1 :: Phylo
412 phyloWithGroupsm1 = updatePhyloByLevel Level_m1 phylo
415 ------------------------------------------------------------------------
416 -- | STEP 3 | -- Split the Corpus into Periods and reduce each Document as a list of Ngrams
419 phyloTerms :: Map (Date, Date) [Document]
420 phyloTerms = toPeriodes date 5 3 $ cleanCorpus cleanedActants phyloCorpus
422 toPeriodes :: (Ord date, Enum date) => (doc -> date)
423 -> Grain -> Step -> [doc] -> Map (date, date) [doc]
424 toPeriodes _ _ _ [] = panic "Empty corpus can not have any periods"
425 toPeriodes f g s es = Map.fromList $ zip hs $ map (inPeriode f es) hs
427 hs = steps g s $ both f (head es, last es)
428 --------------------------------------------------------------------
429 -- | Define overlapping periods of time by following regular steps
430 inPeriode :: Ord b => (t -> b) -> [t] -> (b, b) -> [t]
431 inPeriode f' h (start,end) =
432 fst $ List.partition (\d -> f' d >= start && f' d <= end) h
433 --------------------------------------------------------------------
434 -- | Find steps of linear and homogenous time of integer
435 steps :: (Eq date, Enum date) => Grain -> Step -> (date, date) -> [(date, date)]
436 steps s' o' (start,end) = map (\l -> (head l, last l))
437 $ chunkAlong s' o' [start .. end]
439 cleanCorpus :: MapList -> Corpus -> Corpus
440 cleanCorpus ml = map (\(Document d t) -> Document d (unwords $ filter (\x -> elem x ml) $ monoTexts t))
443 ------------------------------------------------------------------------
444 -- | STEP 2 | -- Find some Ngrams (ie: phyloGroup of level -1) out of the Corpus & init the phylo
447 phylo = Phylo (both date $ (last &&& head) phyloCorpus) phyloNgrams []
449 phyloNgrams :: PhyloNgrams
450 phyloNgrams = Vector.fromList cleanedActants
452 cleanedActants :: [Ngrams]
453 cleanedActants = map toLower actants
456 actants = [ "Cleopatre" , "Ptolemee", "Ptolemee-XIII", "Ptolemee-XIV"
457 , "Marc-Antoine", "Cesar" , "Antoine" , "Octave" , "Rome"
458 , "Alexandrie" , "Auguste" , "Pompee" , "Cassius" , "Brutus"]
461 ------------------------------------------------------------------------
462 -- | STEP 1 | -- Get a corpus of Documents
465 phyloCorpus :: Corpus
466 phyloCorpus = map (\(d,t) -> Document d t) exampleDocuments
468 exampleDocuments :: [(Date, Text)]
469 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")]