]> Git — Sourcephile - gargantext.git/blob - src/Gargantext/Viz/Phylo/Example.hs
More refactoring
[gargantext.git] / src / Gargantext / Viz / Phylo / Example.hs
1 {-|
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
8 Portability : POSIX
9
10 -- | Cesar et Cleôpatre
11 -- Exemple de phylomemie
12 -- French without accents
13
14
15 TODO:
16 - split the functions : RAW -> Document -> Ngrams
17
18 -- reverse history: antechronologique
19 -- metrics support
20
21
22 -}
23
24 {-# LANGUAGE NoImplicitPrelude #-}
25 {-# LANGUAGE FlexibleContexts #-}
26 {-# LANGUAGE OverloadedStrings #-}
27
28 module Gargantext.Viz.Phylo.Example where
29
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)
36
37 import Data.Map (Map, elems, member, adjust, singleton, (!), keys, restrictKeys, mapWithKey)
38 import qualified Data.Map as Map
39
40 import Data.Vector (Vector, fromList, elemIndex)
41 import qualified Data.Vector as Vector
42 import qualified Data.Maybe as Maybe
43
44 import Data.Tuple (fst, snd)
45 import qualified Data.Tuple as Tuple
46
47 import Data.Bool (Bool, not)
48 import qualified Data.Bool as Bool
49
50 import Data.Set (Set)
51 import qualified Data.Set as Set
52 import qualified Data.Matrix as DM'
53
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
59
60 ------------------------------------------------------------------------
61 -- | Types | --
62
63
64 -- | Document : a piece of Text linked to a Date
65 data Document = Document
66 { date :: Date
67 , text :: Text
68 } deriving (Show)
69 -- | Corpus : a list of Documents
70 type Corpus = [Document]
71
72
73
74 type PeriodeSize = Int
75 -- data Periodes b a = Map (b,b) a
76 type Occurrences = Int
77
78 data LinkLvlLabel = Link_m1_0 | Link_0_m1 | Link_1_0 | Link_0_1 | Link_x_y
79 deriving (Show, Eq, Enum, Bounded)
80
81 data LinkLvl = LinkLvl
82 { linkLvlLabel :: LinkLvlLabel
83 , linkLvlFrom :: Int
84 , linkLvlTo :: Int
85 } deriving (Show)
86
87
88 data PhyloError = LevelDoesNotExist
89 | LevelUnassigned
90 deriving (Show)
91
92
93 --------------------------------------------------------------------
94 phyloExampleFinal :: Phylo
95 phyloExampleFinal = undefined
96
97 --------------------------------------------------------------------
98 appariement :: Map (Date, Date) (Map (Set Ngrams) Int)
99 appariement = undefined
100
101 ------------------------------------------------------------------------
102 -- | STEP 10 | -- Incrementaly cluster the PhyloGroups n times, link them through the Periods and build level n of the Phylo
103
104
105 ------------------------------------------------------------------------
106 -- | STEP 8 | -- Cluster the Fis
107
108 ------------------------------------------------------------------------
109 -- | STEP 9 | -- Link the PhyloGroups of level 1 through the Periods
110
111 shouldPair :: PhyloGroup -> PhyloGroup -> Bool
112 shouldPair g g' = (not . null) $ intersect (getGroupNgrams g) (getGroupNgrams g')
113
114
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"
118 Just i -> i
119 where
120 --------------------------------------
121 findPair :: (Int,Int) -> Map (Int,Int) a -> Maybe (Int,Int)
122 findPair (x,y) m
123 | member (x,y) m = Just (x,y)
124 | member (y,x) m = Just (y,x)
125 | otherwise = Nothing
126 --------------------------------------
127
128 -- |
129 listToCombi :: forall a b. (a -> b) -> [a] -> [(b,b)]
130 listToCombi f l = [ (f x, f y) | (x:rest) <- tails l
131 , y <- rest
132 ]
133
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
137 $ concat
138 $ map (\x -> listToCombi (\x -> ngramsToIdx x p) $ (Set.toList . fst) x) fis
139 where
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 --------------------------------------
147 docs :: Double
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 --------------------------------------
153
154
155
156 phyloWithAppariement1 :: Phylo
157 phyloWithAppariement1 = phyloLinked_0_1
158
159 ------------------------------------------------------------------------
160 -- | STEP 8 | -- Find the Fis out of Documents and Ngrams and build level 1 of the Phylo
161
162 phyloLinked_0_1 :: Phylo
163 phyloLinked_0_1 = phyloToLinks lvl_0_1 phyloLinked_1_0
164
165 lvl_0_1 :: LinkLvl
166 lvl_0_1 = (LinkLvl Link_0_1 0 1)
167
168 phyloLinked_1_0 :: Phylo
169 phyloLinked_1_0 = phyloToLinks lvl_1_0 phyloWithGroups1
170
171 lvl_1_0 :: LinkLvl
172 lvl_1_0 = (LinkLvl Link_1_0 1 0)
173
174 phyloWithGroups1 :: Phylo
175 phyloWithGroups1 = updatePhyloByLevel (initLevel 1 Level_1) phyloLinked_m1_0
176
177 cliqueToGroup :: PhyloPeriodId -> Int -> Int -> Ngrams -> (Clique,Support) -> Phylo -> PhyloGroup
178 cliqueToGroup period lvl idx label fis p = PhyloGroup ((period, lvl), idx)
179 label
180 (sort $ map (\x -> ngramsToIdx x p)
181 $ Set.toList
182 $ fst fis
183 )
184 (singleton "support" (fromIntegral $ snd fis))
185 [] [] [] []
186
187 fisToPhyloLevel :: Map (Date, Date) Fis -> Phylo -> Phylo
188 fisToPhyloLevel m p = over (phylo_periods . traverse)
189 (\period ->
190 let periodId = _phylo_periodId period
191 fisList = zip [1..] (Map.toList (m ! periodId))
192 in over (phylo_periodLevels)
193 (\levels ->
194 let groups = map (\fis -> cliqueToGroup periodId 1 (fst fis) "" (snd fis) p) fisList
195 in (PhyloLevel (periodId, 1) groups) : levels
196 ) period
197 ) p
198
199 -- | To preserve nonempty periods from filtering, please use : filterFisBySupport False ...
200 phyloFisFiltered :: Map (Date, Date) Fis
201 phyloFisFiltered = filterFisBySupport True 1 (filterFisByNested phyloFis)
202
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
207
208 filterMinorFis :: Int -> Fis -> Fis
209 filterMinorFis min fis = Map.filter (\s -> s > min) fis
210
211 filterMinorFisNonEmpty :: Int -> Fis -> Fis
212 filterMinorFisNonEmpty min fis = if (Map.null fis') && (not $ Map.null fis)
213 then filterMinorFisNonEmpty (min - 1) fis
214 else fis'
215 where
216 fis' = filterMinorFis min fis
217
218 doesContains :: [Ngrams] -> [Ngrams] -> Bool
219 doesContains l l'
220 | null l' = True
221 | length l' > length l = False
222 | elem (head l') l = doesContains l (tail l')
223 | otherwise = False
224
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)
227
228 filterNestedCliques :: Set Ngrams -> [Set Ngrams] -> [Set Ngrams] -> [Set Ngrams]
229 filterNestedCliques h l l'
230 | null l = if doesAnyContains h l l'
231 then l'
232 else h : l'
233 | doesAnyContains h l l' = filterNestedCliques (head l) (tail l) l'
234 | otherwise = filterNestedCliques (head l) (tail l) (h : l')
235
236
237 filterFisByNested :: Map (Date, Date) Fis -> Map (Date, Date) Fis
238 filterFisByNested = map (\fis -> restrictKeys fis
239 $ Set.fromList
240 $ filterNestedCliques (head (keys fis)) (keys fis) []
241 )
242
243 phyloFis :: Map (Date, Date) Fis
244 phyloFis = termsToFis phyloPeriods
245
246 termsToFis :: Map (Date, Date) [Document]
247 -> Map (Date, Date) Fis
248 termsToFis = corpusToFis (words . text)
249
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))
256
257
258 ------------------------------------------------------------------------
259 -- | STEP 7 | -- Link level -1 to level 0
260
261
262 phyloLinked_m1_0 :: Phylo
263 phyloLinked_m1_0 = phyloToLinks lvl_m1_0 phyloLinked_0_m1
264
265 lvl_m1_0 :: LinkLvl
266 lvl_m1_0 = (LinkLvl Link_m1_0 (-1) 0)
267
268
269 ------------------------------------------------------------------------
270 -- | STEP 6 | -- Link level 0 to level -1
271
272
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
278
279 containsIdx :: [Int] -> [Int] -> Bool
280 containsIdx l l'
281 | null l' = False
282 | last l < head l' = False
283 | head l' `elem` l = True
284 | otherwise = containsIdx l (tail l')
285
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")
294
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
300 where
301 setLevelChilds :: PhyloGroup -> PhyloGroup
302 setLevelChilds = over (phylo_groupLevelChilds) addPointers
303
304 setLevelParents :: PhyloGroup -> PhyloGroup
305 setLevelParents = over (phylo_groupLevelParents) addPointers
306
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)
311 else Nothing
312 ) targets
313
314 addPointers' :: [Pointer] -> [Pointer]
315 addPointers' lp = lp ++ map (\target -> ((getGroupId target),1)) targets
316
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)
321 else group ) groups
322
323 phyloToLinks :: LinkLvl -> Phylo -> Phylo
324 phyloToLinks lvl p = over ( phylo_periods
325 . traverse
326 . phylo_periodLevels
327 . traverse
328 . phylo_levelGroups
329 )
330 (linkGroupsByLevel lvl p) p
331
332 phyloLinked_0_m1 :: Phylo
333 phyloLinked_0_m1 = phyloToLinks lvl_0_m1 phyloWithGroups0
334
335 lvl_0_m1 :: LinkLvl
336 lvl_0_m1 = (LinkLvl Link_0_m1 0 (-1))
337
338
339 ------------------------------------------------------------------------
340 -- | STEP 5 | -- Build level 0 as a copy of level -1
341
342
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)
347 l) p
348
349 phyloWithGroups0 :: Phylo
350 phyloWithGroups0 = updatePhyloByLevel (initLevel 0 Level_0) phyloWithGroupsm1
351
352
353 ------------------------------------------------------------------------
354 -- | STEP 4 | -- Build level -1
355
356
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
360 ((d, d'), lvl)
361 (map (\(f,s) -> initGroup [s] s f lvl d d' p)
362 $ zip [1..]
363 $ (nub . concat)
364 $ map (words . text) docs)
365
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)
369 $ Map.toList levels
370 where
371 --------------------------------------
372 levels :: Map (Date,Date) [PhyloLevel]
373 levels = mapWithKey (\k v -> [docsToPhyloLevel lvl k v p]) docs
374 --------------------------------------
375
376 -- | To update a Phylo for a given Levels
377 updatePhyloByLevel :: Level -> Phylo -> Phylo
378 updatePhyloByLevel lvl p
379 = case getLevelLabel lvl of
380
381 Level_m1 -> appendPhyloPeriods (docsToPhyloPeriods (getLevelValue lvl) lvlData p) p
382 where
383 --------------------------------------
384 lvlData :: Map (Date,Date) [Document]
385 lvlData = phyloPeriods
386 --------------------------------------
387
388 Level_0 -> clonePhyloLevel (getLevelValue lvl) p
389
390 Level_1 -> fisToPhyloLevel phyloFisFiltered p
391
392 _ -> panic ("[ERR][Viz.Phylo.Example.updatePhyloByLevel] Level not defined")
393
394 phyloWithGroupsm1 :: Phylo
395 phyloWithGroupsm1 = updatePhyloByLevel (initLevel (-1) Level_m1) phylo
396
397
398 ------------------------------------------------------------------------
399 -- | STEP 3 | -- Parse the Documents and group them by Periods
400
401
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
407 where
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 --------------------------------------
419
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)
424 $ monoTexts t)) docs
425
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
430
431 phyloPeriods :: Map (Date, Date) [Document]
432 phyloPeriods = groupDocsByPeriod 5 3 phyloDocs phylo
433
434
435 ------------------------------------------------------------------------
436 -- | STEP 2 | -- Init an initial list of Ngrams and a Phylo
437
438
439 -- | To init a Phylomemy
440 initPhylo :: [Document] -> PhyloNgrams -> Phylo
441 initPhylo docs ngrams = Phylo (both date $ (last &&& head) docs) ngrams []
442
443 -- | To init a PhyloNgrams as a Vector of Ngrams
444 initNgrams :: [Ngrams] -> PhyloNgrams
445 initNgrams l = Vector.fromList $ map toLower l
446
447 phylo :: Phylo
448 phylo = initPhylo phyloDocs (initNgrams actants)
449
450
451 ------------------------------------------------------------------------
452 -- | STEP 1 | -- Get a list of Document
453
454
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
458
459 phyloDocs :: [Document]
460 phyloDocs = corpusToDocs corpus
461
462
463 ------------------------------------------------------------------------
464 -- | STEP 0 | -- Let's start with an example
465
466
467 actants :: [Ngrams]
468 actants = [ "Cleopatre" , "Ptolemee", "Ptolemee-XIII", "Ptolemee-XIV"
469 , "Marc-Antoine", "Cesar" , "Antoine" , "Octave" , "Rome"
470 , "Alexandrie" , "Auguste" , "Pompee" , "Cassius" , "Brutus"]
471
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")]
474