Switch to side-by-side view

--- a
+++ b/notebooks/premier-pipeline.md
@@ -0,0 +1,260 @@
+---
+jupyter:
+  jupytext:
+    formats: md,ipynb
+    text_representation:
+      extension: .md
+      format_name: markdown
+      format_version: "1.3"
+      jupytext_version: 1.13.5
+  kernelspec:
+    display_name: "Python 3.9.5 64-bit ('.env': venv)"
+    name: python3
+---
+
+<!-- #region slideshow={"slide_type": "slide"} -->
+
+# EDS-NLP – Présentation
+
+<!-- #endregion -->
+
+## Texte d'exemple
+
+```python
+with open('example.txt', 'r') as f:
+    text = f.read()
+```
+
+```python
+print(text)
+```
+
+## Définition d'un pipeline spaCy
+
+```python slideshow={"slide_type": "slide"}
+# Importation de spaCy
+import spacy
+```
+
+```python
+# Chargement des composants EDS-NLP
+
+```
+
+```python
+# Création de l'instance spaCy
+nlp = spacy.blank('fr')
+
+# Normalisation des accents, de la casse et autres caractères spéciaux
+nlp.add_pipe('normalizer')
+# Détection des fins de phrases
+nlp.add_pipe('sentences')
+
+# Extraction d'entités nommées
+nlp.add_pipe(
+    'matcher',
+    config=dict(
+        terms=dict(respiratoire=[
+            'difficultes respiratoires',
+            'asthmatique',
+            'toux',
+        ]),
+        regex=dict(
+            covid=r'(?i)(?:infection\sau\s)?(covid[\s\-]?19|corona[\s\-]?virus)',
+            traitement=r'(?i)traitements?|medicaments?'),
+        attr='NORM',
+    ),
+)
+
+nlp.add_pipe('dates')
+
+# Qualification des entités
+nlp.add_pipe('negation')
+nlp.add_pipe('hypothesis')
+nlp.add_pipe('family')
+nlp.add_pipe('rspeech')
+```
+
+## Application du pipeline
+
+```python
+doc = nlp(text)
+```
+
+```python
+doc
+```
+
+Les traitements effectués par EDS-NLP (et spaCy en général) sont non-destructifs :
+
+```python
+# Non-destruction
+doc.text == text
+```
+
+Pour des tâches comme la normalisation, EDS-NLP ajoute des attributs aux tokens, sans perte d'information :
+
+```python
+# Normalisation
+print(f"{'texte':<15}", 'normalisation')
+print(f"{'-----':<15}", '-------------')
+for token in doc[3:15]:
+    print(f"{token.text:<15}", f"{token.norm_}")
+```
+
+Le pipeline que nous avons appliqué a extrait des entités avec le `matcher`.
+
+Les entités détectées se retrouvent dans l'attribut `ents` :
+
+```python
+doc.ents
+```
+
+EDS-NLP étant fondée sur spaCy, on peut utiliser tous les outils proposés autour de cette bibliothèque :
+
+```python
+from spacy import displacy
+```
+
+```python
+displacy.render(
+    doc,
+    style='ent',
+    options={'colors': dict(respiratoire='green', covid='orange')},
+)
+```
+
+Focalisons-nous sur la première entité :
+
+```python
+entity = doc.ents[0]
+```
+
+```python
+entity
+```
+
+Chaque entité a été qualifiée par les pipelines de négation, hypothèse, etc. Ces pipelines utilisent des extensions spaCy pour stocker leur résultat :
+
+```python
+entity._.negated
+```
+
+Le pipeline n'a pas détecté de négation pour cette entité.
+
+## Application du pipleline sur une table de textes
+
+Les textes seront le plus souvent disponibles sous la forme d'un DataFrame pandas, qu'on peut simuler ici :
+
+```python
+import pandas as pd
+```
+
+```python
+note = pd.DataFrame(dict(note_text=[text] * 10))
+note['note_id'] = range(len(note))
+note = note[['note_id', 'note_text']]
+```
+
+```python
+note
+```
+
+On peut appliquer la pipeline à l'ensemble des documents en utilisant la fonction `nlp.pipe`, qui permet d'accélérer les traitements en les appliquant en parallèle :
+
+```python
+# Ici on crée une liste qui va contenir les documents traités par spaCy
+docs = list(nlp.pipe(note.note_text))
+```
+
+On veut récupérer les entités détectées et les information associées (empans, qualification, etc) :
+
+```python
+def get_entities(doc):
+    """Extract a list of qualified entities from a spaCy Doc object"""
+    entities = []
+
+    for ent in doc.ents:
+        entity = dict(
+            start=ent.start_char,
+            end=ent.end_char,
+            label=ent.label_,
+            lexical_variant=ent.text,
+            negated=ent._.negated,
+            hypothesis=ent._.hypothesis,
+        )
+
+        entities.append(entity)
+
+    return entities
+```
+
+```python
+note['entities'] = [get_entities(doc) for doc in nlp.pipe(note.note_text)]
+```
+
+```python
+note
+```
+
+On peut maintenant récupérer les entités détectées au format `NOTE_NLP` (ou similaire) :
+
+```python
+# Sélection des colonnes
+note_nlp = note[['note_id', 'entities']]
+
+# "Explosion" des listes d'entités, et suppression des lignes vides (documents sans entité)
+note_nlp = note_nlp.explode('entities').dropna()
+
+# Re-création de l'index, pour des raisons internes à pandas
+note_nlp = note_nlp.reset_index(drop=True)
+```
+
+```python
+note_nlp
+```
+
+Il faut maintenant passer d'une colonne de dictionnaires à une table `NOTE_NLP` :
+
+```python
+note_nlp = note_nlp[['note_id']].join(pd.json_normalize(note_nlp.entities))
+```
+
+```python
+note_nlp
+```
+
+On peut aggréger la qualification des entités en une unique colonne :
+
+```python
+# Création d'une colonne "discard" -> si l'entité est niée ou hypothétique, on la supprime des résultats
+note_nlp['discard'] = note_nlp[['negated', 'hypothesis']].max(axis=1)
+```
+
+```python
+note_nlp
+```
+
+```python
+
+```
+
+```python
+
+```
+
+```python
+
+```
+
+```python
+%%timeit
+for text in texts:
+    nlp(text)
+```
+
+```python
+%%timeit
+for text in nlp.pipe(texts, n_process=-1):
+    pass
+```