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 -- | 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 Levels = Level_m1 | Level_0 | Level_1 | Level_2 | Level_N
79 deriving (Show, Eq, Enum, Bounded)
84 data LinkLvlLabel = Link_m1_0 | Link_0_m1 | Link_1_0 | Link_0_1 | Link_x_y
85 deriving (Show, Eq, Enum, Bounded)
87 data LinkLvl = LinkLvl
88 { linkLvlLabel :: LinkLvlLabel
94 data PhyloError = LevelDoesNotExist
99 --------------------------------------------------------------------
100 phyloExampleFinal :: Phylo
101 phyloExampleFinal = undefined
103 --------------------------------------------------------------------
104 appariement :: Map (Date, Date) (Map (Set Ngrams) Int)
105 appariement = undefined
107 ------------------------------------------------------------------------
108 -- | STEP 10 | -- Incrementaly cluster the PhyloGroups n times, link them through the Periods and build level n of the Phylo
111 ------------------------------------------------------------------------
112 -- | STEP 8 | -- Cluster the Fis
114 ------------------------------------------------------------------------
115 -- | STEP 9 | -- Link the PhyloGroups of level 1 through the Periods
117 shouldPair :: PhyloGroup -> PhyloGroup -> Bool
118 shouldPair g g' = (not . null) $ intersect (getNgrams g) (getNgrams g')
121 getKeyPair :: (Int,Int) -> Map (Int,Int) a -> (Int,Int)
122 getKeyPair (x,y) m = case findPair (x,y) m of
123 Nothing -> panic "[ERR][Viz.Phylo.Example.getKeyPair] Nothing"
126 --------------------------------------
127 findPair :: (Int,Int) -> Map (Int,Int) a -> Maybe (Int,Int)
129 | member (x,y) m = Just (x,y)
130 | member (y,x) m = Just (y,x)
131 | otherwise = Nothing
132 --------------------------------------
135 listToCombi :: forall a b. (a -> b) -> [a] -> [(b,b)]
136 listToCombi f l = [ (f x, f y) | (x:rest) <- tails l
140 fisToCooc :: Map (Date, Date) Fis -> Map (Int, Int) Double
141 fisToCooc m = map (/docs)
142 $ foldl (\mem x -> adjust (+1) (getKeyPair x mem) mem) cooc
144 $ map (\x -> listToCombi findIdx $ (Set.toList . fst) x) fis
146 --------------------------------------
147 fis :: [(Clique,Support)]
148 fis = concat $ map (\x -> Map.toList x) (elems m)
149 --------------------------------------
150 fisNgrams :: [Ngrams]
151 fisNgrams = foldl (\mem x -> union mem $ (Set.toList . fst) x) [] fis
152 --------------------------------------
154 docs = fromIntegral $ foldl (\mem x -> mem + (snd x)) 0 fis
155 --------------------------------------
156 cooc :: Map (Int, Int) (Double)
157 cooc = Map.fromList $ map (\x -> (x,0)) (listToCombi findIdx fisNgrams)
158 --------------------------------------
162 phyloWithAppariement1 :: Phylo
163 phyloWithAppariement1 = phyloLinked_0_1
165 ------------------------------------------------------------------------
166 -- | STEP 8 | -- Find the Fis out of Documents and Ngrams and build level 1 of the Phylo
168 phyloLinked_0_1 :: Phylo
169 phyloLinked_0_1 = phyloToLinks lvl_0_1 phyloLinked_1_0
172 lvl_0_1 = (LinkLvl Link_0_1 0 1)
174 phyloLinked_1_0 :: Phylo
175 phyloLinked_1_0 = phyloToLinks lvl_1_0 phyloWithGroups1
178 lvl_1_0 = (LinkLvl Link_1_0 1 0)
180 phyloWithGroups1 :: Phylo
181 phyloWithGroups1 = updatePhyloByLevel Level_1 phyloLinked_m1_0
183 cliqueToGroup :: PhyloPeriodId -> Int -> Int -> Ngrams -> (Clique,Support) -> PhyloGroup
184 cliqueToGroup period lvl idx label fis = PhyloGroup ((period, lvl), idx)
190 (singleton "support" (fromIntegral $ snd fis))
193 fisToPhyloLevel :: Map (Date, Date) Fis -> Phylo -> Phylo
194 fisToPhyloLevel m p = over (phylo_periods . traverse)
196 let periodId = _phylo_periodId period
197 fisList = zip [1..] (Map.toList (m ! periodId))
198 in over (phylo_periodLevels)
200 let groups = map (\fis -> cliqueToGroup periodId 1 (fst fis) "" (snd fis)) fisList
201 in (PhyloLevel (periodId, 1) groups) : levels
205 -- | To preserve nonempty periods from filtering, please use : filterFisBySupport False ...
206 phyloFisFiltered :: Map (Date, Date) Fis
207 phyloFisFiltered = filterFisBySupport True 1 (filterFisByNested phyloFis)
209 filterFisBySupport :: Bool -> Int -> Map (Date, Date) Fis -> Map (Date, Date) Fis
210 filterFisBySupport empty min m = case empty of
211 True -> Map.map (\fis -> filterMinorFis min fis) m
212 False -> Map.map (\fis -> filterMinorFisNonEmpty min fis) m
214 filterMinorFis :: Int -> Fis -> Fis
215 filterMinorFis min fis = Map.filter (\s -> s > min) fis
217 filterMinorFisNonEmpty :: Int -> Fis -> Fis
218 filterMinorFisNonEmpty min fis = if (Map.null fis') && (not $ Map.null fis)
219 then filterMinorFisNonEmpty (min - 1) fis
222 fis' = filterMinorFis min fis
224 doesContains :: [Ngrams] -> [Ngrams] -> Bool
227 | length l' > length l = False
228 | elem (head l') l = doesContains l (tail l')
231 doesAnyContains :: Set Ngrams -> [Set Ngrams] -> [Set Ngrams] -> Bool
232 doesAnyContains h l l' = any (\c -> doesContains (Set.toList c) (Set.toList h)) (l' ++ l)
234 filterNestedCliques :: Set Ngrams -> [Set Ngrams] -> [Set Ngrams] -> [Set Ngrams]
235 filterNestedCliques h l l'
236 | null l = if doesAnyContains h l l'
239 | doesAnyContains h l l' = filterNestedCliques (head l) (tail l) l'
240 | otherwise = filterNestedCliques (head l) (tail l) (h : l')
243 filterFisByNested :: Map (Date, Date) Fis -> Map (Date, Date) Fis
244 filterFisByNested = map (\fis -> restrictKeys fis
246 $ filterNestedCliques (head (keys fis)) (keys fis) []
249 phyloFis :: Map (Date, Date) Fis
250 phyloFis = termsToFis phyloTerms
252 termsToFis :: Map (Date, Date) [Document]
253 -> Map (Date, Date) Fis
254 termsToFis = corpusToFis (words . text)
256 -- | TODO: parameters has to be checked
257 -- | TODO FIS on monotexts
258 corpusToFis :: (Document -> [Ngrams])
259 -> Map (Date, Date) [Document]
260 -> Map (Date, Date) (Map (Set Ngrams) Int)
261 corpusToFis f = map (\d -> fisWithSizePolyMap (Segment 1 20) 1 (map f d))
264 ------------------------------------------------------------------------
265 -- | STEP 7 | -- Link level -1 to level 0
268 phyloLinked_m1_0 :: Phylo
269 phyloLinked_m1_0 = phyloToLinks lvl_m1_0 phyloLinked_0_m1
272 lvl_m1_0 = (LinkLvl Link_m1_0 (-1) 0)
275 ------------------------------------------------------------------------
276 -- | STEP 6 | -- Link level 0 to level -1
279 addPointer :: Semigroup field
280 => ASetter current target identity (field -> field)
281 -> field -> current -> target
282 addPointer field targetPointer current =
283 set field (<> targetPointer) current
285 containsIdx :: [Int] -> [Int] -> Bool
288 | last l < head l' = False
289 | head l' `elem` l = True
290 | otherwise = containsIdx l (tail l')
292 shouldLink :: LinkLvl -> [Int] -> [Int] -> Bool
293 shouldLink lvl current target = case linkLvlLabel lvl of
294 Link_0_m1 -> containsIdx target current
295 Link_m1_0 -> containsIdx target current
296 Link_0_1 -> containsIdx target current
297 Link_1_0 -> containsIdx target current
298 Link_x_y -> undefined
299 _ -> panic ("error link level to be defined")
301 linkGroupToGroups :: LinkLvl -> PhyloGroup -> [PhyloGroup] -> PhyloGroup
302 linkGroupToGroups lvl current targets
303 | linkLvlFrom lvl < linkLvlTo lvl = setLevelParents current
304 | linkLvlFrom lvl > linkLvlTo lvl = setLevelChilds current
305 | otherwise = current
307 setLevelChilds :: PhyloGroup -> PhyloGroup
308 setLevelChilds = over (phylo_groupLevelChilds) addPointers
310 setLevelParents :: PhyloGroup -> PhyloGroup
311 setLevelParents = over (phylo_groupLevelParents) addPointers
313 addPointers :: [Pointer] -> [Pointer]
314 addPointers lp = lp ++ Maybe.mapMaybe (\target -> if shouldLink lvl (_phylo_groupNgrams current)
315 (_phylo_groupNgrams target )
316 then Just ((getGroupId target),1)
320 addPointers' :: [Pointer] -> [Pointer]
321 addPointers' lp = lp ++ map (\target -> ((getGroupId target),1)) targets
323 linkGroupsByLevel :: LinkLvl -> Phylo -> [PhyloGroup] -> [PhyloGroup]
324 linkGroupsByLevel lvl p groups = map (\group ->
325 if getGroupLvl group == linkLvlFrom lvl
326 then linkGroupToGroups lvl group (getGroupsByLevelAndPeriod (linkLvlTo lvl) (getGroupPeriod group) p)
329 phyloToLinks :: LinkLvl -> Phylo -> Phylo
330 phyloToLinks lvl p = over ( phylo_periods
336 (linkGroupsByLevel lvl p) p
338 phyloLinked_0_m1 :: Phylo
339 phyloLinked_0_m1 = phyloToLinks lvl_0_m1 phyloWithGroups0
342 lvl_0_m1 = (LinkLvl Link_0_m1 0 (-1))
345 ------------------------------------------------------------------------
346 -- | STEP 5 | -- Build level 0 (for the moment it's just a durty copy of level -1)
349 setGroupIdLvl :: Int -> PhyloGroup -> PhyloGroup
350 setGroupIdLvl lvl (PhyloGroup ((period, lvl'), idx) gLabel gNgrams gQ gPP gPC gLP gLC)
351 = PhyloGroup ((period, lvl), idx) gLabel gNgrams gQ gPP gPC gLP gLC
353 setPhyloLevel :: Int -> PhyloLevel -> PhyloLevel
354 setPhyloLevel lvl (PhyloLevel (periodId, lvl') lvlGroups)
355 = PhyloLevel (periodId, lvl) lvlGroups'
357 lvlGroups' = map (\g -> setGroupIdLvl lvl g) lvlGroups
359 copyPhyloLevel :: Int -> [PhyloLevel] -> [PhyloLevel]
360 copyPhyloLevel lvl l = (setPhyloLevel lvl (head l)) : l
362 alterLvl :: Int -> [PhyloPeriod] -> [PhyloPeriod]
363 alterLvl lvl l = map (\p -> PhyloPeriod (_phylo_periodId p) (copyPhyloLevel lvl $ _phylo_periodLevels p)) l
365 phyloWithGroups0 :: Phylo
366 phyloWithGroups0 = updatePhyloByLevel Level_0 phyloWithGroupsm1
369 ------------------------------------------------------------------------
370 -- | STEP 4 | -- Build level -1
373 findIdx :: Ngrams -> Int
374 findIdx n = case (elemIndex n (_phylo_ngrams phylo)) of
375 Nothing -> panic "PhyloError"
378 ngramsToGroup :: [Ngrams] -> Text -> Int -> Int -> Int -> Int -> PhyloGroup
379 ngramsToGroup terms label idx lvl from to = PhyloGroup (((from, to), lvl), idx)
381 (sort (map (\x -> findIdx x) terms))
385 docsToLevel :: (Date, Date) -> Corpus -> PhyloLevel
386 docsToLevel k v = PhyloLevel (k,(-1)) (map (\x ->
387 ngramsToGroup [snd x] (snd x) (fst x) (-1) (fst k) (snd k)
388 ) $ zip [1..] $ (nub . concat) $ map (words . text) v)
390 corpusToPhyloPeriod :: Map (Date,Date) Corpus -> [PhyloPeriod]
391 corpusToPhyloPeriod corpus = map (\x -> PhyloPeriod (fst x) [(snd x)]) $ zip (keys mapLvl) (elems mapLvl)
393 mapLvl :: Map (Date,Date) PhyloLevel
394 mapLvl = mapWithKey docsToLevel corpus
396 updatePhyloByLevel :: Levels -> Phylo -> Phylo
397 updatePhyloByLevel lvl (Phylo pDuration pNgrams pPeriods)
400 Level_m1 -> Phylo pDuration pNgrams pPeriods'
401 where pPeriods' = (corpusToPhyloPeriod phyloTerms) ++ pPeriods
403 Level_0 -> Phylo pDuration pNgrams pPeriods'
404 where pPeriods' = alterLvl 0 pPeriods
406 Level_1 -> fisToPhyloLevel phyloFisFiltered (Phylo pDuration pNgrams pPeriods)
408 _ -> panic ("error level to be defined")
410 phyloWithGroupsm1 :: Phylo
411 phyloWithGroupsm1 = updatePhyloByLevel Level_m1 phylo
414 ------------------------------------------------------------------------
415 -- | STEP 3 | -- Split the Corpus into Periods and reduce each Document as a list of Ngrams
418 phyloTerms :: Map (Date, Date) [Document]
419 phyloTerms = toPeriodes date 5 3 $ cleanCorpus cleanedActants phyloCorpus
421 toPeriodes :: (Ord date, Enum date) => (doc -> date)
422 -> Grain -> Step -> [doc] -> Map (date, date) [doc]
423 toPeriodes _ _ _ [] = panic "Empty corpus can not have any periods"
424 toPeriodes f g s es = Map.fromList $ zip hs $ map (inPeriode f es) hs
426 hs = steps g s $ both f (head es, last es)
427 --------------------------------------------------------------------
428 -- | Define overlapping periods of time by following regular steps
429 inPeriode :: Ord b => (t -> b) -> [t] -> (b, b) -> [t]
430 inPeriode f' h (start,end) =
431 fst $ List.partition (\d -> f' d >= start && f' d <= end) h
432 --------------------------------------------------------------------
433 -- | Find steps of linear and homogenous time of integer
434 steps :: (Eq date, Enum date) => Grain -> Step -> (date, date) -> [(date, date)]
435 steps s' o' (start,end) = map (\l -> (head l, last l))
436 $ chunkAlong s' o' [start .. end]
438 cleanCorpus :: MapList -> Corpus -> Corpus
439 cleanCorpus ml = map (\(Document d t) -> Document d (unwords $ filter (\x -> elem x ml) $ monoTexts t))
442 ------------------------------------------------------------------------
443 -- | STEP 2 | -- Find some Ngrams (ie: phyloGroup of level -1) out of the Corpus & init the phylo
446 phylo = Phylo (both date $ (last &&& head) phyloCorpus) phyloNgrams []
448 phyloNgrams :: PhyloNgrams
449 phyloNgrams = Vector.fromList cleanedActants
451 cleanedActants :: [Ngrams]
452 cleanedActants = map toLower actants
455 actants = [ "Cleopatre" , "Ptolemee", "Ptolemee-XIII", "Ptolemee-XIV"
456 , "Marc-Antoine", "Cesar" , "Antoine" , "Octave" , "Rome"
457 , "Alexandrie" , "Auguste" , "Pompee" , "Cassius" , "Brutus"]
460 ------------------------------------------------------------------------
461 -- | STEP 1 | -- Get a corpus of Documents
464 phyloCorpus :: Corpus
465 phyloCorpus = map (\(d,t) -> Document d t) exampleDocuments
467 exampleDocuments :: [(Date, Text)]
468 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")]