{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", " Analyze_Text.ipynb: Analyze Text with Pandas and Watson Natural Language Understanding\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Introduction\n", "\n", "This notebook shows how the open source library [Text Extensions for Pandas](https://github.com/CODAIT/text-extensions-for-pandas) lets you use [Pandas](https://pandas.pydata.org/) DataFrames and the [Watson Natural Language Understanding](https://www.ibm.com/cloud/watson-natural-language-understanding) service to analyze natural language text. \n", "\n", "We start out with an excerpt from the [plot synopsis from the Wikipedia page\n", "for *Monty Python and the Holy Grail*](https://en.wikipedia.org/wiki/Monty_Python_and_the_Holy_Grail#Plot). \n", "We pass this example document to the Watson Natural Language \n", "Understanding (NLU) service. Then we use Text Extensions for Pandas to convert the output of the \n", "Watson NLU service to Pandas DataFrames. Next, we perform an example analysis task both with \n", "and without Pandas to show how Pandas makes analyzing NLP information easier. Finally, we \n", "walk through all the different DataFrames that Text Extensions for Pandas can extract from \n", "the output of Watson Natural Language Understanding." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Environment Setup\n", "\n", "This notebook requires a Python 3.7 or later environment with the following packages:\n", "* The dependencies listed in the [\"requirements.txt\" file for Text Extensions for Pandas](https://github.com/CODAIT/text-extensions-for-pandas/blob/master/requirements.txt)\n", "* The \"[ibm-watson](https://pypi.org/project/ibm-watson/)\" package, available via `pip install ibm-watson`\n", "* `text_extensions_for_pandas`\n", "\n", "You can satisfy the dependency on `text_extensions_for_pandas` in either of two ways:\n", "\n", "* Run `pip install text_extensions_for_pandas` before running this notebook. This command adds the library to your Python environment.\n", "* Run this notebook out of your local copy of the Text Extensions for Pandas project's [source tree](https://github.com/CODAIT/text-extensions-for-pandas). In this case, the notebook will use the version of Text Extensions for Pandas in your local source tree **if the package is not installed in your Python environment**." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Core Python libraries\n", "import json\n", "import os\n", "import sys\n", "import pandas as pd\n", "from typing import *\n", "\n", "# IBM Watson libraries\n", "import ibm_watson\n", "import ibm_watson.natural_language_understanding_v1 as nlu\n", "import ibm_cloud_sdk_core\n", "\n", "# And of course we need the text_extensions_for_pandas library itself.\n", "try:\n", " import text_extensions_for_pandas as tp\n", "except ModuleNotFoundError as e:\n", " # If we're running from within the project source tree and the parent Python\n", " # environment doesn't have the text_extensions_for_pandas package, use the\n", " # version in the local source tree.\n", " if not os.getcwd().endswith(\"notebooks\"):\n", " raise e\n", " if \"..\" not in sys.path:\n", " sys.path.insert(0, \"..\")\n", " import text_extensions_for_pandas as tp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Set up the Watson Natural Language Understanding Service\n", "\n", "In this part of the notebook, we will use the Watson Natural Language Understanding (NLU) service to extract key features from our example document.\n", "\n", "You can create an instance of Watson NLU on the IBM Cloud for free by navigating to [this page](https://www.ibm.com/cloud/watson-natural-language-understanding) and clicking on the button marked \"Get started free\". You can also install your own instance of Watson NLU on [OpenShift](https://www.openshift.com/) by using [IBM Watson Natural Language Understanding for IBM Cloud Pak for Data](\n", "https://catalog.redhat.com/software/operators/detail/5e9873e13f398525a0ceafe5).\n", "\n", "You'll need two pieces of information to access your instance of Watson NLU: An **API key** and a **service URL**. If you're using Watson NLU on the IBM Cloud, you can find your API key and service URL in the IBM Cloud web UI. Navigate to the [resource list](https://cloud.ibm.com/resources) and click on your instance of Natural Language Understanding to open the management UI for your service. Then click on the \"Manage\" tab to show a page with your API key and service URL.\n", "\n", "The cell that follows assumes that you are using the environment variables `IBM_API_KEY` and `IBM_SERVICE_URL` to store your credentials. If you're running this notebook in Jupyter on your laptop, you can set these environment variables while starting up `jupyter notebook` or `jupyter lab`. For example:\n", "``` console\n", "IBM_API_KEY='' \\\n", "IBM_SERVICE_URL='' \\\n", " jupyter lab\n", "```\n", "\n", "Alternately, you can uncomment the first two lines of code below to set the `IBM_API_KEY` and `IBM_SERVICE_URL` environment variables directly.\n", "**Be careful not to store your API key in any publicly-accessible location!**" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# If you need to embed your credentials inline, uncomment the following two lines and\n", "# paste your credentials in the indicated locations.\n", "# os.environ[\"IBM_API_KEY\"] = \"\"\n", "# os.environ[\"IBM_SERVICE_URL\"] = \"\"\n", "\n", "# Retrieve the API key for your Watson NLU service instance\n", "if \"IBM_API_KEY\" not in os.environ:\n", " raise ValueError(\"Expected Watson NLU api key in the environment variable 'IBM_API_KEY'\")\n", "api_key = os.environ.get(\"IBM_API_KEY\")\n", " \n", "# Retrieve the service URL for your Watson NLU service instance\n", "if \"IBM_SERVICE_URL\" not in os.environ:\n", " raise ValueError(\"Expected Watson NLU service URL in the environment variable 'IBM_SERVICE_URL'\")\n", "service_url = os.environ.get(\"IBM_SERVICE_URL\") " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Connect to the Watson Natural Language Understanding Python API\n", "\n", "This notebook uses the IBM Watson Python SDK to perform authentication on the IBM Cloud via the \n", "`IAMAuthenticator` class. See [the IBM Watson Python SDK documentation](https://github.com/watson-developer-cloud/python-sdk#iam) for more information. \n", "\n", "We start by using the API key and service URL from the previous cell to create an instance of the\n", "Python API for Watson NLU." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "natural_language_understanding = ibm_watson.NaturalLanguageUnderstandingV1(\n", " version=\"2019-07-12\",\n", " authenticator=ibm_cloud_sdk_core.authenticators.IAMAuthenticator(api_key)\n", ")\n", "natural_language_understanding.set_service_url(service_url)\n", "natural_language_understanding" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Pass a Document through the Watson NLU Service\n", "\n", "Once you've opened a connection to the Watson NLU service, you can pass documents through \n", "the service by invoking the [`analyze()` method](https://cloud.ibm.com/apidocs/natural-language-understanding?code=python#analyze).\n", "\n", "The [example document](https://raw.githubusercontent.com/CODAIT/text-extensions-for-pandas/master/resources/holy_grail_short.txt) that we use here is an excerpt from\n", "the plot summary for *Monty Python and the Holy Grail*, drawn from the [Wikipedia entry](https://en.wikipedia.org/wiki/Monty_Python_and_the_Holy_Grail) for that movie.\n", "\n", "Let's show what the raw text looks like:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "Document Text:
In AD 932, King Arthur and his squire, Patsy, travel throughout Britain searching for men to join the Knights of the Round Table. Along the way, he recruits Sir Bedevere the Wise, Sir Lancelot the Brave, Sir Galahad the Pure, Sir Robin the Not-Quite-So-Brave-as-Sir-Lancelot, and Sir Not-Appearing-in-this-Film, along with their squires and Robin's troubadours. Arthur leads the men to Camelot, but upon further consideration (thanks to a musical number) he decides not to go there because it is \"a silly place\". As they turn away, God (an image of W. G. Grace) speaks to them and gives Arthur the task of finding the Holy Grail.
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from IPython.display import display, HTML\n", "doc_file = \"../resources/holy_grail_short.txt\"\n", "with open(doc_file, \"r\") as f:\n", " doc_text = f.read()\n", "\n", "display(HTML(f\"Document Text:
{doc_text}
\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the code below, we instruct Watson Natural Language Understanding to perform five different kinds of analysis on the example document:\n", "* entities (with sentiment)\n", "* keywords (with sentiment and emotion)\n", "* relations\n", "* semantic_roles\n", "* syntax (with sentences, tokens, and part of speech)\n", "\n", "See [the Watson NLU documentation](https://cloud.ibm.com/apidocs/natural-language-understanding?code=python#text-analytics-features) for a full description of the types of analysis that NLU can perform." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# Make the request\n", "response = natural_language_understanding.analyze(\n", " text=doc_text,\n", " # TODO: Use this URL once we've pushed the shortened document to Github\n", " #url=\"https://raw.githubusercontent.com/CODAIT/text-extensions-for-pandas/master/resources/holy_grail_short.txt\",\n", " return_analyzed_text=True,\n", " features=nlu.Features(\n", " entities=nlu.EntitiesOptions(sentiment=True, mentions=True),\n", " keywords=nlu.KeywordsOptions(sentiment=True, emotion=True),\n", " relations=nlu.RelationsOptions(),\n", " semantic_roles=nlu.SemanticRolesOptions(),\n", " syntax=nlu.SyntaxOptions(sentences=True, \n", " tokens=nlu.SyntaxOptionsTokens(lemma=True, part_of_speech=True))\n", " )).get_result()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The response from the `analyze()` method is a Python dictionary. The dictionary contains an entry \n", "for each pass of analysis requested, plus some additional entries with metadata about the API request\n", "itself. Here's a list of the keys in `response`:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dict_keys(['usage', 'syntax', 'semantic_roles', 'relations', 'language', 'keywords', 'entities', 'analyzed_text'])" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "response.keys()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Perform an Example Task\n", "\n", "Let's use the information that Watson Natural Language Understanding has extracted from our example document to perform an example task: *Find all the pronouns in each sentence, broken down by sentence.*\n", "\n", "This task could serve as first step to a number of more complex tasks, such as \n", "resolving anaphora (for example, associating \"King Arthur\" with \"his\" in the phrase \"King Arthur and his squire, Patsy\") or analyzing the relationship between sentiment and the gender of pronouns.\n", "\n", "We'll start by doing this task using straight Python code that operates directly over the output of Watson NLU's `analyze()` method. Then we'll redo the task using Pandas DataFrames and Text Extensions for Pandas. This exercise will show how Pandas DataFrames can represent the intermediate data structures of an NLP application in a way that is both easier to understand and easier to manipulate with less code.\n", "\n", "Let's begin." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Perform the Task Without Using Pandas\n", "\n", "All the information that we need to perform our task is in the \"syntax\" section of the response \n", "we captured above from Watson NLU's `analyze()` method. Syntax analysis captures a large amount\n", "of information, so the \"syntax\" section of the response is very verbose. \n", "\n", "For reference, here's the text of our example document again:\n", "\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "Document Text:
In AD 932, King Arthur and his squire, Patsy, travel throughout Britain searching for men to join the Knights of the Round Table. Along the way, he recruits Sir Bedevere the Wise, Sir Lancelot the Brave, Sir Galahad the Pure, Sir Robin the Not-Quite-So-Brave-as-Sir-Lancelot, and Sir Not-Appearing-in-this-Film, along with their squires and Robin's troubadours. Arthur leads the men to Camelot, but upon further consideration (thanks to a musical number) he decides not to go there because it is \"a silly place\". As they turn away, God (an image of W. G. Grace) speaks to them and gives Arthur the task of finding the Holy Grail.
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "display(HTML(f\"Document Text:
{doc_text}
\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And here's the output of Watson NLU's syntax analysis, converted to a string:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "{'tokens': [{'text': 'In',\n", " 'part_of_speech': 'ADP',\n", " 'location': [0, 2],\n", " 'lemma': 'in'},\n", " {'text': 'AD', 'part_of_speech': 'PROPN', 'location': [3, 5], 'lemma': 'Ad'},\n", " {'text': '932', 'part_of_speech': 'NUM', 'location': [6, 9]},\n", " {'text': ',', 'part_of_speech': 'PUNCT', 'location': [9, 10]},\n", " {'text': 'King',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [11, 15],\n", " 'lemma': 'King'},\n", " {'text': 'Arthur', 'part_of_speech': 'PROPN', 'location': [16, 22]},\n", " {'text': 'and',\n", " 'part_of_speech': 'CCONJ',\n", " 'location': [23, 26],\n", " 'lemma': 'and'},\n", " {'text': 'his',\n", " 'part_of_speech': 'PRON',\n", " 'location': [27, 30],\n", " 'lemma': 'his'},\n", " {'text': 'squire',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [31, 37],\n", " 'lemma': 'squire'},\n", " {'text': ',', 'part_of_speech': 'PUNCT', 'location': [37, 38]},\n", " {'text': 'Patsy',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [39, 44],\n", " 'lemma': 'Patsy'},\n", " {'text': ',', 'part_of_speech': 'PUNCT', 'location': [44, 45]},\n", " {'text': 'travel',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [46, 52],\n", " 'lemma': 'travel'},\n", " {'text': 'throughout',\n", " 'part_of_speech': 'ADP',\n", " 'location': [53, 63],\n", " 'lemma': 'throughout'},\n", " {'text': 'Britain', 'part_of_speech': 'PROPN', 'location': [64, 71]},\n", " {'text': 'searching',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [72, 81],\n", " 'lemma': 'searching'},\n", " {'text': 'for',\n", " 'part_of_speech': 'ADP',\n", " 'location': [82, 85],\n", " 'lemma': 'for'},\n", " {'text': 'men',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [86, 89],\n", " 'lemma': 'man'},\n", " {'text': 'to',\n", " 'part_of_speech': 'PART',\n", " 'location': [90, 92],\n", " 'lemma': 'to'},\n", " {'text': 'join',\n", " 'part_of_speech': 'VERB',\n", " 'location': [93, 97],\n", " 'lemma': 'join'},\n", " {'text': 'the',\n", " 'part_of_speech': 'DET',\n", " 'location': [98, 101],\n", " 'lemma': 'the'},\n", " {'text': 'Knights',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [102, 109],\n", " 'lemma': 'Knight'},\n", " {'text': 'of',\n", " 'part_of_speech': 'ADP',\n", " 'location': [110, 112],\n", " 'lemma': 'of'},\n", " {'text': 'the',\n", " 'part_of_speech': 'DET',\n", " 'location': [113, 116],\n", " 'lemma': 'the'},\n", " {'text': 'Round',\n", " 'part_of_speech': 'ADJ',\n", " 'location': [117, 122],\n", " 'lemma': 'round'},\n", " {'text': 'Table',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [123, 128],\n", " 'lemma': 'table'},\n", " {'text': '.', 'part_of_speech': 'PUNCT', 'location': [128, 129]},\n", " {'text': 'Along',\n", " 'part_of_speech': 'ADP',\n", " 'location': [130, 135],\n", " 'lemma': 'along'},\n", " {'text': 'the',\n", " 'part_of_speech': 'DET',\n", " 'location': [136, 139],\n", " 'lemma': 'the'},\n", " {'text': 'way',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [140, 143],\n", " 'lemma': 'way'},\n", " {'text': ',', 'part_of_speech': 'PUNCT', 'location': [143, 144]},\n", " {'text': 'he',\n", " 'part_of_speech': 'PRON',\n", " 'location': [145, 147],\n", " 'lemma': 'he'},\n", " {'text': 'recruits',\n", " 'part_of_speech': 'VERB',\n", " 'location': [148, 156],\n", " 'lemma': 'recruit'},\n", " {'text': 'Sir',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [157, 160],\n", " 'lemma': 'Sir'},\n", " {'text': 'Bedevere', 'part_of_speech': 'PROPN', 'location': [161, 169]},\n", " {'text': 'the',\n", " 'part_of_speech': 'DET',\n", " 'location': [170, 173],\n", " 'lemma': 'the'},\n", " {'text': 'Wise',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [174, 178],\n", " 'lemma': 'Wise'},\n", " {'text': ',', 'part_of_speech': 'PUNCT', 'location': [178, 179]},\n", " {'text': 'Sir',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [180, 183],\n", " 'lemma': 'Sir'},\n", " {'text': 'Lancelot', 'part_of_speech': 'PROPN', 'location': [184, 192]},\n", " {'text': 'the',\n", " 'part_of_speech': 'DET',\n", " 'location': [193, 196],\n", " 'lemma': 'the'},\n", " {'text': 'Brave',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [197, 202],\n", " 'lemma': 'Brave'},\n", " {'text': ',', 'part_of_speech': 'PUNCT', 'location': [202, 203]},\n", " {'text': 'Sir',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [204, 207],\n", " 'lemma': 'Sir'},\n", " {'text': 'Galahad', 'part_of_speech': 'PROPN', 'location': [208, 215]},\n", " {'text': 'the',\n", " 'part_of_speech': 'DET',\n", " 'location': [216, 219],\n", " 'lemma': 'the'},\n", " {'text': 'Pure', 'part_of_speech': 'PROPN', 'location': [220, 224]},\n", " {'text': ',', 'part_of_speech': 'PUNCT', 'location': [224, 225]},\n", " {'text': 'Sir',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [226, 229],\n", " 'lemma': 'Sir'},\n", " {'text': 'Robin',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [230, 235],\n", " 'lemma': 'Robin'},\n", " {'text': 'the',\n", " 'part_of_speech': 'DET',\n", " 'location': [236, 239],\n", " 'lemma': 'the'},\n", " {'text': 'Not', 'part_of_speech': 'PROPN', 'location': [240, 243]},\n", " {'text': '-', 'part_of_speech': 'PUNCT', 'location': [243, 244]},\n", " {'text': 'Quite', 'part_of_speech': 'PROPN', 'location': [244, 249]},\n", " {'text': '-', 'part_of_speech': 'PUNCT', 'location': [249, 250]},\n", " {'text': 'So',\n", " 'part_of_speech': 'ADV',\n", " 'location': [250, 252],\n", " 'lemma': 'so'},\n", " {'text': '-', 'part_of_speech': 'PUNCT', 'location': [252, 253]},\n", " {'text': 'Brave',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [253, 258],\n", " 'lemma': 'Brave'},\n", " {'text': '-', 'part_of_speech': 'PUNCT', 'location': [258, 259]},\n", " {'text': 'as',\n", " 'part_of_speech': 'ADP',\n", " 'location': [259, 261],\n", " 'lemma': 'as'},\n", " {'text': '-', 'part_of_speech': 'PUNCT', 'location': [261, 262]},\n", " {'text': 'Sir',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [262, 265],\n", " 'lemma': 'Sir'},\n", " {'text': '-', 'part_of_speech': 'PUNCT', 'location': [265, 266]},\n", " {'text': 'Lancelot', 'part_of_speech': 'PROPN', 'location': [266, 274]},\n", " {'text': ',', 'part_of_speech': 'PUNCT', 'location': [274, 275]},\n", " {'text': 'and',\n", " 'part_of_speech': 'CCONJ',\n", " 'location': [276, 279],\n", " 'lemma': 'and'},\n", " {'text': 'Sir',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [280, 283],\n", " 'lemma': 'Sir'},\n", " {'text': 'Not',\n", " 'part_of_speech': 'ADV',\n", " 'location': [284, 287],\n", " 'lemma': 'not'},\n", " {'text': '-', 'part_of_speech': 'PUNCT', 'location': [287, 288]},\n", " {'text': 'Appearing', 'part_of_speech': 'PROPN', 'location': [288, 297]},\n", " {'text': '-', 'part_of_speech': 'PUNCT', 'location': [297, 298]},\n", " {'text': 'in',\n", " 'part_of_speech': 'ADP',\n", " 'location': [298, 300],\n", " 'lemma': 'in'},\n", " {'text': '-', 'part_of_speech': 'PUNCT', 'location': [300, 301]},\n", " {'text': 'this',\n", " 'part_of_speech': 'PRON',\n", " 'location': [301, 305],\n", " 'lemma': 'this'},\n", " {'text': '-', 'part_of_speech': 'PUNCT', 'location': [305, 306]},\n", " {'text': 'Film',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [306, 310],\n", " 'lemma': 'Film'},\n", " {'text': ',', 'part_of_speech': 'PUNCT', 'location': [310, 311]},\n", " {'text': 'along',\n", " 'part_of_speech': 'ADP',\n", " 'location': [312, 317],\n", " 'lemma': 'along'},\n", " {'text': 'with',\n", " 'part_of_speech': 'ADP',\n", " 'location': [318, 322],\n", " 'lemma': 'with'},\n", " {'text': 'their',\n", " 'part_of_speech': 'PRON',\n", " 'location': [323, 328],\n", " 'lemma': 'their'},\n", " {'text': 'squires',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [329, 336],\n", " 'lemma': 'squire'},\n", " {'text': 'and',\n", " 'part_of_speech': 'CCONJ',\n", " 'location': [337, 340],\n", " 'lemma': 'and'},\n", " {'text': 'Robin',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [341, 346],\n", " 'lemma': 'Robin'},\n", " {'text': \"'s\",\n", " 'part_of_speech': 'PART',\n", " 'location': [346, 348],\n", " 'lemma': \"'s\"},\n", " {'text': 'troubadours',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [349, 360],\n", " 'lemma': 'troubadour'},\n", " {'text': '.', 'part_of_speech': 'PUNCT', 'location': [360, 361]},\n", " {'text': 'Arthur', 'part_of_speech': 'PROPN', 'location': [362, 368]},\n", " {'text': 'leads',\n", " 'part_of_speech': 'VERB',\n", " 'location': [369, 374],\n", " 'lemma': 'lead'},\n", " {'text': 'the',\n", " 'part_of_speech': 'DET',\n", " 'location': [375, 378],\n", " 'lemma': 'the'},\n", " {'text': 'men',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [379, 382],\n", " 'lemma': 'man'},\n", " {'text': 'to',\n", " 'part_of_speech': 'ADP',\n", " 'location': [383, 385],\n", " 'lemma': 'to'},\n", " {'text': 'Camelot', 'part_of_speech': 'PROPN', 'location': [386, 393]},\n", " {'text': ',', 'part_of_speech': 'PUNCT', 'location': [393, 394]},\n", " {'text': 'but',\n", " 'part_of_speech': 'CCONJ',\n", " 'location': [395, 398],\n", " 'lemma': 'but'},\n", " {'text': 'upon',\n", " 'part_of_speech': 'ADP',\n", " 'location': [399, 403],\n", " 'lemma': 'upon'},\n", " {'text': 'further',\n", " 'part_of_speech': 'ADJ',\n", " 'location': [404, 411],\n", " 'lemma': 'far'},\n", " {'text': 'consideration',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [412, 425],\n", " 'lemma': 'consideration'},\n", " {'text': '(', 'part_of_speech': 'PUNCT', 'location': [426, 427]},\n", " {'text': 'thanks',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [427, 433],\n", " 'lemma': 'thanks'},\n", " {'text': 'to',\n", " 'part_of_speech': 'ADP',\n", " 'location': [434, 436],\n", " 'lemma': 'to'},\n", " {'text': 'a', 'part_of_speech': 'DET', 'location': [437, 438], 'lemma': 'a'},\n", " {'text': 'musical',\n", " 'part_of_speech': 'ADJ',\n", " 'location': [439, 446],\n", " 'lemma': 'musical'},\n", " {'text': 'number',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [447, 453],\n", " 'lemma': 'number'},\n", " {'text': ')', 'part_of_speech': 'PUNCT', 'location': [453, 454]},\n", " {'text': 'he',\n", " 'part_of_speech': 'PRON',\n", " 'location': [455, 457],\n", " 'lemma': 'he'},\n", " {'text': 'decides',\n", " 'part_of_speech': 'VERB',\n", " 'location': [458, 465],\n", " 'lemma': 'decide'},\n", " {'text': 'not',\n", " 'part_of_speech': 'PART',\n", " 'location': [466, 469],\n", " 'lemma': 'not'},\n", " {'text': 'to',\n", " 'part_of_speech': 'PART',\n", " 'location': [470, 472],\n", " 'lemma': 'to'},\n", " {'text': 'go',\n", " 'part_of_speech': 'VERB',\n", " 'location': [473, 475],\n", " 'lemma': 'go'},\n", " {'text': 'there',\n", " 'part_of_speech': 'ADV',\n", " 'location': [476, 481],\n", " 'lemma': 'there'},\n", " {'text': 'because',\n", " 'part_of_speech': 'SCONJ',\n", " 'location': [482, 489],\n", " 'lemma': 'because'},\n", " {'text': 'it',\n", " 'part_of_speech': 'PRON',\n", " 'location': [490, 492],\n", " 'lemma': 'it'},\n", " {'text': 'is',\n", " 'part_of_speech': 'AUX',\n", " 'location': [493, 495],\n", " 'lemma': 'be'},\n", " {'text': '\"', 'part_of_speech': 'PUNCT', 'location': [496, 497]},\n", " {'text': 'a', 'part_of_speech': 'DET', 'location': [497, 498], 'lemma': 'a'},\n", " {'text': 'silly',\n", " 'part_of_speech': 'ADJ',\n", " 'location': [499, 504],\n", " 'lemma': 'silly'},\n", " {'text': 'place',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [505, 510],\n", " 'lemma': 'place'},\n", " {'text': '\"', 'part_of_speech': 'PUNCT', 'location': [510, 511]},\n", " {'text': '.', 'part_of_speech': 'PUNCT', 'location': [511, 512]},\n", " {'text': 'As',\n", " 'part_of_speech': 'SCONJ',\n", " 'location': [513, 515],\n", " 'lemma': 'as'},\n", " {'text': 'they',\n", " 'part_of_speech': 'PRON',\n", " 'location': [516, 520],\n", " 'lemma': 'they'},\n", " {'text': 'turn',\n", " 'part_of_speech': 'VERB',\n", " 'location': [521, 525],\n", " 'lemma': 'turn'},\n", " {'text': 'away', 'part_of_speech': 'ADP', 'location': [526, 530]},\n", " {'text': ',', 'part_of_speech': 'PUNCT', 'location': [530, 531]},\n", " {'text': 'God',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [532, 535],\n", " 'lemma': 'God'},\n", " {'text': '(', 'part_of_speech': 'PUNCT', 'location': [536, 537]},\n", " {'text': 'an',\n", " 'part_of_speech': 'DET',\n", " 'location': [537, 539],\n", " 'lemma': 'a'},\n", " {'text': 'image',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [540, 545],\n", " 'lemma': 'image'},\n", " {'text': 'of',\n", " 'part_of_speech': 'ADP',\n", " 'location': [546, 548],\n", " 'lemma': 'of'},\n", " {'text': 'W.', 'part_of_speech': 'PROPN', 'location': [549, 551]},\n", " {'text': 'G.', 'part_of_speech': 'PROPN', 'location': [552, 554]},\n", " {'text': 'Grace',\n", " 'part_of_speech': 'PROPN',\n", " 'location': [555, 560],\n", " 'lemma': 'Grace'},\n", " {'text': ')', 'part_of_speech': 'PUNCT', 'location': [560, 561]},\n", " {'text': 'speaks',\n", " 'part_of_speech': 'VERB',\n", " 'location': [562, 568],\n", " 'lemma': 'speak'},\n", " {'text': 'to',\n", " 'part_of_speech': 'ADP',\n", " 'location': [569, 571],\n", " 'lemma': 'to'},\n", " {'text': 'them',\n", " 'part_of_speech': 'PRON',\n", " 'location': [572, 576],\n", " 'lemma': 'they'},\n", " {'text': 'and',\n", " 'part_of_speech': 'CCONJ',\n", " 'location': [577, 580],\n", " 'lemma': 'and'},\n", " {'text': 'gives',\n", " 'part_of_speech': 'VERB',\n", " 'location': [581, 586],\n", " 'lemma': 'give'},\n", " {'text': 'Arthur', 'part_of_speech': 'PROPN', 'location': [587, 593]},\n", " {'text': 'the',\n", " 'part_of_speech': 'DET',\n", " 'location': [594, 597],\n", " 'lemma': 'the'},\n", " {'text': 'task',\n", " 'part_of_speech': 'NOUN',\n", " 'location': [598, 602],\n", " 'lemma': 'task'},\n", " {'text': 'of',\n", " 'part_of_speech': 'SCONJ',\n", " 'location': [603, 605],\n", " 'lemma': 'of'},\n", " {'text': 'finding',\n", " 'part_of_speech': 'VERB',\n", " 'location': [606, 613],\n", " 'lemma': 'find'},\n", " {'text': 'the',\n", " 'part_of_speech': 'DET',\n", " 'location': [614, 617],\n", " 'lemma': 'the'},\n", " {'text': 'Holy', 'part_of_speech': 'PROPN', 'location': [618, 622]},\n", " {'text': 'Grail', 'part_of_speech': 'PROPN', 'location': [623, 628]},\n", " {'text': '.', 'part_of_speech': 'PUNCT', 'location': [628, 629]}],\n", " 'sentences': [{'text': 'In AD 932, King Arthur and his squire, Patsy, travel throughout Britain searching for men to join the Knights of the Round Table.',\n", " 'location': [0, 129]},\n", " {'text': \"Along the way, he recruits Sir Bedevere the Wise, Sir Lancelot the Brave, Sir Galahad the Pure, Sir Robin the Not-Quite-So-Brave-as-Sir-Lancelot, and Sir Not-Appearing-in-this-Film, along with their squires and Robin's troubadours.\",\n", " 'location': [130, 361]},\n", " {'text': 'Arthur leads the men to Camelot, but upon further consideration (thanks to a musical number) he decides not to go there because it is \"a silly place\".',\n", " 'location': [362, 512]},\n", " {'text': 'As they turn away, God (an image of W. G. Grace) speaks to them and gives Arthur the task of finding the Holy Grail.',\n", " 'location': [513, 629]}]}" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "response[\"syntax\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Buried in the above data structure is all the information we need to perform our example task:\n", "* The location of every token in the document.\n", "* The part of speech of every token in the document.\n", "* The location of every sentence in the document.\n", "\n", "The Python code in the next cell uses this information to construct a list of pronouns\n", "in each sentence in the document." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "[{'sentence': {'text': 'In AD 932, King Arthur and his squire, Patsy, travel throughout Britain searching for men to join the Knights of the Round Table.',\n", " 'location': [0, 129]},\n", " 'pronouns': [{'text': 'his',\n", " 'part_of_speech': 'PRON',\n", " 'location': [27, 30],\n", " 'lemma': 'his'}]},\n", " {'sentence': {'text': \"Along the way, he recruits Sir Bedevere the Wise, Sir Lancelot the Brave, Sir Galahad the Pure, Sir Robin the Not-Quite-So-Brave-as-Sir-Lancelot, and Sir Not-Appearing-in-this-Film, along with their squires and Robin's troubadours.\",\n", " 'location': [130, 361]},\n", " 'pronouns': [{'text': 'he',\n", " 'part_of_speech': 'PRON',\n", " 'location': [145, 147],\n", " 'lemma': 'he'},\n", " {'text': 'this',\n", " 'part_of_speech': 'PRON',\n", " 'location': [301, 305],\n", " 'lemma': 'this'},\n", " {'text': 'their',\n", " 'part_of_speech': 'PRON',\n", " 'location': [323, 328],\n", " 'lemma': 'their'}]},\n", " {'sentence': {'text': 'Arthur leads the men to Camelot, but upon further consideration (thanks to a musical number) he decides not to go there because it is \"a silly place\".',\n", " 'location': [362, 512]},\n", " 'pronouns': [{'text': 'he',\n", " 'part_of_speech': 'PRON',\n", " 'location': [455, 457],\n", " 'lemma': 'he'},\n", " {'text': 'it',\n", " 'part_of_speech': 'PRON',\n", " 'location': [490, 492],\n", " 'lemma': 'it'}]},\n", " {'sentence': {'text': 'As they turn away, God (an image of W. G. Grace) speaks to them and gives Arthur the task of finding the Holy Grail.',\n", " 'location': [513, 629]},\n", " 'pronouns': [{'text': 'they',\n", " 'part_of_speech': 'PRON',\n", " 'location': [516, 520],\n", " 'lemma': 'they'},\n", " {'text': 'them',\n", " 'part_of_speech': 'PRON',\n", " 'location': [572, 576],\n", " 'lemma': 'they'}]}]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import collections\n", "\n", "# Create a data structure to hold a mapping from sentence identifier\n", "# to a list of pronouns. This step requires defining sentence ids.\n", "def sentence_id(sentence_record: Dict[str, Any]):\n", " return tuple(sentence_record[\"location\"])\n", "\n", "pronouns_by_sentence_id = collections.defaultdict(list)\n", "\n", "# Pass 1: Use nested for loops to identify pronouns and match them with \n", "# their containing sentences.\n", "# Running time: O(num_tokens * num_sentences), i.e. O(document_size^2)\n", "for t in response[\"syntax\"][\"tokens\"]:\n", " pos_str = t[\"part_of_speech\"] # Decode numeric POS enum\n", " if pos_str == \"PRON\":\n", " found_sentence = False\n", " for s in response[\"syntax\"][\"sentences\"]:\n", " if (t[\"location\"][0] >= s[\"location\"][0] \n", " and t[\"location\"][1] <= s[\"location\"][1]):\n", " found_sentence = True\n", " pronouns_by_sentence_id[sentence_id(s)].append(t)\n", " if not found_sentence:\n", " raise ValueError(f\"Token {t} is not in any sentence\")\n", " pass # Make JupyterLab syntax highlighting happy\n", "\n", "# Pass 2: Translate sentence identifiers to full sentence metadata.\n", "sentence_id_to_sentence = {sentence_id(s): s \n", " for s in response[\"syntax\"][\"sentences\"]}\n", "result = [\n", " {\n", " \"sentence\": sentence_id_to_sentence[key],\n", " \"pronouns\": pronouns\n", " }\n", " for key, pronouns in pronouns_by_sentence_id.items()\n", "]\n", "result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The code above is quite complex given the simplicity of the task. You would need to stare at the previous cell for a few minutes to convince yourself that the algorithm is correct. This implementation also has scalability issues: The worst-case running time of the nested for loops section is proportional to the square of the document length.\n", "\n", "We can do better." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Repeat the Example Task Using Pandas\n", "\n", "Let's revisit the example task we just performed in the previous cell. Again, the task is: *Find all the pronouns in each sentence, broken down by sentence.* This time around, let's perform this task using Pandas.\n", "\n", "Text Extensions for Pandas includes a function `parse_response()` that turns the output of Watson NLU's `analyze()` function into a dictionary of Pandas DataFrames. Let's run our response object through that conversion." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dict_keys(['syntax', 'entities', 'entity_mentions', 'keywords', 'relations', 'semantic_roles'])" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dfs = tp.io.watson.nlu.parse_response(response)\n", "dfs.keys()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The output of each analysis pass that Watson NLU performed is now a DataFrame. \n", "Let's look at the DataFrame for the \"syntax\" pass:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
spanpart_of_speechlemmasentence
0[0, 2): 'In'ADPin[0, 129): 'In AD 932, King Arthur and his squi...
1[3, 5): 'AD'PROPNAd[0, 129): 'In AD 932, King Arthur and his squi...
2[6, 9): '932'NUMNone[0, 129): 'In AD 932, King Arthur and his squi...
3[9, 10): ','PUNCTNone[0, 129): 'In AD 932, King Arthur and his squi...
4[11, 15): 'King'PROPNKing[0, 129): 'In AD 932, King Arthur and his squi...
...............
142[606, 613): 'finding'VERBfind[513, 629): 'As they turn away, God (an image ...
143[614, 617): 'the'DETthe[513, 629): 'As they turn away, God (an image ...
144[618, 622): 'Holy'PROPNNone[513, 629): 'As they turn away, God (an image ...
145[623, 628): 'Grail'PROPNNone[513, 629): 'As they turn away, God (an image ...
146[628, 629): '.'PUNCTNone[513, 629): 'As they turn away, God (an image ...
\n", "

147 rows × 4 columns

\n", "
" ], "text/plain": [ " span part_of_speech lemma \\\n", "0 [0, 2): 'In' ADP in \n", "1 [3, 5): 'AD' PROPN Ad \n", "2 [6, 9): '932' NUM None \n", "3 [9, 10): ',' PUNCT None \n", "4 [11, 15): 'King' PROPN King \n", ".. ... ... ... \n", "142 [606, 613): 'finding' VERB find \n", "143 [614, 617): 'the' DET the \n", "144 [618, 622): 'Holy' PROPN None \n", "145 [623, 628): 'Grail' PROPN None \n", "146 [628, 629): '.' PUNCT None \n", "\n", " sentence \n", "0 [0, 129): 'In AD 932, King Arthur and his squi... \n", "1 [0, 129): 'In AD 932, King Arthur and his squi... \n", "2 [0, 129): 'In AD 932, King Arthur and his squi... \n", "3 [0, 129): 'In AD 932, King Arthur and his squi... \n", "4 [0, 129): 'In AD 932, King Arthur and his squi... \n", ".. ... \n", "142 [513, 629): 'As they turn away, God (an image ... \n", "143 [513, 629): 'As they turn away, God (an image ... \n", "144 [513, 629): 'As they turn away, God (an image ... \n", "145 [513, 629): 'As they turn away, God (an image ... \n", "146 [513, 629): 'As they turn away, God (an image ... \n", "\n", "[147 rows x 4 columns]" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "syntax_df = dfs[\"syntax\"]\n", "syntax_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The DataFrame has one row for every token in the document. Each row has information on\n", "the span of the token, its part of speech, its lemmatized form, and the span of the \n", "containing sentence.\n", "\n", "Let's use this DataFrame to perform our example task a second time." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
sentencespan
7[0, 129): 'In AD 932, King Arthur and his squi...[27, 30): 'his'
31[130, 361): 'Along the way, he recruits Sir Be...[145, 147): 'he'
73[130, 361): 'Along the way, he recruits Sir Be...[301, 305): 'this'
79[130, 361): 'Along the way, he recruits Sir Be...[323, 328): 'their'
104[362, 512): 'Arthur leads the men to Camelot, ...[455, 457): 'he'
111[362, 512): 'Arthur leads the men to Camelot, ...[490, 492): 'it'
120[513, 629): 'As they turn away, God (an image ...[516, 520): 'they'
135[513, 629): 'As they turn away, God (an image ...[572, 576): 'them'
\n", "
" ], "text/plain": [ " sentence span\n", "7 [0, 129): 'In AD 932, King Arthur and his squi... [27, 30): 'his'\n", "31 [130, 361): 'Along the way, he recruits Sir Be... [145, 147): 'he'\n", "73 [130, 361): 'Along the way, he recruits Sir Be... [301, 305): 'this'\n", "79 [130, 361): 'Along the way, he recruits Sir Be... [323, 328): 'their'\n", "104 [362, 512): 'Arthur leads the men to Camelot, ... [455, 457): 'he'\n", "111 [362, 512): 'Arthur leads the men to Camelot, ... [490, 492): 'it'\n", "120 [513, 629): 'As they turn away, God (an image ... [516, 520): 'they'\n", "135 [513, 629): 'As they turn away, God (an image ... [572, 576): 'them'" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pronouns_by_sentence = syntax_df[syntax_df[\"part_of_speech\"] == \"PRON\"][[\"sentence\", \"span\"]]\n", "pronouns_by_sentence" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's it. With the DataFrame version of this data, we can perform our example task with **one line of code**.\n", "\n", "Specifically, we use a Pandas selection condition to filter out the tokens that aren't pronouns, and then we \n", "project down to the columns containing sentence and token spans. The result is another DataFrame that \n", "we can display directly in our Jupyter notebook." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# How it Works\n", "\n", "\n", "Let's take a moment to drill into the internals of the DataFrames we just used.\n", "For reference, here are the first three rows of the syntax analysis DataFrame:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
spanpart_of_speechlemmasentence
0[0, 2): 'In'ADPin[0, 129): 'In AD 932, King Arthur and his squi...
1[3, 5): 'AD'PROPNAd[0, 129): 'In AD 932, King Arthur and his squi...
2[6, 9): '932'NUMNone[0, 129): 'In AD 932, King Arthur and his squi...
\n", "
" ], "text/plain": [ " span part_of_speech lemma \\\n", "0 [0, 2): 'In' ADP in \n", "1 [3, 5): 'AD' PROPN Ad \n", "2 [6, 9): '932' NUM None \n", "\n", " sentence \n", "0 [0, 129): 'In AD 932, King Arthur and his squi... \n", "1 [0, 129): 'In AD 932, King Arthur and his squi... \n", "2 [0, 129): 'In AD 932, King Arthur and his squi... " ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "syntax_df.head(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And here is that DataFrame's data type information:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "span SpanDtype\n", "part_of_speech object\n", "lemma object\n", "sentence TokenSpanDtype\n", "dtype: object" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "syntax_df.dtypes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Two of the columns in this DataFrame — \"span\" and \"sentence\" — contain\n", "extension types from the Text Extensions for Pandas library. Let's look first at the \"span\"\n", "column. \n", "\n", "The \"span\" column is stored internally using the class `SpanArray` from \n", "Text Extensions for Pandas.\n", "`SpanArray` is a subclass of \n", "[`ExtensionArray`](\n", " https://pandas.pydata.org/docs/reference/api/pandas.api.extensions.ExtensionArray.html), \n", "the base class for custom 1-D array types in Pandas.\n", "\n", "You can use the property [`pandas.Series.array`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.array.html) to access the `ExtensionArray` behind any Pandas extension type:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "[ [0, 2): 'In', [3, 5): 'AD', [6, 9): '932',\n", " [9, 10): ',', [11, 15): 'King', [16, 22): 'Arthur',\n", " [23, 26): 'and', [27, 30): 'his', [31, 37): 'squire',\n", " [37, 38): ',',\n", " ...\n", " [581, 586): 'gives', [587, 593): 'Arthur', [594, 597): 'the',\n", " [598, 602): 'task', [603, 605): 'of', [606, 613): 'finding',\n", " [614, 617): 'the', [618, 622): 'Holy', [623, 628): 'Grail',\n", " [628, 629): '.']\n", "Length: 147, dtype: SpanDtype\n" ] } ], "source": [ "print(syntax_df[\"span\"].array)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Internally, a `SpanArray` is stored as Numpy arrays of begin and end offsets, plus a Python string \n", "containing the target text. You can access this internal data as properties if your application needs that\n", "information:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(array([ 0, 3, 6, 9, 11, 16, 23, 27, 31, 37]),\n", " array([ 2, 5, 9, 10, 15, 22, 26, 30, 37, 38]))" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "syntax_df[\"span\"].array.begin[:10], syntax_df[\"span\"].array.end[:10]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also convert an individual element of the array into a Python object of type `Span`:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\"[0, 2): 'In'\" is an object of type \n" ] } ], "source": [ "span_obj = syntax_df[\"span\"].array[0]\n", "print(f\"\\\"{span_obj}\\\" is an object of type {type(span_obj)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or you can convert the entire array (or a slice of it) into Python objects, one object per span:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0, 2): 'In', [3, 5): 'AD', [6, 9): '932', [9, 10): ',',\n", " [11, 15): 'King', [16, 22): 'Arthur', [23, 26): 'and',\n", " [27, 30): 'his', [31, 37): 'squire', [37, 38): ','], dtype=object)" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "syntax_df[\"span\"].iloc[:10].to_numpy()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A `SpanArray` can also render itself using [Jupyter Notebook callbacks](https://ipython.readthedocs.io/en/stable/config/integrating.html). To\n", "see the HTML representation of the `SpanArray`, pass the array object\n", "to Jupyter's [`display()`](https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html#IPython.display.display)\n", "function; or make that object be the last line of the cell, as in the following example:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "
\n", "\n", "
\n", " \n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", "
beginendcontext
002In
135AD
269932
3910,
41115King
51622Arthur
62326and
72730his
83137squire
93738,
\n", "

\n", "\n", "\n", "\n", " In\n", "\n", "\n", "\n", " AD\n", "\n", "\n", "\n", " 932\n", "\n", "\n", "\n", " ,\n", "\n", "\n", "\n", " King\n", "\n", "\n", "\n", " Arthur\n", "\n", "\n", "\n", " and\n", "\n", "\n", "\n", " his\n", "\n", "\n", "\n", " squire\n", "\n", "\n", "\n", " ,\n", " Patsy, travel throughout Britain searching for men to join the Knights of the Round Table. Along the way, he recruits Sir Bedevere the Wise, Sir Lancelot the Brave, Sir Galahad the Pure, Sir Robin the Not-Quite-So-Brave-as-Sir-Lancelot, and Sir Not-Appearing-in-this-Film, along with their squires and Robin's troubadours. Arthur leads the men to Camelot, but upon further consideration (thanks to a musical number) he decides not to go there because it is "a silly place". As they turn away, God (an image of W. G. Grace) speaks to them and gives Arthur the task of finding the Holy Grail.\n", "

\n", "
\n", "\n", " Your notebook viewer does not support Javascript execution. The above rendering will not be interactive.\n", "
\n", "\n", "\n" ], "text/plain": [ "\n", "[ [0, 2): 'In', [3, 5): 'AD', [6, 9): '932',\n", " [9, 10): ',', [11, 15): 'King', [16, 22): 'Arthur',\n", " [23, 26): 'and', [27, 30): 'his', [31, 37): 'squire',\n", " [37, 38): ',']\n", "Length: 10, dtype: SpanDtype" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Show the first 10 tokens in context\n", "syntax_df[\"span\"].iloc[:10].array" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's take another look at our DataFrame of syntax information:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
spanpart_of_speechlemmasentence
0[0, 2): 'In'ADPin[0, 129): 'In AD 932, King Arthur and his squi...
1[3, 5): 'AD'PROPNAd[0, 129): 'In AD 932, King Arthur and his squi...
2[6, 9): '932'NUMNone[0, 129): 'In AD 932, King Arthur and his squi...
\n", "
" ], "text/plain": [ " span part_of_speech lemma \\\n", "0 [0, 2): 'In' ADP in \n", "1 [3, 5): 'AD' PROPN Ad \n", "2 [6, 9): '932' NUM None \n", "\n", " sentence \n", "0 [0, 129): 'In AD 932, King Arthur and his squi... \n", "1 [0, 129): 'In AD 932, King Arthur and his squi... \n", "2 [0, 129): 'In AD 932, King Arthur and his squi... " ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "syntax_df.head(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The \"sentence\" column is backed by an object of type `TokenSpanArray`.\n", "`TokenSpanArray`, another extension type from Text Extensions for Pandas,\n", "is a version of `SpanArray` for representing a set of spans that are \n", "constrained to begin and end on token boundaries. In addition to all the\n", "functionality of a `SpanArray`, a `TokenSpanArray` encodes additional \n", "information about the relationships between its spans and a tokenization\n", "of the document.\n", "\n", "Here are the distinct elements of the \"sentence\" column rendered as HTML:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "
\n", "\n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", "
beginendbegin tokenend tokencontext
00129027In AD 932, King Arthur and his squire, Patsy, travel throughout Britain searching for men to join the Knights of the Round Table.
11303612786Along the way, he recruits Sir Bedevere the Wise, Sir Lancelot the Brave, Sir Galahad the Pure, Sir Robin the Not-Quite-So-Brave-as-Sir-Lancelot, and Sir Not-Appearing-in-this-Film, along with their squires and Robin's troubadours.
236251286119Arthur leads the men to Camelot, but upon further consideration (thanks to a musical number) he decides not to go there because it is "a silly place".
3513629119147As they turn away, God (an image of W. G. Grace) speaks to them and gives Arthur the task of finding the Holy Grail.
\n", "

\n", "\n", "\n", "\n", " In AD 932, King Arthur and his squire, Patsy, travel throughout Britain searching for men to join the Knights of the Round Table.\n", "\n", "\n", "\n", " Along the way, he recruits Sir Bedevere the Wise, Sir Lancelot the Brave, Sir Galahad the Pure, Sir Robin the Not-Quite-So-Brave-as-Sir-Lancelot, and Sir Not-Appearing-in-this-Film, along with their squires and Robin's troubadours.\n", "\n", "\n", "\n", " Arthur leads the men to Camelot, but upon further consideration (thanks to a musical number) he decides not to go there because it is "a silly place".\n", "\n", "\n", "\n", " As they turn away, God (an image of W. G. Grace) speaks to them and gives Arthur the task of finding the Holy Grail.\n", "\n", "

\n", "
\n", "\n", " Your notebook viewer does not support Javascript execution. The above rendering will not be interactive.\n", "
\n", "\n", "\n" ], "text/plain": [ "\n", "[ [0, 129): 'In AD 932, King Arthur and his squire, Patsy, travel throughout Britain [...]',\n", " [130, 361): 'Along the way, he recruits Sir Bedevere the Wise, Sir Lancelot the Brave, [...]',\n", " [362, 512): 'Arthur leads the men to Camelot, but upon further consideration (thanks to [...]',\n", " [513, 629): 'As they turn away, God (an image of W. G. Grace) speaks to them and gives [...]']\n", "Length: 4, dtype: TokenSpanDtype" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "syntax_df[\"sentence\"].unique()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As the table in the previous cell's output shows, each span in the `TokenSpanArray` has begin and end offsets in terms \n", "of both characters and tokens. Internally, the `TokenSpanArray` is stored as follows:\n", "* A Numpy array of begin offsets, measured in tokens\n", "* A Numpy array of end offsets in tokens\n", "* A reference to a `SpanArray` of spans representing the tokens\n", "\n", "The `TokenSpanArray` object computes the character offsets and covered text of its spans on demand.\n", "\n", "Applications can access the internals of a `TokenSpanArray` via the properties `begin_token`, `end_token`, and `document_tokens`:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Offset information (stored in the TokenSpanArray):\n", "`begin_token` property: [ 0 27 86 119]\n", " `end_token` property: [ 27 86 119 147]\n", " \n", "Token information (`document_tokens` property, shared among mulitple TokenSpanArrays):\n", "\n", "[ [0, 2): 'In', [3, 5): 'AD', [6, 9): '932',\n", " [9, 10): ',', [11, 15): 'King', [16, 22): 'Arthur',\n", " [23, 26): 'and', [27, 30): 'his', [31, 37): 'squire',\n", " [37, 38): ',',\n", " ...\n", " [581, 586): 'gives', [587, 593): 'Arthur', [594, 597): 'the',\n", " [598, 602): 'task', [603, 605): 'of', [606, 613): 'finding',\n", " [614, 617): 'the', [618, 622): 'Holy', [623, 628): 'Grail',\n", " [628, 629): '.']\n", "Length: 147, dtype: SpanDtype\n", "\n" ] } ], "source": [ "token_span_array = syntax_df[\"sentence\"].unique()\n", "print(f\"\"\"\n", "Offset information (stored in the TokenSpanArray):\n", "`begin_token` property: {token_span_array.begin_token}\n", " `end_token` property: {token_span_array.end_token}\n", " \n", "Token information (`document_tokens` property, shared among mulitple TokenSpanArrays):\n", "{token_span_array.document_tokens}\n", "\"\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The extension types in Text Extensions for Pandas support the full set of Pandas array operations. For example, we can build up a DataFrame of the spans of all sentences in the document by applying `pandas.DataFrame.drop_duplicates()` to the `sentence` column:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
sentence
0[0, 129): 'In AD 932, King Arthur and his squi...
27[130, 361): 'Along the way, he recruits Sir Be...
86[362, 512): 'Arthur leads the men to Camelot, ...
119[513, 629): 'As they turn away, God (an image ...
\n", "
" ], "text/plain": [ " sentence\n", "0 [0, 129): 'In AD 932, King Arthur and his squi...\n", "27 [130, 361): 'Along the way, he recruits Sir Be...\n", "86 [362, 512): 'Arthur leads the men to Camelot, ...\n", "119 [513, 629): 'As they turn away, God (an image ..." ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "syntax_df[[\"sentence\"]].drop_duplicates()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# A More Complex Example\n", "\n", "Now that we've had an introduction to the Text Extensions for Pandas span types, let's take another\n", "look at the DataFrame that our \"find pronouns by sentence\" code produced:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
sentencespan
7[0, 129): 'In AD 932, King Arthur and his squi...[27, 30): 'his'
31[130, 361): 'Along the way, he recruits Sir Be...[145, 147): 'he'
73[130, 361): 'Along the way, he recruits Sir Be...[301, 305): 'this'
79[130, 361): 'Along the way, he recruits Sir Be...[323, 328): 'their'
104[362, 512): 'Arthur leads the men to Camelot, ...[455, 457): 'he'
111[362, 512): 'Arthur leads the men to Camelot, ...[490, 492): 'it'
120[513, 629): 'As they turn away, God (an image ...[516, 520): 'they'
135[513, 629): 'As they turn away, God (an image ...[572, 576): 'them'
\n", "
" ], "text/plain": [ " sentence span\n", "7 [0, 129): 'In AD 932, King Arthur and his squi... [27, 30): 'his'\n", "31 [130, 361): 'Along the way, he recruits Sir Be... [145, 147): 'he'\n", "73 [130, 361): 'Along the way, he recruits Sir Be... [301, 305): 'this'\n", "79 [130, 361): 'Along the way, he recruits Sir Be... [323, 328): 'their'\n", "104 [362, 512): 'Arthur leads the men to Camelot, ... [455, 457): 'he'\n", "111 [362, 512): 'Arthur leads the men to Camelot, ... [490, 492): 'it'\n", "120 [513, 629): 'As they turn away, God (an image ... [516, 520): 'they'\n", "135 [513, 629): 'As they turn away, God (an image ... [572, 576): 'them'" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pronouns_by_sentence" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This DataFrame contains two columns backed by Text Extensions for Pandas span types:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "sentence TokenSpanDtype\n", "span SpanDtype\n", "dtype: object" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pronouns_by_sentence.dtypes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That means that we can use the full power of Pandas' high-level operations on this DataFrame. \n", "Let's use the output of our earlier task to build up a more complex task: \n", "*Highlight all pronouns in sentences containing the word \"Arthur\"*" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "
\n", "\n", "
\n", " \n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", " \n", " \n", " \n", "\n", " \n", " \n", "\n", " \n", "
beginendcontext
02730his
1455457he
2490492it
3516520they
4572576them
\n", "

\n", "\n", " In AD 932, King Arthur and \n", "\n", " his\n", "\n", " squire, Patsy, travel throughout Britain searching for men to join the Knights of the Round Table. Along the way, he recruits Sir Bedevere the Wise, Sir Lancelot the Brave, Sir Galahad the Pure, Sir Robin the Not-Quite-So-Brave-as-Sir-Lancelot, and Sir Not-Appearing-in-this-Film, along with their squires and Robin's troubadours. Arthur leads the men to Camelot, but upon further consideration (thanks to a musical number) \n", "\n", " he\n", "\n", " decides not to go there because \n", "\n", " it\n", "\n", " is "a silly place". As \n", "\n", " they\n", "\n", " turn away, God (an image of W. G. Grace) speaks to \n", "\n", " them\n", " and gives Arthur the task of finding the Holy Grail.\n", "

\n", "
\n", "\n", " Your notebook viewer does not support Javascript execution. The above rendering will not be interactive.\n", "
\n", "\n", "\n" ], "text/plain": [ "\n", "[ [27, 30): 'his', [455, 457): 'he', [490, 492): 'it',\n", " [516, 520): 'they', [572, 576): 'them']\n", "Length: 5, dtype: SpanDtype" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mask = pronouns_by_sentence[\"sentence\"].map(lambda s: s.covered_text).str.contains(\"Arthur\")\n", "pronouns_by_sentence[\"span\"][mask].values" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's another variation: *Pair each instance of the word \"Arthur\" with the pronouns that occur in the same sentence.*" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
arthur_spanpronoun_spansentence
0[16, 22): 'Arthur'[27, 30): 'his'[0, 129): 'In AD 932, King Arthur and his squi...
1[362, 368): 'Arthur'[455, 457): 'he'[362, 512): 'Arthur leads the men to Camelot, ...
2[362, 368): 'Arthur'[490, 492): 'it'[362, 512): 'Arthur leads the men to Camelot, ...
3[587, 593): 'Arthur'[516, 520): 'they'[513, 629): 'As they turn away, God (an image ...
4[587, 593): 'Arthur'[572, 576): 'them'[513, 629): 'As they turn away, God (an image ...
\n", "
" ], "text/plain": [ " arthur_span pronoun_span \\\n", "0 [16, 22): 'Arthur' [27, 30): 'his' \n", "1 [362, 368): 'Arthur' [455, 457): 'he' \n", "2 [362, 368): 'Arthur' [490, 492): 'it' \n", "3 [587, 593): 'Arthur' [516, 520): 'they' \n", "4 [587, 593): 'Arthur' [572, 576): 'them' \n", "\n", " sentence \n", "0 [0, 129): 'In AD 932, King Arthur and his squi... \n", "1 [362, 512): 'Arthur leads the men to Camelot, ... \n", "2 [362, 512): 'Arthur leads the men to Camelot, ... \n", "3 [513, 629): 'As they turn away, God (an image ... \n", "4 [513, 629): 'As they turn away, God (an image ... " ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(\n", " syntax_df[syntax_df[\"span\"].array.covered_text == \"Arthur\"] # Find instances of \"Arthur\"\n", " .merge(pronouns_by_sentence, on=\"sentence\") # Match with pronouns in the same sentence\n", " .rename(columns={\"span_x\": \"arthur_span\", \"span_y\": \"pronoun_span\"})\n", " [[\"arthur_span\", \"pronoun_span\", \"sentence\"]] # Reorder columns\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Other Outputs of Watson NLU as DataFrames \n", "\n", "The examples so far have used the DataFrame representation of Watson Natural Language Understanding's syntax analysis.\n", "In addition to syntax analysis, Watson NLU can perform several other types of analysis. Let's take a look at the \n", "DataFrames that Text Extensions for Pandas can produce from the output of Watson NLU.\n", "\n", "We'll start by revisiting the results of our earlier code that ran \n", "```python\n", "dfs = tp.io.watson.nlu.parse_response(response)\n", "```\n", "over the `response` object that the Watson NLU's Python API returned. `dfs` is a dictionary of DataFrames." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dict_keys(['syntax', 'entities', 'entity_mentions', 'keywords', 'relations', 'semantic_roles'])" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dfs.keys()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The \"syntax\" element of `dfs` contains the syntax analysis DataFrame that we showed earlier.\n", "Let's take a look at the other elements." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The \"entities\" element of `dfs` contains the named entities that Watson Natural Language \n", "Understanding found in the document." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
typetextsentiment.labelsentiment.scorerelevancecountconfidencedisambiguation.subtypedisambiguation.namedisambiguation.dbpedia_resource
0PersonSir Bedeverepositive0.8358730.95056010.982315NoneNoneNone
1PersonKing Arthurneutral0.0000000.72038110.924937NoneNoneNone
2PersonPatsyneutral0.0000000.67930010.830596NoneNoneNone
3PersonSir Lancelotpositive0.8358730.66290210.956371[MusicalArtist, TVActor]Sir_Lancelot_%28singer%29http://dbpedia.org/resource/Sir_Lancelot_%28si...
4PersonSir Galahadpositive0.8358730.65417010.948409NoneNoneNone
\n", "
" ], "text/plain": [ " type text sentiment.label sentiment.score relevance count \\\n", "0 Person Sir Bedevere positive 0.835873 0.950560 1 \n", "1 Person King Arthur neutral 0.000000 0.720381 1 \n", "2 Person Patsy neutral 0.000000 0.679300 1 \n", "3 Person Sir Lancelot positive 0.835873 0.662902 1 \n", "4 Person Sir Galahad positive 0.835873 0.654170 1 \n", "\n", " confidence disambiguation.subtype disambiguation.name \\\n", "0 0.982315 None None \n", "1 0.924937 None None \n", "2 0.830596 None None \n", "3 0.956371 [MusicalArtist, TVActor] Sir_Lancelot_%28singer%29 \n", "4 0.948409 None None \n", "\n", " disambiguation.dbpedia_resource \n", "0 None \n", "1 None \n", "2 None \n", "3 http://dbpedia.org/resource/Sir_Lancelot_%28si... \n", "4 None " ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dfs[\"entities\"].head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The \"entity_mentions\" element of `dfs` contains the locations of individual mentions of\n", "entities from the \"entities\" DataFrame. " ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
typetextspanconfidence
0PersonSir Bedevere[157, 169): 'Sir Bedevere'0.982315
1PersonKing Arthur[11, 22): 'King Arthur'0.924937
2PersonPatsy[39, 44): 'Patsy'0.830596
3PersonSir Lancelot[180, 192): 'Sir Lancelot'0.956371
4PersonSir Galahad[204, 215): 'Sir Galahad'0.948409
\n", "
" ], "text/plain": [ " type text span confidence\n", "0 Person Sir Bedevere [157, 169): 'Sir Bedevere' 0.982315\n", "1 Person King Arthur [11, 22): 'King Arthur' 0.924937\n", "2 Person Patsy [39, 44): 'Patsy' 0.830596\n", "3 Person Sir Lancelot [180, 192): 'Sir Lancelot' 0.956371\n", "4 Person Sir Galahad [204, 215): 'Sir Galahad' 0.948409" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dfs[\"entity_mentions\"].head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the DataFrame under \"entitiy_mentions\" may contain multiple mentions of the same\n", "name:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
typetextspanconfidence
10PersonArthur[362, 368): 'Arthur'0.996876
11PersonArthur[587, 593): 'Arthur'0.973795
\n", "
" ], "text/plain": [ " type text span confidence\n", "10 Person Arthur [362, 368): 'Arthur' 0.996876\n", "11 Person Arthur [587, 593): 'Arthur' 0.973795" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arthur_mentions = dfs[\"entity_mentions\"][dfs[\"entity_mentions\"][\"text\"] == \"Arthur\"]\n", "arthur_mentions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The \"type\" and \"text\" columns of the \"entity_mentions\" DataFrame refer back to the \n", "\"entities\" DataFrame columns of the same names.\n", "You can combine the global and local information about entities into a single DataFrame\n", "using Pandas' `DataFrame.merge()` method:" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
typetextspanconfidence_mentionsentiment.labelsentiment.scorerelevancecountconfidence_entitydisambiguation.subtypedisambiguation.namedisambiguation.dbpedia_resource
0PersonArthur[362, 368): 'Arthur'0.996876positive0.7219190.31165320.999918NoneNoneNone
1PersonArthur[587, 593): 'Arthur'0.973795positive0.7219190.31165320.999918NoneNoneNone
\n", "
" ], "text/plain": [ " type text span confidence_mention sentiment.label \\\n", "0 Person Arthur [362, 368): 'Arthur' 0.996876 positive \n", "1 Person Arthur [587, 593): 'Arthur' 0.973795 positive \n", "\n", " sentiment.score relevance count confidence_entity \\\n", "0 0.721919 0.311653 2 0.999918 \n", "1 0.721919 0.311653 2 0.999918 \n", "\n", " disambiguation.subtype disambiguation.name disambiguation.dbpedia_resource \n", "0 None None None \n", "1 None None None " ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "arthur_mentions.merge(dfs[\"entities\"], on=[\"type\", \"text\"], suffixes=[\"_mention\", \"_entity\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Watson Natural Language Understanding has several other models besides the `entities` and `syntax` models. Text Extensions for Pandas can also convert these other outputs. Here's the output of the `keywords` model on our example document:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
textsentiment.labelsentiment.scorerelevanceemotion.sadnessemotion.joyemotion.fearemotion.disgustemotion.angercount
0Sir Bedeverepositive0.8358730.8843590.0313010.4963180.1356500.0155450.0229611
1King Arthurneutral0.0000000.8508740.4412300.3305590.0437140.0200160.0259051
2Sir Lancelotpositive0.8358730.8236450.0313010.4963180.1356500.0155450.0229611
3image of W. G. Gracepositive0.7219190.7220260.0441300.9012050.0397730.0128380.0275991
4musical numberneutral0.0000000.6214320.3122460.1743430.0327260.0777070.0455921
\n", "
" ], "text/plain": [ " text sentiment.label sentiment.score relevance \\\n", "0 Sir Bedevere positive 0.835873 0.884359 \n", "1 King Arthur neutral 0.000000 0.850874 \n", "2 Sir Lancelot positive 0.835873 0.823645 \n", "3 image of W. G. Grace positive 0.721919 0.722026 \n", "4 musical number neutral 0.000000 0.621432 \n", "\n", " emotion.sadness emotion.joy emotion.fear emotion.disgust emotion.anger \\\n", "0 0.031301 0.496318 0.135650 0.015545 0.022961 \n", "1 0.441230 0.330559 0.043714 0.020016 0.025905 \n", "2 0.031301 0.496318 0.135650 0.015545 0.022961 \n", "3 0.044130 0.901205 0.039773 0.012838 0.027599 \n", "4 0.312246 0.174343 0.032726 0.077707 0.045592 \n", "\n", " count \n", "0 1 \n", "1 1 \n", "2 1 \n", "3 1 \n", "4 1 " ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dfs[\"keywords\"].head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Take a look at the notebook [Sentiment_Analysis.ipynb](./Sentiment_Analysis.ipynb) for more information on the `keywords` model and its sentiment-related outputs.\n", "\n", "Watson Natural Language Understanding also has a `relations` model that finds relationships between pairs of nouns:" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
typesentence_spanscorearguments.0.spanarguments.1.spanarguments.0.entities.typearguments.1.entities.typearguments.0.entities.textarguments.1.entities.text
0partOfMany[130, 361): 'Along the way, he recruits Sir Be...0.610221[208, 215): 'Galahad'[323, 328): 'their'PersonPersonGalahadtheir
1partOfMany[130, 361): 'Along the way, he recruits Sir Be...0.710112[266, 274): 'Lancelot'[323, 328): 'their'PersonPersonLancelottheir
2parentOf[130, 361): 'Along the way, he recruits Sir Be...0.382100[323, 328): 'their'[329, 336): 'squires'PersonPersontheirsquires
3residesIn[362, 512): 'Arthur leads the men to Camelot, ...0.492869[362, 368): 'Arthur'[386, 393): 'Camelot'PersonGeopoliticalEntityKing ArthurCamelot
4locatedAt[362, 512): 'Arthur leads the men to Camelot, ...0.339446[379, 382): 'men'[386, 393): 'Camelot'PersonGeopoliticalEntitymenCamelot
\n", "
" ], "text/plain": [ " type sentence_span score \\\n", "0 partOfMany [130, 361): 'Along the way, he recruits Sir Be... 0.610221 \n", "1 partOfMany [130, 361): 'Along the way, he recruits Sir Be... 0.710112 \n", "2 parentOf [130, 361): 'Along the way, he recruits Sir Be... 0.382100 \n", "3 residesIn [362, 512): 'Arthur leads the men to Camelot, ... 0.492869 \n", "4 locatedAt [362, 512): 'Arthur leads the men to Camelot, ... 0.339446 \n", "\n", " arguments.0.span arguments.1.span arguments.0.entities.type \\\n", "0 [208, 215): 'Galahad' [323, 328): 'their' Person \n", "1 [266, 274): 'Lancelot' [323, 328): 'their' Person \n", "2 [323, 328): 'their' [329, 336): 'squires' Person \n", "3 [362, 368): 'Arthur' [386, 393): 'Camelot' Person \n", "4 [379, 382): 'men' [386, 393): 'Camelot' Person \n", "\n", " arguments.1.entities.type arguments.0.entities.text \\\n", "0 Person Galahad \n", "1 Person Lancelot \n", "2 Person their \n", "3 GeopoliticalEntity King Arthur \n", "4 GeopoliticalEntity men \n", "\n", " arguments.1.entities.text \n", "0 their \n", "1 their \n", "2 squires \n", "3 Camelot \n", "4 Camelot " ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dfs[\"relations\"].head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `semantic_roles` model identifies places where the document describes events and extracts a subject-verb-object triple for each such event: " ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
subject.textsentenceobject.textaction.verb.textaction.verb.tenseaction.textaction.normalized
0for menIn AD 932, King Arthur and his squire, Patsy, ...the Knights of the Round Tablejoininfinitivejoinjoin
1heAlong the way, he recruits Sir Bedevere the Wi...Sir Bedevere the Wise, Sir Lancelot the Brave,...recruitpresentrecruitsrecruit
2ArthurArthur leads the men to Camelot, but upon furt...the men to Camelotleadpresentleadslead
3heArthur leads the men to Camelot, but upon furt...not to go there because it is \"a silly place\"decidepresentdecidesdecide
4heArthur leads the men to Camelot, but upon furt...Nonegoinfinitivegogo
\n", "
" ], "text/plain": [ " subject.text sentence \\\n", "0 for men In AD 932, King Arthur and his squire, Patsy, ... \n", "1 he Along the way, he recruits Sir Bedevere the Wi... \n", "2 Arthur Arthur leads the men to Camelot, but upon furt... \n", "3 he Arthur leads the men to Camelot, but upon furt... \n", "4 he Arthur leads the men to Camelot, but upon furt... \n", "\n", " object.text action.verb.text \\\n", "0 the Knights of the Round Table join \n", "1 Sir Bedevere the Wise, Sir Lancelot the Brave,... recruit \n", "2 the men to Camelot lead \n", "3 not to go there because it is \"a silly place\" decide \n", "4 None go \n", "\n", " action.verb.tense action.text action.normalized \n", "0 infinitive join join \n", "1 present recruits recruit \n", "2 present leads lead \n", "3 present decides decide \n", "4 infinitive go go " ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dfs[\"semantic_roles\"].head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Take a look at our [market intelligence tutorial](../tutorials/market/Market_Intelligence_Part1.ipynb) to learn more about the `semantic_roles` model." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.8.17" } }, "nbformat": 4, "nbformat_minor": 4 }