{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Sujet 5 : Analyse des dialogues dans l'Avare de Molière" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "__Rappel du contexte du sujet__ :\n", "\n", "L’Observatoire de la vie littéraire ([OBVIL](http://obvil.sorbonne-universite.site/obvil/presentation)) promeut une approche de l'analyse des textes littéraires fondée sur le numérique. \n", "Dans le cadre du [Projet Molière](http://obvil.sorbonne-universite.site/projets/projet-moliere), des pièces de cet auteur ont été numérisées et sont accessibles librement dans différents formats utilisables par un programme informatique. \n", "\n", "Grâce à ces numérisations, il est possible d'écrire des programmes pour réaliser des analyses syntaxiques et sémantiques. Ce sujet se propose de reproduire une étude réalisée par l'OBVIL sur les dialogues de l'Avare de Molière.\n", "\n", "__Rappel des objectifs de ce sujet__ :\n", "\n", "1. Classez les personnages selon la quantité de parole grâce à une analyse syntaxique du texte (scènes / répliques / mots). En particulier, quel est celui qui parle le plus ? Quel est celui qui ne parle pas du tout ? Attention, les noms des personnages ne sont pas forcément homogènes (casse et accents par exemple).\n", "2. Réalisez un graphique qui montrera le nombre de mots que chaque acteur prononce dans chaque scène. Pour cela, vous pouvez vous inspirer de l'[étude de l'Avare de Molière réalisée par l'OBVIL](https://obvil.sorbonne-universite.fr/corpus/moliere/moliere_avare) (graphe de gauche). Dans ce graphique, les lignes sont de longueur égale et la hauteur représente le nombre de mots prononcés au total dans la scène. La largeur de chaque rectangle indique le pourcentage de la scène qu’un acteur occupe. \n", "3. Facultatif : Construisez un graphe d’interlocution permettant de visualiser les échanges entre les personnages. Pour cela, vous pouvez vous inspirer de l'[étude de l'Avare de Molière réalisée par l'OBVIL](https://obvil.sorbonne-universite.fr/corpus/moliere/moliere_avare) (graphe de droite).\n", "4. Déposer votre résultat dans FUN\n", "\n", "La version numérisée que l'on se propose d'utiliser est le fichier texte au format markdown disponible ici [moliere_avare](http://dramacode.github.io/markdown/moliere_avare.txt)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "data_url = \"http://dramacode.github.io/markdown/moliere_avare.txt\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On va s'assurer qu'un fichier texte en local au format markdown contienne la pièce. Si le fichier \"moliere_avare.md\" existe on considère que c'est bon et s'il n'existe pas nous allons télécharger le contenu disponible à l'URL renseignée ci-dessus et l'écrire dans ce fichier local \"moliere_avare.md\". " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from os import path as pth\n", "import requests\n", "\n", "local_filename = \"moliere_avare.md\"\n", "# Si le fichier csv des données d'incidence existe en local\n", "# il n'est pas nécessaire de le télécharger par l'URL\n", "if not pth.exists(local_filename):\n", " print(\"Le fichier local contenant la pièce de théâtre n'existe pas.\")\n", " # Si le fichier n'existe pas en local dans le dossier courant\n", " # nous téléchargons les données et les écrivons\n", " # dans un fichier en local\n", " # Téléchargement des données\n", " response = requests.get(data_url)\n", " # Ecriture des données téléchargées dans le fichier local\n", " with open(local_filename, \"wb\") as f:\n", " f.write(response.content)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Maintenant que nous sommes assurés d'avoir un fichier en local contenant le texte de l'Avare dont on va faire l'analyse, on va donc l'ouvrir, parcourir son contenu et le traiter au fur et à mesure.\n", "\n", "Ce que l'on sait déjà c'est que l'on va devoir créer une structure de données pour l'analyse.\n", "On va passer par la bibliothèque Pandas et la création d'un dataframe, permettant de différencier les différents personnages et de qualifier leur \"activité\" au travers des différents actes et scènes. Néanmoins, après avoir parcouru le web, il est recommandé de passer par une structure intermédiaire pour la création du dataframe pandas. Nous allons choisir la structure de données native de python des dictionnaires en tant que structure de données intermédiaire. \n", "\n", "Il y a plusieurs choses auxquelles il est déjà nécessaire de penser vis-à-vis de la problématique posée et des représentations graphiques demandées, notamment celle relative à la question facultative.\n", "\n", "Pour commencer, afin d'obtenir une bonne lisibilité des graphiques, il sera intéressant d'associer à chaque personnage une couleur différente.\n", "\n", "Ensuite, la question facultative demande de déterminer à qui s'adresse chaque réplique afin d'avoir le graphe directionnel des interactions entre les personnages de la pièce. La structure de données devra donc permettre de savoir pour chaque réplique l'auteur mais aussi le destinataire de cette dernière. Ce sont des informations assez simples à obtenir mais à prendre en compte dans la manière de \"parser\" le fichier texte." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# On va d'ores et déjà utiliser une instruction afin que les graphiques s'affichent directement au sein du notebook\n", "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# On déclare l'utilisation de la bibliothèque pandas et on crée également le dictionnaire\n", "# qui va nous servir d'intermédiaire avant la création du dataframe pandas\n", "import pandas as pd\n", "# Le dictionnaire établi une structure en tableau à 5 colonnes permettant d'enregistrer\n", "# l'auteur, le destinataire, l'acte, la scène, ainsi que la longueur en termes de mots \n", "# pour chaque réplique de la pièce\n", "avareAnalysisDict = {'author':[],'recipient':[],'act':[],'scene':[],'speech_length':[]}\n", "\n", "# Nous créons également d'ores et déjà un dictionnaire des personnages de la scène\n", "# permettant d'enregistrer les informations de liens avec les autres personnages\n", "# de définir une couleur de représentation.\n", "\n", "# Ce dictionnaire est initialisé vide car il sera rempli en utilisant le nom de chaque personnage\n", "# comme clés associées à des valeurs qui seront des dictionnaires à deux entrées\n", "# 'links' donnant les liens avec les autres personnages sous la forme d'une liste\n", "# 'color' permettant de régler une couleur de représentation\n", "avarePersoDict = {}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Afin de parser le fichier en s'appuyant notamment sur les symboles de titres utilisés par le format Markdown,\n", "il nous faut avoir recours à l'utilisation d'un outil d'analyse des expressions régulières (cf. [Wikipedia_Expression_régulière](https://fr.wikipedia.org/wiki/Expression_r%C3%A9guli%C3%A8re)). La bibliothèque\n", "[re](https://docs.python.org/3/library/re.html) disponible nativement dans python permet de faire ce travail.\n", "Un rapide parcours du fichier montre que:\n", "- les actes sont indiqués par des titres header 2, par une ligne commençant par ##\n", "- les scènes sont indiquées par des titres header 3, par une ligne commençant par ###\n", "- que les personnages d'une scène sont donnés sur la ligne suivant l'indication de la scène\n", "- que la ligne précédant chaque réplique contient le \"nom\" de son auteur en majuscule\n", "\n", "Voici un extrait illustrant ces propos:\\\n", "\"\\\n", "_\\##_ _Acte_ _Premier_.\n", "\n", "\n", "_\\### Scène Première.\\\n", "Valère, Élise_\n", "\n", "\n", " VALÈRE.\n", "_Hé quoi, charmante Élise, vous devenez mélancolique, après les obligeantes assurances que vous avez eu la bonté de me donner de votre foi ?Je vous vois soupirer, hélas, au milieu de ma joie !Est-ce du regret, dites-moi, de m'avoir fait heureux ? et vous repentez-vous de cet engagement où mes feux ont pu vous contraindre ?_\n", "\n", " ÉLISE.\n", "_Non, Valère, je ne puis pas me repentir de tout ce que je fais pour vous. Je m'y sens entraîner par une trop douce puissance, et je n'ai pas même la force de souhaiter que les choses ne fussent pas. Mais, à vous dire vrai, le succès me donne de l'inquiétude ; et je crains fort de vous aimer un peu plus que je ne devrais._\n", "\\\n", "\"" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "import re" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'ACTEURS.'" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Chaîne de caractère indiquant le début d'analyse et de récupération des personnages\n", "persoCaptureSTartLine = \"# ACTEURS.\"\n", "# L'expression régulière suivante permet de valider qu'une chaîne de caractère\n", "# contenue dans une ligne lue correspond à la ligne précédant la définition\n", "# de la liste des personnages\n", "m = re.search('(?<=# )ACTEURS\\.$', persoCaptureSTartLine)\n", "m.group(0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "En effet, le résultats obtenus n'est pas un objet null, à savoir une valeur None en python" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"Harpagon, Père de Cléante et d'Élise, et Amoureux de Mariane\"" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Exemple d'une ligne de la liste des personnages\n", "persoLineExample = \" – Harpagon, Père de Cléante et d'Élise, et Amoureux de Mariane.\"\n", "# L'expression régulière proposée est la suivante\n", "# On cherche une chaîne de caractères commencant par une majuscule --> [A-ZÀ-Ÿ]{1}\n", "# potentiellement accentuée et ensuite composées de lettres potentiellement\n", "# accentuées elles aussi, contenant des espaces, des virgules, des apostrophes --> [a-zA-ZÀ-ÿ\\s,\\']+\n", "# et précédée d'un espace suivi d'un tiret suivi d'un espace (ce n'est pas le tiret du 6) --> (?<=\\s–\\s)\n", "# , chaîne de caractères qui ne sera pas capturée\n", "m1 = re.search('(?<=\\s–\\s)[A-ZÀ-Ÿ]{1}[a-zA-ZÀ-ÿ\\s,\\']+',persoLineExample)\n", "m1.group(0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Voici un lien vers un site de test en ligne d'expressions régulières python \\([regexp_test](https://pythex.org/)\\) qui a aidé à mettre en place l'expression régulière précédente." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "# Fonction qui permet d'extraire la liste des personnages\n", "# de la stocker dans un dictionnaire en parcourant ligne par ligne\n", "# un fichier texte passé en entrée.\n", "def fill_perso_dict(fileToAnalyse, emptyPersoDict):\n", " currentLine = fileToAnalyse.readline()\n", " isStartPersoListLine = False\n", " while (currentLine and not isStartPersoListLine):\n", " m = re.search('(?<=# )ACTEURS\\.$', currentLine)\n", " if m is not None:\n", " isStartPersoListLine = True\n", " currentLine = fileToAnalyse.readline()\n", " \n", " # La lecture s'est arrêtée car la ligne de début de définition de la liste des personnages a été rencontrée.\n", " # Nous avons néanmoins lu la ligne suivante qui est obligatoirement un personnage.\n", " # Nous devons maintenant lire ligne par ligne, la liste des personnages au format suivant:\n", " # \"- NomPersonnage, lien, lien, ...\"\n", " # Ainsi dès que la ligne ne commence plus par un tiret nous pouvons arrêter la lecture et le remplissage\n", " # du dictionnaire.\n", " isAPersoLine = True\n", " # On extrait la chaîne de caractères qui nous intéresse\n", " m = re.search('(?<=\\s–\\s)[A-ZÀ-Ÿ]{1}[a-zA-ZÀ-ÿ\\s,\\']+', persoLineExample)\n", " extractedString = m.group(0)\n", " while (currentLine and isAPersoLine):\n", " # Traitement de la ligne courante qui est obligatoirement\n", " # une ligne listant un personnage de la pièce.\n", " # Comme le montre l'exemple au-dessus, le nom ainsi que\n", " # les différents types de liens sont séparés par une virgule.\n", " parts = extractedString.split(',')\n", " # La ligne au-dessus crée une liste dont chaque élément\n", " # est séparé par des virgules\n", " # typiquement \"tata, titi, toto\".split(',') --> ['tata', ' titi', ' toto']\n", " # Création d'un dictionnaire vide temporaire pour enregistrer les liens\n", " # ainsi que la couleur à paramétrer\n", " persoCaracs = {\"links\":[],\"color\":None}\n", " # Le premier élément donne le nom du personnage, indice 0 de la liste\n", " # Les éléments suivants donnent les liens avec les autres personnages\n", " # On parcours le reste de ces derniers\n", " for elt in parts[1:]:\n", " # En regardant cette partie dans le fichier,\n", " # on voit que soit les parties commencent\n", " # par un espace et une lettre,\n", " # soit un espace et un \"et\" que l'on ne\n", " # souhaite pas capturer\n", " # On vérifie si l'élément commence par \" et \" ou non\n", " if elt.startswith(\" et \"):\n", " # L'élément commence bien par \" et \"\n", " # on enlève cette partie\n", " currentLink = elt[4:]\n", " else:\n", " # Ce n'est pas le cas, on enlève juste\n", " # l'espace\n", " currentLink = elt[1:]\n", " persoCaracs[\"links\"].append(currentLink)\n", " # Fin de la boucle for\n", " \n", " # On enregistre le dictionnaire temporaire des caractéristiques\n", " # du personnage courant rempli, dans le dictionnaire\n", " # global des personnages\n", " emptyPersoDict[parts[0]] = persoCaracs\n", " currentLine = fileToAnalyse.readline()\n", " m = re.search('(?<=\\s–\\s)[A-ZÀ-Ÿ]{1}[a-zA-ZÀ-ÿ\\s,\\']+', currentLine)\n", " if m is not None:\n", " extractedString = m.group(0)\n", " else:\n", " isAPersoLine = False\n", " \n", " # Fin de la boucle while" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "##################################################\n", "Contenu du dictionnaire des personnages initialisé\n", "##################################################\n", "\n", "{'Harpagon': {'links': [\"Père de Cléante et d'Élise\", 'Amoureux de Mariane'], 'color': None}, 'Cléante': {'links': [\"Fils d'Harpagon\", 'Amant de Mariane'], 'color': None}, 'Élise': {'links': [\"Fille d'Harpagon\", 'Amante de Valère'], 'color': None}, 'Valère': {'links': [\"Fils d'Anselme\", \"Amant d'Élise\"], 'color': None}, 'Mariane': {'links': ['Amante de Cléante', \"aimée d'Harpagon\"], 'color': None}, 'Anselme': {'links': ['Père de Valère et de Mariane'], 'color': None}, 'Frosine': {'links': [\"Femme d'Intrigue\"], 'color': None}, 'Maitre Simon': {'links': ['Courtier'], 'color': None}, 'Maitre Jacques': {'links': [\"Cuisinier et Cocher d'Harpagon\"], 'color': None}, 'La Flèche': {'links': ['Valet de Cléante'], 'color': None}, 'Dame Claude': {'links': [\"Servante d'Harpagon\"], 'color': None}, 'Brindavoine': {'links': [\"laquais d'Harpagon\"], 'color': None}, 'La Merluche': {'links': [\"laquais d'Harpagon\"], 'color': None}, 'Le commissaire': {'links': ['son clerc'], 'color': None}}\n", "\n", "##################################################\n" ] } ], "source": [ "# On ouvre le fichier local en lecture 'r' pour en faire l'analyse\n", "# en utilisant l'instruction with qui se chargera de fermer\n", "# le fichier une fois sortie de l'instruction.\n", "# (pas d'erreur possible par oubli d'appel à l'instruction close)\n", "# Un rapide coup d'oeil au fichier texte nous montre une organisation\n", "# , des symboles en début de ligne etc, que l'on va utiliser pour \"parser\"\n", "# le fichier, à savoir le lire de manière à ranger les données\n", "# de manière intelligente dans une structure de données facilitant\n", "# la manipulation et l'analyse. \n", "with open(local_filename,'r') as avareFile:\n", " # On va commencer par parser le fichier afin de récupérer la liste des personnages\n", " fill_perso_dict(avareFile, avarePersoDict)\n", " print(\"##################################################\")\n", " print(\"Contenu du dictionnaire des personnages initialisé\")\n", " print(\"##################################################\\n\")\n", " print(avarePersoDict)\n", " print(\"\\n##################################################\")\n", "\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notes pour plus tard: lien stack overflow vers code de customisation de graphes de la bibliothèque python networkx\n", "https://stackoverflow.com/questions/25639169/networkx-change-color-width-according-to-edge-attributes-inconsistent-result\n", "lien github vers morceau de code ajoutant de la couleur et le réglage de l'épaisseur des arêtes sur un graphe\n", "https://gist.github.com/AruniRC/2c53fe7680eeb578593ec816bbfb1653\n", "Lien vers une page donnant un exemple d'affichage par ensemble de barres\n", "https://www.geeksforgeeks.org/stacked-percentage-bar-plot-in-matplotlib/" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.4" } }, "nbformat": 4, "nbformat_minor": 2 }