diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..e69de29b diff --git a/notebooks/Understand_Tables.ipynb b/notebooks/Understand_Tables.ipynb deleted file mode 100644 index 670b6970..00000000 --- a/notebooks/Understand_Tables.ipynb +++ /dev/null @@ -1,4483 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Understand_Tables.ipynb:\n", - "

\n", - "Extract Structured Information from Tables in PDF Documents\n", - " using IBM Watson Discovery and Text Extensions for Pandas\n", - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Introduction\n", - "\n", - "Many organizations have valuable information hidden in tables inside human-readable documents like PDF files and web pages. Table identification and extraction technology can turn this human-readable information into a format that data science tools can import and use. Text Extensions for Pandas and Watson Discovery make this process much easier.\n", - "\n", - "In this notebook, we'll follow the journey of Allison, an analyst at an investment bank. Allison's employer has assigned her to cover several different companies, one of which is IBM. As part of her analysis, Allison wants to track IBM's revenue over time, broken down by geographical region. That detailed revenue information is all there in IBM's filings with the U.S. Securities and Exchange Commission (SEC). For example, here's IBM's 2019 annual report:\n", - "\n", - "![IBM Annual Report for 2019 (146 pages)](images/IBM_Annual_Report_2019.png)\n", - "\n", - "Did you see the table of revenue by geography? It's here, on page 39:\n", - "\n", - "![Page 39 of IBM Annual Report for 2019](images/IBM_Annual_Report_2019_page_39.png)\n", - "\n", - "Here's what that table looks like close up:\n", - "\n", - "![Table: Geographic Revenue (from IBM 2019 annual report)](images/screenshot_table_2019.png)\n", - "\n", - "But this particular table only gives two years' revenue figures. Allison needs to have enough data to draw a meaningful chart of revenue over time. 10 years of annual revenue figures would be a good starting point. \n", - "\n", - "Allison has a collection of IBM annual reports going back to 2009. In total, these documents contain about 1500 pages of financial information. Hidden inside those 1500 pages are the detailed revenue figures that Allison wants. She needs to find those figures, extract them from the documents, and import them into her data science tools.\n", - "\n", - "Fortunately, Allison has [Watson Discovery](https://www.ibm.com/cloud/watson-discovery), IBM's suite of tools for managing and extracting value from collections of human-readable documents.\n", - "\n", - "The cells that follow will show how Allison uses Text Extensions for Pandas and Watson Discovery to import the detailed revenue information from her PDF documents into a Pandas DataFrame...\n", - "\n", - "![Screenshot of a DataFrame from later in this notebook.](images/revenue_table.png)\n", - "\n", - "...that she then uses to generate a chart of revenue over time:\n", - "\n", - "![Chart of revenue over time, from later in this notebook.](images/revenue_over_time.png)\n", - "\n", - "But first, let's set your environment up so that you can run Allison's code yourself.\n", - "\n", - "(If you're just reading through the precomputed outputs of this notebook, you can skip ahead to the section labeled [\"Extract Tables with Watson Discovery\"](#watson_discovery))." - ] - }, - { - "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", - "* `matplotlib`\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**.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# Core Python libraries\n", - "import json\n", - "import os\n", - "import sys\n", - "from typing import *\n", - "import pandas as pd\n", - "from matplotlib import pyplot as plt\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": [ - "

\n", - "\n", - "# Extract Tables with Watson Discovery\n", - "\n", - "Allison connects to the [Watson Discovery](https://cloud.ibm.com/docs/discovery-data?topic=discovery-data-install) component of her firm's [IBM Cloud Pak for Data](\n", - "https://www.ibm.com/products/cloud-pak-for-data) installation on their [OpenShift](https://www.openshift.com/) cluster.\n", - "\n", - "She creates a new Watson Discovery project and uploads her stack of IBM annual reports to her project. Then she uses the Watson Discovery's [Table Understanding enrichment](https://cloud.ibm.com/docs/discovery-data?topic=discovery-data-understanding_tables) to identify tables in the PDF documents and to extract detailed information about the cells and headers that make up each table.\n", - "\n", - "To keep this notebook short, we've captured the output of Table Understanding on Allison's documents and checked it into Github [here](https://github.com/CODAIT/text-extensions-for-pandas/tree/master/resources/tables/Financial_table_demo/IBM_10-K). We will use these JSON files as input for the rest of this scenario. If you'd like to learn more about importing and managing document collections in Watson Discovery, take a look at the [Getting Started Guide for Watson Discovery](https://cloud.ibm.com/docs/discovery-data?topic=discovery-data-getting-started).\n", - "\n", - "Allison reads the JSON output from Watson Discovery's table enrichment into a Python variable, then prints out what the 2019 \"Geographic Revenue\" table looks like in this raw output." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"location\": {\n", - " \"begin\": 664612,\n", - " \"end\": 673296\n", - " },\n", - " \"text\": \"($ in millions)\\n For the year ended December 31: 2019 2018\\nYr.-to-Yr. Percent Change\\nYr.-to-Yr. Percent Change Adjusted for Currency\\nYr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency\\nTotal revenue $77,147 $79,591 (3.1 )% (1.0)% 0.2%\\nAmericas $36,274 $36,994 (1.9)% (1.1)% 0.8%\\nEurope/Middle East/Africa 24,443 25,491 (4.1) 0.4 1.3\\nAsia Pacific 16,430 17,106 (4.0) (3.0) (2.5)\\n\",\n", - " \"section_title\": {\n", - " \"location\": {\n", - " \"begin\": 663834,\n", - " \"end\": 663852\n", - " },\n", - " \"text\": \"Geographic Revenue\"\n", - " },\n", - " \"title\": {},\n", - " \"table_headers\": [\n", - " {\n", - " \"cell_id\": \"tableHeader-664612-664628\",\n", - " \"location\": {\n", - " \"begin\": 664612,\n", - " \"end\": 664628\n", - " },\n", - " \"text\": \"($ in millions)\",\n", - " \"row_index_begin\": 0,\n", - " \"row_index_end\": 0,\n", - " \"column_index_begin\": 0,\n", - " \"column_index_end\": 0\n", - " }\n", - " ],\n", - " \"row_headers\": [\n", - " {\n", - " \"cell_id\": \"rowHeader-667212-667226\",\n", - " \"location\": {\n", - " \"begin\": 667212,\n", - " \"end\": 667226\n", - " },\n", - " \"text\": \"Total revenue\",\n", - " \"text_normalized\": \"Total revenue\",\n", - " \"row_index_begin\": 2,\n", - " \"row_index_end\": 2,\n", - " \"column_index_begin\": 0,\n", - " \"column_index_end\": 0\n", - " },\n", - " {\n", - " \"cell_id\": \"rowHeader-668801-668810\",\n", - " \"location\": {\n", - " \"begin\": 668801,\n", - " \"end\": 668810\n", - " },\n", - " \"text\": \"Americas\",\n", - " \"text_normalized\": \"Americas\",\n", - " \"row_index_begin\": 3,\n", - " \"row_index_end\": 3,\n", - " \"column_index_begin\": 0,\n", - " \"column_index_end\": 0\n", - " },\n", - " {\n", - " \"cell_id\": \"rowHeader-670386-670412\",\n", - " \"location\": {\n", - " \"begin\": 670386,\n", - " \"end\": 670412\n", - " },\n", - " \"text\": \"Europe/Middle East/Africa\",\n", - " \"text_normalized\": \"Europe/Middle East/Africa\",\n", - " \"row_index_begin\": 4,\n", - " \"row_index_end\": 4,\n", - " \"column_index_begin\": 0,\n", - " \"column_index_end\": 0\n", - " },\n", - " {\n", - " \"cell_id\": \"rowHeader-671979-671992\",\n", - " \"location\": {\n", - " \"begin\": 671979,\n", - " \"end\": 671992\n", - " },\n", - " \"text\": \"Asia Pacific\",\n", - " \"text_normalized\": \"Asia Pacific\",\n", - " \"row_index_begin\": 5,\n", - " \"row_index_end\": 5,\n", - " \"column_index_begin\": 0,\n", - " \"column_index_end\": 0\n", - " }\n", - " ],\n", - " \"column_headers\": [\n", - " {\n", - " \"cell_id\": \"colHeader-664705-664706\",\n", - " \"location\": {\n", - " \"begin\": 664705,\n", - " \"end\": 664706\n", - " },\n", - " \"text\": \"\",\n", - " \"text_normalized\": \"\",\n", - " \"row_index_begin\": 0,\n", - " \"row_index_end\": 0,\n", - " \"column_index_begin\": 1,\n", - " \"column_index_end\": 1\n", - " },\n", - " {\n", - " \"cell_id\": \"colHeader-664770-664771\",\n", - " \"location\": {\n", - " \"begin\": 664770,\n", - " \"end\": 664771\n", - " },\n", - " \"text\": \"\",\n", - " \"text_normalized\": \"\",\n", - " \"row_index_begin\": 0,\n", - " \"row_index_end\": 0,\n", - " \"column_index_begin\": 2,\n", - " \"column_index_end\": 2\n", - " },\n", - " {\n", - " \"cell_id\": \"colHeader-664835-664836\",\n", - " \"location\": {\n", - " \"begin\": 664835,\n", - " \"end\": 664836\n", - " },\n", - " \"text\": \"\",\n", - " \"text_normalized\": \"\",\n", - " \"row_index_begin\": 0,\n", - " \"row_index_end\": 0,\n", - " \"column_index_begin\": 3,\n", - " \"column_index_end\": 3\n", - " },\n", - " {\n", - " \"cell_id\": \"colHeader-664900-664901\",\n", - " \"location\": {\n", - " \"begin\": 664900,\n", - " \"end\": 664901\n", - " },\n", - " \"text\": \"\",\n", - " \"text_normalized\": \"\",\n", - " \"row_index_begin\": 0,\n", - " \"row_index_end\": 0,\n", - " \"column_index_begin\": 4,\n", - " \"column_index_end\": 4\n", - " },\n", - " {\n", - " \"cell_id\": \"colHeader-664965-664966\",\n", - " \"location\": {\n", - " \"begin\": 664965,\n", - " \"end\": 664966\n", - " },\n", - " \"text\": \"\",\n", - " \"text_normalized\": \"\",\n", - " \"row_index_begin\": 0,\n", - " \"row_index_end\": 0,\n", - " \"column_index_begin\": 5,\n", - " \"column_index_end\": 5\n", - " },\n", - " {\n", - " \"cell_id\": \"colHeader-665217-665249\",\n", - " \"location\": {\n", - " \"begin\": 665217,\n", - " \"end\": 665249\n", - " },\n", - " \"text\": \"For the year ended December 31:\",\n", - " \"text_normalized\": \"For the year ended December 31:\",\n", - " \"row_index_begin\": 1,\n", - " \"row_index_end\": 1,\n", - " \"column_index_begin\": 0,\n", - " \"column_index_end\": 0\n", - " },\n", - " {\n", - " \"cell_id\": \"colHeader-665513-665518\",\n", - " \"location\": {\n", - " \"begin\": 665513,\n", - " \"end\": 665518\n", - " },\n", - " \"text\": \"2019\",\n", - " \"text_normalized\": \"2019\",\n", - " \"row_index_begin\": 1,\n", - " \"row_index_end\": 1,\n", - " \"column_index_begin\": 1,\n", - " \"column_index_end\": 1\n", - " },\n", - " {\n", - " \"cell_id\": \"colHeader-665788-665793\",\n", - " \"location\": {\n", - " \"begin\": 665788,\n", - " \"end\": 665793\n", - " },\n", - " \"text\": \"2018\",\n", - " \"text_normalized\": \"2018\",\n", - " \"row_index_begin\": 1,\n", - " \"row_index_end\": 1,\n", - " \"column_index_begin\": 2,\n", - " \"column_index_end\": 2\n", - " },\n", - " {\n", - " \"cell_id\": \"colHeader-666061-666087\",\n", - " \"location\": {\n", - " \"begin\": 666061,\n", - " \"end\": 666087\n", - " },\n", - " \"text\": \"Yr.-to-Yr. Percent Change\",\n", - " \"text_normalized\": \"Yr.-to-Yr. Percent Change\",\n", - " \"row_index_begin\": 1,\n", - " \"row_index_end\": 1,\n", - " \"column_index_begin\": 3,\n", - " \"column_index_end\": 3\n", - " },\n", - " {\n", - " \"cell_id\": \"colHeader-666356-666404\",\n", - " \"location\": {\n", - " \"begin\": 666356,\n", - " \"end\": 666404\n", - " },\n", - " \"text\": \"Yr.-to-Yr. Percent Change Adjusted for Currency\",\n", - " \"text_normalized\": \"Yr.-to-Yr. Percent Change Adjusted for Currency\",\n", - " \"row_index_begin\": 1,\n", - " \"row_index_end\": 1,\n", - " \"column_index_begin\": 4,\n", - " \"column_index_end\": 4\n", - " },\n", - " {\n", - " \"cell_id\": \"colHeader-666675-666948\",\n", - " \"location\": {\n", - " \"begin\": 666675,\n", - " \"end\": 666948\n", - " },\n", - " \"text\": \"Yr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency\",\n", - " \"text_normalized\": \"Yr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency\",\n", - " \"row_index_begin\": 1,\n", - " \"row_index_end\": 1,\n", - " \"column_index_begin\": 5,\n", - " \"column_index_end\": 5\n", - " }\n", - " ],\n", - " \"body_cells\": [\n", - " {\n", - " \"cell_id\": \"bodyCell-667480-667488\",\n", - " \"location\": {\n", - " \"begin\": 667480,\n", - " \"end\": 667488\n", - " },\n", - " \"text\": \"$77,147\",\n", - " \"row_index_begin\": 2,\n", - " \"row_index_end\": 2,\n", - " \"column_index_begin\": 1,\n", - " \"column_index_end\": 1,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-667212-667226\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Total revenue\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Total revenue\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664705-664706\",\n", - " \"colHeader-665513-665518\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"2019\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"2019\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Currency\",\n", - " \"text\": \"$77,147\",\n", - " \"location\": {\n", - " \"begin\": 667480,\n", - " \"end\": 667487\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-667744-667752\",\n", - " \"location\": {\n", - " \"begin\": 667744,\n", - " \"end\": 667752\n", - " },\n", - " \"text\": \"$79,591\",\n", - " \"row_index_begin\": 2,\n", - " \"row_index_end\": 2,\n", - " \"column_index_begin\": 2,\n", - " \"column_index_end\": 2,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-667212-667226\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Total revenue\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Total revenue\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664770-664771\",\n", - " \"colHeader-665788-665793\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"2018\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"2018\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Currency\",\n", - " \"text\": \"$79,591\",\n", - " \"location\": {\n", - " \"begin\": 667744,\n", - " \"end\": 667751\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-668006-668014\",\n", - " \"location\": {\n", - " \"begin\": 668006,\n", - " \"end\": 668014\n", - " },\n", - " \"text\": \"(3.1 )%\",\n", - " \"row_index_begin\": 2,\n", - " \"row_index_end\": 2,\n", - " \"column_index_begin\": 3,\n", - " \"column_index_end\": 3,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-667212-667226\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Total revenue\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Total revenue\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664835-664836\",\n", - " \"colHeader-666061-666087\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"3.1\",\n", - " \"location\": {\n", - " \"begin\": 668007,\n", - " \"end\": 668010\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-668266-668273\",\n", - " \"location\": {\n", - " \"begin\": 668266,\n", - " \"end\": 668273\n", - " },\n", - " \"text\": \"(1.0)%\",\n", - " \"row_index_begin\": 2,\n", - " \"row_index_end\": 2,\n", - " \"column_index_begin\": 4,\n", - " \"column_index_end\": 4,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-667212-667226\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Total revenue\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Total revenue\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664900-664901\",\n", - " \"colHeader-666356-666404\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change Adjusted for Currency\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change Adjusted for Currency\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"1.0\",\n", - " \"location\": {\n", - " \"begin\": 668267,\n", - " \"end\": 668270\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-668530-668535\",\n", - " \"location\": {\n", - " \"begin\": 668530,\n", - " \"end\": 668535\n", - " },\n", - " \"text\": \"0.2%\",\n", - " \"row_index_begin\": 2,\n", - " \"row_index_end\": 2,\n", - " \"column_index_begin\": 5,\n", - " \"column_index_end\": 5,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-667212-667226\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Total revenue\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Total revenue\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664965-664966\",\n", - " \"colHeader-666675-666948\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Percentage\",\n", - " \"text\": \"0.2%\",\n", - " \"location\": {\n", - " \"begin\": 668530,\n", - " \"end\": 668534\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-669065-669073\",\n", - " \"location\": {\n", - " \"begin\": 669065,\n", - " \"end\": 669073\n", - " },\n", - " \"text\": \"$36,274\",\n", - " \"row_index_begin\": 3,\n", - " \"row_index_end\": 3,\n", - " \"column_index_begin\": 1,\n", - " \"column_index_end\": 1,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-668801-668810\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Americas\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Americas\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664705-664706\",\n", - " \"colHeader-665513-665518\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"2019\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"2019\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Currency\",\n", - " \"text\": \"$36,274\",\n", - " \"location\": {\n", - " \"begin\": 669065,\n", - " \"end\": 669072\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-669330-669338\",\n", - " \"location\": {\n", - " \"begin\": 669330,\n", - " \"end\": 669338\n", - " },\n", - " \"text\": \"$36,994\",\n", - " \"row_index_begin\": 3,\n", - " \"row_index_end\": 3,\n", - " \"column_index_begin\": 2,\n", - " \"column_index_end\": 2,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-668801-668810\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Americas\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Americas\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664770-664771\",\n", - " \"colHeader-665788-665793\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"2018\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"2018\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Currency\",\n", - " \"text\": \"$36,994\",\n", - " \"location\": {\n", - " \"begin\": 669330,\n", - " \"end\": 669337\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-669591-669598\",\n", - " \"location\": {\n", - " \"begin\": 669591,\n", - " \"end\": 669598\n", - " },\n", - " \"text\": \"(1.9)%\",\n", - " \"row_index_begin\": 3,\n", - " \"row_index_end\": 3,\n", - " \"column_index_begin\": 3,\n", - " \"column_index_end\": 3,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-668801-668810\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Americas\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Americas\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664835-664836\",\n", - " \"colHeader-666061-666087\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"1.9\",\n", - " \"location\": {\n", - " \"begin\": 669592,\n", - " \"end\": 669595\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-669853-669860\",\n", - " \"location\": {\n", - " \"begin\": 669853,\n", - " \"end\": 669860\n", - " },\n", - " \"text\": \"(1.1)%\",\n", - " \"row_index_begin\": 3,\n", - " \"row_index_end\": 3,\n", - " \"column_index_begin\": 4,\n", - " \"column_index_end\": 4,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-668801-668810\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Americas\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Americas\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664900-664901\",\n", - " \"colHeader-666356-666404\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change Adjusted for Currency\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change Adjusted for Currency\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"1.1\",\n", - " \"location\": {\n", - " \"begin\": 669854,\n", - " \"end\": 669857\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-670116-670121\",\n", - " \"location\": {\n", - " \"begin\": 670116,\n", - " \"end\": 670121\n", - " },\n", - " \"text\": \"0.8%\",\n", - " \"row_index_begin\": 3,\n", - " \"row_index_end\": 3,\n", - " \"column_index_begin\": 5,\n", - " \"column_index_end\": 5,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-668801-668810\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Americas\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Americas\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664965-664966\",\n", - " \"colHeader-666675-666948\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Percentage\",\n", - " \"text\": \"0.8%\",\n", - " \"location\": {\n", - " \"begin\": 670116,\n", - " \"end\": 670120\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-670663-670670\",\n", - " \"location\": {\n", - " \"begin\": 670663,\n", - " \"end\": 670670\n", - " },\n", - " \"text\": \"24,443\",\n", - " \"row_index_begin\": 4,\n", - " \"row_index_end\": 4,\n", - " \"column_index_begin\": 1,\n", - " \"column_index_end\": 1,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-670386-670412\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Europe/Middle East/Africa\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Europe/Middle East/Africa\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664705-664706\",\n", - " \"colHeader-665513-665518\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"2019\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"2019\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"24,443\",\n", - " \"location\": {\n", - " \"begin\": 670663,\n", - " \"end\": 670669\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-670924-670931\",\n", - " \"location\": {\n", - " \"begin\": 670924,\n", - " \"end\": 670931\n", - " },\n", - " \"text\": \"25,491\",\n", - " \"row_index_begin\": 4,\n", - " \"row_index_end\": 4,\n", - " \"column_index_begin\": 2,\n", - " \"column_index_end\": 2,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-670386-670412\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Europe/Middle East/Africa\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Europe/Middle East/Africa\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664770-664771\",\n", - " \"colHeader-665788-665793\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"2018\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"2018\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"25,491\",\n", - " \"location\": {\n", - " \"begin\": 670924,\n", - " \"end\": 670930\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-671187-671193\",\n", - " \"location\": {\n", - " \"begin\": 671187,\n", - " \"end\": 671193\n", - " },\n", - " \"text\": \"(4.1)\",\n", - " \"row_index_begin\": 4,\n", - " \"row_index_end\": 4,\n", - " \"column_index_begin\": 3,\n", - " \"column_index_end\": 3,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-670386-670412\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Europe/Middle East/Africa\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Europe/Middle East/Africa\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664835-664836\",\n", - " \"colHeader-666061-666087\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"4.1\",\n", - " \"location\": {\n", - " \"begin\": 671188,\n", - " \"end\": 671191\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-671449-671453\",\n", - " \"location\": {\n", - " \"begin\": 671449,\n", - " \"end\": 671453\n", - " },\n", - " \"text\": \"0.4\",\n", - " \"row_index_begin\": 4,\n", - " \"row_index_end\": 4,\n", - " \"column_index_begin\": 4,\n", - " \"column_index_end\": 4,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-670386-670412\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Europe/Middle East/Africa\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Europe/Middle East/Africa\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664900-664901\",\n", - " \"colHeader-666356-666404\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change Adjusted for Currency\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change Adjusted for Currency\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"0.4\",\n", - " \"location\": {\n", - " \"begin\": 671449,\n", - " \"end\": 671452\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-671711-671715\",\n", - " \"location\": {\n", - " \"begin\": 671711,\n", - " \"end\": 671715\n", - " },\n", - " \"text\": \"1.3\",\n", - " \"row_index_begin\": 4,\n", - " \"row_index_end\": 4,\n", - " \"column_index_begin\": 5,\n", - " \"column_index_end\": 5,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-670386-670412\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Europe/Middle East/Africa\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Europe/Middle East/Africa\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664965-664966\",\n", - " \"colHeader-666675-666948\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"1.3\",\n", - " \"location\": {\n", - " \"begin\": 671711,\n", - " \"end\": 671714\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-672244-672251\",\n", - " \"location\": {\n", - " \"begin\": 672244,\n", - " \"end\": 672251\n", - " },\n", - " \"text\": \"16,430\",\n", - " \"row_index_begin\": 5,\n", - " \"row_index_end\": 5,\n", - " \"column_index_begin\": 1,\n", - " \"column_index_end\": 1,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-671979-671992\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Asia Pacific\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Asia Pacific\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664705-664706\",\n", - " \"colHeader-665513-665518\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"2019\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"2019\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"16,430\",\n", - " \"location\": {\n", - " \"begin\": 672244,\n", - " \"end\": 672250\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-672505-672512\",\n", - " \"location\": {\n", - " \"begin\": 672505,\n", - " \"end\": 672512\n", - " },\n", - " \"text\": \"17,106\",\n", - " \"row_index_begin\": 5,\n", - " \"row_index_end\": 5,\n", - " \"column_index_begin\": 2,\n", - " \"column_index_end\": 2,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-671979-671992\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Asia Pacific\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Asia Pacific\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664770-664771\",\n", - " \"colHeader-665788-665793\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"2018\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"2018\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"17,106\",\n", - " \"location\": {\n", - " \"begin\": 672505,\n", - " \"end\": 672511\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-672767-672773\",\n", - " \"location\": {\n", - " \"begin\": 672767,\n", - " \"end\": 672773\n", - " },\n", - " \"text\": \"(4.0)\",\n", - " \"row_index_begin\": 5,\n", - " \"row_index_end\": 5,\n", - " \"column_index_begin\": 3,\n", - " \"column_index_end\": 3,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-671979-671992\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Asia Pacific\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Asia Pacific\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664835-664836\",\n", - " \"colHeader-666061-666087\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"4.0\",\n", - " \"location\": {\n", - " \"begin\": 672768,\n", - " \"end\": 672771\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-673028-673034\",\n", - " \"location\": {\n", - " \"begin\": 673028,\n", - " \"end\": 673034\n", - " },\n", - " \"text\": \"(3.0)\",\n", - " \"row_index_begin\": 5,\n", - " \"row_index_end\": 5,\n", - " \"column_index_begin\": 4,\n", - " \"column_index_end\": 4,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-671979-671992\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Asia Pacific\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Asia Pacific\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664900-664901\",\n", - " \"colHeader-666356-666404\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change Adjusted for Currency\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change Adjusted for Currency\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"3.0\",\n", - " \"location\": {\n", - " \"begin\": 673029,\n", - " \"end\": 673032\n", - " }\n", - " }\n", - " ]\n", - " },\n", - " {\n", - " \"cell_id\": \"bodyCell-673290-673296\",\n", - " \"location\": {\n", - " \"begin\": 673290,\n", - " \"end\": 673296\n", - " },\n", - " \"text\": \"(2.5)\",\n", - " \"row_index_begin\": 5,\n", - " \"row_index_end\": 5,\n", - " \"column_index_begin\": 5,\n", - " \"column_index_end\": 5,\n", - " \"row_header_ids\": [\n", - " \"rowHeader-671979-671992\"\n", - " ],\n", - " \"row_header_texts\": [\n", - " \"Asia Pacific\"\n", - " ],\n", - " \"row_header_texts_normalized\": [\n", - " \"Asia Pacific\"\n", - " ],\n", - " \"column_header_ids\": [\n", - " \"colHeader-664965-664966\",\n", - " \"colHeader-666675-666948\"\n", - " ],\n", - " \"column_header_texts\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency\"\n", - " ],\n", - " \"column_header_texts_normalized\": [\n", - " \"\",\n", - " \"Yr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency\"\n", - " ],\n", - " \"attributes\": [\n", - " {\n", - " \"type\": \"Number\",\n", - " \"text\": \"2.5\",\n", - " \"location\": {\n", - " \"begin\": 673291,\n", - " \"end\": 673294\n", - " }\n", - " }\n", - " ]\n", - " }\n", - " ],\n", - " \"contexts\": [\n", - " {\n", - " \"location\": {\n", - " \"begin\": 664050,\n", - " \"end\": 664171\n", - " },\n", - " \"text\": \"In addition to the revenue presentation by reportable segment, we also measure revenue performance on a geographic basis.\"\n", - " },\n", - " {\n", - " \"location\": {\n", - " \"begin\": 673587,\n", - " \"end\": 674274\n", - " },\n", - " \"text\": \"Total revenue of $77,147 million in 2019 decreased 3.1 percent year to year as reported (1 percent adjusted for currency), but increased 0.2 percent excluding divested businesses and adjusted for currency.\"\n", - " },\n", - " {\n", - " \"location\": {\n", - " \"begin\": 674465,\n", - " \"end\": 674948\n", - " },\n", - " \"text\": \"Americas revenue decreased 1.9 percent as reported (1 percent adjusted for currency), but grew 1 percent excluding divested businesses and adjusted for currency.\"\n", - " }\n", - " ],\n", - " \"key_value_pairs\": []\n", - "}\n" - ] - } - ], - "source": [ - "# Location of the output from Watson Discovery's Table Understanding enrichment\n", - "# (relative to this notebook file)\n", - "FILES_DIR = \"../resources/tables/financial_table_demo/IBM_10-K\"\n", - "\n", - "with open(f\"{FILES_DIR}/2019.json\", \"r\") as f:\n", - " ibm_2019_json = json.load(f)\n", - "\n", - "# Find the table in the \"Geographic Revenue\" section.\n", - "table_index = [i for i in range(len(ibm_2019_json[\"tables\"])) \n", - " if ibm_2019_json[\"tables\"][i][\"section_title\"][\"text\"] == \"Geographic Revenue\"][0]\n", - "print(json.dumps(ibm_2019_json[\"tables\"][table_index], indent=2))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "That raw output contains everything Allison needs to extract the revenue figures from this document, but it's in a format that's cumbersome to deal with. So Allison uses Text Extensions for Pandas to convert this JSON into a collection of Pandas DataFrames. These DataFrames encode information about the row headers, column headers, and cells that make up the table." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "dict_keys(['row_headers', 'col_headers', 'body_cells', 'given_loc'])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "table_data = tp.io.watson.tables.parse_response(ibm_2019_json,\n", - " select_table=\"Geographic Revenue\")\n", - "table_data.keys()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "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", - "
textcolumn_index_begincolumn_index_endrow_index_beginrow_index_endcell_idcolumn_header_idscolumn_header_textsrow_header_idsrow_header_textsattributes.textattributes.type
0$77,1471122bodyCell-667480-667488[colHeader-664705-664706, colHeader-665513-665...[2019][rowHeader-667212-667226][Total revenue][$77,147][Currency]
1$79,5912222bodyCell-667744-667752[colHeader-664770-664771, colHeader-665788-665...[2018][rowHeader-667212-667226][Total revenue][$79,591][Currency]
2(3.1 )%3322bodyCell-668006-668014[colHeader-664835-664836, colHeader-666061-666...[Yr.-to-Yr. Percent Change][rowHeader-667212-667226][Total revenue][3.1][Number]
3(1.0)%4422bodyCell-668266-668273[colHeader-664900-664901, colHeader-666356-666...[Yr.-to-Yr. Percent Change Adjusted for Currency][rowHeader-667212-667226][Total revenue][1.0][Number]
40.2%5522bodyCell-668530-668535[colHeader-664965-664966, colHeader-666675-666...[Yr.-to-Yr. Percent Change\\n Excluding Diveste...[rowHeader-667212-667226][Total revenue][0.2%][Percentage]
\n", - "
" - ], - "text/plain": [ - " text column_index_begin column_index_end row_index_begin \\\n", - "0 $77,147 1 1 2 \n", - "1 $79,591 2 2 2 \n", - "2 (3.1 )% 3 3 2 \n", - "3 (1.0)% 4 4 2 \n", - "4 0.2% 5 5 2 \n", - "\n", - " row_index_end cell_id \\\n", - "0 2 bodyCell-667480-667488 \n", - "1 2 bodyCell-667744-667752 \n", - "2 2 bodyCell-668006-668014 \n", - "3 2 bodyCell-668266-668273 \n", - "4 2 bodyCell-668530-668535 \n", - "\n", - " column_header_ids \\\n", - "0 [colHeader-664705-664706, colHeader-665513-665... \n", - "1 [colHeader-664770-664771, colHeader-665788-665... \n", - "2 [colHeader-664835-664836, colHeader-666061-666... \n", - "3 [colHeader-664900-664901, colHeader-666356-666... \n", - "4 [colHeader-664965-664966, colHeader-666675-666... \n", - "\n", - " column_header_texts \\\n", - "0 [2019] \n", - "1 [2018] \n", - "2 [Yr.-to-Yr. Percent Change] \n", - "3 [Yr.-to-Yr. Percent Change Adjusted for Currency] \n", - "4 [Yr.-to-Yr. Percent Change\\n Excluding Diveste... \n", - "\n", - " row_header_ids row_header_texts attributes.text attributes.type \n", - "0 [rowHeader-667212-667226] [Total revenue] [$77,147] [Currency] \n", - "1 [rowHeader-667212-667226] [Total revenue] [$79,591] [Currency] \n", - "2 [rowHeader-667212-667226] [Total revenue] [3.1] [Number] \n", - "3 [rowHeader-667212-667226] [Total revenue] [1.0] [Number] \n", - "4 [rowHeader-667212-667226] [Total revenue] [0.2%] [Percentage] " - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "table_data[\"body_cells\"].head(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Text Extensions for Pandas can convert these DataFrames into a single Pandas DataFrame that matches the layout of the original table in the document. Allison calls the `make_table()` function to perform that conversion and inspects the output." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "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", - "
20192018Yr.-to-Yr. Percent ChangeYr.-to-Yr. Percent Change Adjusted for CurrencyYr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency
Total revenue$77,147$79,591-3.1-1.00.2
Americas$36,274$36,994-1.9-1.10.8
Europe/Middle East/Africa24,44325,491-4.10.41.3
Asia Pacific16,43017,106-4.0-3.0-2.5
\n", - "
" - ], - "text/plain": [ - " \\\n", - " 2019 2018 Yr.-to-Yr. Percent Change \n", - "Total revenue $77,147 $79,591 -3.1 \n", - "Americas $36,274 $36,994 -1.9 \n", - "Europe/Middle East/Africa 24,443 25,491 -4.1 \n", - "Asia Pacific 16,430 17,106 -4.0 \n", - "\n", - " \\\n", - " Yr.-to-Yr. Percent Change Adjusted for Currency \n", - "Total revenue -1.0 \n", - "Americas -1.1 \n", - "Europe/Middle East/Africa 0.4 \n", - "Asia Pacific -3.0 \n", - "\n", - " \n", - " Yr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency \n", - "Total revenue 0.2 \n", - "Americas 0.8 \n", - "Europe/Middle East/Africa 1.3 \n", - "Asia Pacific -2.5 " - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "revenue_2019_df = tp.io.watson.tables.make_table(table_data)\n", - "revenue_2019_df" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "  \n", - "\n", - "The reconstructed dataframe looks good! Here's what the original table in the PDF document looked like:\n", - "![Table: Geographic Revenue (from IBM 2019 annual report)](images/screenshot_table_2019.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If Allison just wanted to create a DataFrame of 2018/2019 revenue figures, her task would be done. But Allison wants to reconstruct ten years of revenue by geographic region. To do that, she will need to combine information from multiple documents. For tables like this one that have multiple levels of header information, this kind of integration is easier to perform over the \"exploded\" version of the table, where each cell in the table is represented a single row containing all the corresponding header values.\n", - "\n", - "Allison passes the same table data from the 2019 report through the Text Extensions for Pandas function `make_exploded_df()` to produce the exploded represention of the table:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
textrow_header_texts_0column_header_textsattributes.type
0$77,147Total revenue2019[Currency]
1$79,591Total revenue2018[Currency]
2(3.1 )%Total revenueYr.-to-Yr. Percent Change[Number]
3(1.0)%Total revenueYr.-to-Yr. Percent Change Adjusted for Currency[Number]
40.2%Total revenueYr.-to-Yr. Percent Change\\n Excluding Divested...[Percentage]
5$36,274Americas2019[Currency]
6$36,994Americas2018[Currency]
7(1.9)%AmericasYr.-to-Yr. Percent Change[Number]
8(1.1)%AmericasYr.-to-Yr. Percent Change Adjusted for Currency[Number]
90.8%AmericasYr.-to-Yr. Percent Change\\n Excluding Divested...[Percentage]
1024,443Europe/Middle East/Africa2019[Number]
1125,491Europe/Middle East/Africa2018[Number]
12(4.1)Europe/Middle East/AfricaYr.-to-Yr. Percent Change[Number]
130.4Europe/Middle East/AfricaYr.-to-Yr. Percent Change Adjusted for Currency[Number]
141.3Europe/Middle East/AfricaYr.-to-Yr. Percent Change\\n Excluding Divested...[Number]
1516,430Asia Pacific2019[Number]
1617,106Asia Pacific2018[Number]
17(4.0)Asia PacificYr.-to-Yr. Percent Change[Number]
18(3.0)Asia PacificYr.-to-Yr. Percent Change Adjusted for Currency[Number]
19(2.5)Asia PacificYr.-to-Yr. Percent Change\\n Excluding Divested...[Number]
\n", - "
" - ], - "text/plain": [ - " text row_header_texts_0 \\\n", - "0 $77,147 Total revenue \n", - "1 $79,591 Total revenue \n", - "2 (3.1 )% Total revenue \n", - "3 (1.0)% Total revenue \n", - "4 0.2% Total revenue \n", - "5 $36,274 Americas \n", - "6 $36,994 Americas \n", - "7 (1.9)% Americas \n", - "8 (1.1)% Americas \n", - "9 0.8% Americas \n", - "10 24,443 Europe/Middle East/Africa \n", - "11 25,491 Europe/Middle East/Africa \n", - "12 (4.1) Europe/Middle East/Africa \n", - "13 0.4 Europe/Middle East/Africa \n", - "14 1.3 Europe/Middle East/Africa \n", - "15 16,430 Asia Pacific \n", - "16 17,106 Asia Pacific \n", - "17 (4.0) Asia Pacific \n", - "18 (3.0) Asia Pacific \n", - "19 (2.5) Asia Pacific \n", - "\n", - " column_header_texts attributes.type \n", - "0 2019 [Currency] \n", - "1 2018 [Currency] \n", - "2 Yr.-to-Yr. Percent Change [Number] \n", - "3 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] \n", - "4 Yr.-to-Yr. Percent Change\\n Excluding Divested... [Percentage] \n", - "5 2019 [Currency] \n", - "6 2018 [Currency] \n", - "7 Yr.-to-Yr. Percent Change [Number] \n", - "8 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] \n", - "9 Yr.-to-Yr. Percent Change\\n Excluding Divested... [Percentage] \n", - "10 2019 [Number] \n", - "11 2018 [Number] \n", - "12 Yr.-to-Yr. Percent Change [Number] \n", - "13 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] \n", - "14 Yr.-to-Yr. Percent Change\\n Excluding Divested... [Number] \n", - "15 2019 [Number] \n", - "16 2018 [Number] \n", - "17 Yr.-to-Yr. Percent Change [Number] \n", - "18 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] \n", - "19 Yr.-to-Yr. Percent Change\\n Excluding Divested... [Number] " - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exploded_df, row_header_names, col_header_names = (\n", - " tp.io.watson.tables.make_exploded_df(table_data, col_explode_by=\"concat\"))\n", - "exploded_df" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This exploded version of the table is the exact same data, just represented in a different way. If she wants, Allison can convert it back to the format from the original document by calling `pandas.DataFrame.pivot()`:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "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", - "
column_header_texts20182019Yr.-to-Yr. Percent ChangeYr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for CurrencyYr.-to-Yr. Percent Change Adjusted for Currency
row_header_texts_0
Americas$36,994$36,274(1.9)%0.8%(1.1)%
Asia Pacific17,10616,430(4.0)(2.5)(3.0)
Europe/Middle East/Africa25,49124,443(4.1)1.30.4
Total revenue$79,591$77,147(3.1 )%0.2%(1.0)%
\n", - "
" - ], - "text/plain": [ - "column_header_texts 2018 2019 Yr.-to-Yr. Percent Change \\\n", - "row_header_texts_0 \n", - "Americas $36,994 $36,274 (1.9)% \n", - "Asia Pacific 17,106 16,430 (4.0) \n", - "Europe/Middle East/Africa 25,491 24,443 (4.1) \n", - "Total revenue $79,591 $77,147 (3.1 )% \n", - "\n", - "column_header_texts Yr.-to-Yr. Percent Change\\n Excluding Divested Businesses And Adjusted for Currency \\\n", - "row_header_texts_0 \n", - "Americas 0.8% \n", - "Asia Pacific (2.5) \n", - "Europe/Middle East/Africa 1.3 \n", - "Total revenue 0.2% \n", - "\n", - "column_header_texts Yr.-to-Yr. Percent Change Adjusted for Currency \n", - "row_header_texts_0 \n", - "Americas (1.1)% \n", - "Asia Pacific (3.0) \n", - "Europe/Middle East/Africa 0.4 \n", - "Total revenue (1.0)% " - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exploded_df.pivot(index=\"row_header_texts_0\", columns=\"column_header_texts\", values=\"text\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But because she is about to merge this DataFrame with similar data from other documents, Allison keeps the data in exploded format for now. \n", - "\n", - "Allison's next task is to write some Pandas transformations that will clean and reformat the DataFrame for each source table prior to merging them all together. She uses the 2019 report's data as a test case for creating this code. The first step is to convert the cell values in the Watson Discovery output from text to numeric values. Text Extensions for Pandas includes a more robust version of [`pandas.to_numeric()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_numeric.html) that can handle common idioms for representing currencies and percentages. Allison uses this function, called `convert_cols_to_numeric()`, to convert all the cell values to numbers. She adds a new column \"value\" to her DataFrame to hold these numbers." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
textrow_header_texts_0column_header_textsattributes.typevalue
0$77,147Total revenue2019[Currency]77147.0
1$79,591Total revenue2018[Currency]79591.0
2(3.1 )%Total revenueYr.-to-Yr. Percent Change[Number]-3.1
3(1.0)%Total revenueYr.-to-Yr. Percent Change Adjusted for Currency[Number]-1.0
40.2%Total revenueYr.-to-Yr. Percent Change\\n Excluding Divested...[Percentage]0.2
5$36,274Americas2019[Currency]36274.0
6$36,994Americas2018[Currency]36994.0
7(1.9)%AmericasYr.-to-Yr. Percent Change[Number]-1.9
8(1.1)%AmericasYr.-to-Yr. Percent Change Adjusted for Currency[Number]-1.1
90.8%AmericasYr.-to-Yr. Percent Change\\n Excluding Divested...[Percentage]0.8
1024,443Europe/Middle East/Africa2019[Number]24443.0
1125,491Europe/Middle East/Africa2018[Number]25491.0
12(4.1)Europe/Middle East/AfricaYr.-to-Yr. Percent Change[Number]-4.1
130.4Europe/Middle East/AfricaYr.-to-Yr. Percent Change Adjusted for Currency[Number]0.4
141.3Europe/Middle East/AfricaYr.-to-Yr. Percent Change\\n Excluding Divested...[Number]1.3
1516,430Asia Pacific2019[Number]16430.0
1617,106Asia Pacific2018[Number]17106.0
17(4.0)Asia PacificYr.-to-Yr. Percent Change[Number]-4.0
18(3.0)Asia PacificYr.-to-Yr. Percent Change Adjusted for Currency[Number]-3.0
19(2.5)Asia PacificYr.-to-Yr. Percent Change\\n Excluding Divested...[Number]-2.5
\n", - "
" - ], - "text/plain": [ - " text row_header_texts_0 \\\n", - "0 $77,147 Total revenue \n", - "1 $79,591 Total revenue \n", - "2 (3.1 )% Total revenue \n", - "3 (1.0)% Total revenue \n", - "4 0.2% Total revenue \n", - "5 $36,274 Americas \n", - "6 $36,994 Americas \n", - "7 (1.9)% Americas \n", - "8 (1.1)% Americas \n", - "9 0.8% Americas \n", - "10 24,443 Europe/Middle East/Africa \n", - "11 25,491 Europe/Middle East/Africa \n", - "12 (4.1) Europe/Middle East/Africa \n", - "13 0.4 Europe/Middle East/Africa \n", - "14 1.3 Europe/Middle East/Africa \n", - "15 16,430 Asia Pacific \n", - "16 17,106 Asia Pacific \n", - "17 (4.0) Asia Pacific \n", - "18 (3.0) Asia Pacific \n", - "19 (2.5) Asia Pacific \n", - "\n", - " column_header_texts attributes.type value \n", - "0 2019 [Currency] 77147.0 \n", - "1 2018 [Currency] 79591.0 \n", - "2 Yr.-to-Yr. Percent Change [Number] -3.1 \n", - "3 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] -1.0 \n", - "4 Yr.-to-Yr. Percent Change\\n Excluding Divested... [Percentage] 0.2 \n", - "5 2019 [Currency] 36274.0 \n", - "6 2018 [Currency] 36994.0 \n", - "7 Yr.-to-Yr. Percent Change [Number] -1.9 \n", - "8 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] -1.1 \n", - "9 Yr.-to-Yr. Percent Change\\n Excluding Divested... [Percentage] 0.8 \n", - "10 2019 [Number] 24443.0 \n", - "11 2018 [Number] 25491.0 \n", - "12 Yr.-to-Yr. Percent Change [Number] -4.1 \n", - "13 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] 0.4 \n", - "14 Yr.-to-Yr. Percent Change\\n Excluding Divested... [Number] 1.3 \n", - "15 2019 [Number] 16430.0 \n", - "16 2018 [Number] 17106.0 \n", - "17 Yr.-to-Yr. Percent Change [Number] -4.0 \n", - "18 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] -3.0 \n", - "19 Yr.-to-Yr. Percent Change\\n Excluding Divested... [Number] -2.5 " - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "exploded_df[\"value\"] = \\\n", - " tp.io.watson.tables.convert_cols_to_numeric(exploded_df[[\"text\"]])\n", - "exploded_df" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now all the cell values have been converted to floating-point numbers, but only some of these numbers represent revenue. Looking at the 2019 data, Allison can see that the revenue numbers have 4-digit years in their column headers. So she filters the DataFrame down to just those rows with 4-digit numbers in the \"column_header_texts\" column." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "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", - "
textrow_header_texts_0column_header_textsattributes.typevalue
0$77,147Total revenue2019[Currency]77147.0
1$79,591Total revenue2018[Currency]79591.0
5$36,274Americas2019[Currency]36274.0
6$36,994Americas2018[Currency]36994.0
1024,443Europe/Middle East/Africa2019[Number]24443.0
1125,491Europe/Middle East/Africa2018[Number]25491.0
1516,430Asia Pacific2019[Number]16430.0
1617,106Asia Pacific2018[Number]17106.0
\n", - "
" - ], - "text/plain": [ - " text row_header_texts_0 column_header_texts attributes.type \\\n", - "0 $77,147 Total revenue 2019 [Currency] \n", - "1 $79,591 Total revenue 2018 [Currency] \n", - "5 $36,274 Americas 2019 [Currency] \n", - "6 $36,994 Americas 2018 [Currency] \n", - "10 24,443 Europe/Middle East/Africa 2019 [Number] \n", - "11 25,491 Europe/Middle East/Africa 2018 [Number] \n", - "15 16,430 Asia Pacific 2019 [Number] \n", - "16 17,106 Asia Pacific 2018 [Number] \n", - "\n", - " value \n", - "0 77147.0 \n", - "1 79591.0 \n", - "5 36274.0 \n", - "6 36994.0 \n", - "10 24443.0 \n", - "11 25491.0 \n", - "15 16430.0 \n", - "16 17106.0 " - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rows_to_retain = exploded_df[exploded_df[\"column_header_texts\"].str.fullmatch(\"\\d{4}\")].copy()\n", - "rows_to_retain" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "That's looking good! Now Allison drops the unnecessary columns and gives some more friendly names to the columns that remain." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "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", - "
YearRegionRevenue
02019Total revenue77147.0
12018Total revenue79591.0
52019Americas36274.0
62018Americas36994.0
102019Europe/Middle East/Africa24443.0
112018Europe/Middle East/Africa25491.0
152019Asia Pacific16430.0
162018Asia Pacific17106.0
\n", - "
" - ], - "text/plain": [ - " Year Region Revenue\n", - "0 2019 Total revenue 77147.0\n", - "1 2018 Total revenue 79591.0\n", - "5 2019 Americas 36274.0\n", - "6 2018 Americas 36994.0\n", - "10 2019 Europe/Middle East/Africa 24443.0\n", - "11 2018 Europe/Middle East/Africa 25491.0\n", - "15 2019 Asia Pacific 16430.0\n", - "16 2018 Asia Pacific 17106.0" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "rows_to_retain.rename(\n", - " columns={\n", - " \"row_header_texts_0\": \"Region\",\n", - " \"column_header_texts\": \"Year\",\n", - " \"value\": \"Revenue\"\n", - " })[[\"Year\", \"Region\", \"Revenue\"]]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The code from the last few cells worked to clean up the 2019 data, so Allison copies and pastes that code into a Python function:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "def dataframe_for_file(filename: str):\n", - " with open(f\"{FILES_DIR}/{filename}\", \"r\") as f:\n", - " json_output = json.load(f)\n", - " table_data = tp.io.watson.tables.parse_response(json_output,\n", - " select_table=\"Geographic Revenue\")\n", - " exploded_df, _, _ = tp.io.watson.tables.make_exploded_df(\n", - " table_data, col_explode_by=\"concat\")\n", - " rows_to_retain = exploded_df[exploded_df[\"column_header_texts\"].str.fullmatch(\"\\d{4}\")\n", - " & (exploded_df[\"text\"].str.len() > 0)].copy()\n", - " rows_to_retain[\"value\"] = tp.io.watson.tables.convert_cols_to_numeric(\n", - " rows_to_retain[[\"text\"]])\n", - " rows_to_retain[\"file\"] = filename\n", - " return (\n", - " rows_to_retain.rename(columns={\n", - " \"row_header_texts_0\": \"Region\", \"column_header_texts\": \"Year\", \"value\": \"Revenue\"})\n", - " [[\"Year\", \"Region\", \"Revenue\"]]\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then she calls that function on the Watson Discovery output from the 2019 annual report to verify that it produces the same answer. " - ] - }, - { - "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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
YearRegionRevenue
02019Total revenue77147.0
12018Total revenue79591.0
52019Americas36274.0
62018Americas36994.0
102019Europe/Middle East/Africa24443.0
112018Europe/Middle East/Africa25491.0
152019Asia Pacific16430.0
162018Asia Pacific17106.0
\n", - "
" - ], - "text/plain": [ - " Year Region Revenue\n", - "0 2019 Total revenue 77147.0\n", - "1 2018 Total revenue 79591.0\n", - "5 2019 Americas 36274.0\n", - "6 2018 Americas 36994.0\n", - "10 2019 Europe/Middle East/Africa 24443.0\n", - "11 2018 Europe/Middle East/Africa 25491.0\n", - "15 2019 Asia Pacific 16430.0\n", - "16 2018 Asia Pacific 17106.0" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dataframe_for_file(\"2019.json\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Looks good! Time to run the same function over an entire stack of reports. Allison puts the names of all her Watson Discovery output files into a single Python list." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['2009.json',\n", - " '2010.json',\n", - " '2012.json',\n", - " '2013.json',\n", - " '2015.json',\n", - " '2016.json',\n", - " '2017.json',\n", - " '2018.json',\n", - " '2019.json']" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "all_files = sorted([f for f in os.listdir(FILES_DIR) if f.endswith(\".json\")])\n", - "all_files" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that the annual reports for 2011 and 2014 aren't in the collection of files that Allison has. But that's ok; each report contains the previous year's figures, so Allison can reconstruct the missing data from adjacent years.\n", - "\n", - "Allison calls her `dataframe_for_file()` function on each of the files, then concatenates all of the resulting Pandas DataFrames into a single large DataFrame." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "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", - "
YearRegionRevenue
02009Total revenue:95758.0
12008Total revenue:103630.0
42009Geographies:93477.0
52008Geographies:100939.0
82009Americas40184.0
............
62018Americas36994.0
102019Europe/Middle East/Africa24443.0
112018Europe/Middle East/Africa25491.0
152019Asia Pacific16430.0
162018Asia Pacific17106.0
\n", - "

82 rows × 3 columns

\n", - "
" - ], - "text/plain": [ - " Year Region Revenue\n", - "0 2009 Total revenue: 95758.0\n", - "1 2008 Total revenue: 103630.0\n", - "4 2009 Geographies: 93477.0\n", - "5 2008 Geographies: 100939.0\n", - "8 2009 Americas 40184.0\n", - ".. ... ... ...\n", - "6 2018 Americas 36994.0\n", - "10 2019 Europe/Middle East/Africa 24443.0\n", - "11 2018 Europe/Middle East/Africa 25491.0\n", - "15 2019 Asia Pacific 16430.0\n", - "16 2018 Asia Pacific 17106.0\n", - "\n", - "[82 rows x 3 columns]" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "revenue_df = pd.concat([dataframe_for_file(f) for f in all_files])\n", - "revenue_df" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Allison can see that the first four lines of this DataFrame contain total worldwide revenue; and that this total occurred\n", - "under different names in different documents. Allison is interested in the fine-grained revenue figures, not\n", - "the totals, so she needs to filter out all these rows with worldwide revenue.\n", - "\n", - "What are all the names of geographic regions that IBM annual reports have used over the last ten years?" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "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", - "
Region
0Total revenue:
4Geographies:
8Americas
12Europe/Middle East/Africa
16Asia Pacific
0Total revenue
4Geographies
16Asia Pacifi c
\n", - "
" - ], - "text/plain": [ - " Region\n", - "0 Total revenue:\n", - "4 Geographies:\n", - "8 Americas\n", - "12 Europe/Middle East/Africa\n", - "16 Asia Pacific\n", - "0 Total revenue\n", - "4 Geographies\n", - "16 Asia Pacifi c" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "revenue_df[[\"Region\"]].drop_duplicates()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It looks like all the worldwide revenue figures are under some variation of \"Geographies\" or \"Total revenue\". \n", - "Allison uses Pandas' string matching facilities to filter out the rows whose \"Region\" column contains the \n", - "words \"geographies\" or \"total\"." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
YearRegionRevenue
82009Americas40184.0
92008Americas42807.0
122009Europe/Middle East/Africa32583.0
132008Europe/Middle East/Africa37020.0
162009Asia Pacific20710.0
172008Asia Pacific21111.0
82010Americas42044.0
92009Americas40184.0
122010Europe/Middle East/Africa31866.0
132009Europe/Middle East/Africa32583.0
162010Asia Pacific23150.0
172009Asia Pacific20710.0
82012Americas44556.0
92011Americas44944.0
122012Europe/Middle East/Africa31775.0
132011Europe/Middle East/Africa33952.0
162012Asia Pacific25937.0
172011Asia Pacific25273.0
82013Americas43249.0
92012Americas44556.0
122013Europe/Middle East/Africa31628.0
132012Europe/Middle East/Africa31775.0
162013Asia Pacific22923.0
172012Asia Pacific25937.0
82015Americas38486.0
92014Americas41410.0
122015Europe/Middle East/Africa26073.0
132014Europe/Middle East/Africa30700.0
162015Asia Pacifi c16871.0
172014Asia Pacifi c20216.0
82016Americas37513.0
92015Americas38486.0
122016Europe/Middle East/Africa24769.0
132015Europe/Middle East/Africa26073.0
162016Asia Pacifi c17313.0
172015Asia Pacifi c16871.0
82017Americas37479.0
92016Americas37513.0
122017Europe/Middle East/Africa24345.0
132016Europe/Middle East/Africa24769.0
162017Asia Pacific16970.0
172016Asia Pacific17313.0
42018Americas36994.0
82018Europe/Middle East/Africa25491.0
122018Asia Pacific17106.0
52019Americas36274.0
62018Americas36994.0
102019Europe/Middle East/Africa24443.0
112018Europe/Middle East/Africa25491.0
152019Asia Pacific16430.0
162018Asia Pacific17106.0
\n", - "
" - ], - "text/plain": [ - " Year Region Revenue\n", - "8 2009 Americas 40184.0\n", - "9 2008 Americas 42807.0\n", - "12 2009 Europe/Middle East/Africa 32583.0\n", - "13 2008 Europe/Middle East/Africa 37020.0\n", - "16 2009 Asia Pacific 20710.0\n", - "17 2008 Asia Pacific 21111.0\n", - "8 2010 Americas 42044.0\n", - "9 2009 Americas 40184.0\n", - "12 2010 Europe/Middle East/Africa 31866.0\n", - "13 2009 Europe/Middle East/Africa 32583.0\n", - "16 2010 Asia Pacific 23150.0\n", - "17 2009 Asia Pacific 20710.0\n", - "8 2012 Americas 44556.0\n", - "9 2011 Americas 44944.0\n", - "12 2012 Europe/Middle East/Africa 31775.0\n", - "13 2011 Europe/Middle East/Africa 33952.0\n", - "16 2012 Asia Pacific 25937.0\n", - "17 2011 Asia Pacific 25273.0\n", - "8 2013 Americas 43249.0\n", - "9 2012 Americas 44556.0\n", - "12 2013 Europe/Middle East/Africa 31628.0\n", - "13 2012 Europe/Middle East/Africa 31775.0\n", - "16 2013 Asia Pacific 22923.0\n", - "17 2012 Asia Pacific 25937.0\n", - "8 2015 Americas 38486.0\n", - "9 2014 Americas 41410.0\n", - "12 2015 Europe/Middle East/Africa 26073.0\n", - "13 2014 Europe/Middle East/Africa 30700.0\n", - "16 2015 Asia Pacifi c 16871.0\n", - "17 2014 Asia Pacifi c 20216.0\n", - "8 2016 Americas 37513.0\n", - "9 2015 Americas 38486.0\n", - "12 2016 Europe/Middle East/Africa 24769.0\n", - "13 2015 Europe/Middle East/Africa 26073.0\n", - "16 2016 Asia Pacifi c 17313.0\n", - "17 2015 Asia Pacifi c 16871.0\n", - "8 2017 Americas 37479.0\n", - "9 2016 Americas 37513.0\n", - "12 2017 Europe/Middle East/Africa 24345.0\n", - "13 2016 Europe/Middle East/Africa 24769.0\n", - "16 2017 Asia Pacific 16970.0\n", - "17 2016 Asia Pacific 17313.0\n", - "4 2018 Americas 36994.0\n", - "8 2018 Europe/Middle East/Africa 25491.0\n", - "12 2018 Asia Pacific 17106.0\n", - "5 2019 Americas 36274.0\n", - "6 2018 Americas 36994.0\n", - "10 2019 Europe/Middle East/Africa 24443.0\n", - "11 2018 Europe/Middle East/Africa 25491.0\n", - "15 2019 Asia Pacific 16430.0\n", - "16 2018 Asia Pacific 17106.0" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geo_revenue_df = (\n", - " revenue_df[~( # \"~\" operator inverts a Pandas selection condition\n", - " (revenue_df[\"Region\"].str.contains(\"geographies\", case=False))\n", - " | (revenue_df[\"Region\"].str.contains(\"total\", case=False))\n", - " )]).copy()\n", - "geo_revenue_df" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now every row contains a regional revenue figure. What are the regions represented? " - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "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", - "
Region
8Americas
12Europe/Middle East/Africa
16Asia Pacific
16Asia Pacifi c
\n", - "
" - ], - "text/plain": [ - " Region\n", - "8 Americas\n", - "12 Europe/Middle East/Africa\n", - "16 Asia Pacific\n", - "16 Asia Pacifi c" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geo_revenue_df[[\"Region\"]].drop_duplicates()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "That's strange — one of the regions is \"Asia Pacifi c\", with a space before the last \"c\". It looks like the PDF conversion on the 2016 annual report added an extra space. Allison uses the function `pandas.Series.replace()` to correct that issue." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
YearRegionRevenue
82009Americas40184.0
92008Americas42807.0
122009Europe/Middle East/Africa32583.0
132008Europe/Middle East/Africa37020.0
162009Asia Pacific20710.0
172008Asia Pacific21111.0
82010Americas42044.0
92009Americas40184.0
122010Europe/Middle East/Africa31866.0
132009Europe/Middle East/Africa32583.0
162010Asia Pacific23150.0
172009Asia Pacific20710.0
82012Americas44556.0
92011Americas44944.0
122012Europe/Middle East/Africa31775.0
132011Europe/Middle East/Africa33952.0
162012Asia Pacific25937.0
172011Asia Pacific25273.0
82013Americas43249.0
92012Americas44556.0
122013Europe/Middle East/Africa31628.0
132012Europe/Middle East/Africa31775.0
162013Asia Pacific22923.0
172012Asia Pacific25937.0
82015Americas38486.0
92014Americas41410.0
122015Europe/Middle East/Africa26073.0
132014Europe/Middle East/Africa30700.0
162015Asia Pacific16871.0
172014Asia Pacific20216.0
82016Americas37513.0
92015Americas38486.0
122016Europe/Middle East/Africa24769.0
132015Europe/Middle East/Africa26073.0
162016Asia Pacific17313.0
172015Asia Pacific16871.0
82017Americas37479.0
92016Americas37513.0
122017Europe/Middle East/Africa24345.0
132016Europe/Middle East/Africa24769.0
162017Asia Pacific16970.0
172016Asia Pacific17313.0
42018Americas36994.0
82018Europe/Middle East/Africa25491.0
122018Asia Pacific17106.0
52019Americas36274.0
62018Americas36994.0
102019Europe/Middle East/Africa24443.0
112018Europe/Middle East/Africa25491.0
152019Asia Pacific16430.0
162018Asia Pacific17106.0
\n", - "
" - ], - "text/plain": [ - " Year Region Revenue\n", - "8 2009 Americas 40184.0\n", - "9 2008 Americas 42807.0\n", - "12 2009 Europe/Middle East/Africa 32583.0\n", - "13 2008 Europe/Middle East/Africa 37020.0\n", - "16 2009 Asia Pacific 20710.0\n", - "17 2008 Asia Pacific 21111.0\n", - "8 2010 Americas 42044.0\n", - "9 2009 Americas 40184.0\n", - "12 2010 Europe/Middle East/Africa 31866.0\n", - "13 2009 Europe/Middle East/Africa 32583.0\n", - "16 2010 Asia Pacific 23150.0\n", - "17 2009 Asia Pacific 20710.0\n", - "8 2012 Americas 44556.0\n", - "9 2011 Americas 44944.0\n", - "12 2012 Europe/Middle East/Africa 31775.0\n", - "13 2011 Europe/Middle East/Africa 33952.0\n", - "16 2012 Asia Pacific 25937.0\n", - "17 2011 Asia Pacific 25273.0\n", - "8 2013 Americas 43249.0\n", - "9 2012 Americas 44556.0\n", - "12 2013 Europe/Middle East/Africa 31628.0\n", - "13 2012 Europe/Middle East/Africa 31775.0\n", - "16 2013 Asia Pacific 22923.0\n", - "17 2012 Asia Pacific 25937.0\n", - "8 2015 Americas 38486.0\n", - "9 2014 Americas 41410.0\n", - "12 2015 Europe/Middle East/Africa 26073.0\n", - "13 2014 Europe/Middle East/Africa 30700.0\n", - "16 2015 Asia Pacific 16871.0\n", - "17 2014 Asia Pacific 20216.0\n", - "8 2016 Americas 37513.0\n", - "9 2015 Americas 38486.0\n", - "12 2016 Europe/Middle East/Africa 24769.0\n", - "13 2015 Europe/Middle East/Africa 26073.0\n", - "16 2016 Asia Pacific 17313.0\n", - "17 2015 Asia Pacific 16871.0\n", - "8 2017 Americas 37479.0\n", - "9 2016 Americas 37513.0\n", - "12 2017 Europe/Middle East/Africa 24345.0\n", - "13 2016 Europe/Middle East/Africa 24769.0\n", - "16 2017 Asia Pacific 16970.0\n", - "17 2016 Asia Pacific 17313.0\n", - "4 2018 Americas 36994.0\n", - "8 2018 Europe/Middle East/Africa 25491.0\n", - "12 2018 Asia Pacific 17106.0\n", - "5 2019 Americas 36274.0\n", - "6 2018 Americas 36994.0\n", - "10 2019 Europe/Middle East/Africa 24443.0\n", - "11 2018 Europe/Middle East/Africa 25491.0\n", - "15 2019 Asia Pacific 16430.0\n", - "16 2018 Asia Pacific 17106.0" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geo_revenue_df[\"Region\"] = geo_revenue_df[\"Region\"].replace(\"Asia Pacifi c\", \"Asia Pacific\")\n", - "geo_revenue_df" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Allison inspects the time series of revenue for the \"Americas\" region:" - ] - }, - { - "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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
YearRegionRevenue
92008Americas42807.0
82009Americas40184.0
92009Americas40184.0
82010Americas42044.0
92011Americas44944.0
82012Americas44556.0
92012Americas44556.0
82013Americas43249.0
92014Americas41410.0
92015Americas38486.0
82015Americas38486.0
92016Americas37513.0
82016Americas37513.0
82017Americas37479.0
42018Americas36994.0
62018Americas36994.0
52019Americas36274.0
\n", - "
" - ], - "text/plain": [ - " Year Region Revenue\n", - "9 2008 Americas 42807.0\n", - "8 2009 Americas 40184.0\n", - "9 2009 Americas 40184.0\n", - "8 2010 Americas 42044.0\n", - "9 2011 Americas 44944.0\n", - "8 2012 Americas 44556.0\n", - "9 2012 Americas 44556.0\n", - "8 2013 Americas 43249.0\n", - "9 2014 Americas 41410.0\n", - "9 2015 Americas 38486.0\n", - "8 2015 Americas 38486.0\n", - "9 2016 Americas 37513.0\n", - "8 2016 Americas 37513.0\n", - "8 2017 Americas 37479.0\n", - "4 2018 Americas 36994.0\n", - "6 2018 Americas 36994.0\n", - "5 2019 Americas 36274.0" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geo_revenue_df[geo_revenue_df[\"Region\"] == \"Americas\"].sort_values(\"Year\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Every year from 2008 to 2019 is present, but many of the years appear twice. That's to be expected, \n", - "since each of the annual reports contains two years of geographical revenue figures.\n", - "Allison drops the duplicate values using `pandas.DataFrame.drop_duplicates()`." - ] - }, - { - "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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
YearRegionRevenue
82009Americas40184.0
92008Americas42807.0
122009Europe/Middle East/Africa32583.0
132008Europe/Middle East/Africa37020.0
162009Asia Pacific20710.0
172008Asia Pacific21111.0
82010Americas42044.0
122010Europe/Middle East/Africa31866.0
162010Asia Pacific23150.0
82012Americas44556.0
92011Americas44944.0
122012Europe/Middle East/Africa31775.0
132011Europe/Middle East/Africa33952.0
162012Asia Pacific25937.0
172011Asia Pacific25273.0
82013Americas43249.0
122013Europe/Middle East/Africa31628.0
162013Asia Pacific22923.0
82015Americas38486.0
92014Americas41410.0
122015Europe/Middle East/Africa26073.0
132014Europe/Middle East/Africa30700.0
162015Asia Pacific16871.0
172014Asia Pacific20216.0
82016Americas37513.0
122016Europe/Middle East/Africa24769.0
162016Asia Pacific17313.0
82017Americas37479.0
122017Europe/Middle East/Africa24345.0
162017Asia Pacific16970.0
42018Americas36994.0
82018Europe/Middle East/Africa25491.0
122018Asia Pacific17106.0
52019Americas36274.0
102019Europe/Middle East/Africa24443.0
152019Asia Pacific16430.0
\n", - "
" - ], - "text/plain": [ - " Year Region Revenue\n", - "8 2009 Americas 40184.0\n", - "9 2008 Americas 42807.0\n", - "12 2009 Europe/Middle East/Africa 32583.0\n", - "13 2008 Europe/Middle East/Africa 37020.0\n", - "16 2009 Asia Pacific 20710.0\n", - "17 2008 Asia Pacific 21111.0\n", - "8 2010 Americas 42044.0\n", - "12 2010 Europe/Middle East/Africa 31866.0\n", - "16 2010 Asia Pacific 23150.0\n", - "8 2012 Americas 44556.0\n", - "9 2011 Americas 44944.0\n", - "12 2012 Europe/Middle East/Africa 31775.0\n", - "13 2011 Europe/Middle East/Africa 33952.0\n", - "16 2012 Asia Pacific 25937.0\n", - "17 2011 Asia Pacific 25273.0\n", - "8 2013 Americas 43249.0\n", - "12 2013 Europe/Middle East/Africa 31628.0\n", - "16 2013 Asia Pacific 22923.0\n", - "8 2015 Americas 38486.0\n", - "9 2014 Americas 41410.0\n", - "12 2015 Europe/Middle East/Africa 26073.0\n", - "13 2014 Europe/Middle East/Africa 30700.0\n", - "16 2015 Asia Pacific 16871.0\n", - "17 2014 Asia Pacific 20216.0\n", - "8 2016 Americas 37513.0\n", - "12 2016 Europe/Middle East/Africa 24769.0\n", - "16 2016 Asia Pacific 17313.0\n", - "8 2017 Americas 37479.0\n", - "12 2017 Europe/Middle East/Africa 24345.0\n", - "16 2017 Asia Pacific 16970.0\n", - "4 2018 Americas 36994.0\n", - "8 2018 Europe/Middle East/Africa 25491.0\n", - "12 2018 Asia Pacific 17106.0\n", - "5 2019 Americas 36274.0\n", - "10 2019 Europe/Middle East/Africa 24443.0\n", - "15 2019 Asia Pacific 16430.0" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geo_revenue_df.drop_duplicates([\"Region\", \"Year\"], inplace=True)\n", - "geo_revenue_df" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now Allison has a clean and complete set of revenue figures by geographical region for the years 2008-2019.\n", - "She uses Pandas' `pandas.DataFrame.pivot()` method to convert this data into a compact table." - ] - }, - { - "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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Year200820092010201120122013201420152016201720182019
Region
Americas42807.040184.042044.044944.044556.043249.041410.038486.037513.037479.036994.036274.0
Asia Pacific21111.020710.023150.025273.025937.022923.020216.016871.017313.016970.017106.016430.0
Europe/Middle East/Africa37020.032583.031866.033952.031775.031628.030700.026073.024769.024345.025491.024443.0
\n", - "
" - ], - "text/plain": [ - "Year 2008 2009 2010 2011 2012 \\\n", - "Region \n", - "Americas 42807.0 40184.0 42044.0 44944.0 44556.0 \n", - "Asia Pacific 21111.0 20710.0 23150.0 25273.0 25937.0 \n", - "Europe/Middle East/Africa 37020.0 32583.0 31866.0 33952.0 31775.0 \n", - "\n", - "Year 2013 2014 2015 2016 2017 \\\n", - "Region \n", - "Americas 43249.0 41410.0 38486.0 37513.0 37479.0 \n", - "Asia Pacific 22923.0 20216.0 16871.0 17313.0 16970.0 \n", - "Europe/Middle East/Africa 31628.0 30700.0 26073.0 24769.0 24345.0 \n", - "\n", - "Year 2018 2019 \n", - "Region \n", - "Americas 36994.0 36274.0 \n", - "Asia Pacific 17106.0 16430.0 \n", - "Europe/Middle East/Africa 25491.0 24443.0 " - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "revenue_table = geo_revenue_df.pivot(index=\"Region\", columns=\"Year\", values=\"Revenue\")\n", - "revenue_table" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then she uses that table to produce a plot of revenue by region over that 11-year period." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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uwM17/G/4z4/puReOrr+/fsTGhpKUlISl112GVdeeSVXX+1c786YMYMePXpw1FFHUVFR4X2ceOKJlJeXM2+es/DzvHnzGDVqVLXBs5GRkZx66qn7PXdhYSGLFi3ivPPO8yb3AJmZmRx99NHMmjWrWv2+fft6k3uAtLQ00tLS2LRpU7N/DiIiItJ+1NeCHwrk1yirer+j5cMRaVumTZtGt27d2LFjB4899hhPPfUUI0eO5KKLLiInJ4eNGzcSGhrqd9/c3FwAtm7dyuDBtReB7tSp037PvXv3bqy1dOnSpda2zp07s3HjxmplSUlJteqFh4cf0Gk9RUREpO1pyCw66caYXj7vg33K9/hWtNaua6nARNqCwYMHe/uvjx8/nsMOO4ybbrqJs846i+TkZDIzM3njjTf87lvVBadLly7k5OTU2r59+/b9njsxMRFjDNu2bau1bdu2bX4TehEREZGGzPP3Fs7KtVWPFW759Brlq/3tLNJRhIeH88gjj5CTk8NTTz3FSSedRFZWFjExMQwfPrzWIyUlBXCmvpw7dy6bN2/2Hqu4uJgPP/xwv+eLjo7miCOO4M0336SystJbvnHjRr799lvGjRt3QD6niIiItG/1teD/plWiEGknJk2axIgRI3j00UdZvXo1zz33HBMmTODGG29kyJAhlJWVsXbtWt577z2mT59OVFQUf/zjH3n66aeZOHEid911F+Hh4Tz22GOEh4fXmiazpnvvvZdTTz2V0047jd///vcUFBRw1113ER8fz4033thKn1pERETak/0m+Nbaqa0ViEh7cd999zFx4kSeffZZPvnkEx588EGeeeYZ1q9fT3R0NL179+bUU08lLCwMgJSUFL744guuvfZaLrroIpKTk/nd737Hzp07eeGFF/Z7rpNOOokPP/yQu+++m3PPPZewsDDGjRvHww8/TNeuXVvj44qIiEg7Y3wWppUWMHz4cLtw4cI6ty9fvpwBAwa0YkTSFlVWVjJs2DBv8i8C+vdBREQaxxjzvbV2eM3yhgyyFZFmuuOOO+jTpw89evQgNzeXZ599lh9//JGPPvoo0KGJiIhIB6MEX6QVGGO455572LJlC8YYDjvsMKZPn87JJ58c6NBERESkg1GCL9IK7rnnHu65555AhyEiIiIHgYZMkykiIiIiIu1Eqyf4xphuxph/GGPmGmOKjDHWGNPTT70IY8wjxpitxphit/5YP/WCjDF/McZsMMaUGGOWGGPOquPcVxhjVhhjSo0xK40xv6uj3mRjzA/u8TYaY243xgT7qysiIiIi0pYEogW/D3AusBv4ej/1/gtcAdwJnAZsBT4xxgytUe9eYArwJHAyMA940xhzim8lY8wVwL+Bt4GTgDeBp4wxV9WoN9Gt8517vCeA24EHGvcxRURERERaXyD64M+21nYCMMZcDpxYs4IxZghwAXCptfY5t2wWsBS4B5jklqUBfwIetNb+zd39K2NMH+BB4CO3XghwP/CitfY2n3pdgXuNMc9aa8vd8geBOdba3/rUiwFuN8Y8bq3d1mI/CRERERGRFtbqLfjWWk8Dqk0CyoHXffarAF4DJhpjwt3iiUAY8FKN/V8CDjXGZLrvRwOpfuq9CCQDxwAYY7oDQ+uoF4rToi8iIiIi0ma11UG2g4D11tqiGuVLcRL6Pj71SoE1fuoBDPSpB/BzU+pZa9cDRT71RERERETapLaa4Cfh9NGvaZfP9qrnPbb2crz+6uHnmA2tV1WW5KccY8xvjTELjTELd+zY4a+KiIiIiEiraKsJfrtirX3GWjvcWjs8NTU10OEEzBVXXIExhhtuuCHQodQyc+ZMjDHMnDkz0KGIiIiIHFBtNcHfDST6Ka9qQd/lUy/BGGMaUA8/x2xovaqyXX7KBSguLuaNN94A4JVXXqGioiLAEVU3bNgw5s6dy7BhwwIdioiIiMgB1VYT/KVApjEmqkb5QKCMfX3ulwLhQG8/9QCW+dSDfX3sG1XPnac/yqee1DB9+nTy8vI45ZRTyMnJYcaMGYEOCYDKykoqKiqIi4tj1KhRxMXFBTokERERkQOqrSb47+PMWnNOVYE71eV5wKfW2lK3eAbObDu/qrH/hcDP7uBYgLnAzjrq7QK+AbDWbgKW1FGvHPi46R+pY5s6dSqJiYk8//zzREZGMnXq1Grbp0yZgjGGFStWMHHiRKKjo8nIyOC5554D4MUXX6R///7ExMRw3HHHsXbt2lrneOaZZxgyZAgRERGkpKRw2WWXsWtX9Zsqxhhuu+02HnzwQTIzMwkLC+Onn36qs4vOtGnTOProo4mJiSEuLo4jjzyS9957z7v9ySefZPTo0SQlJZGQkMCoUaP48MMPqx2joqKCO+64g969e3tjO+aYY5gzZ05zfqQiIiIiTRKIefAxxpztvjzCfT7ZGLMD2GGtnWWt/cEY8zrwd2NMKLAeuArIxCf5ttbmGGMeA/5ijMkHFuFcBIzHnSvfrVdujLkDZ2GrbOBzt86lwDXW2jKf8G4FPjDG/Bt4FTgcZ6GrJzQHvn9btmzh888/54orriA1NZXJkyfzzjvvsHv3bhITq/d2Ouecc7jiiiv405/+xFNPPcWll17K6tWrmTlzJg8++CDl5eVcd911XHDBBcyfP9+73y233MKjjz7KtddeyyOPPEJ2dja33347P//8M99++y3BwfsWGn7++efp1asXf/vb34iOjqZr167s3bu3Vtz/+Mc/uPbaa5k8eTJTp04lJiaGRYsWsWHDBm+dDRs2cPnll9OzZ08qKip4//33Oe200/j444856aSTAHjooYd4/PHHuf/++xk6dCh5eXksXLiw1sWHiIiISGsISIKPs4qsr6fc51nAOPf1b3AWp7oPSMBpWT/JWruoxr63AQXAdUBnYCVwrrX2A99K1tp/GWMscCNwE7AJuNpa+1SNeh+5FyB3AZcA23FWsb2/CZ+zQe5+fynLtuQdqMM3yMCucdx1es0eTA3z0ksvUVlZyUUXXQTAxRdfzKuvvsrrr7/O7373u2p1b7rpJm+94cOH8/777/Pvf/+b9evXe7vPbN26leuuu46NGzfSo0cPNmzYwCOPPMJdd93FnXfe6T1Wv379OOaYY3j//feZPHmyt9xay6effkpkZKS3bPny5dXiyMvL49Zbb+XMM8/knXfe8ZZPnDixWr2//e1v3tcej4cJEyawatUqnn76aW+CP3fuXE488USuu+46b93TTz+94T9AERERkRYUkC461lpTx2OcT51ia+0frbWdrbUR1tqR1tqZfo5Vaa29z1rbw1obbq09zFr7Vh3n/be1tp9br2/N5N6n3jvW2iFuvQxr7T3W2sqW+vwdzdSpU+nbty+jR48G4Pjjj6dr1661uukAnHzyvrXCEhMTSUtLq9U3vn///gBkZWUB8Nlnn+HxePjVr35FRUWF9zFy5EhiY2OZPXt2tXOcdNJJ1ZJ7f7799lsKCgr47W9/u99633//PaeddhqdOnUiJCSE0NBQPvvsM1auXOmtM2LECD766CNuu+025syZQ1lZ2X6OKCIiInJgBaoFX3w0teW8LVi4cCHLli3j5ptvZs+ePd7yX/ziFzz55JOsWrWKfv36ectrdtkJCwvzWwZQUlICQE5ODgB9+vTBn9zc3Grvu3TpUm/cVft069atzjpZWVlMmDCBgQMH8o9//IOMjAxCQkK44447qt0RuPXWW4mIiOCll17igQceICYmhrPPPptHHnmElJSUemMRERERaUlK8KVZqlrpH3roIR566KFa21944QXuu+++Zp0jOTkZgE8//bTWxYDv9iq1Z02trSrxzs7OZvDgwX7rzJgxg7179/LGG29UuxAoKqq+wHJoaCg333wzN998M9u2beODDz7gj3/8I0VFRbz++uv1xiIiIiLSkpTgS5OVlZXx6quvMnLkSB588MFa22+44QZefPFF7r333mad54QTTiAoKIhNmzZxwgknNOtYVY466ihiYmJ45plnavW7r1KVyIeGhnrLVq1axTfffFNny3/nzp25/PLL+eijj/j5559bJFYRERGRxlCCL0324Ycfkpuby6OPPsq4ceNqbb/yyiu56qqrmr16bO/evbn55pu5+uqrWblyJcceeywRERFkZWXx2Wefcfnll3Pcccc16pixsbH89a9/5ZprruGss87iV7/6FbGxsSxevJiIiAiuueYajj/+eEJCQrjooou48cYb2bp1K3fddRcZGRl4PB7vsc444wyGDBnCsGHDSExM5IcffmDGjBlceeWVzfrcIiIiIk3RVufBl3Zg6tSpxMbGcs455/jdfv755/udE78pHnjgAZ555hlmz57NueeeyxlnnMFDDz1EYmIiffv2bdIxr776at588002b97Mr371K8466yzeeustMjMzARg0aBAvv/wyGzduZNKkSTz88MM8+OCDjB07ttpxxo4dy6effspll13GSSedxNNPP82f//xnHn744WZ/bhEREZHGMtbaQMfQoQwfPtwuXLiwzu3Lly9nwIABrRiRiLQX+vdBREQawxjzvbV2eM1yteCLiIiIiHQgSvBFRERERDoQJfgiIiIiIh2IEnwRERERkQ5ECb6IiIiISAeiBF9EREREpANRgi8iIiIi0oEowRcRERER6UCU4IuIiIiIdCBK8EVEREREOhAl+NJirrjiCowx3HDDDU3af+bMmRhjmDlzZovEc8kll2CM8T5SU1MZO3YsM2bMaJHj+/IXu8fj4frrr6dLly4EBQUxefJkNmzYgDGG559/vsVjEBEREQEl+NJCiouLeeONNwB45ZVXqKioaPQxhg0bxty5cxk2bFiLxZWamsrcuXOZO3cu//nPf7DWcsopp/DFF1+02DnAf+xvvfUWTzzxBDfddBPffPMNDz/8MF26dGHu3LmceuqpLXp+ERERkSohgQ5AOobp06eTl5fHKaecwkcffcSMGTM47bTTGnWMuLg4Ro0a1aJxhYWFVTvm+PHjycjI4IknnmDChAktdh5/sS9fvhyA66+/nqCgfdfSLf0ZRURERHypBV9axNSpU0lMTOT5558nMjKSqVOn1qqzatUqzjzzTNLS0oiIiCAjI4NzzjnH29rvr5vLp59+yimnnEKXLl2Iiopi8ODBPProo1RWVjYpzri4OPr168eaNWsAeO211xg/fjypqanExMRw+OGH+429oqKChx56iIEDBxIREUFqaionnXQSK1as8Bt7z549mTJlCgDBwcHebjl1ddGZNWsWJ5xwAvHx8URHRzNkyBD++9//NukzioiIyMFNLfjSbFu2bOHzzz/niiuuIDU1lcmTJ/POO++we/duEhMTvfVOPfVUEhMTefrpp0lJSSE7O5uPPvoIj8dT57HXrVvHhAkTuOaaa4iIiGDhwoVMmTKFHTt28OCDDzY61oqKCrKyssjMzPQe/+yzz+aWW24hKCiI2bNnc/nll1NcXMzvfvc7736//OUvmT59Otdffz3HH388JSUlzJ49m61bt9K/f/9a55k2bRr/93//x/PPP8/cuXMB6N27N4WFhbXqvvvuu5x11lkcffTR/Pvf/yYlJYWlS5eycePGRn8+ERERESX4bcHHt8C2nwIbQ+dD4eTGJ8wAL730EpWVlVx00UUAXHzxxbz66qu8/vrr3iR5586drFmzhnfffZdJkyZ5973gggv2e2zfJNtay5gxYygrK+Nvf/sbDzzwQLWuL3WpukOwbds27r33XrZt28bNN98MwK233uqt5/F4GDduHFu3buXpp5/2nvvLL7/k7bff5oknnuDaa6/11p88eXKd5zz88MNJT08HqnfJqZngW2u57rrrGDp0KF999ZX38xx//PH1fi4RERERf5TgS7NNnTqVvn37Mnr0aMBJTrt27crUqVO9SXJycjK9evXilltuYfv27YwbN46+ffvWe+ytW7cyZcoUZsyYwZYtW6oN3s3JyaFz58773T87O5vQ0FDv+5iYGO655x5vor569WruvPNOZs+ezbZt27x3E8LDw737fPrppxhjuOKKKxr4E2m4lStXsnHjRu8dBBEREZHmUoLfFjSx5bwtWLhwIcuWLePmm29mz5493vJf/OIXPPnkk6xatYp+/fphjOGzzz5jypQp/OUvfyE3N5fMzExuuukmrrrqKr/H9ng8TJo0iS1btjBlyhT69+9PZGQk06dP5/7776ekpKTe+NLS0vjwww8xxpCcnEz37t0JDg4GoKCggBNOOIGoqCgefPBBevfuTVhYGE8//TT/+9//vMfIzc0lKSmJyMjI5v2w/MjNzQWgW7duLX5sEREROTgpwZdmqRqQ+tBDD/HQQw/V2v7CCy9w3333AdCrVy9eeOEFrLUsWbKEJ598kt///vf07NmTk08+uda+a9euZeHChbz44otceOGF3vL333+/wfGFhoYyfPhwv9vmzp3Lxo0b+frrrznmmGO85TWn+ExJSWHXrl0UFxe3eJKfkpICOHcaRERERFqC+gRIk5WVlfHqq68ycuRIvvrqq1qPoUOH8uKLL2KtrbafMYahQ4fy2GOPAfDzzz/7PX5RURFAtS425eXlvPzyyy0Sv7/j7969m3fffbdavRNPPBFrLc8++2yLnNdXv3796NmzJ88++2ytn5OIiIhIU6gFX5rsww8/JDc3l0cffZRx48bV2n7llVdy1VVXMXPmTJKTk7nuuus477zz6NOnD5WVlTz//POEhIQwfvx4v8cfMGAAPXr04LbbbiM4OJjQ0FAef/zxFov/qKOOIi4ujj/84Q/cfffdFBYWct9995GSksLevXu99Y477jjOOuss/vjHP5KVlcX48eMpLy9n9uzZnHrqqX4/e0MZY/j73//OL37xC8aPH8/vfvc7UlNTWb58OTk5Odx9990t8ElFRETkYKIWfGmyqVOnEhsbyznnnON3+/nnn++dE79z585kZGTw2GOPMWnSJM4//3y2bNnCBx98wBFHHOF3/7CwMKZPn07nzp256KKL+MMf/sDYsWO55ZZbWiT+1NRUpk2bRmVlJWeffTZ/+ctfuPzyy6t1B6ry2muvMWXKFKZPn86kSZO49NJLWbp0KV26dGl2HGeccQafffYZAJdddhmTJk3imWeeoWfPns0+toiIiBx8jLoFtKzhw4fbhQsX1rl9+fLlDBgwoBUjEpH2Qv8+iIhIYxhjvrfW1hpsqBZ8EREREZEORAm+iIiIiEgHogRfRERERKQDUYIvIiIiItKBKMEXEREREelAlOCLiIiIiHQgSvBFRERERDoQJfgiIiIiIh2IEnwRERERkQ5ECb6IiIiISAeiBF+a5fnnn8cY4/eRkJAQ6PBa1IMPPkjfvn2B6p971apVterOmjXLu/3zzz/3ll9yySX07Nmz3nNVHX/Dhg37rbdhwwaMMTz//PONPkdD1fX9GmOYPn16i52nypQpU/jyyy/r3D5v3jyMMWzevLla+csvv4wxhsMPP9zvfoWFhfz6178mLS0NYwzXX399vXEYYxodv4iISKCFBDoA6RjefPNNunXrVq0sJKRj/XpNnz6dM844o1pZbGwsL774Ivfee2+18qlTpxIbG0t+fn618jvuuIPrrrvugMfa0i655BKuvPLKWuWHHHJIi5/r7rvv5rbbbmP8+PF+t0+fPp0jjjii1u/b1KlTAVi8eDE//fQThx56aLXt//znP3n11Vf53//+R79+/ejSpct+47j88ss56aSTmvFJREREAqNjZWASMEOHDqVPnz4H5Njl5eWEhIQEtDV169atLFiwgL/97W/Vyn/xi1/w0ksvcc8993jjKy4u5q233uKss86q1rIO0Lt379YKuUWlp6czatSoQIcBOAn+hRdeWK0sOzubL774gpNPPpmPP/6YqVOn1vquli9fTteuXbnooov2e/zS0lLCw8Pp1q1brYsIERGR9kBddKRV1NXdoWZ3kqouJ0899RR//vOf6dq1K+Hh4ezZswdrLY8//jiHHHIIYWFhdOnShauvvpq8vLxqxzTGcNttt3H//ffTrVs3IiMjGTt2LIsXL651/nfeeYdRo0YRFRVFQkIC55xzDps2bapV79133yU1NZWjjjqqWvmvf/1rNm7cyJw5c7xl06ZNw+PxcNZZZ9X7eQHWrVvHqaeeSlRUFKmpqVx33XWUlpbW2reoqIjf//73JCcnExMTw6RJk2p1U6lLUVERN998M5mZmYSFhZGZmcn999+Px+Np0P4N8eSTTzJ69GiSkpJISEhg1KhRfPjhh9XqVFRUcMcdd9C7d28iIiJISUnhmGOO8f78qn5H7r//fm83oClTpnj3X7FiBStXrmTy5MnVjvviiy/i8Xi4++67Ofroo3n55ZeprKz0bq/qxpSVleU97syZM5k5cybGGN555x2uuOIKUlNT6dSpE+D/d7aiooKHHnqIgQMHEhERQWpqKieddBIrVqwAoKSkhBtuuIHBgwcTExND586dOf30073bRUREWoNa8KVFVFZWUlFRUa0sKCiIoKCmXUPef//9jBgxgmeeeYbKykoiIiK47bbb+Otf/8of/vAHTj/9dJYtW8Ydd9zBkiVLmDVrVrVzvfDCC2RkZPDkk09SWlrKnXfeyYQJE1i9ejVJSUkA/Otf/+Kqq67iN7/5DXfeeSf5+flMmTKFY489lh9//JHY2Fjv8aZPn87pp59e6/P06NGDsWPH8uKLLzJmzBjvuc8880xiYmLq/ZxlZWWccMIJFBcX889//pO0tDT+/e9/884779Sqe+WVV/L6669z1113MWLECD777DMuuOCCes9RUVHBxIkTvT+vQw89lHnz5v1/e/ceHkWV53/8/cUkJCKXQLhkGC663ER3dTVcx1UUBMJFnFGRFQXxAXX1p46yPvhDnchwmVFUVHSGXXEQUbmsLhEQGEBYIkxQGC87IqAiOBERkHARCCSQs39UdZt0dy6EJE06n9fz1FPk1KlTp+rQ3d8+feoUEydOJDc3l2eeeabMMpxzYe0LxYdh7dy5k9GjR9O2bVtOnjzJ4sWLGTRoEMuWLQsOdXnyySeZNm0akydP5tJLL+Xw4cNs2rSJ3NxcALKzs+nRo0exIUFFe9EzMzNp164dF198cbF6zJ49mwsvvJAuXbowYsQI7rrrLlasWEF6enqw3CeeeIJPP/2UhQsXAtC5c2c++ugjAO677z7S09OZM2cOx48fL/E6DBs2jMzMTH7961/Tp08fjh8/TlZWFrt376ZTp06cOHGCH3/8kccee4zU1FRyc3P5wx/+QI8ePdiyZQstWrQo81qLiIicKQX4Z4EnP3ySrbnR7eHr1LgT47qOq/j+nTqFpQ0cOJAlS5ZUqLzmzZuzcOHCYA9qIBAdOXIkL774IgD9+vWjadOm3HbbbSxZsoTrrrsuuH9eXh4rVqygXr16AHTr1o327dszbdo0Jk6cyJEjRxg3bhyjRo3iT3/6U3C/rl270rFjR1555ZXgTZiHDx9mzZo1vP322xHrOmLECMaOHcsLL7zAgQMHWLVqFcuWLSvXec6ePZuvv/6a7Ozs4BCY9PT0sPHj27Zt480332Ty5Mk88sgjAPTt25cjR44wY8aMUo8xd+5c1q1bx9q1a7nyyisB6N27N+CNdx83bhzNmjUrtYwpU6YwZcqUsPR9+/aRkpICUGxITGFhIb179+aLL77gj3/8YzDAz87Opm/fvsXuQxg8eHDw34FrUNKQoEj3QXz44Yds3bo1WL+hQ4fywAMPMHv27GCA3717d1JSUqhbt27Ecrt27crMmTNLvQarV6/m7bff5vnnn+f+++8Pphf9NaFhw4bFyjl16hT9+vWjefPmzJ07lwcffLDUY4iIiFQGDdGRSrFw4UI2btxYbHnuuecqXN71119fbHjEhg0byM/PDxt7PWzYMOLi4li7dm2x9AEDBgSDe4C2bdvSvXt3srOzAS/QPHz4MMOHD+fkyZPBpVWrVnTq1ImsrKzgvkuXLiUhIYE+ffpErOtNN93EiRMnWLx4MW+88QYtWrQIBtBlyc7OplWrVsWCzjp16jB06NBi+T744AMKCwvD0ocNG1bmMZYvX06bNm3o2bNnsXPt27cvBQUFbNiwocwy7rjjjrD23bhxY7GZkv76178yaNAgmjdvTlxcHPHx8axcuZJt27YF83Tp0oWlS5fy6KOPsm7dOvLz88s8dkDgPojQ4TmzZ8+mTp06wf8bjRo1YsiQIbzzzjscOnSoXGX/8pe/LDPPihUrMDPGjBlTar4FCxbQrVs3GjVqRFxcHPXq1ePIkSPFroOIiEhVUg/+WeBMes7PFhdffHGl3mQbOsNJYAhHaHpcXBxNmjQJbg8IjKMOTdu8eTMAe/fuBSgxaE9OTg7+OzMzk379+pGYmBgxb/369bn++uuZM2cOO3fuZPjw4eUemrR79+4S6xqaL1J6pH1D7d27l2+++Yb4+PiI2/fv319mGampqaSlpZW4PScnh969e9O5c2emT59O69atiYuL4/HHH2fLli3BfOPHjycxMZHXX3+dKVOmcN5553HjjTcyderU4C8BJYl0H0R+fj7z5s2jR48e1K9fn4MHDwJewD5//nwWLFhQZkAeOL+y7N+/n8aNG5OUlFRinsWLF3PzzTczcuRIMjIySElJoU6dOgwYMKDUoT8iIiKVSQG+VItAcJyfn09CQkIwvaTgMvTmxsC4+e+//56LLroomH7y5Mlg4FXUnj17wsrcs2cPLVu2BKBJkyaAN9980fICAuPv8/PzWbZsGS+99FKp5zdixAgGDhxIYWEhc+fOLTVvUampqcEvHaXVPxCA7tmzhwsuuKDEfJE0adKE888/nwULFkTcXhlz5i9fvpxDhw6xYMGCYmPmjx07VixffHw848aNY9y4cXz//fcsWbKEhx56iGPHjjF//vxSjxHpPojFixeTm5vL+vXri30pC5g9e3a5AvzyzNCUkpJCbm4ueXl5JQb58+bNo127dsVmTyooKAj7AioiIlKVNERHqkWbNm0A+Oyzz4JpBw8e5C9/+Uu59u/evTsJCQnMmzevWPr8+fM5efIkvXr1Kpa+dOlSjh49Gvx7586dbNiwgR49egDQs2dP6tevz1dffUVaWlrYEpjfffXq1Rw7doxBgwaVWr9rr72WoUOHcvfdd0f8wlCSHj16kJOTU2yYTGFhYVgw3q1bN+rUqROWHno9Iunfvz85OTmcd955Ec+1rJ7z8ggE8kV/Jfjiiy9Yv359ifu0aNGC0aNH06dPn2L/LxISEsjLyyuWN3AfRKThOfXq1WPVqlWsWbOm2HL77bezfv16tm/ffsbnB949D865UsfqHzt2LOz5D3PmzCk2o4+IiEhVUw++VIpPPvmEH374ISw9LS2NuLg40tPTadiwIWPGjGHChAmcOHGCp556qlwzzYDXgz927Fh+97vfUa9ePQYMGMCWLVt47LHHuOKKKxg4cGCx/ElJSfTt25eHH36YEydOkJGRQYMGDYI3OTZo0ICpU6dy7733sm/fvmD9du3axdq1a+nVqxe33HILmZmZXHXVVWU+lfecc845rZ77gJEjR/L73/+eX/3qV0yZMoVmzZoxY8aMsKk/O3bsyC233MJvfvMbCgsL6dKlCytWrGDp0qVlHmP48OHMmjWL3r17M3bsWC655BLy8/PZvn07ixYtIjMzk3PPPbfUMnbt2hVxrH6bNm1ITU2lT58+xMXFBW843r17NxkZGbRu3brYVJxDhgzhkksu4bLLLiM5OZmPP/6Y5cuXF3uIVufOnXn33Xfp378/ycnJ/OxnPyMrKyvsPoi9e/eybNkybr311oj3PLRo0YJXX32V1157jQkTJpR5ncpy9dVXc8MNN/DQQw+Rk5PDNddcQ0FBAVlZWQwcOJBevXrRv39/MjMzefDBBxk0aBCbNm1i+vTpMfdUZxEROcs557RU4nL55Ze70nz++eelbq9pZs2a5YASl3379gXzvv/++y4tLc0lJSW59u3buzlz5riRI0e6Nm3aBPPs2LHDAe7ll18OO1ZhYaF79tlnXYcOHVx8fLxr0aKFu+eee9yhQ4eK5QPc+PHj3eTJk13Lli1d3bp13RVXXOE+/vjjsDLfffdd16tXL1e/fn2XlJTk2rVr50aNGuU2b97sCgsLXWpqqps+fXqJ5/3ll1+WeG3WrFnjALdy5cpgWuj5Oufc9u3bXXp6uktKSnIpKSnu/vvvdzNmzHCA27FjRzDf0aNH3d133+2Sk5NdvXr13ODBg926desc4GbNmlXqMfLy8lxGRobr2LGjS0hIcMnJyS4tLc1lZGS4goKCEs8hcD1LWqZOnRrMN3/+fNexY0dXt25d17lzZzd37tywujz99NOuW7durnHjxi4xMdF16NDBZWRkuPz8/GCedevWucsuu8zVrVvXAS4jI8PdfPPN7oYbbihWr2nTpjnAZWVllVj3nj17urZt27rCwkI3fPjwsOsSqY0CMjIynPcW+ZOCggI3adIk1759excfH+9SUlJcenq627p1q3POuVOnTrlHH33UpaamuqSkJHfllVe6jz76yLVp08aNHDmy1OvsXOy9P4iISNUCNrkI8ah526SypKWluU2bNpW4fcuWLVx44YXVWKPaJ/Cgq0mTJp1ROYEhPTk5OXqiaRTl5+fTtGlTXnrppbBZlGKN3h9EROR0mNlfnXNhs2BoiI5ICbp3746+AEdfQkJCuae7FBEREd1kKyIiIiISU9SDLzFHve4iIiJSm6kHX0REREQkhijAFxERERGJIQrwo0BDSEQklN4XRESksijAr2bx8fFhT+kUEcnLy6Nu3brRroaIiMQABfjVrFmzZuzatYtjx46px06klnPOUVBQQG5uLt9++y1NmjSJdpVERCQGaBadatagQQMAvvvuOwoKCqJcGxGJtri4OBITE2ndujWJiYnRro6IiMQABfhR0KBBg2CgLyIiIiJSmTRER0REREQkhijAj8DMWpnZW2Z2yMwOm9l/m1nraNdLRERERKQsCvBDmNm5wGqgEzASuA1oD6wxs3rRrJuIiIiISFk0Bj/cGOACoKNz7isAM/tf4EvgLuDZKNZNRERERKRU6sEPdx2wIRDcAzjndgDrgSFRq5WIiIiISDkowA93EfBZhPTNQOdqrouIiIiIyGnREJ1wjYEDEdJzgeRIO5jZncCd/p9HzGxbFdWtNCnAD1E4rpw5tV3NpbarmdRuNZfaruZS21WNNpESFeBXAufcfwL/Gc06mNkm51xaNOsgFaO2q7nUdjWT2q3mUtvVXGq76qUhOuEOELmnvqSefRERERGRs4YC/HCb8cbhh+oMfF7NdREREREROS0K8MMtArqb2QWBBDNrC/zC33a2iuoQITkjaruaS21XM6ndai61Xc2ltqtG5pyLdh3OKv7DrD4F8oDHAAdMBOoD/+ScOxLF6omIiIiIlEo9+CGcc0eBa4AvgDnAG8AO4BoF9yIiIiJytlMPvoiIiIhIDFEPfhSZ2Y1m9raZfWNmeWa2zcx+Z2b1Q/Ilm9lMM/vBzI6a2Soz+8cI5SWa2VQz2+2Xl21mV0bI18TMnjezr/18O8zsRTNrWpXnG0uqoO2mmNkKM9tvZs7Mbi/l2GPMbKuZnfCPe3cVnGLMikbbmVmqf4xNZnbQzPaZ2XuRXp8SWTRfc0X26WlmhX5+TTNdTlF+v0w2s+fM7O/+e+a3ZvZq5Z9lbIpW25nZuWY2wcy+8I+bY2avmXdPpJSDAvzo+nfgFDAe6A/8Efg3YKWZ1QEwMwMW+9vvA24A4oE1ZvbzkPJeAcYAvwEGAbuBP5vZpYEMfnmLgFuAqUC6vx4GLPa3S9kqu+3uA5KAJaUd1MzGAP8BvO2X+1/AH8zs3yrntGqFaLTd5cDNwDvATcDtwHHgf8xsUKWcVeyLymsuwMzi8V57e874TGqfaL1fJgPrgD5499Rd69flx0o5q9ohWq+7mcDDwMvAALz2uxJ4z8zOO/PTqgWcc1qitABNI6SNwLux9xr/7yH+31cXydMQ78m6LxRJu8TPN6pIWhywDVhUJK2Dn+/OkOPe7ad3jPZ1qQlLZbadn17HX7fz97k9QvlxwF5gdkj6n/CeDhgf7etSE5YotV0jIC5Ce24DsqJ9TWrCEo12C8k/HvgMmOznj6voudS2JVptB8wAvgEaRPsa1NQlSu+X5wIngSkh6f39ffpF+7rUhEU9+FHknNsXIXmjv27pr68DvnPOrSmy3yG8b8tDiux3HVAAzC+S7yQwD+hnZnX95AR/fTjkuAf9tf5PlEMltx3OucJyHLYH0BR4PSR9DtAEuKIcZdR60Wg759xB//VYNO0k8EmRY0opovSaA8DM/gGvB/EevPdZOQ3RaDvzZsQbAcx0zoV+3kk5Rel1d46/KE45A7pIZ5+r/PUWf30RXq9RqM1A6yI/VV0E7HDOHYuQLwHv23Lg7yzgcTNLM7PzzKwr3rCeZc65LUhFVbTtyivwALbQMjf7686nWZ78pKrbLoyZJeB9adNrruKqq91mAP/lnMuq4P4Srqrb7nK8oSB7zOwtfxz3ETPLNLPzK1Zl8VVp2znnfsTruLrfzK7245SL8IYTfwq8V7Fq1y4K8M8iZtYS+C2wyjm3yU9uDByIkD3XXyeXM19jAOf9zjUAb2jARryxiB8AX+ONm5MKOMO2K6/G/jq0zNyQ7XIaqqntInkC+DnwZCWUVetUV7uZ2a14weLDFamnhKumtvuZv34abwz5dcCdwD/j3ftSv6QdpWTV+H45ClgIrMaLUz7DG9d/rXMuvwLl1ToK8M8S/jfcd/DGnY2q4sO9DHTHG3d/lb9OA94K3DQj5VfNbSeVKFptZ2a3AI8AE51z71fXcWNFdbWbmTUGngXGO+f2VtVxapNqfM0FPsu+BoY551Y6594EhgKtgVur8NgxqZrfLyfhtdG/48Upt+ENRV3mD7+SMmiar7OAmSXhjVW7ALjKOfdtkc0HiPztN7Q39wDQppR8uf6xBgL/CvRxzgV+5soys6+BFcBgvBewlEMltV15BfIn482QFFpeLlJu1dx2RY87GHgVeMU5l1HRcmqram63SXivtQVm1shPS/TXDc3suPMejijlUM1tt99fv+f/cg2Ac+4DMzuM15Mv5VSdbecPx3kEGO2ce6VI+gd4DyEdDTx/OmXWRuqtjTJ/6rW38HrQBzjn/haSZTM/jb0uqjPwd/fT03U3A+eb2bkR8uUDX/l/B+al3RiS70N/feHpnUHtVYltV16BsfahZQbG3n9+muXVWlFou8Bxe+NNbboQuKsiZdRmUWi3zsA/4QWLB/xlnL/tB7wnnUs5RPH9siTlvsm6totC20WMU5xzX+LdaKs4pRwU4EeRPxzmDeAa4Hrn3IYI2RYBLc3sqiL7NcDraV9UJN9ivPFpNxXJF4c39/YK59wJP/l7f9015Djd/PWuip1N7VLJbVde2XhBxfCQ9Fvxeu/XV6DMWidKbYeZ9cD7dew94NbTmcVFotZuvwauDllm+9sCc6tLGaLRdn4P8ybg2qLPd/Ffhw0I7+SSCKL0uosYp5hZB7wphxWnlIOG6ETXS3gB+WTgqJl1L7LtW/8NahFeYPe6mT2M14P0/wEDngpkds59bGbzgef8b9s78B5GcT7FA8L/9o/3mplNBLYCnYAMIAevZ1HKVmltB+C/MTYFWvhJaWZ2BMA595a/LjCzx/EebLULWIX3pnsHcJ9uPCq3am87M+sEvIv3BW0qcHmRmIMSPjSluGi85j4JrYSZ9fL/uTZ06lMpUbW3ne8R4M9495fN9PeZjPe592alnmHsikbbvY83W84z/sPKNuHdN/EYcIifvmRLaap6on0tJS/ATryHNkRaniiSrzHew4xygWN4PYCXRCgvCe+GsO/xnpL5AdArQr5WeE+93eHn24F3423LaF+TmrJUQdv9T0nlRch7F944xBPAl8A90b4eNWmJRtvhPbm2pGOGtbGWs6PdSqjHE+hBVzWm7fCe1r4R77NuP/Aa0Dza16SmLNFqO7wbap/B+4zLw+uAnI8exlnuxfwLKSIiIiIiMUBj8EVEREREYogCfBERERGRGKIAX0REREQkhijAFxERERGJIQrwRURERERiiAJ8EREREZEYogBfRETOiJm9ZWa5ZtY8wrZeZlZoZg9Eo24iIrWR5sEXEZEz4gf2nwOrnXM3FUlPAv4X2Av8i3OuMEpVFBGpVdSDLyIiZ8Q5twd4ALjRzK4vsukJ4OfAHVUd3JtZvJlZVR5DRKSmUIAvIiJnzDn3OvAu8JKZNTSzy4CxeI+z3wZgZnea2admdtzMfjCzV8yscdFyzOz/mVm2P+TnoJltMLOBIXnampkzs3vM7Ckz+w44ATSqlpMVETnLaYiOiIhUCjNrCWwGFgKXAieB7s65U2b2e7yA/wXgz0BLYBLwLdDTOXfKL+NpYAuwE4gDBgP3AunOueV+nrbADuA7YCMwEzgHWOGcy6uGUxUROaspwBcRkUpjZqOBl4EC4HLn3N/8gHw7MME599sieX8BrAN+6ZzLjFBWHbxfmpcCec65IX56W7wA/2P/GPogExEpQkN0RESk0jjnZgK7gUzn3N/85GvxPm/eMLO4wAJ8APwIXBnY38wuN7MlZrYH7xeAAn//jhEOl6ngXkQknAJ8ERGpbPn+EtDMX3+FF7AXXeoDTQDMrBXwHtAYuA/oCXQBlgOJEY6zuwrqLiJS48VFuwIiIhLz9vvrvsCBUrb3BxoCQ51z3wY2mtm5JZSr3nsRkQgU4IuISFVbCRQCrZ1zK0vJFwjkCwIJZtYB+AXezbgiIlIOCvBFRKRKOee2m9mTwItm1hFYCxwHWuGNr5/pnFsDrMIbd/+amT0DpAITgL+jIaUiIuWmAF9ERKqcc268mW3Bm/LyXrzhNTl4Y+6/9PNsNrPhwG+BRXgz7zyCN3SnVxSqLSJSI2maTBERERGRGKKfPEVEREREYogCfBERERGRGKIAX0REREQkhijAFxERERGJIQrwRURERERiiAJ8EREREZEYogBfRERERCSGKMAXEREREYkh/wcW8OTMZVnVdQAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.rcParams.update({'font.size': 16})\n", - "_ = revenue_table.transpose().plot(title=\"Revenue by Geographic Region\",\n", - " ylabel=\"Revenue (Millions of US$)\",\n", - " figsize=(12, 7), ylim=(0, 50000))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now Allison has a clear picture of the detailed revenue data that was hidden inside those 1500 pages of PDF\n", - "files. As she works on her analyst report, Allison can use the same process to extract DataFrames for\n", - "other financial metrics too!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "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.7.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/notebooks/Understand_Tables_API.ipynb b/notebooks/Understand_Tables_API.ipynb new file mode 100644 index 00000000..7618c986 --- /dev/null +++ b/notebooks/Understand_Tables_API.ipynb @@ -0,0 +1,4761 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Understand_Tables_API.ipynb:\n", + "

\n", + "Extract Structured Information from Tables in PDF Documents\n", + " using IBM Watson Discovery's Python SDK and Text Extensions for Pandas\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Introduction\n", + "\n", + "Many organizations have valuable information hidden in tables inside human-readable documents like PDF files and web pages. Table identification and extraction technology can turn this human-readable information into a format that data science tools can import and use. Text Extensions for Pandas and Watson Discovery make this process much easier.\n", + "\n", + "In this notebook, we'll follow the journey of Allison, an analyst at an investment bank. Allison's employer has assigned her to cover several different companies, one of which is IBM. As part of her analysis, Allison wants to track IBM's revenue over time, broken down by geographical region. That detailed revenue information is all there in IBM's filings with the U.S. Securities and Exchange Commission (SEC). For example, here's IBM's 2019 annual report:\n", + "\n", + "![IBM Annual Report for 2019 (146 pages)](images/IBM_Annual_Report_2019.png)\n", + "\n", + "Did you see the table of revenue by geography? It's here, on page 39:\n", + "\n", + "![Page 39 of IBM Annual Report for 2019](images/IBM_Annual_Report_2019_page_39.png)\n", + "\n", + "Here's what that table looks like close up:\n", + "\n", + "![Table: Geographic Revenue (from IBM 2019 annual report)](images/screenshot_table_2019.png)\n", + "\n", + "But this particular table only gives two years' revenue figures. Allison needs to have enough data to draw a meaningful chart of revenue over time. 10 years of annual revenue figures would be a good starting point. \n", + "\n", + "Allison has a collection of IBM annual reports going back to 2009. In total, these documents contain about 1500 pages of financial information. Hidden inside those 1500 pages are the detailed revenue figures that Allison wants. She needs to find those figures, extract them from the documents, and import them into her data science tools.\n", + "\n", + "Fortunately, Allison has [Watson Discovery](https://www.ibm.com/cloud/watson-discovery), IBM's suite of tools for managing and extracting value from collections of human-readable documents.\n", + "\n", + "The cells that follow will show how Allison uses Text Extensions for Pandas and Watson Discovery to import the detailed revenue information from her PDF documents into a Pandas DataFrame...\n", + "\n", + "![Screenshot of a DataFrame from later in this notebook.](images/revenue_table.png)\n", + "\n", + "...that she then uses to generate a chart of revenue over time:\n", + "\n", + "![Chart of revenue over time, from later in this notebook.](images/revenue_over_time.png)\n", + "\n", + "But first, let's set your environment up so that you can run Allison's code yourself.\n", + "\n", + "(If you're just reading through the precomputed outputs of this notebook, you can skip ahead to the section labeled [\"Extract Tables with Watson Discovery\"](#watson_discovery))." + ] + }, + { + "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", + "* `matplotlib`\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**.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Core Python libraries\n", + "import json\n", + "import os\n", + "import sys\n", + "import requests\n", + "import glob\n", + "from typing import *\n", + "import pandas as pd\n", + "from matplotlib import pyplot as plt\n", + "from IPython.display import HTML\n", + "from base64 import b64encode\n", + "\n", + "# IBM libraries\n", + "from ibm_watson import DiscoveryV2\n", + "from ibm_cloud_sdk_core.authenticators import IAMAuthenticator\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": [ + "

\n", + "\n", + "# Extract Tables with Watson Discovery\n", + "\n", + "Allison connects to the [Watson Discovery](https://cloud.ibm.com/docs/discovery-data?topic=discovery-data-install) component of her firm's [IBM Cloud Pak for Data](https://www.ibm.com/products/cloud-pak-for-data) installation on their [OpenShift](https://www.openshift.com/) cluster and uploads her stack of IBM annual reports to her project. Then she uses the Watson Discovery's [Table Understanding enrichment](https://cloud.ibm.com/docs/discovery-data?topic=discovery-data-understanding_tables) to identify tables in the PDF documents and to extract detailed information about the cells and headers that make up each table.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You'll need two pieces of information to access your instance of Watson Discovery: An **API key** and a **service URL**. If you're using Watson Discovery 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 Watson Discovery 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", + "Alternately, you can uncomment the first two lines of code below to set the `IBM_API_KEY` and `IBM_SERVICE_URL` environment variables directly. **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", + "# Retrieve the API key for your Watson Discovery service instance\n", + "if \"IBM_API_KEY\" not in os.environ:\n", + " raise ValueError(\"Expected Watson Discovery api key in the environment variable 'IBM_API_KEY'\")\n", + "api_key = os.environ.get(\"IBM_API_KEY\") \n", + "# Retrieve the service URL for your Watson Discovry service instance\n", + "if \"IBM_SERVICE_URL\" not in os.environ:\n", + " raise ValueError(\"Expected Watson Discovery 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 Discovery 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", + "Allison starts by using the API key and service URL from the previous cell to create an instance of the\n", + "Python API for Watson Discovery." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "authenticator = IAMAuthenticator(api_key)\n", + "version='2020-08-30'\n", + "discovery = DiscoveryV2(\n", + " version=version,\n", + " authenticator=authenticator\n", + ")\n", + "\n", + "discovery.set_service_url(service_url)\n", + "discovery.set_disable_ssl_verification(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note: This notebook shows you how to run the whole usecase end to end using the Python SDK. Alternatively, Allison could use the Watson Discovery tooling to create a project, create a collection, enable the pre-trained model to apply the Table Understanding enrichment, and add the pdf documents to the collection before querying the collection as shown in [this additional notebook](./Understand_Tables_Tooling.ipynb#watson_discovery_tooling_demo)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "# Create a Project through the Watson Discovery Service API\n", + "\n", + "She creates a new Watson Discovery project:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "project = discovery.create_project(\n", + " \"TextExtensionsForPandasTableUnderstanding\",\n", + " \"document_retrieval\"\n", + " ).get_result()\n", + "project_id = project['project_id']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create a Collection through the Watson Discovery Service API\n", + "She then creates a new collection in her project." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "collection = discovery.create_collection(\n", + " project_id = project_id,\n", + " name = \"IBM-10K\"\n", + " ).get_result()\n", + "collection_id = collection['collection_id']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before Allison can apply the table understanding to her collection, she needs to first enable the Pre-trained-models option for her collection to get the tables annotated for her automatically using the Smart Document Understanding tool, to do so she calls following function:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def enable_pretrained_models(project_name:str, collection_name:str):\n", + " auth = (\"apikey\", api_key)\n", + " response = requests.get(f\"{service_url}/v2/datasets?version={version}\", auth=auth)\n", + " for dataset in response.json()['datasets']:\n", + " if (dataset['name'] == collection_name and \n", + " dataset['collections'][0]['project_name'] == project_name):\n", + " config_id = dataset['id']\n", + " configurations = requests.get \\\n", + " (f\"{service_url}/v2/configurations/{config_id}/converters?version={version}\", auth=auth).json()\n", + " for config in configurations[\"converterConfigurations\"]:\n", + " if config['name'] == \"ama-converter-sdu-converter\":\n", + " config['converterSettings']['structure_model'] = 'static'\n", + " headers = {'Content-type': 'application/json'}\n", + " response = requests.put \\\n", + " (f\"{service_url}/v2/configurations/{config_id}/converters?version={version}\", \\\n", + " auth = auth, data = json.dumps(configurations), headers = headers)\n", + "\n", + "enable_pretrained_models('TextExtensionsForPandasTableUnderstanding', 'IBM-10K')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then she applies the Table Understanding enrichment to get detailed information about tables and table-related data within documents.\n", + "\n", + "She should apply the enrichment only to a field that contains an HTML representation of the table. That's the only way that the enrichment can read the parts of the table, such as header rows and columns, and interpret the information in the table properly." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "text/plain": [ + "{'name': 'IBM-10K',\n", + " 'collection_id': '8c655b3a-0ad4-3f36-0000-017eea50450a',\n", + " 'description': '',\n", + " 'created': '2022-02-11T19:43:57.451Z',\n", + " 'language': 'en',\n", + " 'enrichments': [{'enrichment_id': '701db916-fc83-57ab-0000-000000000012',\n", + " 'fields': ['html']}]}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "discovery.update_collection(\n", + " project_id = project_id,\n", + " collection_id = collection_id,\n", + " enrichments = [{\"enrichment_id\": \"701db916-fc83-57ab-0000-000000000012\", \"fields\": [\"html\"] }]\n", + " ).get_result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Pass Documents to the Watson Discovery Service API\n", + "\n", + "Once she's created a collection and updated the collection with Table Understanding enrichment, she can upload the documents through \n", + "the service by invoking the [`add_document()` method](https://cloud.ibm.com/apidocs/discovery-data?code=python#adddocument)." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n", + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n", + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n", + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n", + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n", + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n", + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n", + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n", + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n", + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n", + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "for file in glob.glob(\"../resources/IBM Annual Report/*.pdf\"):\n", + " with open(file,'rb') as fileinfo:\n", + " response = discovery.add_document(\n", + " project_id = project_id,\n", + " collection_id = collection_id,\n", + " file=fileinfo,\n", + " filename=file,\n", + " fileinfo='application/pdf'\n", + " ).get_result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "# Query the Project\n", + "She can then query the project with an empty string to retreive all the documents." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "geograpies_results = discovery.query(\n", + " project_id = project_id,\n", + " collection_ids = [collection_id],\n", + " natural_language_query= \"\" \n", + " ).get_result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "She then searches through all the tables and keeps thoese tables which are under the \"Geographic Revenue\" section in the documents." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"section_title\": {\n", + " \"location\": {\n", + " \"end\": 651156,\n", + " \"begin\": 651138\n", + " },\n", + " \"text\": \"Geographic Revenue\"\n", + " },\n", + " \"row_headers\": [\n", + " {\n", + " \"column_index_begin\": 0,\n", + " \"row_index_begin\": 4,\n", + " \"location\": {\n", + " \"end\": 656019,\n", + " \"begin\": 656005\n", + " },\n", + " \"text\": \"Total revenue\",\n", + " \"row_index_end\": 4,\n", + " \"cell_id\": \"rowHeader-656005-656019\",\n", + " \"column_index_end\": 0,\n", + " \"text_normalized\": \"Total revenue\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 0,\n", + " \"row_index_begin\": 5,\n", + " \"location\": {\n", + " \"end\": 657316,\n", + " \"begin\": 657304\n", + " },\n", + " \"text\": \"Geographies\",\n", + " \"row_index_end\": 5,\n", + " \"cell_id\": \"rowHeader-657304-657316\",\n", + " \"column_index_end\": 0,\n", + " \"text_normalized\": \"Geographies\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 0,\n", + " \"row_index_begin\": 6,\n", + " \"location\": {\n", + " \"end\": 658609,\n", + " \"begin\": 658600\n", + " },\n", + " \"text\": \"Americas\",\n", + " \"row_index_end\": 6,\n", + " \"cell_id\": \"rowHeader-658600-658609\",\n", + " \"column_index_end\": 0,\n", + " \"text_normalized\": \"Americas\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 0,\n", + " \"row_index_begin\": 7,\n", + " \"location\": {\n", + " \"end\": 659908,\n", + " \"begin\": 659882\n", + " },\n", + " \"text\": \"Europe/Middle East/Africa\",\n", + " \"row_index_end\": 7,\n", + " \"cell_id\": \"rowHeader-659882-659908\",\n", + " \"column_index_end\": 0,\n", + " \"text_normalized\": \"Europe/Middle East/Africa\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 0,\n", + " \"row_index_begin\": 8,\n", + " \"location\": {\n", + " \"end\": 661196,\n", + " \"begin\": 661183\n", + " },\n", + " \"text\": \"Asia Pacific\",\n", + " \"row_index_end\": 8,\n", + " \"cell_id\": \"rowHeader-661183-661196\",\n", + " \"column_index_end\": 0,\n", + " \"text_normalized\": \"Asia Pacific\"\n", + " }\n", + " ],\n", + " \"table_headers\": [],\n", + " \"location\": {\n", + " \"end\": 662216,\n", + " \"begin\": 652301\n", + " },\n", + " \"text\": \" Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent Adjusted for For the year ended December 31: 2017 2016\\nChange\\n Currency\\nTotal revenue $79,139 $79,919 (1.0)% (1.3)%\\nGeographies $78,793 $79,594 (1.0)% (1.4)%\\nAmericas 37,479 37,513 (0.1) (0.6)\\nEurope/Middle East/Africa 24,345 24,769 (1.7) (2.8)\\nAsia Pacific 16,970 17,313 (2.0) (1.1)\\n\",\n", + " \"body_cells\": [\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-656005-656019\"\n", + " ],\n", + " \"column_index_begin\": 1,\n", + " \"row_index_begin\": 4,\n", + " \"row_header_texts\": [\n", + " \"Total revenue\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2017\"\n", + " ],\n", + " \"column_index_end\": 1,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652366-652367\",\n", + " \"colHeader-652910-652911\",\n", + " \"colHeader-653663-653664\",\n", + " \"colHeader-654875-654880\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2017\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 656281,\n", + " \"begin\": 656273\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 656280,\n", + " \"begin\": 656273\n", + " },\n", + " \"text\": \"$79,139\",\n", + " \"type\": \"Currency\"\n", + " }\n", + " ],\n", + " \"text\": \"$79,139\",\n", + " \"row_index_end\": 4,\n", + " \"row_header_texts_normalized\": [\n", + " \"Total revenue\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-656273-656281\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-656005-656019\"\n", + " ],\n", + " \"column_index_begin\": 2,\n", + " \"row_index_begin\": 4,\n", + " \"row_header_texts\": [\n", + " \"Total revenue\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2016\"\n", + " ],\n", + " \"column_index_end\": 2,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652431-652432\",\n", + " \"colHeader-652975-652976\",\n", + " \"colHeader-653728-653729\",\n", + " \"colHeader-655140-655145\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2016\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 656537,\n", + " \"begin\": 656529\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 656536,\n", + " \"begin\": 656529\n", + " },\n", + " \"text\": \"$79,919\",\n", + " \"type\": \"Currency\"\n", + " }\n", + " ],\n", + " \"text\": \"$79,919\",\n", + " \"row_index_end\": 4,\n", + " \"row_header_texts_normalized\": [\n", + " \"Total revenue\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-656529-656537\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-656005-656019\"\n", + " ],\n", + " \"column_index_begin\": 3,\n", + " \"row_index_begin\": 4,\n", + " \"row_header_texts\": [\n", + " \"Total revenue\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Percent\",\n", + " \"\",\n", + " \"Change\",\n", + " \"\"\n", + " ],\n", + " \"column_index_end\": 4,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652496-652497\",\n", + " \"colHeader-652748-652759\",\n", + " \"colHeader-653225-653236\",\n", + " \"colHeader-653497-653512\",\n", + " \"colHeader-653974-653982\",\n", + " \"colHeader-654059-654060\",\n", + " \"colHeader-655408-655415\",\n", + " \"colHeader-655492-655493\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Percent\",\n", + " \"\",\n", + " \"Change\",\n", + " \"\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 656792,\n", + " \"begin\": 656785\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 656789,\n", + " \"begin\": 656786\n", + " },\n", + " \"text\": \"1.0\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"(1.0)%\",\n", + " \"row_index_end\": 4,\n", + " \"row_header_texts_normalized\": [\n", + " \"Total revenue\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-656785-656792\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-656005-656019\"\n", + " ],\n", + " \"column_index_begin\": 5,\n", + " \"row_index_begin\": 4,\n", + " \"row_header_texts\": [\n", + " \"Total revenue\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Adjusted for\",\n", + " \"Currency\"\n", + " ],\n", + " \"column_index_end\": 5,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652748-652759\",\n", + " \"colHeader-653497-653512\",\n", + " \"colHeader-654309-654322\",\n", + " \"colHeader-655738-655747\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Adjusted for\",\n", + " \"Currency\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 657047,\n", + " \"begin\": 657040\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 657044,\n", + " \"begin\": 657041\n", + " },\n", + " \"text\": \"1.3\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"(1.3)%\",\n", + " \"row_index_end\": 4,\n", + " \"row_header_texts_normalized\": [\n", + " \"Total revenue\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-657040-657047\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-657304-657316\"\n", + " ],\n", + " \"column_index_begin\": 1,\n", + " \"row_index_begin\": 5,\n", + " \"row_header_texts\": [\n", + " \"Geographies\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2017\"\n", + " ],\n", + " \"column_index_end\": 1,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652366-652367\",\n", + " \"colHeader-652910-652911\",\n", + " \"colHeader-653663-653664\",\n", + " \"colHeader-654875-654880\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2017\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 657579,\n", + " \"begin\": 657571\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 657578,\n", + " \"begin\": 657571\n", + " },\n", + " \"text\": \"$78,793\",\n", + " \"type\": \"Currency\"\n", + " }\n", + " ],\n", + " \"text\": \"$78,793\",\n", + " \"row_index_end\": 5,\n", + " \"row_header_texts_normalized\": [\n", + " \"Geographies\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-657571-657579\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-657304-657316\"\n", + " ],\n", + " \"column_index_begin\": 2,\n", + " \"row_index_begin\": 5,\n", + " \"row_header_texts\": [\n", + " \"Geographies\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2016\"\n", + " ],\n", + " \"column_index_end\": 2,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652431-652432\",\n", + " \"colHeader-652975-652976\",\n", + " \"colHeader-653728-653729\",\n", + " \"colHeader-655140-655145\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2016\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 657834,\n", + " \"begin\": 657826\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 657833,\n", + " \"begin\": 657826\n", + " },\n", + " \"text\": \"$79,594\",\n", + " \"type\": \"Currency\"\n", + " }\n", + " ],\n", + " \"text\": \"$79,594\",\n", + " \"row_index_end\": 5,\n", + " \"row_header_texts_normalized\": [\n", + " \"Geographies\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-657826-657834\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-657304-657316\"\n", + " ],\n", + " \"column_index_begin\": 3,\n", + " \"row_index_begin\": 5,\n", + " \"row_header_texts\": [\n", + " \"Geographies\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Percent\",\n", + " \"\",\n", + " \"Change\",\n", + " \"\"\n", + " ],\n", + " \"column_index_end\": 4,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652496-652497\",\n", + " \"colHeader-652748-652759\",\n", + " \"colHeader-653225-653236\",\n", + " \"colHeader-653497-653512\",\n", + " \"colHeader-653974-653982\",\n", + " \"colHeader-654059-654060\",\n", + " \"colHeader-655408-655415\",\n", + " \"colHeader-655492-655493\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Percent\",\n", + " \"\",\n", + " \"Change\",\n", + " \"\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 658087,\n", + " \"begin\": 658080\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 658084,\n", + " \"begin\": 658081\n", + " },\n", + " \"text\": \"1.0\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"(1.0)%\",\n", + " \"row_index_end\": 5,\n", + " \"row_header_texts_normalized\": [\n", + " \"Geographies\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-658080-658087\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-657304-657316\"\n", + " ],\n", + " \"column_index_begin\": 5,\n", + " \"row_index_begin\": 5,\n", + " \"row_header_texts\": [\n", + " \"Geographies\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Adjusted for\",\n", + " \"Currency\"\n", + " ],\n", + " \"column_index_end\": 5,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652748-652759\",\n", + " \"colHeader-653497-653512\",\n", + " \"colHeader-654309-654322\",\n", + " \"colHeader-655738-655747\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Adjusted for\",\n", + " \"Currency\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 658343,\n", + " \"begin\": 658336\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 658340,\n", + " \"begin\": 658337\n", + " },\n", + " \"text\": \"1.4\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"(1.4)%\",\n", + " \"row_index_end\": 5,\n", + " \"row_header_texts_normalized\": [\n", + " \"Geographies\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-658336-658343\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-658600-658609\"\n", + " ],\n", + " \"column_index_begin\": 1,\n", + " \"row_index_begin\": 6,\n", + " \"row_header_texts\": [\n", + " \"Americas\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2017\"\n", + " ],\n", + " \"column_index_end\": 1,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652366-652367\",\n", + " \"colHeader-652910-652911\",\n", + " \"colHeader-653663-653664\",\n", + " \"colHeader-654875-654880\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2017\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 658868,\n", + " \"begin\": 658861\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 658867,\n", + " \"begin\": 658861\n", + " },\n", + " \"text\": \"37,479\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"37,479\",\n", + " \"row_index_end\": 6,\n", + " \"row_header_texts_normalized\": [\n", + " \"Americas\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-658861-658868\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-658600-658609\"\n", + " ],\n", + " \"column_index_begin\": 2,\n", + " \"row_index_begin\": 6,\n", + " \"row_header_texts\": [\n", + " \"Americas\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2016\"\n", + " ],\n", + " \"column_index_end\": 2,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652431-652432\",\n", + " \"colHeader-652975-652976\",\n", + " \"colHeader-653728-653729\",\n", + " \"colHeader-655140-655145\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2016\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 659120,\n", + " \"begin\": 659113\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 659119,\n", + " \"begin\": 659113\n", + " },\n", + " \"text\": \"37,513\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"37,513\",\n", + " \"row_index_end\": 6,\n", + " \"row_header_texts_normalized\": [\n", + " \"Americas\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-659113-659120\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-658600-658609\"\n", + " ],\n", + " \"column_index_begin\": 3,\n", + " \"row_index_begin\": 6,\n", + " \"row_header_texts\": [\n", + " \"Americas\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Percent\",\n", + " \"\",\n", + " \"Change\",\n", + " \"\"\n", + " ],\n", + " \"column_index_end\": 4,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652496-652497\",\n", + " \"colHeader-652748-652759\",\n", + " \"colHeader-653225-653236\",\n", + " \"colHeader-653497-653512\",\n", + " \"colHeader-653974-653982\",\n", + " \"colHeader-654059-654060\",\n", + " \"colHeader-655408-655415\",\n", + " \"colHeader-655492-655493\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Percent\",\n", + " \"\",\n", + " \"Change\",\n", + " \"\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 659374,\n", + " \"begin\": 659368\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 659372,\n", + " \"begin\": 659369\n", + " },\n", + " \"text\": \"0.1\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"(0.1)\",\n", + " \"row_index_end\": 6,\n", + " \"row_header_texts_normalized\": [\n", + " \"Americas\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-659368-659374\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-658600-658609\"\n", + " ],\n", + " \"column_index_begin\": 5,\n", + " \"row_index_begin\": 6,\n", + " \"row_header_texts\": [\n", + " \"Americas\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Adjusted for\",\n", + " \"Currency\"\n", + " ],\n", + " \"column_index_end\": 5,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652748-652759\",\n", + " \"colHeader-653497-653512\",\n", + " \"colHeader-654309-654322\",\n", + " \"colHeader-655738-655747\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Adjusted for\",\n", + " \"Currency\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 659625,\n", + " \"begin\": 659619\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 659623,\n", + " \"begin\": 659620\n", + " },\n", + " \"text\": \"0.6\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"(0.6)\",\n", + " \"row_index_end\": 6,\n", + " \"row_header_texts_normalized\": [\n", + " \"Americas\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-659619-659625\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-659882-659908\"\n", + " ],\n", + " \"column_index_begin\": 1,\n", + " \"row_index_begin\": 7,\n", + " \"row_header_texts\": [\n", + " \"Europe/Middle East/Africa\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2017\"\n", + " ],\n", + " \"column_index_end\": 1,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652366-652367\",\n", + " \"colHeader-652910-652911\",\n", + " \"colHeader-653663-653664\",\n", + " \"colHeader-654875-654880\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2017\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 660166,\n", + " \"begin\": 660159\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 660165,\n", + " \"begin\": 660159\n", + " },\n", + " \"text\": \"24,345\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"24,345\",\n", + " \"row_index_end\": 7,\n", + " \"row_header_texts_normalized\": [\n", + " \"Europe/Middle East/Africa\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-660159-660166\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-659882-659908\"\n", + " ],\n", + " \"column_index_begin\": 2,\n", + " \"row_index_begin\": 7,\n", + " \"row_header_texts\": [\n", + " \"Europe/Middle East/Africa\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2016\"\n", + " ],\n", + " \"column_index_end\": 2,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652431-652432\",\n", + " \"colHeader-652975-652976\",\n", + " \"colHeader-653728-653729\",\n", + " \"colHeader-655140-655145\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2016\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 660420,\n", + " \"begin\": 660413\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 660419,\n", + " \"begin\": 660413\n", + " },\n", + " \"text\": \"24,769\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"24,769\",\n", + " \"row_index_end\": 7,\n", + " \"row_header_texts_normalized\": [\n", + " \"Europe/Middle East/Africa\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-660413-660420\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-659882-659908\"\n", + " ],\n", + " \"column_index_begin\": 3,\n", + " \"row_index_begin\": 7,\n", + " \"row_header_texts\": [\n", + " \"Europe/Middle East/Africa\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Percent\",\n", + " \"\",\n", + " \"Change\",\n", + " \"\"\n", + " ],\n", + " \"column_index_end\": 4,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652496-652497\",\n", + " \"colHeader-652748-652759\",\n", + " \"colHeader-653225-653236\",\n", + " \"colHeader-653497-653512\",\n", + " \"colHeader-653974-653982\",\n", + " \"colHeader-654059-654060\",\n", + " \"colHeader-655408-655415\",\n", + " \"colHeader-655492-655493\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Percent\",\n", + " \"\",\n", + " \"Change\",\n", + " \"\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 660673,\n", + " \"begin\": 660667\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 660671,\n", + " \"begin\": 660668\n", + " },\n", + " \"text\": \"1.7\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"(1.7)\",\n", + " \"row_index_end\": 7,\n", + " \"row_header_texts_normalized\": [\n", + " \"Europe/Middle East/Africa\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-660667-660673\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-659882-659908\"\n", + " ],\n", + " \"column_index_begin\": 5,\n", + " \"row_index_begin\": 7,\n", + " \"row_header_texts\": [\n", + " \"Europe/Middle East/Africa\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Adjusted for\",\n", + " \"Currency\"\n", + " ],\n", + " \"column_index_end\": 5,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652748-652759\",\n", + " \"colHeader-653497-653512\",\n", + " \"colHeader-654309-654322\",\n", + " \"colHeader-655738-655747\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Adjusted for\",\n", + " \"Currency\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 660928,\n", + " \"begin\": 660922\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 660926,\n", + " \"begin\": 660923\n", + " },\n", + " \"text\": \"2.8\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"(2.8)\",\n", + " \"row_index_end\": 7,\n", + " \"row_header_texts_normalized\": [\n", + " \"Europe/Middle East/Africa\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-660922-660928\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-661183-661196\"\n", + " ],\n", + " \"column_index_begin\": 1,\n", + " \"row_index_begin\": 8,\n", + " \"row_header_texts\": [\n", + " \"Asia Pacific\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2017\"\n", + " ],\n", + " \"column_index_end\": 1,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652366-652367\",\n", + " \"colHeader-652910-652911\",\n", + " \"colHeader-653663-653664\",\n", + " \"colHeader-654875-654880\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2017\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 661455,\n", + " \"begin\": 661448\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 661454,\n", + " \"begin\": 661448\n", + " },\n", + " \"text\": \"16,970\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"16,970\",\n", + " \"row_index_end\": 8,\n", + " \"row_header_texts_normalized\": [\n", + " \"Asia Pacific\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-661448-661455\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-661183-661196\"\n", + " ],\n", + " \"column_index_begin\": 2,\n", + " \"row_index_begin\": 8,\n", + " \"row_header_texts\": [\n", + " \"Asia Pacific\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2016\"\n", + " ],\n", + " \"column_index_end\": 2,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652431-652432\",\n", + " \"colHeader-652975-652976\",\n", + " \"colHeader-653728-653729\",\n", + " \"colHeader-655140-655145\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"\",\n", + " \"\",\n", + " \"2016\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 661708,\n", + " \"begin\": 661701\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 661707,\n", + " \"begin\": 661701\n", + " },\n", + " \"text\": \"17,313\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"17,313\",\n", + " \"row_index_end\": 8,\n", + " \"row_header_texts_normalized\": [\n", + " \"Asia Pacific\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-661701-661708\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-661183-661196\"\n", + " ],\n", + " \"column_index_begin\": 3,\n", + " \"row_index_begin\": 8,\n", + " \"row_header_texts\": [\n", + " \"Asia Pacific\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Percent\",\n", + " \"\",\n", + " \"Change\",\n", + " \"\"\n", + " ],\n", + " \"column_index_end\": 4,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652496-652497\",\n", + " \"colHeader-652748-652759\",\n", + " \"colHeader-653225-653236\",\n", + " \"colHeader-653497-653512\",\n", + " \"colHeader-653974-653982\",\n", + " \"colHeader-654059-654060\",\n", + " \"colHeader-655408-655415\",\n", + " \"colHeader-655492-655493\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Percent\",\n", + " \"\",\n", + " \"Change\",\n", + " \"\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 661962,\n", + " \"begin\": 661956\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 661960,\n", + " \"begin\": 661957\n", + " },\n", + " \"text\": \"2.0\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"(2.0)\",\n", + " \"row_index_end\": 8,\n", + " \"row_header_texts_normalized\": [\n", + " \"Asia Pacific\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-661956-661962\"\n", + " },\n", + " {\n", + " \"row_header_ids\": [\n", + " \"rowHeader-661183-661196\"\n", + " ],\n", + " \"column_index_begin\": 5,\n", + " \"row_index_begin\": 8,\n", + " \"row_header_texts\": [\n", + " \"Asia Pacific\"\n", + " ],\n", + " \"column_header_texts\": [\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Adjusted for\",\n", + " \"Currency\"\n", + " ],\n", + " \"column_index_end\": 5,\n", + " \"column_header_ids\": [\n", + " \"colHeader-652748-652759\",\n", + " \"colHeader-653497-653512\",\n", + " \"colHeader-654309-654322\",\n", + " \"colHeader-655738-655747\"\n", + " ],\n", + " \"column_header_texts_normalized\": [\n", + " \"Yr.-to-Yr.\",\n", + " \"Percent Change\",\n", + " \"Adjusted for\",\n", + " \"Currency\"\n", + " ],\n", + " \"location\": {\n", + " \"end\": 662216,\n", + " \"begin\": 662210\n", + " },\n", + " \"attributes\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 662214,\n", + " \"begin\": 662211\n", + " },\n", + " \"text\": \"1.1\",\n", + " \"type\": \"Number\"\n", + " }\n", + " ],\n", + " \"text\": \"(1.1)\",\n", + " \"row_index_end\": 8,\n", + " \"row_header_texts_normalized\": [\n", + " \"Asia Pacific\"\n", + " ],\n", + " \"cell_id\": \"bodyCell-662210-662216\"\n", + " }\n", + " ],\n", + " \"contexts\": [\n", + " {\n", + " \"location\": {\n", + " \"end\": 651629,\n", + " \"begin\": 651345\n", + " },\n", + " \"text\": \"In addition to the revenue presentation by reportable segment, the company also measures revenue performance on a geographic basis.\"\n", + " },\n", + " {\n", + " \"location\": {\n", + " \"end\": 651727,\n", + " \"begin\": 651630\n", + " },\n", + " \"text\": \"The following geographic, regional and country-specific revenue performance excludes OEM revenue.\"\n", + " },\n", + " {\n", + " \"location\": {\n", + " \"end\": 651925,\n", + " \"begin\": 651910\n", + " },\n", + " \"text\": \"($ in millions)\"\n", + " },\n", + " {\n", + " \"location\": {\n", + " \"end\": 662716,\n", + " \"begin\": 662417\n", + " },\n", + " \"text\": \"Total geographic revenue of $78,793 million in 2017 decreased 1.0 percent as reported (1 percent adjusted for currency) compared to the prior year.\"\n", + " },\n", + " {\n", + " \"location\": {\n", + " \"end\": 663593,\n", + " \"begin\": 662900\n", + " },\n", + " \"text\": \"Americas revenue was essentially flat year to year as reported, but decreased 1 percent adjusted for currency with a decline in North America partially offset by growth in Latin America, both as reported and adjusted for currency.\"\n", + " },\n", + " {\n", + " \"location\": {\n", + " \"end\": 664024,\n", + " \"begin\": 663594\n", + " },\n", + " \"text\": \"Within North America, the U.S. decreased 1.4 percent and Canada increased 4.9 percent (3 percent adjusted for currency).\"\n", + " }\n", + " ],\n", + " \"key_value_pairs\": [],\n", + " \"title\": {},\n", + " \"column_headers\": [\n", + " {\n", + " \"column_index_begin\": 0,\n", + " \"row_index_begin\": 0,\n", + " \"location\": {\n", + " \"end\": 652302,\n", + " \"begin\": 652301\n", + " },\n", + " \"text\": \"\",\n", + " \"row_index_end\": 0,\n", + " \"cell_id\": \"colHeader-652301-652302\",\n", + " \"column_index_end\": 0,\n", + " \"text_normalized\": \"\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 1,\n", + " \"row_index_begin\": 0,\n", + " \"location\": {\n", + " \"end\": 652367,\n", + " \"begin\": 652366\n", + " },\n", + " \"text\": \"\",\n", + " \"row_index_end\": 0,\n", + " \"cell_id\": \"colHeader-652366-652367\",\n", + " \"column_index_end\": 1,\n", + " \"text_normalized\": \"\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 2,\n", + " \"row_index_begin\": 0,\n", + " \"location\": {\n", + " \"end\": 652432,\n", + " \"begin\": 652431\n", + " },\n", + " \"text\": \"\",\n", + " \"row_index_end\": 0,\n", + " \"cell_id\": \"colHeader-652431-652432\",\n", + " \"column_index_end\": 2,\n", + " \"text_normalized\": \"\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 3,\n", + " \"row_index_begin\": 0,\n", + " \"location\": {\n", + " \"end\": 652497,\n", + " \"begin\": 652496\n", + " },\n", + " \"text\": \"\",\n", + " \"row_index_end\": 0,\n", + " \"cell_id\": \"colHeader-652496-652497\",\n", + " \"column_index_end\": 3,\n", + " \"text_normalized\": \"\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 4,\n", + " \"row_index_begin\": 0,\n", + " \"location\": {\n", + " \"end\": 652759,\n", + " \"begin\": 652748\n", + " },\n", + " \"text\": \"Yr.-to-Yr.\",\n", + " \"row_index_end\": 0,\n", + " \"cell_id\": \"colHeader-652748-652759\",\n", + " \"column_index_end\": 5,\n", + " \"text_normalized\": \"Yr.-to-Yr.\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 0,\n", + " \"row_index_begin\": 1,\n", + " \"location\": {\n", + " \"end\": 652846,\n", + " \"begin\": 652845\n", + " },\n", + " \"text\": \"\",\n", + " \"row_index_end\": 1,\n", + " \"cell_id\": \"colHeader-652845-652846\",\n", + " \"column_index_end\": 0,\n", + " \"text_normalized\": \"\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 1,\n", + " \"row_index_begin\": 1,\n", + " \"location\": {\n", + " \"end\": 652911,\n", + " \"begin\": 652910\n", + " },\n", + " \"text\": \"\",\n", + " \"row_index_end\": 1,\n", + " \"cell_id\": \"colHeader-652910-652911\",\n", + " \"column_index_end\": 1,\n", + " \"text_normalized\": \"\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 2,\n", + " \"row_index_begin\": 1,\n", + " \"location\": {\n", + " \"end\": 652976,\n", + " \"begin\": 652975\n", + " },\n", + " \"text\": \"\",\n", + " \"row_index_end\": 1,\n", + " \"cell_id\": \"colHeader-652975-652976\",\n", + " \"column_index_end\": 2,\n", + " \"text_normalized\": \"\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 3,\n", + " \"row_index_begin\": 1,\n", + " \"location\": {\n", + " \"end\": 653236,\n", + " \"begin\": 653225\n", + " },\n", + " \"text\": \"Yr.-to-Yr.\",\n", + " \"row_index_end\": 1,\n", + " \"cell_id\": \"colHeader-653225-653236\",\n", + " \"column_index_end\": 3,\n", + " \"text_normalized\": \"Yr.-to-Yr.\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 4,\n", + " \"row_index_begin\": 1,\n", + " \"location\": {\n", + " \"end\": 653512,\n", + " \"begin\": 653497\n", + " },\n", + " \"text\": \"Percent Change\",\n", + " \"row_index_end\": 1,\n", + " \"cell_id\": \"colHeader-653497-653512\",\n", + " \"column_index_end\": 5,\n", + " \"text_normalized\": \"Percent Change\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 0,\n", + " \"row_index_begin\": 2,\n", + " \"location\": {\n", + " \"end\": 653599,\n", + " \"begin\": 653598\n", + " },\n", + " \"text\": \"\",\n", + " \"row_index_end\": 2,\n", + " \"cell_id\": \"colHeader-653598-653599\",\n", + " \"column_index_end\": 0,\n", + " \"text_normalized\": \"\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 1,\n", + " \"row_index_begin\": 2,\n", + " \"location\": {\n", + " \"end\": 653664,\n", + " \"begin\": 653663\n", + " },\n", + " \"text\": \"\",\n", + " \"row_index_end\": 2,\n", + " \"cell_id\": \"colHeader-653663-653664\",\n", + " \"column_index_end\": 1,\n", + " \"text_normalized\": \"\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 2,\n", + " \"row_index_begin\": 2,\n", + " \"location\": {\n", + " \"end\": 653729,\n", + " \"begin\": 653728\n", + " },\n", + " \"text\": \"\",\n", + " \"row_index_end\": 2,\n", + " \"cell_id\": \"colHeader-653728-653729\",\n", + " \"column_index_end\": 2,\n", + " \"text_normalized\": \"\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 3,\n", + " \"row_index_begin\": 2,\n", + " \"location\": {\n", + " \"end\": 653982,\n", + " \"begin\": 653974\n", + " },\n", + " \"text\": \"Percent\",\n", + " \"row_index_end\": 2,\n", + " \"cell_id\": \"colHeader-653974-653982\",\n", + " \"column_index_end\": 3,\n", + " \"text_normalized\": \"Percent\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 4,\n", + " \"row_index_begin\": 2,\n", + " \"location\": {\n", + " \"end\": 654060,\n", + " \"begin\": 654059\n", + " },\n", + " \"text\": \"\",\n", + " \"row_index_end\": 2,\n", + " \"cell_id\": \"colHeader-654059-654060\",\n", + " \"column_index_end\": 4,\n", + " \"text_normalized\": \"\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 5,\n", + " \"row_index_begin\": 2,\n", + " \"location\": {\n", + " \"end\": 654322,\n", + " \"begin\": 654309\n", + " },\n", + " \"text\": \"Adjusted for\",\n", + " \"row_index_end\": 2,\n", + " \"cell_id\": \"colHeader-654309-654322\",\n", + " \"column_index_end\": 5,\n", + " \"text_normalized\": \"Adjusted for\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 0,\n", + " \"row_index_begin\": 3,\n", + " \"location\": {\n", + " \"end\": 654611,\n", + " \"begin\": 654579\n", + " },\n", + " \"text\": \"For the year ended December 31:\",\n", + " \"row_index_end\": 3,\n", + " \"cell_id\": \"colHeader-654579-654611\",\n", + " \"column_index_end\": 0,\n", + " \"text_normalized\": \"For the year ended December 31:\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 1,\n", + " \"row_index_begin\": 3,\n", + " \"location\": {\n", + " \"end\": 654880,\n", + " \"begin\": 654875\n", + " },\n", + " \"text\": \"2017\",\n", + " \"row_index_end\": 3,\n", + " \"cell_id\": \"colHeader-654875-654880\",\n", + " \"column_index_end\": 1,\n", + " \"text_normalized\": \"2017\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 2,\n", + " \"row_index_begin\": 3,\n", + " \"location\": {\n", + " \"end\": 655145,\n", + " \"begin\": 655140\n", + " },\n", + " \"text\": \"2016\",\n", + " \"row_index_end\": 3,\n", + " \"cell_id\": \"colHeader-655140-655145\",\n", + " \"column_index_end\": 2,\n", + " \"text_normalized\": \"2016\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 3,\n", + " \"row_index_begin\": 3,\n", + " \"location\": {\n", + " \"end\": 655415,\n", + " \"begin\": 655408\n", + " },\n", + " \"text\": \"Change\",\n", + " \"row_index_end\": 3,\n", + " \"cell_id\": \"colHeader-655408-655415\",\n", + " \"column_index_end\": 3,\n", + " \"text_normalized\": \"Change\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 4,\n", + " \"row_index_begin\": 3,\n", + " \"location\": {\n", + " \"end\": 655493,\n", + " \"begin\": 655492\n", + " },\n", + " \"text\": \"\",\n", + " \"row_index_end\": 3,\n", + " \"cell_id\": \"colHeader-655492-655493\",\n", + " \"column_index_end\": 4,\n", + " \"text_normalized\": \"\"\n", + " },\n", + " {\n", + " \"column_index_begin\": 5,\n", + " \"row_index_begin\": 3,\n", + " \"location\": {\n", + " \"end\": 655747,\n", + " \"begin\": 655738\n", + " },\n", + " \"text\": \"Currency\",\n", + " \"row_index_end\": 3,\n", + " \"cell_id\": \"colHeader-655738-655747\",\n", + " \"column_index_end\": 5,\n", + " \"text_normalized\": \"Currency\"\n", + " }\n", + " ]\n", + "}\n", + ".\n", + ".\n", + ".\n", + " 41 more tables\n" + ] + } + ], + "source": [ + "key = \"enriched_html\"\n", + "tables = [table for result in geograpies_results['results'] \n", + " if key in result for enriched_html in result ['enriched_html']\n", + " for table in enriched_html['tables']\n", + " if table['section_title']['text']==\"Geographic Revenue\"]\n", + "print(json.dumps(tables[0], indent=2))\n", + "print(f\".\\n.\\n.\\n {(len(tables)-1)} more tables\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That raw output contains everything Allison needs to extract the revenue figures from this document, but it's in a format that's cumbersome to deal with. So Allison uses Text Extensions for Pandas to convert the output into a collection of Pandas DataFrames. These DataFrames encode information about the row headers, column headers, and cells that make up the table." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['row_headers', 'col_headers', 'body_cells', 'given_loc'])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "table_data = tp.io.watson.tables.parse_response({\"tables\":tables})\n", + "table_data.keys()" + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
textcolumn_index_begincolumn_index_endrow_index_beginrow_index_endcell_idcolumn_header_idscolumn_header_textsrow_header_idsrow_header_textsattributes.textattributes.type
0$79,1391144bodyCell-656273-656281[colHeader-652366-652367, colHeader-652910-652...[2017][rowHeader-656005-656019][Total revenue][$79,139][Currency]
1$79,9192244bodyCell-656529-656537[colHeader-652431-652432, colHeader-652975-652...[2016][rowHeader-656005-656019][Total revenue][$79,919][Currency]
2(1.0)%3444bodyCell-656785-656792[colHeader-652496-652497, colHeader-652748-652...[Yr.-to-Yr., Yr.-to-Yr., Percent Change, Perce...[rowHeader-656005-656019][Total revenue][1.0][Number]
3(1.3)%5544bodyCell-657040-657047[colHeader-652748-652759, colHeader-653497-653...[Yr.-to-Yr., Percent Change, Adjusted for, Cur...[rowHeader-656005-656019][Total revenue][1.3][Number]
4$78,7931155bodyCell-657571-657579[colHeader-652366-652367, colHeader-652910-652...[2017][rowHeader-657304-657316][Geographies][$78,793][Currency]
\n", + "
" + ], + "text/plain": [ + " text column_index_begin column_index_end row_index_begin \\\n", + "0 $79,139 1 1 4 \n", + "1 $79,919 2 2 4 \n", + "2 (1.0)% 3 4 4 \n", + "3 (1.3)% 5 5 4 \n", + "4 $78,793 1 1 5 \n", + "\n", + " row_index_end cell_id \\\n", + "0 4 bodyCell-656273-656281 \n", + "1 4 bodyCell-656529-656537 \n", + "2 4 bodyCell-656785-656792 \n", + "3 4 bodyCell-657040-657047 \n", + "4 5 bodyCell-657571-657579 \n", + "\n", + " column_header_ids \\\n", + "0 [colHeader-652366-652367, colHeader-652910-652... \n", + "1 [colHeader-652431-652432, colHeader-652975-652... \n", + "2 [colHeader-652496-652497, colHeader-652748-652... \n", + "3 [colHeader-652748-652759, colHeader-653497-653... \n", + "4 [colHeader-652366-652367, colHeader-652910-652... \n", + "\n", + " column_header_texts \\\n", + "0 [2017] \n", + "1 [2016] \n", + "2 [Yr.-to-Yr., Yr.-to-Yr., Percent Change, Perce... \n", + "3 [Yr.-to-Yr., Percent Change, Adjusted for, Cur... \n", + "4 [2017] \n", + "\n", + " row_header_ids row_header_texts attributes.text attributes.type \n", + "0 [rowHeader-656005-656019] [Total revenue] [$79,139] [Currency] \n", + "1 [rowHeader-656005-656019] [Total revenue] [$79,919] [Currency] \n", + "2 [rowHeader-656005-656019] [Total revenue] [1.0] [Number] \n", + "3 [rowHeader-656005-656019] [Total revenue] [1.3] [Number] \n", + "4 [rowHeader-657304-657316] [Geographies] [$78,793] [Currency] " + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "table_data[\"body_cells\"].head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Text Extensions for Pandas can convert these DataFrames into a single Pandas DataFrame that matches the layout of the original table in the document. Allison calls the `make_table()` function to perform that conversion and inspects the output." + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Yr.-to-Yr.
Yr.-to-Yr.Percent Change
Yr.-to-Yr.Adjusted for
20172016Percent ChangeCurrency
Percent
Change
Total revenue$79,139$79,919(1.0)%(1.3)%
Geographies$78,793$79,594(1.0)%(1.4)%
Americas3747937513-0.1-0.6
Europe/Middle East/Africa2434524769-1.7-2.8
Asia Pacific1697017313-2-1.1
\n", + "
" + ], + "text/plain": [ + " Yr.-to-Yr.\n", + " Yr.-to-Yr. Percent Change\n", + " Yr.-to-Yr. Adjusted for\n", + " 2017 2016 Percent Change Currency\n", + " Percent \n", + " \n", + " Change \n", + " \n", + "Total revenue $79,139 $79,919 (1.0)% (1.3)%\n", + "Geographies $78,793 $79,594 (1.0)% (1.4)%\n", + "Americas 37479 37513 -0.1 -0.6\n", + "Europe/Middle East/Africa 24345 24769 -1.7 -2.8\n", + "Asia Pacific 16970 17313 -2 -1.1" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "revenue_2017_df = tp.io.watson.tables.make_table(table_data)\n", + "revenue_2017_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "  \n", + "\n", + "The reconstructed dataframe looks good! Here's what the original table in the PDF document looked like:\n", + "![Table: Geographic Revenue (from IBM 2017 annual report)](images/screenshot_table_2017.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If Allison just wanted to create a DataFrame of 2016/2017 revenue figures, her task would be done. But Allison wants to reconstruct ten years of revenue by geographic region. To do that, she will need to combine information from multiple documents. For tables like this one that have multiple levels of header information, this kind of integration is easier to perform over the \"exploded\" version of the table, where each cell in the table is represented a single row containing all the corresponding header values.\n", + "\n", + "Allison passes the same table data from the 2017 report through the Text Extensions for Pandas function `make_exploded_df()` to produce the exploded represention of the table:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
textrow_header_texts_0column_header_textsattributes.type
0$79,139Total revenue2017[Currency]
1$79,919Total revenue2016[Currency]
2(1.0)%Total revenueYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]
3(1.3)%Total revenueYr.-to-Yr. Percent Change Adjusted for Currency[Number]
4$78,793Geographies2017[Currency]
5$79,594Geographies2016[Currency]
6(1.0)%GeographiesYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]
7(1.4)%GeographiesYr.-to-Yr. Percent Change Adjusted for Currency[Number]
837,479Americas2017[Number]
937,513Americas2016[Number]
10(0.1)AmericasYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]
11(0.6)AmericasYr.-to-Yr. Percent Change Adjusted for Currency[Number]
1224,345Europe/Middle East/Africa2017[Number]
1324,769Europe/Middle East/Africa2016[Number]
14(1.7)Europe/Middle East/AfricaYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]
15(2.8)Europe/Middle East/AfricaYr.-to-Yr. Percent Change Adjusted for Currency[Number]
1616,970Asia Pacific2017[Number]
1717,313Asia Pacific2016[Number]
18(2.0)Asia PacificYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]
19(1.1)Asia PacificYr.-to-Yr. Percent Change Adjusted for Currency[Number]
\n", + "
" + ], + "text/plain": [ + " text row_header_texts_0 \\\n", + "0 $79,139 Total revenue \n", + "1 $79,919 Total revenue \n", + "2 (1.0)% Total revenue \n", + "3 (1.3)% Total revenue \n", + "4 $78,793 Geographies \n", + "5 $79,594 Geographies \n", + "6 (1.0)% Geographies \n", + "7 (1.4)% Geographies \n", + "8 37,479 Americas \n", + "9 37,513 Americas \n", + "10 (0.1) Americas \n", + "11 (0.6) Americas \n", + "12 24,345 Europe/Middle East/Africa \n", + "13 24,769 Europe/Middle East/Africa \n", + "14 (1.7) Europe/Middle East/Africa \n", + "15 (2.8) Europe/Middle East/Africa \n", + "16 16,970 Asia Pacific \n", + "17 17,313 Asia Pacific \n", + "18 (2.0) Asia Pacific \n", + "19 (1.1) Asia Pacific \n", + "\n", + " column_header_texts attributes.type \n", + "0 2017 [Currency] \n", + "1 2016 [Currency] \n", + "2 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] \n", + "3 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] \n", + "4 2017 [Currency] \n", + "5 2016 [Currency] \n", + "6 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] \n", + "7 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] \n", + "8 2017 [Number] \n", + "9 2016 [Number] \n", + "10 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] \n", + "11 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] \n", + "12 2017 [Number] \n", + "13 2016 [Number] \n", + "14 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] \n", + "15 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] \n", + "16 2017 [Number] \n", + "17 2016 [Number] \n", + "18 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] \n", + "19 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] " + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exploded_df, row_header_names, col_header_names = (\n", + " tp.io.watson.tables.make_exploded_df(table_data, col_explode_by=\"concat\"))\n", + "exploded_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This exploded version of the table is the exact same data, just represented in a different way. If she wants, Allison can convert it back to the format from the original document by calling `pandas.DataFrame.pivot()`:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "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", + "
column_header_texts20162017Yr.-to-Yr. Percent Change Adjusted for CurrencyYr.-to-Yr. Yr.-to-Yr. Percent Change Percent Change
row_header_texts_0
Americas37,51337,479(0.6)(0.1)
Asia Pacific17,31316,970(1.1)(2.0)
Europe/Middle East/Africa24,76924,345(2.8)(1.7)
Geographies$79,594$78,793(1.4)%(1.0)%
Total revenue$79,919$79,139(1.3)%(1.0)%
\n", + "
" + ], + "text/plain": [ + "column_header_texts 2016 2017 \\\n", + "row_header_texts_0 \n", + "Americas 37,513 37,479 \n", + "Asia Pacific 17,313 16,970 \n", + "Europe/Middle East/Africa 24,769 24,345 \n", + "Geographies $79,594 $78,793 \n", + "Total revenue $79,919 $79,139 \n", + "\n", + "column_header_texts Yr.-to-Yr. Percent Change Adjusted for Currency \\\n", + "row_header_texts_0 \n", + "Americas (0.6) \n", + "Asia Pacific (1.1) \n", + "Europe/Middle East/Africa (2.8) \n", + "Geographies (1.4)% \n", + "Total revenue (1.3)% \n", + "\n", + "column_header_texts Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent Change \n", + "row_header_texts_0 \n", + "Americas (0.1) \n", + "Asia Pacific (2.0) \n", + "Europe/Middle East/Africa (1.7) \n", + "Geographies (1.0)% \n", + "Total revenue (1.0)% " + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exploded_df.pivot(index=\"row_header_texts_0\", columns=\"column_header_texts\", values=\"text\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But because she is about to merge this DataFrame with similar data from other documents, Allison keeps the data in exploded format for now. \n", + "\n", + "Allison's next task is to write some Pandas transformations that will clean and reformat the DataFrame for each source table prior to merging them all together. She uses the 2017 report's data as a test case for creating this code. The first step is to convert the cell values in the Watson Discovery output from text to numeric values. Text Extensions for Pandas includes a more robust version of [`pandas.to_numeric()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_numeric.html) that can handle common idioms for representing currencies and percentages. Allison uses this function, called `convert_cols_to_numeric()`, to convert all the cell values to numbers. She adds a new column \"value\" to her DataFrame to hold these numbers." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
textrow_header_texts_0column_header_textsattributes.typevalue
0$79,139Total revenue2017[Currency]79139.0
1$79,919Total revenue2016[Currency]79919.0
2(1.0)%Total revenueYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]-1.0
3(1.3)%Total revenueYr.-to-Yr. Percent Change Adjusted for Currency[Number]-1.3
4$78,793Geographies2017[Currency]78793.0
5$79,594Geographies2016[Currency]79594.0
6(1.0)%GeographiesYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]-1.0
7(1.4)%GeographiesYr.-to-Yr. Percent Change Adjusted for Currency[Number]-1.4
837,479Americas2017[Number]37479.0
937,513Americas2016[Number]37513.0
10(0.1)AmericasYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]-0.1
11(0.6)AmericasYr.-to-Yr. Percent Change Adjusted for Currency[Number]-0.6
1224,345Europe/Middle East/Africa2017[Number]24345.0
1324,769Europe/Middle East/Africa2016[Number]24769.0
14(1.7)Europe/Middle East/AfricaYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]-1.7
15(2.8)Europe/Middle East/AfricaYr.-to-Yr. Percent Change Adjusted for Currency[Number]-2.8
1616,970Asia Pacific2017[Number]16970.0
1717,313Asia Pacific2016[Number]17313.0
18(2.0)Asia PacificYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]-2.0
19(1.1)Asia PacificYr.-to-Yr. Percent Change Adjusted for Currency[Number]-1.1
\n", + "
" + ], + "text/plain": [ + " text row_header_texts_0 \\\n", + "0 $79,139 Total revenue \n", + "1 $79,919 Total revenue \n", + "2 (1.0)% Total revenue \n", + "3 (1.3)% Total revenue \n", + "4 $78,793 Geographies \n", + "5 $79,594 Geographies \n", + "6 (1.0)% Geographies \n", + "7 (1.4)% Geographies \n", + "8 37,479 Americas \n", + "9 37,513 Americas \n", + "10 (0.1) Americas \n", + "11 (0.6) Americas \n", + "12 24,345 Europe/Middle East/Africa \n", + "13 24,769 Europe/Middle East/Africa \n", + "14 (1.7) Europe/Middle East/Africa \n", + "15 (2.8) Europe/Middle East/Africa \n", + "16 16,970 Asia Pacific \n", + "17 17,313 Asia Pacific \n", + "18 (2.0) Asia Pacific \n", + "19 (1.1) Asia Pacific \n", + "\n", + " column_header_texts attributes.type value \n", + "0 2017 [Currency] 79139.0 \n", + "1 2016 [Currency] 79919.0 \n", + "2 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] -1.0 \n", + "3 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] -1.3 \n", + "4 2017 [Currency] 78793.0 \n", + "5 2016 [Currency] 79594.0 \n", + "6 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] -1.0 \n", + "7 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] -1.4 \n", + "8 2017 [Number] 37479.0 \n", + "9 2016 [Number] 37513.0 \n", + "10 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] -0.1 \n", + "11 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] -0.6 \n", + "12 2017 [Number] 24345.0 \n", + "13 2016 [Number] 24769.0 \n", + "14 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] -1.7 \n", + "15 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] -2.8 \n", + "16 2017 [Number] 16970.0 \n", + "17 2016 [Number] 17313.0 \n", + "18 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] -2.0 \n", + "19 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] -1.1 " + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exploded_df[\"value\"] = \\\n", + " tp.io.watson.tables.convert_cols_to_numeric(exploded_df[[\"text\"]])\n", + "exploded_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now all the cell values have been converted to floating-point numbers, but only some of these numbers represent revenue. Looking at the 2017 data, Allison can see that the revenue numbers have 4-digit years in their column headers. So she filters the DataFrame down to just those rows with 4-digit numbers in the \"column_header_texts\" column." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "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", + "
textrow_header_texts_0column_header_textsattributes.typevalue
0$79,139Total revenue2017[Currency]79139.0
1$79,919Total revenue2016[Currency]79919.0
4$78,793Geographies2017[Currency]78793.0
5$79,594Geographies2016[Currency]79594.0
837,479Americas2017[Number]37479.0
937,513Americas2016[Number]37513.0
1224,345Europe/Middle East/Africa2017[Number]24345.0
1324,769Europe/Middle East/Africa2016[Number]24769.0
1616,970Asia Pacific2017[Number]16970.0
1717,313Asia Pacific2016[Number]17313.0
\n", + "
" + ], + "text/plain": [ + " text row_header_texts_0 column_header_texts attributes.type \\\n", + "0 $79,139 Total revenue 2017 [Currency] \n", + "1 $79,919 Total revenue 2016 [Currency] \n", + "4 $78,793 Geographies 2017 [Currency] \n", + "5 $79,594 Geographies 2016 [Currency] \n", + "8 37,479 Americas 2017 [Number] \n", + "9 37,513 Americas 2016 [Number] \n", + "12 24,345 Europe/Middle East/Africa 2017 [Number] \n", + "13 24,769 Europe/Middle East/Africa 2016 [Number] \n", + "16 16,970 Asia Pacific 2017 [Number] \n", + "17 17,313 Asia Pacific 2016 [Number] \n", + "\n", + " value \n", + "0 79139.0 \n", + "1 79919.0 \n", + "4 78793.0 \n", + "5 79594.0 \n", + "8 37479.0 \n", + "9 37513.0 \n", + "12 24345.0 \n", + "13 24769.0 \n", + "16 16970.0 \n", + "17 17313.0 " + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rows_to_retain = exploded_df[((exploded_df[\"column_header_texts\"].str.fullmatch(\"\\d{4}\"))) &\n", + " (exploded_df[\"text\"].str.len() > 0)].copy()\n", + "rows_to_retain" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That's looking good! Now Allison drops the unnecessary columns and gives some more friendly names to the columns that remain." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "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", + "
YearRegionRevenue
02017Total revenue79139.0
12016Total revenue79919.0
42017Geographies78793.0
52016Geographies79594.0
82017Americas37479.0
92016Americas37513.0
122017Europe/Middle East/Africa24345.0
132016Europe/Middle East/Africa24769.0
162017Asia Pacific16970.0
172016Asia Pacific17313.0
\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "0 2017 Total revenue 79139.0\n", + "1 2016 Total revenue 79919.0\n", + "4 2017 Geographies 78793.0\n", + "5 2016 Geographies 79594.0\n", + "8 2017 Americas 37479.0\n", + "9 2016 Americas 37513.0\n", + "12 2017 Europe/Middle East/Africa 24345.0\n", + "13 2016 Europe/Middle East/Africa 24769.0\n", + "16 2017 Asia Pacific 16970.0\n", + "17 2016 Asia Pacific 17313.0" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rows_to_retain.rename(\n", + " columns={\n", + " \"row_header_texts_0\": \"Region\",\n", + " \"column_header_texts\": \"Year\",\n", + " \"value\": \"Revenue\"\n", + " })[[\"Year\", \"Region\", \"Revenue\"]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The code from the last few cells worked to clean up the 2017 data, so Allison copies and pastes that code into a Python function:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "def dataframe_for_table(table: str):\n", + "\n", + " table_data = tp.io.watson.tables.parse_response({\"tables\":[table]})\n", + " exploded_df, _, _ = tp.io.watson.tables.make_exploded_df(\n", + " table_data, col_explode_by=\"concat\")\n", + " \n", + " if \"column_header_texts\" in exploded_df.columns and \"row_header_texts_0\" in exploded_df.columns: \n", + " rows_to_retain = exploded_df[((exploded_df[\"column_header_texts\"].str.fullmatch(\"\\d{4}\"))\n", + " & (exploded_df[\"text\"].str.len() > 0)\n", + " )].copy()\n", + " rows_to_retain[\"value\"] = tp.io.watson.tables.convert_cols_to_numeric(\n", + " rows_to_retain[[\"text\"]])\n", + " rows_to_retain[\"file\"] = \"filename\"\n", + " return (\n", + " rows_to_retain.rename(columns={\n", + " \"row_header_texts_0\": \"Region\", \"column_header_texts\": \"Year\", \"value\": \"Revenue\"})\n", + " [[\"Year\", \"Region\", \"Revenue\"]]\n", + " )\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then she calls that function on the Watson Discovery output from the 2017 annual report to verify that it produces the same answer. " + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
YearRegionRevenue
02017Total revenue79139.0
12016Total revenue79919.0
42017Geographies78793.0
52016Geographies79594.0
82017Americas37479.0
92016Americas37513.0
122017Europe/Middle East/Africa24345.0
132016Europe/Middle East/Africa24769.0
162017Asia Pacific16970.0
172016Asia Pacific17313.0
\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "0 2017 Total revenue 79139.0\n", + "1 2016 Total revenue 79919.0\n", + "4 2017 Geographies 78793.0\n", + "5 2016 Geographies 79594.0\n", + "8 2017 Americas 37479.0\n", + "9 2016 Americas 37513.0\n", + "12 2017 Europe/Middle East/Africa 24345.0\n", + "13 2016 Europe/Middle East/Africa 24769.0\n", + "16 2017 Asia Pacific 16970.0\n", + "17 2016 Asia Pacific 17313.0" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dataframe_for_table(tables[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Looks good! Time to run the same function over an entire stack of reports." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Allison calls her `dataframe_for_table()` function on each of the tables, then concatenates all of the resulting Pandas DataFrames into a single large DataFrame." + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
YearRegionRevenue
02017Total revenue79139.0
12016Total revenue79919.0
42017Geographies78793.0
52016Geographies79594.0
82017Americas37479.0
............
172009Asia Pacific20710.0
02018Total revenue79591.0
52018Americas36994.0
92018Europe/Middle East/Africa25491.0
132018Asia Pacific17106.0
\n", + "

170 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "0 2017 Total revenue 79139.0\n", + "1 2016 Total revenue 79919.0\n", + "4 2017 Geographies 78793.0\n", + "5 2016 Geographies 79594.0\n", + "8 2017 Americas 37479.0\n", + ".. ... ... ...\n", + "17 2009 Asia Pacific 20710.0\n", + "0 2018 Total revenue 79591.0\n", + "5 2018 Americas 36994.0\n", + "9 2018 Europe/Middle East/Africa 25491.0\n", + "13 2018 Asia Pacific 17106.0\n", + "\n", + "[170 rows x 3 columns]" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "revenue_df = pd.concat([dataframe_for_table(t) for t in tables])\n", + "revenue_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Allison can see that the first four lines of this DataFrame contain total worldwide revenue; and that this total occurred\n", + "under different names in different documents. Allison is interested in the fine-grained revenue figures, not\n", + "the totals, so she needs to filter out all these rows with worldwide revenue.\n", + "\n", + "What are all the names of geographic regions that IBM annual reports have used over the last ten years?" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "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", + "
Region
0Total revenue
4Geographies
8Americas
12Europe/Middle East/Africa
16Asia Pacific
0Total revenue:
4Geographies:
92009. The increase in total expense and other ...
25Examples of the company's investments include:
29NaN
33• Industry sales skills to support Smarter Planet
16Asia Pacifi c
3of intellectual property
6Licensing/royalty-based fees
9Custom development income
12Total
3other (income)
\n", + "
" + ], + "text/plain": [ + " Region\n", + "0 Total revenue\n", + "4 Geographies\n", + "8 Americas\n", + "12 Europe/Middle East/Africa\n", + "16 Asia Pacific\n", + "0 Total revenue:\n", + "4 Geographies:\n", + "9 2009. The increase in total expense and other ...\n", + "25 Examples of the company's investments include:\n", + "29 NaN\n", + "33 • Industry sales skills to support Smarter Planet\n", + "16 Asia Pacifi c\n", + "3 of intellectual property\n", + "6 Licensing/royalty-based fees\n", + "9 Custom development income\n", + "12 Total\n", + "3 other (income)" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "revenue_df[[\"Region\"]].drop_duplicates()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It looks like all the worldwide revenue figures are under some variation of \"Geographies\" or \"Total revenue\". \n", + "Allison uses Pandas' string matching facilities to keep the rows whose \"Region\" column contains the \n", + "name of the regions in the world." + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
YearRegionRevenue
82017Americas37479.0
92016Americas37513.0
122017Europe/Middle East/Africa24345.0
132016Europe/Middle East/Africa24769.0
162017Asia Pacific16970.0
............
162010Asia Pacific23150.0
172009Asia Pacific20710.0
52018Americas36994.0
92018Europe/Middle East/Africa25491.0
132018Asia Pacific17106.0
\n", + "

93 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "8 2017 Americas 37479.0\n", + "9 2016 Americas 37513.0\n", + "12 2017 Europe/Middle East/Africa 24345.0\n", + "13 2016 Europe/Middle East/Africa 24769.0\n", + "16 2017 Asia Pacific 16970.0\n", + ".. ... ... ...\n", + "16 2010 Asia Pacific 23150.0\n", + "17 2009 Asia Pacific 20710.0\n", + "5 2018 Americas 36994.0\n", + "9 2018 Europe/Middle East/Africa 25491.0\n", + "13 2018 Asia Pacific 17106.0\n", + "\n", + "[93 rows x 3 columns]" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "geo_revenue_df = (\n", + " revenue_df[(\n", + " (revenue_df[\"Region\"].str.contains(\"Americas\", case=False))\n", + " | (revenue_df[\"Region\"].str.contains(\"Europe/Middle East/Africa\", case=False))\n", + " | (revenue_df[\"Region\"].str.contains(\"Asia Pacifi\", case=False))\n", + " )]).copy()\n", + "geo_revenue_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now every row contains a regional revenue figure. What are the regions represented? " + ] + }, + { + "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", + "
Region
8Americas
12Europe/Middle East/Africa
16Asia Pacific
16Asia Pacifi c
\n", + "
" + ], + "text/plain": [ + " Region\n", + "8 Americas\n", + "12 Europe/Middle East/Africa\n", + "16 Asia Pacific\n", + "16 Asia Pacifi c" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "geo_revenue_df[[\"Region\"]].drop_duplicates()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That's strange — one of the regions is \"Asia Pacifi c\", with a space before the last \"c\". It looks like the PDF conversion on the 2016 annual report added an extra space. Allison uses the function `pandas.Series.replace()` to correct that issue." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "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", + "
YearRegionRevenue
82017Americas37479.0
92016Americas37513.0
122017Europe/Middle East/Africa24345.0
132016Europe/Middle East/Africa24769.0
162017Asia Pacific16970.0
............
162010Asia Pacific23150.0
172009Asia Pacific20710.0
52018Americas36994.0
92018Europe/Middle East/Africa25491.0
132018Asia Pacific17106.0
\n", + "

93 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "8 2017 Americas 37479.0\n", + "9 2016 Americas 37513.0\n", + "12 2017 Europe/Middle East/Africa 24345.0\n", + "13 2016 Europe/Middle East/Africa 24769.0\n", + "16 2017 Asia Pacific 16970.0\n", + ".. ... ... ...\n", + "16 2010 Asia Pacific 23150.0\n", + "17 2009 Asia Pacific 20710.0\n", + "5 2018 Americas 36994.0\n", + "9 2018 Europe/Middle East/Africa 25491.0\n", + "13 2018 Asia Pacific 17106.0\n", + "\n", + "[93 rows x 3 columns]" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "geo_revenue_df[\"Region\"] = geo_revenue_df[\"Region\"].replace(\"Asia Pacifi c\", \"Asia Pacific\")\n", + "geo_revenue_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Allison inspects the time series of revenue for the \"Americas\" region:" + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
YearRegionRevenue
92008Americas42807.0
92009Americas40184.0
82009Americas40184.0
92009Americas40184.0
82010Americas42044.0
92010Americas42044.0
82010Americas42044.0
92010Americas42044.0
82011Americas44944.0
82011Americas44944.0
92011Americas44944.0
92011Americas44944.0
82012Americas44556.0
82012Americas44556.0
92012Americas44556.0
92013Americas43249.0
92013Americas43249.0
82013Americas43249.0
82014Americas41410.0
82014Americas41410.0
92014Americas41410.0
92014Americas41410.0
92015Americas38486.0
82015Americas38486.0
82015Americas38486.0
92015Americas38486.0
82016Americas37513.0
82016Americas37513.0
92016Americas37513.0
82017Americas37479.0
52018Americas36994.0
\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "9 2008 Americas 42807.0\n", + "9 2009 Americas 40184.0\n", + "8 2009 Americas 40184.0\n", + "9 2009 Americas 40184.0\n", + "8 2010 Americas 42044.0\n", + "9 2010 Americas 42044.0\n", + "8 2010 Americas 42044.0\n", + "9 2010 Americas 42044.0\n", + "8 2011 Americas 44944.0\n", + "8 2011 Americas 44944.0\n", + "9 2011 Americas 44944.0\n", + "9 2011 Americas 44944.0\n", + "8 2012 Americas 44556.0\n", + "8 2012 Americas 44556.0\n", + "9 2012 Americas 44556.0\n", + "9 2013 Americas 43249.0\n", + "9 2013 Americas 43249.0\n", + "8 2013 Americas 43249.0\n", + "8 2014 Americas 41410.0\n", + "8 2014 Americas 41410.0\n", + "9 2014 Americas 41410.0\n", + "9 2014 Americas 41410.0\n", + "9 2015 Americas 38486.0\n", + "8 2015 Americas 38486.0\n", + "8 2015 Americas 38486.0\n", + "9 2015 Americas 38486.0\n", + "8 2016 Americas 37513.0\n", + "8 2016 Americas 37513.0\n", + "9 2016 Americas 37513.0\n", + "8 2017 Americas 37479.0\n", + "5 2018 Americas 36994.0" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "geo_revenue_df[geo_revenue_df[\"Region\"] == \"Americas\"].sort_values(\"Year\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Every year from 2008 to 2019 is present, but many of the years appear multiple times. That's to be expected, \n", + "since each of the annual reports contains two years of geographical revenue figures and some of the annual reports contain two tables.\n", + "Allison drops the duplicate values using `pandas.DataFrame.drop_duplicates()`." + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
YearRegionRevenue
82017Americas37479.0
92016Americas37513.0
122017Europe/Middle East/Africa24345.0
132016Europe/Middle East/Africa24769.0
162017Asia Pacific16970.0
172016Asia Pacific17313.0
92015Americas38486.0
132015Europe/Middle East/Africa26073.0
172015Asia Pacific16871.0
82010Americas42044.0
92009Americas40184.0
122010Europe/Middle East/Africa31866.0
132009Europe/Middle East/Africa32583.0
162010Asia Pacific23150.0
172009Asia Pacific20710.0
92008Americas42807.0
132008Europe/Middle East/Africa37020.0
172008Asia Pacific21111.0
92014Americas41410.0
132014Europe/Middle East/Africa30700.0
172014Asia Pacific20216.0
92013Americas43249.0
132013Europe/Middle East/Africa31628.0
172013Asia Pacific22923.0
92012Americas44556.0
132012Europe/Middle East/Africa31775.0
172012Asia Pacific25937.0
92011Americas44944.0
132011Europe/Middle East/Africa33952.0
172011Asia Pacific25273.0
52018Americas36994.0
92018Europe/Middle East/Africa25491.0
132018Asia Pacific17106.0
\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "8 2017 Americas 37479.0\n", + "9 2016 Americas 37513.0\n", + "12 2017 Europe/Middle East/Africa 24345.0\n", + "13 2016 Europe/Middle East/Africa 24769.0\n", + "16 2017 Asia Pacific 16970.0\n", + "17 2016 Asia Pacific 17313.0\n", + "9 2015 Americas 38486.0\n", + "13 2015 Europe/Middle East/Africa 26073.0\n", + "17 2015 Asia Pacific 16871.0\n", + "8 2010 Americas 42044.0\n", + "9 2009 Americas 40184.0\n", + "12 2010 Europe/Middle East/Africa 31866.0\n", + "13 2009 Europe/Middle East/Africa 32583.0\n", + "16 2010 Asia Pacific 23150.0\n", + "17 2009 Asia Pacific 20710.0\n", + "9 2008 Americas 42807.0\n", + "13 2008 Europe/Middle East/Africa 37020.0\n", + "17 2008 Asia Pacific 21111.0\n", + "9 2014 Americas 41410.0\n", + "13 2014 Europe/Middle East/Africa 30700.0\n", + "17 2014 Asia Pacific 20216.0\n", + "9 2013 Americas 43249.0\n", + "13 2013 Europe/Middle East/Africa 31628.0\n", + "17 2013 Asia Pacific 22923.0\n", + "9 2012 Americas 44556.0\n", + "13 2012 Europe/Middle East/Africa 31775.0\n", + "17 2012 Asia Pacific 25937.0\n", + "9 2011 Americas 44944.0\n", + "13 2011 Europe/Middle East/Africa 33952.0\n", + "17 2011 Asia Pacific 25273.0\n", + "5 2018 Americas 36994.0\n", + "9 2018 Europe/Middle East/Africa 25491.0\n", + "13 2018 Asia Pacific 17106.0" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "geo_revenue_df.drop_duplicates([\"Region\", \"Year\"], inplace=True)\n", + "geo_revenue_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now Allison has a clean and complete set of revenue figures by geographical region for the years 2008-2019.\n", + "She uses Pandas' `pandas.DataFrame.pivot()` method to convert this data into a compact table." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "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", + "
Year20082009201020112012201320142015201620172018
Region
Americas42807.040184.042044.044944.044556.043249.041410.038486.037513.037479.036994.0
Asia Pacific21111.020710.023150.025273.025937.022923.020216.016871.017313.016970.017106.0
Europe/Middle East/Africa37020.032583.031866.033952.031775.031628.030700.026073.024769.024345.025491.0
\n", + "
" + ], + "text/plain": [ + "Year 2008 2009 2010 2011 2012 \\\n", + "Region \n", + "Americas 42807.0 40184.0 42044.0 44944.0 44556.0 \n", + "Asia Pacific 21111.0 20710.0 23150.0 25273.0 25937.0 \n", + "Europe/Middle East/Africa 37020.0 32583.0 31866.0 33952.0 31775.0 \n", + "\n", + "Year 2013 2014 2015 2016 2017 \\\n", + "Region \n", + "Americas 43249.0 41410.0 38486.0 37513.0 37479.0 \n", + "Asia Pacific 22923.0 20216.0 16871.0 17313.0 16970.0 \n", + "Europe/Middle East/Africa 31628.0 30700.0 26073.0 24769.0 24345.0 \n", + "\n", + "Year 2018 \n", + "Region \n", + "Americas 36994.0 \n", + "Asia Pacific 17106.0 \n", + "Europe/Middle East/Africa 25491.0 " + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "revenue_table = geo_revenue_df.pivot(index=\"Region\", columns=\"Year\", values=\"Revenue\")\n", + "revenue_table" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then she uses that table to produce a plot of revenue by region over that 11-year period." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAvgAAAHICAYAAAA7sJ7TAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAACaJElEQVR4nOzdd3xUVfrH8c9J7w0CCb2L9KaCXVABUYrd1VV3bevade0N266ubf2tq6vrKlh21wYoil2xgkpTeq8hIUCAVNLm/P64k8lkMullkvB9v17zmsy5Z+59ZiaB55557jnGWouIiIiIiLQNQYEOQEREREREGo8SfBERERGRNkQJvoiIiIhIG6IEX0RERESkDVGCLyIiIiLShijBFxERERFpQ5Tgi4i0QMaYGcYYa4zpEehYpO6MMZe6P79L6/Aca4yZ33RRtQzGmPnGGM3RLdKElOCLSJ0YY3q4ExHvW5ExZpsx5lVjzGGBjlEazhiTZIy52xjzvTFmjzGm2BizzxjzkzHmr8aYQYGOUWrHz99rsTFmpzHmHWPMkYGOT0QaX0igAxCRVms98B/3z3HA0cBvganGmKOstasDFpk0iDHmFOB/QBKwFpgNZAIxwFDgRuBPxpizrLWzAxVnG3Q4kN9E+94NPOf+OQoYDpwFTDHGnGKtnd9Ex/XnYncMItJElOCLSH2ts9ZO924wxvwD+CNwJ85/4tLKGGNGAu8DpcAF1tr/+emTivMZJzRvdG2btXZNE+4+08/f623AY8CDwPFNeOwKrLXbmutYIocqleiISGOa4b4f6bvBGNPbGPOKMWaHu6RnhzHmOWNMslef7sYYlzHmQ387N8Yku8sLvvFpTzHG/J8xZpMxptAYs8sY87oxpqeffVh3DXBHY8xMY8xuY0yBMWahMeZEP/23GGO2VBGP31piY0y4MeZWY8wvxph8Y8wBY8znxpgT/O2nBsHuUpmN7te2zhhzkzHGeB3vd+7XdWsVcf7Gvf2uWhzvGSACuNpfcg9grU231l4PvObnWDV+zj79rzDGLDLG5Bljcowx3xpjplXRt7cx5l33+5ltjPnUGDPU3/UK3jXwxpgpxpgfjDG5xphl7u3xxpg73MfL8Coze8EYk+Ln2GXH6F3T5+Hnuae6j59njNnr/r1r56ef3xp8Y0yqMeYZY8wGY8xB9+/s16YO9f1VeMV97+/vtdZ/U+7+1xpj1rjj22SMucf9XlljzAyfvlX93XQwxjxrjNnq/jzS3e+Vv7/jLe5bjPu92emO81djzNn1eztE2g6N4ItIUyj2fmCMGQN8jJM4vg9sBvoDfwDGG2OOsNZmWWu3GmO+B041xiRba3f77Pc8nH+3Xvfad19gPpACzAPeBboC57r3Pdpau9FnPwnAd8AB9746uPf9iTFmpLV2RX1fuDEmAvgUOA74GXgRiAamAF8YY8611s6qwy6fAY4E3gKKgDOBp4AewA3uPm8CfwN+DzzuZx+X4YzIz6gh9n7AMcBW4I2aArPWlvg8v1afs1f/54Cr3cd7AQjD+dxmGWNus9Y+7tW3K/A90BF4D1iFU2byDbCsmjDPA052x/Mt5f/vHQ5MB74E3gEKcMqPrsT5/Rthrd3nZ3+1+Ty8TQYmAXOBH3BGyi8GegPHVhM3AMaYw4GvcF73V+5Y44AR7uPNqGkfteD791qnvyljzJ9xvtHZATwPBAPXAqNrG4AxpgPwI877+BlO+d9hOGV/k4wxx/r5hiMU528t0R1jFHA+8JYxZoK19tPaHl+kzbHW6qabbrrV+obzH7AFPvCz7Tn3tme92sJwErgs4HCf/uf46X+Vu+1aP/tfABQCiX7ajvfpOwYncfnAp926b/8AgrzaL3O3/9On/xZgSxXvxXznn9EKbX9x7+cOn/Zk9752A5G1eJ9nuPeTAaR6tccCK93bRvl574/22U9PwAW8X4tjXuLex8x6/F7U9XM+0d32CxDj1d4JSHd/dr292t9w97/OZ9/3eX2mPbzaL3W3lQIn+ok3Hkjy036R+3n3NPDzKDt+MXCMV3swTqJugdF+fjfn+7Qtdrf/1k+snWv52VhghZ/22/Dzt0wd/qZwTuBKgY1Agld7R2Cne/8zavF3U/b+3ufT/nt3+1d+/i4tMAcI82of527/uK6/w7rp1pZuAQ9AN910a103yhP8dTgjoNOBJ4GFXu0pXv3PdLffVsX+FgG7vR4nuZOLBT79erv3M9urbYS77bkq9v2OO/mI92qzQC5eSaW7PcSdvCz2ad9CLRN8nLLHfcDKKvpf6z7+6bV4n8sSnrv8bPuNe9szXm3D3G0v+fR9yN0+pRbHvN3d9y9+tqV4fd5lt0sb8Dm/4u4/zU/fP3kne0A4cBBnhDjEp28UsJeqE/x36vj7bXC+2ZnfwM+j7PiVTpa8tvmerFRI8IGj3G2f1+U1+DmexblIuuxzewxnlNwCu/A6IaOOf1Pu/Vmckq6qfp9m1PB3E47zDcouIMLP57HcvZ9uXu1b3G09/Rx3C7C3Ie+Zbrq19ptKdESkvvoC9/u0bQCOtdZmerUd5b4faIyZ7mc/kUB7Y0x7a+0ea22WMeYjnNk9etvyUoDfuO9f93pu2b47V7HvVJykuy9OgllmnbU217ujtbbEGLOLhl04epj7+duqiKev+74/8EEt9/mtn7bv3PdDyxqstcuMMT8D5xljbrTW5hpjgnCSyQzA73UNdZBC5c/7a8pLROr0OXvF/rWfvvPd92V9+uMkgYusT1mQtTbfXVc/toq4F1XRjjFmHM6MQEcC7XBG18ukVvG0Wn0eXhb7advhvk+oKja3I9z3jVFqkkzlz283zij9Wq+2uv5Nlb3mBX76/lDL2A7DKev6zFp70HuDtdYaY74GBrmP5X2B7n5r7WY/+9uB822DyCFLCb6I1NeH1trTAYwxHXHqrKcD7xhjxnolYknu+5pm1YkG9rh/fh2nZv1CnBk+cP98gIqJcdm+J7tv1e3bW3YV/UqomOTVVVk8Q9y32sZTnUw/bbvc9/E+7S8C/8KplX4ZGA90AR7zTYyrULbfTr4brLXLcEZTMca0x0kOvdX1c44DSqxXTb6XDPd9nPs+1n3ve8wy/t6jMrv8NRpjzgP+C+TgXDewBWcUGZykP7wOx6rq8wD/v2tln0VNv2tl+9tZQ7/aWGmtHQTOGgc4terPAO+5r43Icfer699UdZ9NdZ+Lt7LP2e9nReXfhzIHquhfgiYRkUOc/gBEpMGstbustQ8A/8S5uPQ6r81lCc5Ea62p5rbV6zkf4PznfSF4pm48DKfcotDPvq+uYd/+Rolry0XVgyG+CV1ZPG/WEM8DdTh+Bz9tHd33vglOWcL6e/fjy9z3/67lscpGXE+oblaYKtT1c84GQtzJpq+OXn3AeU3gjEL74+89KmOraL8Pp+xnpLX2PGvt7daZRvIBnOsJqlKXz6Oh9rvvK51wNYR1Lmh/DrgH5+/qYa/Ndf2bqu6zqe5z8VZ2zI5VbPf9fRCRGijBF5HGdC9Offvdxpiykb2f3Pe1nlHD/TX9u0A/Y8wRuBN9Kpbn1Gvf9bAP6GCMqZDkG2OiKS+5KbMaJ+EZZYxpyDcB3o7z01Y2+8ov3o3W2jyc2UeOMcYchzMC+7W1dn1tDmStXYeT5HenvCSqtur6WSxz3/ubf/0Enz5rcK7LGOXnc4jEuf6grnoDq6y1G3zah+OUE1Wl1p9HI/jZfX9qI++3zNM4JS9XG2O6u9vq+jmWvWZ//WtbJrMW52TrSGOMv29Oyn5HGvv9FWmzlOCLSKNx11b/A6ee+Xp38xxgO3CrMaZSEmCMiTTGHOXbTnkyfzFOOcF2fOq1rbU/4iQkvzXGTPWz71BjTI1TEdbgZ5zp+MpOMnCPbv8Fn1IbdxnMP3GSx7/4S/KNMUcZY+qyiuf1xllYquz5scDd7oeV5qHHKdMBZyXaUGo/eu85Hk6y9c9q5hP3V4oyh7p9zq+67+93nyyV9UvBuci2BPdKye5vbWYDnXFmWfJ2M+VlJXWxDejrnp6x7NhxOGUr1anr51Fv1tqfgCXAOGPMRb7bjTGdG7j/Ipzf41Cc0fz6/E29ifMt15+MMfFe/ZLxP22ovzgK3fvpCNzic7xLcMrd5lstkCVSa6rBF5HG9iTObDG3GGP+bq3NNsacA3wE/GCM+RRnDvMQnBl5TsC5QG+Cz36+xrlY7iqcBOSv1lp/5Ra/wZl2cLYx5jtgKU5y2B1ntDUL5yLN+noW+B3wkjHmFJxa4+NwLpD8hcoXVt4HjAJuBSYbY751x9AFZ0Ghw3AuVMyv5fEXAb8YY97EmeXnTJzX9n/W2koXkFprlxhjluDMhnIAZ9aTWrPWLjbGTME5QXjbGLMG58LS3Tg10L1w5pW3eF1Eaa0trMvnbK2db4x5Hmce/OXGmNmUz4PfAWc2Hu/1C+7AmQLx78aYk3G+LRmGM2//tzifiasOL/VZnGR+iTHmXZya+4k49e7V1bzX6fNoBBfhXHT8mjHmdzjJdyzOa4/G+cahIV7GOUG51BjzZ/dFq7X+m7LWrjbGPIEz5eZyY8w7ONcWnItzgXEnave53IbzO/KIMeZ4nBObw4BpOLMkXd3A1ylyaAnkFD666aZb67tRzTz4Xn0ed/e536utG05StRGn3GIfzvR3fweOqGI/f6V8jvPB1RyvHc5I5CqcCyWzcRLAfwPjfPpWmIrQZ9sW/EyJCZyEMw3oQZwLRF/FSULn4zOft7t/CHCN+znZ7pg24YxyX4zPVI9VxDLDHWtvnARsk/t9W48zam2qee71VDPVYS0/5yT3cX/ASeiK3Z/ZIpyTuEFVPK/WnzPORbtX4CSC+TjlXd8CZ1ax7z44I/nZ7tunOCdYc92v13t9hEvdbZdWsS/j/ozKfme24ywWFuvv96Cun0d1x6d8DYDptfndxPnm4h/uuIpwLl6dD1xcy8/S4mcefK/t17j7vFLPvynj/p1b635PNuOU65VN8/mMT//5+P+76eD+3dnmfp0ZOH9rVU2FWelvtbr966bboXQz1lZ1/VHTMM5S8F/52XTAWpvg1S8RJ0mYilMPuQC4yVq73Gd/ETjzPF+EM6K2DLjdWuu7lH0Qzpy8V+FM97YWeNBa+66fGK/A+ZqwJ84/Ik9ba/9Zt1cqIhIYxph/41xoO9JauyTQ8TQldxnURiDKWlvbizrrc5wZOAuB9bTWbmmq47QlxpjLgJeAa6xzUa+INJNA1uBfj3MBTtnt5LIN7vrWuThf5V4HnIXzFf1XxpguPvv5N84I0H3A6TgrIH5ijBnm0+8hnCn8nsX5GnYhztfPp3l3cif3L+Bc4DcBeBt4zhijrwdFpMVz14dfAPzc1pJ779p3L7filI6818zhiJsxJtn3ehP3Z3U3TnlObdd8EJFGEsgR/FOstZ9X0WcKzlfZY621X7nb4nG+9nvdWnu9u20ozoj97621r7jbQnCWDV9rrZ3sbuuA8/Xro9ba+72O8wWQbK0d4vXcncBH1tpLvPq9jDMbRaq1trhR3ggRkUZkjJmEU+N/Ds6iQGdYa9tUYmWM2Y9Tg77a3XQkzuwtu3C+rUhrwmPPQCP4fhlj/oRzQe2XOINsXXAG3OKBR6y19wQwPJFDUkudRWcysLMsuQew1h7AGdWf4tOvGOfq+7J+JTgXh433mm5rPM7FW75T7L0ODDbG9HQ/HoMzl69vv9dw6hEbOhuHiEhTOQdnDvcEnHLGNpXcu72Ac9HmpcAfcRLJfwNHNmVyLzX6Dmew7VTgJpz/p1cBlyi5FwmMQM6i84Z7NcT9wCfAHbZ8CqyBwAo/z1kJXGyMibHOMvMDgc3WWt/ZKFbiJPR93D8PxLnwx3e+45Xu+wE43w4MdD/2PbZ3P3/XD4iIBJS19lKcxLfNstbejnMtVSCOfSlt/P2tL2vtQuCMQMchIuUCkeAfwJmB4Wucq/KHA3cBC4wxw621mTizN2zx89yyJc0TcWZbSMKZoaGqfkle9/tt5Xokf/3ws0/ffhUYY64ErgSIjo4e2b9/Q2bkExERERGp2eLFi/dYayutJN3sCb61dinOnLplvjbGfINTV3k97sU2WhNr7Yu4F5cZNWqUXbSoKaZCFhEREREpZ4zZ6q+9RdTgu2d6WAcc4W7ahzNK78t3hL2mflle/RLcs/PU1A8/+/TtJyIiIiLSIrWIBN9LWQlNWd28rwHANnf9fVm/nn6WfR+As0jGBq9+4TgLlPj2A+dioLJ++Dm2bz8RERERkRapRST4xphROEtS/+Rueh/obIw5watPHM5FPO97PXUuzvz453j1CwHOAz611ha6mz/GmW3nQp9DX4Szut9m9+MFOKtU+uuXBXxfn9cnIiIiItJcmr0G3xjzBs6MNUtwZtAZDtwJpAH/5+72Pk6y/box5lac0pk7cZbD/mvZvqy1S40xbwJ/M8aEuvd7Nc4KtBd69cs0xjwF3GmMyXEf+zxgLM5Um2X9io0x9+IsbJUGfO7u83vgOmttUeO+GyIiIiIijSsQs+iswFll8TogCsgAZgH3W2v3AFhrXcaY04EngOeACJyE/yRr7Xaf/f0OeAR4GGf+51+ACX5WcLwbZ+adG4AUYC1wru9c0dbafxpjLHALzgqJ24Brtcy2iIiIiLQGzb6SbVunWXREREREpDkYYxZba0f5treIGnwREREREWkcSvBFRERERNoQJfgiIiIiIm2IEnwRERERkTZECb6IiIiISBuiBF9EREREpA1Rgi8iIiIi0oYowRcRERERaUOU4IuIiIiItCFK8EVERERE2hAl+CIiIiIibYgSfBERERGRNkQJvoiIiIhIG6IEX0RERESkDVGCLyIiIiLShijBFxERERFpQ5Tgi4iIiIi0IUrwRURERETaECX4IiIiIiJtiBJ8EREREZE2RAm+iIiIiEgbogRfRERERKQNUYIvIiIiItKGKMEXEREREWlDlOCLiIiIiLQhSvBFRERERNoQJfgiIiIiIm2IEnwRERERkTZECb6IiIiISBuiBF9EREREpA0JCXQAIiK+tu7NY/bSNN5btpO0fQVEhQcTHRZCdHgw0eEh5T+HhTjbPG0hRIcFExUeQkx4MFFhIcSEhxAVFuzch4cQFRpMUJAJ9EsUERFpMkrwRaRF2J9fxNxf05mzNI3FW/dhDIzp1Y5TB3Ykv7CUvKIS8gpLyC8qJbewhN05hZ62vKJSikpctT5WVFhZ8u91EuB1ElG5zTlxiA6veJIR5W4LDwnCGJ00iIhIy6AEX0QCprCklK/WZDJrSRpfrc2kuNTSr2MMt0/oz5RhneiUEFnrfRWXuiqcCOQVlTr3XicF+UUl5BaWku+13WkrISuviG1Z+RX24bK1O3ZwkPGcAHi+LSg7MfCcEFQ8cfD08TlxiAoLIS4iRCcMIiJSb0rwRaRZWWtZtHUfs5em8eGv6RwoKCY5NpxLxvRg2ojODEiNq1dyGxocRHxUEPFRoY0WZ2GJyzkxqMWJQ15haaW2tP0F7m3O9oLi0lodOyY8hN4dYuhbdusYQ98OsXROiFR5kYiI1EgJvog0i8178pi9ZAezl6WxPauAyNBgJgxKYerwzhzTux0hwS3rmn9jDBGhwUSEBkNM4+yz1GXJL/I6CajixGHHvgLWZ+bwzbrdvLN4h+f5EaFB9E4uS/pj6eM+AeiWFNXi3j8REQkcJfgi0mSy8oqY+8tOZi9NY9n2/QQZOKZPe246uR/jB6YQHX5o/RMUHGSIjQglNiKUjrV8zoGCYjZk5rIhM4f1u3JZn5nLz1v2MWfZTk+fsOAgeraPpk/HslF/J/nv0T6K8JDgpnkxIiLSYh1a/7uKSJM7WFzKF6szmb10B/PX7qbEZTk8NY67TuvPlGGd6RgXEegQW5X4yFBGdk9kZPfECu15hSVs3J3rSfo3ZOawIu0A85anY93XDgQHGbq3i/Ik/X07xtA72blFhinxFxFpq5Tgi0iDuVyWn7ZkMXtJGvOWp5NTWELHuHAuO7Yn00Z0pn9KXKBDbHOiw0MY0iWBIV0SKrQfLC5l0+481mfmsCGz7AQgh89XZ1LqvmrYGOia6CT+fTrG0Ce5vOQn5hD7VkVEpC3Sv+QiUm8bMnOZvXQHc5buJG1/AVFhTl39mcO7MKZ3O4J1QWiziwgNZkCnOAZ0qnhSVVTiYuvePNZ7Jf0bMnP5dv0eikrLpxjtFB9Bn46xngt8+7hH/xvr4mUREWl6SvBFpE725Bby/jKnrn552gGCDBzXN5nbJhzGKQM6EhWmf1ZaorCQIPp2jKVvx1gYXN5eUupi+74C1u/KcZf6OMn/Gz/u5WBxeeKfHBtenvR3jHWP+sfQLjpMU3qKiLQw+p9YRGpUUFTKZ6t3MXvJDr5Zv4dSl2VgpzjumXQ4k4d1okOs6upbqxD3Bbo920dz6sDydpfLkra/wJPwl9X6v7skjdzCEk+/xKhQ56JeT6mPM+LfMS5cib+ISIAowRcRv1wuy8JNe5m1NI2PV2SQW1hCp/gIrjy+F9OGd6Zfx9hAhyhNKCjI0DUpiq5JUZzUv4On3VpLRvZBr/p+5wLfsjUNysSGh3hm9enjNbOP5vIXEWl6SvBFpIJ1u3KYtSSN95alkX7gIDHhIUwclMK0EZ0Z3bOdkrNDnDGG1PhIUuMjOa5vsqfdWsue3KLyKT3dJwBfrtnNW4vK5/KPDA2mjzvp79OhfE7/7klR+t0SEWkkSvBFhMycg7y/bCezlqSxKj2b4CDDCf2Sueu0wzllQEdnsSeRahhjSI4NJzk2nDG921XYtj+/yF3qU36B78JNe5m9NM3TJy4ihBHdExnVPZER3RMZ1jVB13OIiNSTsWUTJkujGDVqlF20aFGgwxCpUX5RCZ+u3MWspWl8t343LgtDusQzbXhnzhjaifYx4YEOUdq4nIPFbNydx7qMHJZu38firftYtysXgJAgw4BOcYzsnsio7kmM6pGoNRRERHwYYxZba0dValeC37iU4EtLVuqy/LBxD7OXpPHxygzyi0rpnBDJtOGdmTq8M306xAQ6RDnEHcgvZsm2fSzamsWiLfv4Zcd+z2w+XRIj3Ql/IiO7J3FYSqymYhWRQ1pVCb6+/2wDFm/N4qnP1jFxUCrjB6aQHKuRV6lo1c5s5ixz6up3ZRcSGxHC5KGdmDa8M0f0SFLts7QY8VGhnNS/g+fC3uJSFyt3ZrNoSxaLt+7jh417eW/ZTsC5kHdYtwTPCP+wrglEa6EuERGN4De2QIzgf7lmFw9/sJpNe/IIMnBEjyROG5zKhEEp+kr7EJZx4CDvLUtj9tI01mTkEBJkOPGwDpw5ojNj+3dQXb20StZaduwr8IzwL966j7W7crAWggwcnhrnjPD3SGJU90Q6JUQGOmQRkSajEp1mEqgSHWst63blMm95OvOWp7M+MxdjYGS3RCYOTmXioBT9R3cIyC0s4ZMVGcxemsb3G/dgLQzrmsCZIzpz+pBOJEWHBTpEkUZ3oKCYZdv3s3hLFou27mPZ9v3kF5UCzsq8I3skMbJbAqN6JNE/JZaQ4KAARywi0jiU4DeTllKDvyEzh4+WZzBvRQar07MBJ9E7bXAKEwel0jUpKsARSmMpKXXx3YY9zF6axqcrd1FQXErXpEimDe/C1GGd6JWsuno5tJSUulidnuOM8m/dx+It+8jIPghAdFgww7olMLK7M8I/vFsCsRGhAY5YRKR+lOA3k5aS4HvbvCePj1ak89HyDJanHQBgcOd4Jg5O4bRBqfRoHx3gCKWurLWs3JnN7KVpvLdsJ3tyC4mPDGXSkFTOHN6Zkd0TtYqoiJe0/QWeOv5FW/axJiMblwVj4LCOsYzq4czWM7J7Il0SI/X3IyKtghL8ZtISE3xv27Py+WhFOvOWZ7Bs+37AqVk9bVAKEwenahaVFm7n/gLmLEtj9pI01mfmEhpsGNu/A9OGd+Gk/smEh6iuXqQ2cg66y3q2OnX8S7ftJ7ewBICOceGeZH9k90QGdIojVGU9ItICKcFvJi09wfeWtr+Aj1dk8NHydBZt3QdAv44xTByUymmDU+nXMUajWAG2K/sgi7aUTxm4YucBrIVR3ROZOrwzpw9JJSFKdfUiDVXqsqzJyPaM8C/euo+0/QWAs/ru0K7xTtLfI5ER3RKJj1RZj4gEnhL8ZtKaEnxvGQcO8snKDOYtT+enLVlYC72SozltUCoTB6cwIDVOyX4Tc7ksG3bn8vOWLBZv2cfPW7PYnuUkGBGhQQzvmsjRvdsxZVhnurXTNRQiTS39QEGFhH9VejalLosx0K9DrGfl3VE9EumWFKV/I0Wk2SnBbyatNcH3tjunkE9WZvDRinQWbsqi1GXp3i6KCYOcmv0hXeL1H1kjOFhcyq87DjgJvbtM4EBBMQDtY8I5oodTHnBEjySVCIi0AHmFJfyyfT+Ltu5j0dZ9LN26jxx3WU/7mHBPsj+yeyIDO8UTFqK/WRFpWkrwm0lbSPC97c0t5LNVu/hoRQbfb9hDicvSOSGSie6a/eFdE7RIUi3tzS10RgO37mPRliyWpx2guNT5++vTIcadHDgze3Rvp9FAkZau1GVZn5njGeFf5PWtW3hIEEO7JDCyR9nKu4kqpxORRqcEv5m0tQTf24H8Yj5bvYuPlqfz7fo9FJW6SImLcEb2B6cysnuilo13s9ayZW8+P2/JYpF7bu5Nu/MACAsOYkiXeEb2SOQI94V8iZqfXqRNyMw+6EzN6T6ZX5l2gBJX+Yn8yG6Jzt9+jyR66EReRBpICX4zacsJvrfsg8V8uTqTecvTmb9uN0UlLpJjw5kwMIWJg1M4skfSIbWYTFGJi5U7D1S4IHZvXhEACVGh7hG8JI7okcigzvFaRVbkEFFQVMovO/a7a/mdcrzsg05Zz2EdY5k2ojNTh3UmJV6rjotI3SnBbyaHSoLvLbewhK/WZPLRinS+XJPJwWIX7aLDOHVgCqcNTmF0r3Ztrn78QEExS7Y5/2Ev2uKsnFlY4gKge7soT+38ET0S6dU+RmVMIgI4F9Nv3J3LDxv38t6yNJZs248xcEzv9pw5ojPjB6YQHR4S6DBFpJVQgt9MDsUE31t+UQlfr93NvBUZfLl6F3lFpSREhXLqgI5MHJzKMb3bt7oLz6y17kVyykfn1+7KwVoIDjIM6hTnGZ0f2SORDrEaiROR2tm8J4/ZS9OYvXQH27MKiAoLZsLAFM4c0YUxvdup7FFEqqUEv5kc6gm+t4PFpXyzbjcfrcjg81W7yCksITYihFMGdOS0Qakc27d9iyxVKSl1sSYjx1M7v8hrmfuY8JAKU+MN65pAVJhG20SkYay1LNq6j1lLdvDBr+nkHCyhY1w4U4d35szhXTgsJTbQIYpIC6QEv5kowfevsKSU7zfsYd7yDD5btYsDBcXEhIcw7vAOTByUwgn9OhAZFphkP6+whGXb93umq1yydR95RaUAdIqPcGa2cS9jf1hKrEbURKRJHSwu5YvVmcxasoOv1+2mxGUZ2CmOacM7M2VYZ5JjwwMdooi0EErwm4kS/JoVl7pYsHEvH61I55OVu8jKKyIyNJix/TswcXAKJx3WoUlrUMtWhy1L6L0Xr+mfEucZnR/VI4nOCZFNFoeISE325BYy95edzF6axq87DhAcZDi+b3umjejCqQM6tshvQUWk+bTYBN8Y8zEwHnjEWnuPV3si8DgwFYgEFgA3WWuX+zw/AngIuAhIAJYBt1trv/HpFwTcDlwFpABrgQette/6iekK4BagJ7AFeNpa+8/avB4l+HVTUurip81ZzFuRzscrdrEnt5DwkCBOPCyZ0wanMrZ/B2Ij6r8kvMtlWZ+Z66mdX+RnddiyZH54twTiGnAsEZGmtCEzh1lL0pi9NI30AweJDQ/htMGpTBvRmSN7JOlifpFDUItM8I0xFwBP4STcngTfOBMDfwv0AG4F9gF3AgOBYdbaHV77eAOY5O63CbgGmAiMsdYu8+r3CPAn4G5gMXA+cAVwurV2nle/K4AXgL8AnwPjgLuAa6y1z9f0mpTg11+py7JoSxYfrXBW0d2VXUhYcBDH92vPxEGpnDygI/GR1SfgvqvDLtqS5ZmSTqvDikhb4HJZFm7ay6ylaXy0PJ28olI6J0QybXhnpo3oTO/kmECHKCLNpMUl+O4R+tXATcB/qJjgTwHmAGOttV+52+KBzcDr1trr3W1DcUbsf2+tfcXdFgKsBNZaaye72zoA24FHrbX3e8XwBZBsrR3i9dydwEfW2ku8+r0MTAZSrbXF1b0uJfiNw+WyLN2+j3nLM/hoeTo7DxwkNNhwTJ/2nDYolVMGdCQxOkyrw4rIIS2/qITPVu3i3SVpfLd+Ny4LQ7smcNaIzpw+pBNJWkRPpE1riQn+i0Ava+3JxhhLxQT/38AEa21nn+fMBE601nZ3P74XuBdIsNbme/V7ALgDiLPWFhpjfgu8CvSz1q736vc74GV3HJuNMccB3wCnWms/8+p3EvAlXiccVVGC3/istfyy4wAfLU9n3op0tmcVEBxk6JQQ4Sm30eqwInKoy8w+yHvLdvLukh2sycghNNhw4mEdOGtEZ07q34HwENXri7Q1VSX4AZnfzxhzLHAxMLSKLgOBFX7aVwIXG2NirLW57n6bvZN7r35hQB/3zwOBQmCDn34AA3C+HRjofux7bO9+1Sb40viMMQzrmsCwrgncMbE/K3dmM295Opt25/GbI7trdVgREaBDXARXHN+LK47vxaqd2cxeuoM5y3by2apdxEeGcvqQVM4c0ZkR3RL1baZIG9fsCb4xJgynxv0Ja+3aKrol4Vzc6ivLfZ8I5Lr77aumX5LX/X5b+esKf/3ws0/ffhUYY64ErgTo1q2bvy7SSIwxDOocz6DO8YEORUSkxRrQKY4BnQZw+4T+fL9xL7OX7ODdJTt448dtdG8XxZnDuzBteGe6tYsKdKgi0gQCMYJ/G86sOI8E4NhNwlr7IvAiOCU6AQ5HREQEgJDgIE7ol8wJ/ZLJLSzho+XpzF6axt++WMfTn6/jiB6JTBvehUlDUmucxEBEWo9mTfCNMd1wZrG5HAg3xniv1hFujEkAcnBG0BP97MJ3hH0f0L2aflle/RKMMcZnFN9fP9zHTq+mn4iISKsSEx7COaO6cs6oruzcX8CcZWnMWpLGXbOXM33uSk4+vANnDu/CCYcla4YxkVauuUfwewERwOt+tv3JfRuOU/N+qp8+A4Bt7vp73P2mGWOifOrwBwBFlNfcrwTCgd5UrMMf4L5f5dUPnFr89Gr6iYiItFqdEiL544l9uPqE3qxIy+bdJTt4/5edzFueQVJ0GJOHduLMEZ0Z3Dle9foirVCzzqLjHqEf5mfTVzhJ/7+BRcDJwGycGXO+dj83DudC2P9Ya69ztw0HlgCXWmtnuttCgOXABmvtGe62DsAOnJl6HvCK53Ogo7V2sPtxKM40mR9Ya3/n1e8lYBrONJlF1b1GzaIjIiKtUXGpi2/W7WbWkjQ+W72LohIXvZOjOXNEF6YO76yVvUVaoBYxi461dj8w37fdPTqw1Vo73/34fZyVa183xngvdGWAv3rtb6kx5k3gb+7kfDNwNc4KtBd69cs0xjwF3GmMycE5KTgPGIszv31Zv2L31JvPGWPScBa6Ggv8HriupuReRESktQoNDmLc4R0Zd3hHDhQUM295OrOXpPH4J2t54tO1jO7ZjjNHdGbi4FRiwgMyCZ+I1FJAV7L1BOEzD767LQl4ApiKU9azALjZWvuLz3PLLtj9DZAA/ALcXnay4NUvGOck4QqclXPXAg9aa9/xE89VwC049f3bgKettc/V5rVoBF9ERNqS7Vn5zF6axqwlO9iyN5+I0CDGD0xh2vDOHNunPSGq1xcJmBa30FVbpQRfRETaImstS7btZ/bSHcz9JZ0DBcUkx4YzdVgnpg3vwoBOcYEOUeSQowS/mSjBFxGRtq6wpJSv1mQya0kaX63NpLjU0j8lljNHdGbKsM50jIsIdIgihwQl+M1ECb6IiBxK9uUV8cGvO3l3SRrLtu8nyMCxfZM5c3hnTh3Ykagw1euLNBUl+M1ECb6IiByqNu3Oddfrp5G2v4DosGAmDErlrBGdGd2rHUFBmnJTpDEpwW8mSvBFRORQ53JZft6Sxawlacxbnk5OYQkdYsPp1zGWzgmRdE6M9Nx3SYwkJS5CF+uK1IMS/GaiBF9ERKTcweJSPlu1i09X7WJbVj5p+wrYk1tYoU9wkCElLqJC8t8lsfznTgmRRIQGB+gViLRcLWIefBERETm0RIQGc8bQTpwxtJOn7WBxKTv3F7BjXwFp+wtI87r/aXMW6QcKcPmMP7aPCXdG/H2S/7L72IjQZn5lIi2XEnwRERFpVhGhwfRKjqFXcozf7SWlLjKyDzonAD4nAavSsz0r7XqLiwihc2KUk/yXnQR4nQAkRYeVLawp0ubVKsE3xoQBZwITgNFAJ5zFp/biLBj1NfCmtXZVE8UpIiIih4iQ4CC6JEbRJTHK73aXy7Inr9DvCcC2vfks2LiX3MKSCs+JDA2uNOpffjIQRYfYcF0ELG1GtQm+MSYKuBW4FkgEVgM/AbuBAiAJ6Onefo8x5jvgLmvt900ZtIiIiBy6goIMHWIj6BAbwYhuiZW2W2s5UFDstwQobX8Bv+7Yz7784grPCQ02pMZH+r0IuEtCFCnxEYSF6EJgaR1qGsHfBKQD9wFvWWv3VtXRGHMMcBHwiTHmFmvtC40XpoiIiEjtGGNIiAojISqMQZ3j/fbJLyohbV8BO/ycAHy7fjeZOYV4z0NiDHSMjaiU/Jd/ExBFZJguBJaWodpZdIwxU6y179Vph8Z0BHpYa39saHCtkWbRERERaf2KSlykHyiodBKwY18+afsLSN9/kBKfK4GTosOc5N+d9HeMiyAoyGBwThAMeB5jvNuNZ3vZY8r6G/c2r37O0ys/36kw8t6XVx+f/RpjCPI5NpVi8YnLs7/Kx3a/JEKDg4iNCCEuIpSosGBd99DE6jWLTl2Te/dzdgG76vo8ERERkZYiLCSI7u2i6d4u2u/2UpclM+egV+Jffr8+M4f56zI5WOzy+9xDRXCQ8ST7Fe4jKz+O8zwOJS4yhFj3tlCtj1AvmkVHREREpI6Cg5ya/dT4SCoNn+JcB5BTWIJ1gcViLVjAZct+dhoseB5bz2PrKQ8q7+9ud/enQv/y57u8nut3vz7Pd9mK7dXF5bJl29xtfvZbXOoi52AJOQeLyT5YTM7BErIL3PcHi9mWle95nONzIbQ/kaHBxEX6nhw4JwTeJwNlJwjlj53+h+q3CPVO8I0xnYB7gWRgrrV2ZqNFJSIiItKKGWOI09z81Sp1WXILK54AlJ0Q+Ds5yDlYQlZeEVv35nv6FJdWv2Brdd8i+PtGIc7PNwytcZXl2k6T+TYQaq2d6n4cAswHOgDbgWnGmGhr7XNNFKdUY92+dTy56EluGnkT/ZP6BzocERERkRoFBxniI0OJj6zfiZC1lsISF9kHi8kuKPvWwH1f0HjfIkSFBfspMSr/FiE1PoJLju5Rr9fQVGo7gj8GuMfr8VSgMzDIWrvZGPNX4I+AEvwASMtJY+XelZw791zO6H0G1w2/jpTolECHJSIiItJkjDFEhAYTERpMh9j67aPUZcl1J/5VfWtQ07cIXROjWleCb4w5Hufi6Y5AnPsxwNk4C1x1NcZ0BdYB13ht32Kt3dZEMYuPk7qdxLyUebz060u8vvp1Pt3yKb8d8FsuG3wZ0aH+Lw4SEREROdQFBxnio0KJj6r/twhFpS3vYuqaiopOAk7ESfIHux+XtWV6PR4IhLrbTwJ6NEGsUo24sDhuHnUzc6fN5aRuJ/Gv5f/itFmn8dbatyhx1fz1k4iIiIjUjTGG8JCWt/5BtfPgezoZswWYaa293xjTA2f0foK19iv39qOBN621XZsw1lahpcyDv3z3cp5Y9ARLMpfQK74XN4+8meO7HH9IXkkuIiIi0hZVNQ9+bS8Lfhm41xizGFiEU5Iz32v7icDSBsYojWhw8mBmTJjB3076G6W2lGu/vJbLP72cVXtXBTo0EREREWlCtU3wHwJuxJkx522c0Xvvof8hwEuNG5o0lDGGcd3GMXvKbO488k7W71vPeR+cx13f3kVGXkagwxMRERGRJlCrEh2pvZZSouNPTlEOLy1/iddXvY4xxrkQd9BlxITFBDo0EREREamjhpboSBsQGxbLTSNvYu60uZzc/WReWv4Sk2ZP4n9r/kexqzjQ4YmIiIhII6g2wTfG9DbGnOGnfawx5idjTK4xZr0x5sqmC1EaW6eYTjx63KP8b9L/6BXfi0d+fISz3j+Lr7Z9hb7REREREWndahrBvxe4w7vBGHMY8AFwOPAJcBB43hgzrUkilCYzsP1AXh7/Mv930v9hreX6r67nsk8vY+XelYEOTURERETqqaYE/yici2q9XQuEAeOstWcBQ4Ev3O3SyhhjOKnbScyaMou7j7qbDfs2cP4H53Pnt3eSnpse6PBEREREpI5qSvA7Aat92iYCS621PwFYa104M+gMa/TopNmEBoVyfv/z+fDMD7l88OV8tvUzTp99Ok8vfpqcopxAhyciIiIitVRTgm+AUs8DYzoAvYDvffrtBDQVSxsQGxbLDSNuYO7UuYzvMZ6XV7zMpFmT+M/q/+hCXBEREZFWoKYEfxNOmU6ZUwALfOXTrwOwpxHjkgBLjUnlz8f9mTdPf5O+iX35y09/4cz3zuTLbV/qQlwRERGRFqymBH8mcLsx5lpjzDk4C17tAT716XcisL7xw5NAG9BuAC+d+hLPjn0WYww3fHUDv/vkd6zYsyLQoYmIiIiIHzUl+P8APgf+D3gTSAJ+b60tKOtgjIkCLnD3kzbIGMMJXU9g1uRZ3Dv6XjYf2MwFH17Abd/cRlpuWqDDExEREREvtVrJ1hjTEye5X2OtzfPZFgMcBmyw1h5okihbkZa8km1jyS3K5eUVL/Pqqlex1nLhgAu5fPDlxIXFBTo0ERERkUNGVSvZ1irBl9o7FBL8Mhl5Gfx96d+Zu3Eu8eHx/GHoHzi337mEBocGOjQRERGRNq9eCb4x5vfV7LME2AUs1Mh9uUMpwS+zeu9qnlz0JD9m/Ej3uO7cNOImxnYbizEm0KGJiIiItFn1TfBdtdh3AfAXa+3DDYivzTgUE3wAay3fpn3LU4ueYuOBjYzoMIJbRt3CkOQhgQ5NREREpE2qb4LfvZp9BuMshHU2ziq2l1trZzQwzlbvUE3wy5S4Spi9YTb/WPoP9h7cy8QeE7l+xPV0ie0S6NBERERE2pQmrcE3xvwTGGGtPbLBO2vlDvUEv0xecR6vrHiFmStnUmpLufBw50Lc+PD4QIcmIiIi0iZUleDXNE1mbX0ADGikfUkbEB0azbXDr+WDaR8wqdckZq6cyaTZk3h91esUl2pFXKlZsauYZZnLWJq5VKsoi4iI1EFjjeBPAGZZa6MaHlLrphF8/9ZmreWJRU+wMH0hXWO7ctPImzi528m6EFc8XNbF2qy1/Jj+Iz9m/MjiXYspKHGW3IgOjebIlCMZ02kMR3c6mm6x3fS7IyIih7ymLtF5DJhgrR3a4J21ckrwq2at5fud3/PkoifZsH8Dw5KH8acj/sTQ5EP+1+aQZK1la/ZWT0L/c8bP7C/cD0DP+J4clXIUo1NH48LFgp0L+GHnD56F1TrHdGZ06miO7nQ0R6UepdIvERE5JNX3ItvqSnjKLrI9C/gLcIe19umGBtraKcGvWYmrhPc2vMezy55lT8EexvcYzw0jbqBrbNdAhyZNbFfeLn7M+NFJ6tN/ZFf+LgBSolM4KuUojkp1bh2iOlR6rrWW7TnbPcn+Txk/kVucS5AJYmC7gZ7R/SHJQwgN0loMIiLS9jVkmsyahvgN8BJwldWqWUrw6yC/OJ8ZK2cwY+UMil3F/Kb/b7hyyJUajW1DDhQe4KeMnzwJ/ZbsLQAkhCdwZMqRHJXqjNJ3je1a55KbElcJK/as4IedP/DDzh9Yvmc5LusiKiTKU84zptMYesT1UDmPiIi0SfVN8KdTdYJfAmQC86216xsjyLZACX7dZeZn8o9l/2D2+tnEhsVy1ZCrOL//+YQFhwU6NKmj/OJ8lmQu4af0n1iYvpA1WWuwWKJCohjZcaRnhL5fYj+Cqv2CsO6yi7L5Of1nT8K/I3cHAKnRqRzd6WhGdxrN6JTRJEQkNOpxRUREAqVJa/ClnBL8+lubtZanFz/N9zu/p0tMF24ceSOndj9Vo68tWHFpMb/u+dWT0P+651dKXCWEBoUyNHmoJ6Ef1H5Qs5fNbM/ezoJ0dzlP+k/kFOdgMJ5ynjGdxjAseRihwSrnERGR1kkJfjNRgt9w36d9z5OLn2T9vvUMTR7Kn0b9iWEdhgU6LMGZ6WZN1honoc9YyJJdSygoKcBgGNBugJPQpxzF8I7DiQyJDHS4HmXlPAvSF7Bg5wJ+3f0rpbaUyJBIjkg5gqM7Hc2YTmPoGddTJ5QiItJqKMFvJkrwG0epq5T3N77P35f+nd0Fuzml+yncNOImusbpQtzm5DvTzU8ZP3Gg8AAAveJ7eRL6USmjWtW1EzlFOfyc4ZTzLNi5gG052wDnYt8xqWM8s/MkRiQGOFIREZGqKcFvJkrwG1d+cT4zV83klRWvUOwq5vzDzueqIVepjroJVTXTTWp0KkelHuW5ONbfTDet1Y6cHZ7R/YXpC8kpcsp5Dm93uDO6nzqGYR2G6boQERFpUZTgNxMl+E1jd/5u50LcDbOJDo3mqiFXcUH/C5RwNYL9B/fz866fK810kxieyBEpRzRoppvWqNRVysq9Kz2j+7/u/pUSW0JkSCSjOo7ylPP0iu91SLwfIiLSctV3Fp3JwNfW2gNNGVxbogS/aa3ft56nFj/Fd2nfERsWS2p0Ku0i2tEusl35vffPEe1IjEgkJCgk0KG3GGUz3ZQl9P5muhmdOpq+iX0bfaab1ii3KNdTzrMwfaHnBKhDVAfP6P7oTqNJikgKbKAiInLIqW+CXwqMsdb+5P1zE8bZ6inBbx4Ldi7gky2fsLdgL3sP7vXcF5YWVuprMCSEJ3gS/qTIpMonBe77pIikNvetQNlMN2UJvfdMN8M6DOPIlCMZnTqage0HaoGoWkjLTWPBzvJynuyibAAOTzrcs9jW8A7D29zvkYiItDz1TfAPAGdbaz9zL3o1Wgl+9ZTgB461lrzivAoJv/d91sGsCo/zS/L97ic2LLbKEwDfbwkiQiKa+VXWrGymm7KEfkmmn5luUo9ieIeWNdNNa1TqKmXV3lWe6Th/yfyFEltCRHAEI1NGcnTq0Rzd6Wh6J/RWOY+IiDS6+ib4XwI9gG+Ai4EPgd1VdLfW2ssaHmrrpgS/9SgoKfB7IuDvpCCnOMfvPqJDo0mKqPobAe+fo0KimiTJs9ayJXuLJ6H/edfPlWe6ST2KUR1b10w3rVFecR6LMhZ5FtvylPNEdmB0p9HOglupo2kX2S6wgYqISJtQ3wT/MOBpoD/QHdgDFFXR3VpruzVCrK2aEvy2qbC0kKyCrIqJv58Tgr0H97K/cL/ffUQER1RbJpQUkeR5HBcWV+3JQEZehieh/zHjRzLzM4HymW7Kpq9MjkpuirdDaik9N90zur8wfaHnxKt/Un9nsa3UMYzoOILw4PAARyoiIq1Rg2fRUYlO7SjBl2JXMfsO7vP/7YDP/f7C/bisq9I+QoNCKyT8Zfc5RTn8lPFThZlujkx1pq0cnTKaLrFdVArSQpW6SlmTtcaZnSd9AUszl1LiKiE8OJxRHUd5Vtftm9BXn6GIiNRKYyT4JwCLrbW5jR1cW6IEX+qi1FXKvsJ9lb8V8LrPKsjybA8LDmNUyiiOSnFG6TXTTeuVX5zPol2LPNNxbjqwCYD2ke3pFtuN4KBgQkwIIUEhBAcFExoUWuFxSFCI53HZLdgEe34ODQqt8Ljs59Cg0Ep9PTfjte+gEEJNaIXHwSbY8/yQoBD97omIBFijzYNvjBkEnAAkAVnAfGvtykaJsg1Qgi9NxWVdWGsJDgoOdCjSBDLyMpzZedIXsLdgLyWuEkpsiXPvKqHUVVrhsff2Ulep53FzMpgKJweeEwGvkxPf7d4nDN59kiKSSI1OJTU6lZToFFKiU0iOTNbvu4hINRpjBD8EmAFcAHh/f2yB/wCXWmtLGx5q66YEX0QCxVpLqS0tPymwpRS7iis89j058D1BKHYVV3hc6irfR4Xn++7Da1uxq7hiHK5Sim1xxWP5PN/fxezBJpgOUR0qJP2+JwE1Xa8iItKWVZXg12X1n/uBc4H7gNeBDCAFuMi9bZP7XkREAsAY4xkVb41yinLIyMsgPS+djLyMCj//svsXPt36KSWuit9SRIVEVToB8D4R6BjdUWsSiMghpy7/C1wEPGytfcSrbSvwiDEmGPgdSvBFRKSeYsNiiQ2LpW9iX7/bXdbF3oK9pOelVzoJSM9LZ3XWarIOZlV6XruIdk7CH5NKx6iOnp9TolJIjUklKSJJ1xOISJtSlwS/E/BDFdt+AO5ueDgiIiL+BZkgkqOSSY5KZkjyEL99DpYcZFf+rgqJ/668XaTnpbNh/wa+S/uOgpKCCs8JDQqt9A2AbzlQdGh0c7xEEZFGUZcEfydwDPC5n21Hu7eLiIgETERIBN3jutM9rrvf7dZasouyneQ/N52MfHcZUG4GGfkZ/JTxE5n5mZWmr40Ni61U/+9dDpQclUxoUGhzvEQRkRrVJcF/A7jbPR/+G0A6Tg3++Tij9481fngiIiKNxxhDfHg88eHx9E/q77dPiauEPQV7PCcBvuVAy3Yv8yxaVibIBNE+sr3nJKCs/t/7pCAhPEEXBItIs6jrLDqv4iT03k8ywH+BS6xt5jnaWiDNoiMi0vblF+dXugbA98LgIlfFhd8jgiM8o/+dYzpzdr+zGdR+UIBegYi0BY05D/5A4HjK58H/pi7z4BtjxgO3AwOARGA3Tg3/dGvtKq9+XYGngVNwTiI+B2601m7z2V8i8DgwFYgEFgA3WWuX+/SLAB7CuVg4AVgG3G6t/canX5A7vqtwvqFYCzxorX23Nq9PCb6IiFhryTqYVSHh974mYPOBzeQU5zCl9xRuGHEDyVHJgQ5ZRFqhRkvwGyGQC4ARwI84yX034A6gKzDYWrvVGBMF/AIUAvfgfGPwMBAFDLHW5rn3ZYBvgR7ArcA+4E5gIDDMWrvD67hvAJPc/TYB1wATgTHW2mVe/R4B/oRTdrQY5xuLK4DTrbXzanp9SvBFRKQmuUW5/Gv5v3ht1WuEBoVy5ZAr+e2A32pKTxGpkxaT4PtjjDkMWAP8yVr7pDHmBuAp4DBr7QZ3n57AeuA2a+1T7rYpwBxgrLX2K3dbPLAZeN1ae727bSjOiP3vrbWvuNtCgJXAWmvtZHdbB2A78Ki11jPlpzHmCyDZWut/2gYvSvBFRKS2tmVv44lFT/DV9q/oGtuVP436Eyd1PUm1+iJSK1Ul+C1l4t+97vuyGv7JwMKy5B7AWrsZ+B6Y4vW8ycDOsuTe3e8AMNdPv2LgTa9+JcD/gPHGmHB383ggDGchL2+vA4PdJxkiIiKNoltcN/5v7P/xwikvEBYUxg1f3cCVn13Jhn0ban6yiEgVApbgG2OCjTFhxpi+wAs4K+P+1715ILDCz9NW4tTuU4t+3YwxMV79Nltr8/30CwP6ePUrBHz/ZS27xmAAIiIijezoTkfz9uS3uePIO1i5dyVnzz2bP//450qz9YiI1EYgR/B/xEmm1wFDcMpsMt3bknDq6X1l4VyYSy364dW3pn5JXvf7beW6Jd9+FRhjrjTGLDLGLNq9e7e/LiIiItUKDQrlwsMv5MNpH3J2v7N5c+2bTJo9if+t+R8lrkN+kjoRqYNAJvi/BUYDvwGygc+MMT0CGE+9WWtftNaOstaOSk7WTAgiIlJ/iRGJ3DP6Ht4+420OSzyMR358hHPmnsOP6T8GOjQRaSVqneAbY/oZY470ehxpjPmLMWauMebauh7YWrvaWvujtfa/wDggBmc2HXBG2xP9PM13JL66fnj1ralflle/BFP56ibffiIiIk2qX2I/Xjr1Jf524t8oKCng8k8v58avbmR7zvZAhyYiLVxdRvCfBc72evwIcAvQCXjaGHNNfYOw1u7HqXsvq4VfiVMP72sAsMrrcXX9tllrc7369XRPv+nbr4jymvuVQDjQ208/fI4tIiLSpIwxjOs+jvemvscNI27gh50/MGXOFJ5Z8gz5xb6XlYmIOOqS4A/FmcWmbDGoi3EWihqJM0f9lfUNwhjTEegPbHQ3vQ+MNsb08urTAzjGvQ2vfp2NMSd49YsDzvDpNxcIBc7x6hcCnAd8aq0tdDd/jDPbzoU+IV4ErHDP5CMiItKswoPDuXzw5cydOpcJPSbw0vKXOH326by/8X1c1hXo8ESkhan1PPjGmIPAydba74wxI4GfgB7W2u3uBPsDa21sLfYzG1gC/IpTe98PuAln1dgjrbXrjDHROAtdFVC+0NVDQCzOQle57n0FAd/hLJLlvdDVEGCotXa713H/hzMN5q048+RfDZwOHG2tXeLV71HgRuAud5zn4axqO9la+0FNr0/z4IuISFP7ZfcvPPbTYyzfs5wh7Ydw+5G3MyS5xqVaRKSNaYx58HdRXkJzKrDRK4GOoXwO+5osBKYCM4EPgZuBr3FWnl0H4F6pdizODDuvAW/gJOVjvcpusNa6cJL0z4DngNlAKXCSd3Lv9jvgFZxvGz7EOSmY4J3cu93t7nMD8AnOtwbn1ia5FxERaQ5Dk4fy+mmv88ixj7AzbycXzruQu7+7m8z8zJqfLCJtXl1G8P+OU4P/BnAp8IK19m73tjuAc9zlOoc0jeCLiEhzyivO46XlLzFz5UxCgkK4csiV/HbAbwkPDq/5ySLSqjXGCP4dwAc4ZS7v41xkW2Yy8GmDIhQREZE6iw6N5oYRN/De1Pc4utPRPLPkGabMmcIXW7+gtoN4ItK21HoEX2pHI/giIhJIC9MX8thPj7Fh/waOSjmK2468jX6J/QIdlog0gcYYwRcREZEWbnTqaN4+423uPupu1uxbwzlzz+GRhY+w/+D+QIcmIs2kTiP4xphLgAuAbkCEz2ZrrfWdP/6QoxF8ERFpKQ4UHuAfy/7BW2vfIjo0mmuGXcO5h51LSFBIoEMTkUZQ1Qh+XS6yvRd4AFjhvhX69rHW/q6BcbZ6SvBFRKSlWb9vPY/9/Bg/pv9In4Q+3HbEbYzpNCbQYYlIAzVGgr8FmG2tvamRY2tTlOCLiEhLZK3lq+1f8fjPj7MjdwcndT2JW0fdSte4roEOTUTqqTFq8NvhrAgrIiIirYwxhrHdxvLe1Pe4ccSN/Jj+I1Pem8LTi58mrzgv0OGJSCOqS4L/NTC0qQIRERGRphcWHMZlgy/jg2kfcFrP03h5xcucPvt05myYg8u6Ah2eiDSCuiT4NwK/M8ZcbIxpb4wJ8r01UYwiIiLSyJKjknn42If5z2n/oVNMJ+79/l4u/PBClmUuC3RoItJAdanBLzutr+oJ1lp7yF+Wrxp8ERFpbVzWxYebPuRvi/9GZkEmp/c6nRtH3EjH6I6BDk1EqlFVDX5dEvIHqTq5FxERkVYqyARxRu8zGNdtHC8tf4mZK2fyxbYvuHzw5Vw84GIiQnxnxhaRlkwr2TYyjeCLiEhrtyNnB08tforPtn5G55jO3DLqFk7udjLGmECHJiJeGnUlW2NMjDGmqzEmpuGhiYiISEvSJbYLT534FP8+9d9EhUZx8/ybuezTy1ibtTbQoYlILdQpwTfGjDfGLAL2A1uA/caYn4wxpzRBbCIiIhJAR6YeyVunv8W9o+9l/b71nPvBuTy04CH2HdwX6NBEpBq1TvCNMeOBD4EY4CHgj8DDQCwwT0m+iIhI2xMSFMK5h53LB9M+4IL+F/Du+neZNHsSb6x+g2JXcaDDExE/6jKLzgJgH3C6teUT5bqnx/wASLDWHt0kUbYiqsEXEZG2bOP+jTz202MsSF9Ar/he3HbEbRzT+ZhAhyVySGqMGvyhwD+8k3sA9+PngGENilBERERavN4JvXnhlBf4+9i/U+Iq4Q+f/4HrvriOrdlbAx2aiLjVJcEvBOKq2Bbr3i4iIiJtnDGGE7ueyOwps7l55M38vOtnpr43lacWPUVuUW6gwxM55NUlwZ8PPGSM6endaIzpBkwHvmq8sERERKSlCwsO43eDfscH0z7gjF5nMGPlDCbNnsTs9bNxVfzCX0SaUV1q8PsB3wPxwEIgHUgBRuPMqnOstXZ904TZeqgGX0REDlUr96zk0Z8eZdnuZQxoN4A7jryD4R2GBzoskTarwTX41tp1wBDg/4BwYAQQATwDDFNyLyIicmgb2H4gr058lceOe4w9BXu4+KOLue2b28jIywh0aCKHFK1k28g0gi8iIgL5xfm8vOJlZqycQZAJ4pKBl3BMp2PoldCLuLCqLukTkbqoagRfCX4jU4IvInViLRzcD3l7nFv+XmjfF5IPC3RkIo0iLTeNpxY9xadbP/W0JUcm0yuhF73je9Mrvpfzc0JvkiKSAhipSOtTrwTfGPMl8Edr7Rr3z9Wx1tpxDYyz1VOCL3KIc7nKE/b8PRXv/f2cvxdcJZX303kkDLsQBp0FkQnN/SpEGt3O3J2s37eeTQc2sXH/Rs99fkm+p09ieCI943vSO6E3vRPcyX98LzpEdcAYE8DoRVqmqhL8kJqe5/VzEFDdcL/+8kSk7fFO2PN2+0/WK7TtBVvqf1/hcRDdHqLaQ2J36DzCeRyd7LRFt4PIRNi6AJa9AR/eDB/fCYefDsN+A71OgqDgZn35Io2lU0wnOsV04oSuJ3jarLXsyt/Fpv2b2HhgIxv3b2Tzgc18suUTsouyPf1iQmPoleAk+73je3tG/FOjUwkydZkQUOTQoBKdRqYRfJEWzuWCgn3upHy3V4K+t2ICn1/2OKuahD3enaC3L0/QPcl6e4hyPy77OSS89nFaC+nLYOkbsPxt5yQjthMMPd8Z2W/fpzHeDZEWyVrL3oN72bR/U4UR/00HNrGnYI+nX2RIJD3ienhG/HvG96R3fG+6xHYhJKimMUyRxuGyroCdaKoGv5kowRdpZq5SJ2H3HWEvS9A9P7u3F2RBVfNzR8RXk6D7JPBR7SAkrHleY0khrJ0Hy/4DGz534u96lJPoD5wGEbpgUQ4dBwoPeJL+shH/jQc2VpipJzQolO5x3Z3E3z3i3yu+F93juhMW3Ex/t9KmFJcWsz13O9uzt7M1eyvbcraxLXsb23K2ER0azbuT3w1IXPWtwT++Lgex1n5Tj9jaFCX4Io3E5YLMVbBnXcUEvWy0vWwEvmBf1Ql7ZGI1ybqfEfbg0OZ9jfWRnQ6/vumU8OxZByGRMGCyU8LT43gIUrmCHJpyi3LZfGCzk/wf2OiU/ezfSFpuGtZdYRxsguka27W8vt99oW+P+B5EhkQG+BVIoBWXFrMjdwfbc5wkfmv2Vs/P6XnpFRZviw2LpXtsd7rFdaNvYl8uH3x5QGKub4Lvovq6e09XnItsD/niUCX4Ig2wfxts+ho2zYfNXzsJvLfIpFqWw7SHqKTWkbDXl7WQtthJ9Je/C4UHIL4rDL0Ahl0ASb0CHaFIi3Cw5CBbsrdUGvHflr2NUnf5ncHQKaaTZ8S/7ELfXvG9iAmLCfArkMZU7ComLSfNMwLvPRq/M29nxSQ+NJZucd3oFteN7nHd6Rbr/jm2O/Hh8S3iwu/6JvgnVLnRD2vt1/WIrU1Rgi9SB/lZsOVbJ6HfNB+yNjntMSnQ60TodQKkDnUS98gkCFZNrV/FBbDmQyfZ3/gVYKH7Mc6o/oCpEK4ERcRXcWkxW7O3Vhjx33RgE5sPbKbYVezp1zGqo3Nxb0LvClN7JkQkBC54qVaxq5iduTsrjMCXJfPpeemeEztwLuAuS9rLkvlusU5CnxCe0CKS+OqoBr+ZKMEXqUbxQdi+sDyh37kMsBAWCz2OdSf1JzpzwLfwf1RbrANp8Mt/nXr9rI0QGg0DpsDwC6Hb0SrhEalBiauEtNy08gt73TP8bD6wmYKSAk+/pIikClN5ll3o2y6iXYtPCtuCYlcx6bnpFUbgt+Y4ifzO3J1+k3jPCLzXaHxieGKr/ryU4DcTJfgiXlylkPFreUK/bSGUHISgEOhyZHlC33lE2y6nCQRrYfuPzqj+itlQlAMJ3Z0Lc4ee70zTKSK15rIuMvIyKszhX3YCkFOc4+kXGxZL73gn2e8W1424sDiiQ6M9t5jQGKJCo4gJjSEmNIZQ/dtXpRJXCTtzd7ItZ5tnFN5TTpO7kxJbvoZIdGi0Z+S9a2xXusd19/ycFJHUqpP46jRkoava0kJXKMGXQ5y1TpnN5rI6+m+ci2ABOgwsT+i7H62ykeZUlAerP4BlrzufCUDP451k//AzICw6sPGJtGLWWnYX7K404r9p/yb2Fe6r8fmhQaEVkn5/JwKetrDKbd59Q4Na38lCiauE9Nz08iTea3aatJy0Ckl8VEiUM/ruZzS+LSfx1alvgj+f2l1kC4C19qR6RdeGKMGXQ07u7vKEftPXcGCb0x7XpTyh73k8xHYMYJDisX8bLPuvM7K/f6tTHjVwqpPsdxut0iiRRpRblEtucS75xfnkFueSV5znufk+Lmvz19e7NKg64cHhFZJ+75OAKk8afNvCookOiSa4ERfVK3WVsjNvZ4UR+LL6+B25OyhxVUzivevgy0bju8V1U/mTHyrRaSZK8KXNK8pzVlrd9JWT0O9a7rRHxDuJfK8TnRVXk3opWWzJXC7Y9oNTq79yDhTnOZ/ZsN84M/HEdwl0hCLiVuIqIb8k30n+qzlpqOrEwbu9sLSwVseMDImsdIJQ3YlD2c8u6yq/sNWdzPsm8ZEhkRWTd6/ReCXxdaMEv5kowZc2p7QEdi4tr6Pf/iO4iiE4zBnxLRulTx0GjTjiI82oMBdWveck+1u/A4zzmQ6/CPpPglDNDy7SVhS7iskvzq/0jYHn56Jc8kryyCvK89xXddJQ5Cqq8jiRIZGexL1sNL7s5/aR7ZXEN5KGLHS1xFqbW5tFr7TQlRJ8aQOshT3ryxP6Ld9CYTZgIHVIeULfdTSERQU0VGkCWZvgl/85ZTwHtkF4PAw60ynh6TJK38qIiEdxaXGlbw0slq6xXUmOTFYS3wwastDVaGvtTzUseqWFrtyU4EurlJNRvsDUpvmQs9NpT+xRntD3ON5ZXEoODS6Xc3K37A1Y9T6UFED7fk4Jz5DzIS410BGKiBzyGrLQ1WL3CH6Ni15poSsl+NJKHMyGrd+XJ/S71zjtUe3K6+h7ngBJPQMYpLQYB7Nh5WynhGf7QjBB0HucM7d+v4kQGhHoCEVEDkmqwW8mSvClRSopgrRF5Qn9jkVgSyEk0pmysmyUvuMgLYQk1duzAX75j1PGk50GEQkw+GynhKfTcJXwiIg0IyX4zUQJvrQI1kLmKq86+u+dWVJMEHQa4VVHfySEhAc2VmmdXKXO79ay/8DquVBaCB0GOCU8g8/VtKgiIs2gviU6r9bhGNZae0l9gmtLlOBLwOzfXnE++rxMp71dX686+mMhMiFwMUrbVLAfVs6CpW843xSZYOh7qpPs95sAIWGBjlBEpE1qyEW2ucAenAtpq2Ottb0aFGUboARfmk3BPtjyXfko/d4NTnt0h/KEvtcJms9cmtfutc6Fub+8CbkZEJkEQ851SnhShwQ6OhGRNqW+Cf4moDswH3gVeMdam9dUQbYFSvClyZQUwraF5Ql9+jKwLgiNdkbmy5L6DoerDloCr7QENn7pJPtr50FpEXQc7FyYO/gciG4f6AhFRFq9etfgG2OOAy4GzgZCgNk4yf4XVgX8lSjBl0aVnwXrP3USpA1fQFEuBIVAlyPKE/rOIyE4NNCRilQtPwtWvOsk+zuXOr/D/SY4o/p9T9Hvr4hIPTX4IltjTDgwDfgtcCqwC3gdeMVau7YRY23VlOBLg+3d6CT0az+CbQucUfqYFDhsIvQb74zWh8cGOkqR+tm10rkw99c3IW83RCfDkPOcUf3Uofr2SUSkDhp1Fh1jTEfgJuAWYK619syGh9g2KMGXOnOVwo6fy5P6Peuc9o6DnaT+sImQOkzTV0rbUloMGz6Hpa/Duo/BVQJJvWDgmTBwGnQcqGRfRKQGVSX4IXXcSRgwFWcUfzzOKP7HjRGgyCGlKA82fuUk9Os+hvw9TtlCj+PgiCvgsAmQ0C3QUYo0neDQ8hPY/CxY/b6zmNZ3T8G3Tzir5g6c5iT8HfoHOloRkValViP4xpjjcZL6c6hYh/+56vAr0gi+VCk73Unm137kXCRbWggR8c50godNhD4nO49FDmW5u2H1e7BitrPaMtaZX78s2W/fJ9ARioi0GPWdRedh4EKgK85MOq+hmXSqpQRfPKx16o3XfuSU3+xc4rQndIf+k5ykvtsYXWAoUpWcDFj1njOyv22B05YyuDzZT+oZ2PhERAKsIfPgZwNzgG01HMNaa+9vSJBtgRL8Q1xpsTPqWJbU73f/2XQ5wl2OcBok91dtsUhdHUiDVXOcZH/Hz05bp+HuZH+aStpE5JDUkAS/tqy1Nrg+wbUlSvAPQQX7nYsF186D9Z9D4QEIiYBeJ7lnvpkAsR0DHaVI27F/G6yc46yeu3Op09blCCfRHzAV4jsHMjoRkWbTqLPoSNWU4B8i9m0pH6Xf+oMzA0h0spPMH3aaMz99WFSgoxRp+7I2lSf7Gcudtq6jYdCZMGAKxKYENDwRkaakBL+ZKMFvo1wup4a+bCrLzFVOe/Lh5aU3nUdqKkuRQNqzwSnhWTnL/TdqoPsxMGgaHD4FYpIDHaGISKNSgt9MlOC3IcUFsOlrJ6lf9zHk7gITDN2PdhL6wyY483aLSMuTuaY82d+zDkyQMw3toDPh8MkQlRToCEVEGqy+NfjLgAeAObWZDtMY0wW4Ddhhrf1r/cNtvZTgt3K5mbDuE2eUfuOXUFIAYbHQ92Qnqe9zshIDkdbEWmc0f8UsJ9nP2uScqPc60Un2+0+CyMRARykiUi/1TfBvBu4CCoG3gG+BX4Dd7rZEoBdwJHAGcALwBXCdtXZdI7+GVkEJfitjLexeW156s+NnwEJ81/JFeLofCyFhgY5URBrKWsj41Z3sz4b9WyEoFHqPdZL9wyZqLQoRaVXqXaJjjIkHLgcuA/oDvk8wOMn+e8Dz1tqvGyXiVkoJfitQWgLbF8KaeU5iv2+z0546zBml738adBykqSxF2jJrnetqVs52LtI9sB2Cw51v6Qad6VwwHx4T6ChFRKrVKDX4xphuwGigExAB7AXWAD9ZawsbKdZWTQl+C3UwGzZ+4YzSr/sEDu6H4DDoeUL5VJaaWk/k0ORyQdoid7I/G3LSnalu+57qJPt9T4Ww6EBHKSJSiS6ybSYBSfC3/wRLXoWwGGfEyXMfW/640rYYCGrjyxbs3+5cHLt2Hmz+FlzFEJnknspyIvQ+CcJjAx2liLQkLpfzDV/ZyH5eJoRGOf9uDDrTGeEPjQx0lCIiQNUJfkgggpFGlr0TNnwBRblQmEPlKqoqhEY5o1KVTgjK2mIrnhBUeQIR3TJOGKyF9F/c89N/WD4ndrs+MPpqp/ym65GBj1NEWq6gIGemrO5Hw4RHnZWpV86GVe85F+mGxTj/lgw606ndDwkPdMQiIpVoBL+RBbxEx1oozofC3PKEvyjP6+fc8m3eP/vt726r0wlDHU4IqjuBCIup3ZzyJYXO6HzZRbI5O53p8LoeVT4/ffu+DXpLRUQoLYEt3zpJ/uq5ULAPwuOdWXgGnemU++lifBFpZirRaSYBT/Abm8vlnDAU5TqJv+9JQrUnEH76F+XW/tih0dWfEBRkwcavnH2GRkOfsU5C3/dUiG7fdO+JiBzaSoudNTJWzoLVH0DhAYhIgMPPgIHTnGQ/WF+Qi0jTU4LfTNpcgt/YvE8YCnOhKKf6E4JK3yh49Q8Ohz7jnBG0HsdBaESgX52IHGpKCp2BhpWznJm5inIgqp2zmNbAadDjWJUFikiTaREJvjHmbOACYBTQAdgGzAL+bK3N8eqXCDwOTAUigQXATdba5T77iwAeAi4CEoBlwO3W2m98+gUBtwNXASnAWuBBa+27fmK8ArgF6AlsAZ621v6ztq9RCb6IyCGq+CBs+NxJ9td+DMV5EN0BBkxxkv1uY2pXeigiUkuNmuAbY2KAdsBOa21xHZ63ECepfw/YAQwHpuNMtXm0tdZljDE4C2r1AG4F9gF3AgOBYdbaHV77ewOY5O63CbgGmAiMsdYu8+r3CPAn4G5gMXA+cAVwurV2nle/K4AXgL8AnwPjcBb6usZa+3xtXqMSfBERoSgf1n/qJPvrPnVWxY5NhQFTnWS/yxFK9kWkwRprHvzTgQeBoThXXh5prV1ijHkJ+NJa+58anp9srd3t03YxMBMYZ6390hgzBZgDjLXWfuXuEw9sBl631l7vbhuKM2L/e2vtK+62EGAlsNZaO9nd1gHYDjxqrb3f67hfAMnW2iFez90JfGStvcSr38vAZCC1NiczSvBFRKSCwlxnyt6Vs2H9Z1BaCHFdYPhFMPJSiEsNdIQi0kpVleDXevjAGDMVZ+R9D065i/dzNwOX+HlaBb7JvdvP7vuyVYYm43wz8JXX8w4Ac4EpXs+bDBQDb3r1KwH+B4w3xpTNXTYeCANe9znu68BgY0xP9+MxQLKffq/hfFtxbE2vT0REpJLwGBh8Npz/Bty6Ac78F3ToD18/Bn8bBG9dAlu+c2ZBExFpBHX5fvB+4BVr7anA33y2rQAG1TOGE9z3q933A93787US6OYuDyrrt9lam++nXxjQx6tfIbDBTz+AAV798HNs334iIiL1ExEHQ86Fi96F65c4a3Rsmg8zJsHzR8PPLzkj/iIiDVCXBP9wykfLfYcZ9uGMcteJMaYzTsnP59basrqWJPf+fGW57xNr2S/J636/rVyL5K8ffvbp268SY8yVxphFxphFu3f7+5JCRETER1IvOPVhuGUNTPkHBIfCh7fAk/1h3q2we12gIxSRVqouCX42UNXk4j2AOmW27pH494AS4Hd1eW5LY6190Vo7ylo7Kjk5OdDhiIhIaxIa6dTjX/k1XPY59D8NFs+AfxwBMyc7C2uVlgQ6ShFpReqS4H8G3GmMSfBqs+5a92uBj2q7I2NMJE5NfS9gvPfMODgj6Il+nuY7wl5Tvyyvfgnu2Xlq6oefffr2ExERaXzGQNcj4MwX4aZVMO4+yNoEb14EzwyFbx6H3MxARykirUBdEvy7KZ9D/iWcMp07cGay6YIz3WWNjDGhwDs4c+Gf5ju3PU7N+8BKT3Rq4LdZa3O9+vU0xkT56VdEec39SiAc6O2nH8Aqr374ObZvPxERkaYVkwzH3QI3/ALn/wfa94UvH4anBsC7l8O2H3VRrohUqdYJvrV2CzAC+AA4BSgFjgcWAkdZa3fWtA/3glNvAGOBqdbahX66vQ90Nsac4PW8OOAM97Yyc4FQ4ByvfiHAecCn1tpCd/PHOLPtXOhznIuAFdbaze7HC3BmCPLXLwv4vqbXJyIi0qiCgp3Vui+eA9cugiMud+bVf/lUeOE4WDzTmXNfRMRLc69k+zzwB+ARnBMFbzustTvcJwHfAV2puNDVEGCotXa71/7+hzMN5q04U3VeDZyOs2jWEq9+jwI34ixatQTnJOAqYLK19gOvfn8AngP+jLPQ1VjgHuA6a+0/avMaNQ++iIg0qaI8+PUt+OlfkLkSIuJh2EVwxGXQzvfLahFpyxp1JdsGBLEF6F7F5gestdPd/ZKAJ4CpQATO6PrN1tpffPYXiXOy8BsgAfgFuN1aO9+nXzDOScIVlJcZPWitfcdPjFcBt7jj3AY8ba19rravUQm+iIg0C2th2wIn0V/9PrhKoPc4OPIK6HuqM/ovIm1agxN894qu1bHW2svqE1xbogRfRESaXU6GU66z+BXISYeEbjDqMhj+W4iu8yzWItJKNEaCv4XK898nAbHAfpy55ns1LMzWTwm+iIgETGkxrPnQWTBry7cQHA6DzoIjL4fOIwMdnYg0sqoS/JDa7sBa26OKHR8P/JPKF6eKiIhIcwoOhYFTnVvmaifR/+V/8Mt/oNMIp3xn4JkQGhHoSEWkCTVKDb4x5grgEmvtsQ0PqXXTCL6IiLQoB7Ph1zedWv09ayEyCUb81inhSazqsjgRaQ2qGsGvyzz41dkEDG+kfYmIiEhjiYhzRu6v+REumQs9joUfnnUWz/rPebD+c3C5Ah2liDSiWpfoVMU99/ylwI4auoqIiEigGAM9j3duB9Jg8Qzntu4sSOrlvij3Qoj0t0i8iLQmdbnI9ks/zWFAP6Ad8Adr7b8aMbZWSSU6IiLSapQUOVNs/vQv2L4QQiJh8NnOiH/q0EBHJyI1aPBFtjjlPL5nAznALOB/vnPPi4iISAsXEuYk9IPPhozlTqK//G1Y+hp0ORKOvBIGTIaQ8EBHKiJ10KwLXR0KNIIvIiKtWsF+WPYfZwaerI0QnQwjLoFRv4P4LoGOTkS8tIiVbA8FSvBFRKRNcLlg01dOor/uY6ftsNOc8p2eJzg1/SISUI1RooMxJg44DegG+E6ia621D9U/RBEREWkxgoKgzzjntm+rs0ru4pmw5gNo3w+OuByGXuDM0iMiLUpdLrI9BpgLJFTRxVprgxsprlZLI/giItJmFR+ElbPh539B2mIIjYah58ERV0DHAYGOTuSQ0+ASHWPMz0AwcAWw3Fpb1Lghtg1K8EVE5JCQtsQp31n+DpQWQvdjnPKd/qc7K+q2NS4XFOVCYQ4UZvvce90OZpe3lxyEkAgIi4bQKAiLck6KwqLcj/21+2xvi++lNJrGSPBzgXOttfMaO7i2RAm+iIgcUvKznFl3fv437N8Ksakw8lLnFpsS6OjAVVo5Ca8ySc/2k6y7fy7Kqd3xwmIgPNa5hUQ4SX5RPhTnOfelhXWLPyi0licGXu2ebTWcRIRE6FqK2rAWSovBVVL5VlrsvIcJ3QISWmMk+KuAu621sxs7uLZECb6IiBySXKWw4XNnqs0Nn0NQMBx+hlO+0/3ouieSpSVOUl1phPxANQm7nxH1otzaHS8s1rmeoCw5r3CL87n3/dn93LAY53XX9LqK851bUZ773usEoMr2mra7b3VhgnxOCOpwElHV9pBwr+S3LBEuS45Lq0+UXaW161/qtd1V7Od4dd2313Z/fW0NKz0n9oQbltXtvW8kjXGR7QPAHcaYL6y12Y0XmoiIiLR6QcHQb7xzy9rkjOgvfd2p2e8wAIZfBKGRVZez+N6K82pxUOMn0U5wRlP9JeGeNp/2sBjnouLmEBwCwXFNc3GyywUlBdWcEPicGBRX0zc/q3K7LW38mOvKBEFQiPPNRlCI83sX7PWzpz3Eea/Lfg4KdU4+gqJr0T/UvS2kdvuOiA/0u1JJXUbwXwOOA2KBBUCWTxdrrb2kccNrfTSCLyIi4laUDyvedS7KTf+lvN0EuZPr+CpGzGOdpKmmkfTQqOZLzA911kJpUc3fOpQUeiXFXrdgr6S5yiTct7+fxFqfdwWNMYJ/LM5KttnAQD/bNaG+iIiIlAuLghG/dUbvD+xwkrayxFy1362LMc4IeEg4kBToaKQGtU7wrbU9mzIQERERaaOMgYSugY5C5JCh7zlERERERNqQOiX4xphoY8z1xph3jDFfGWP6utvPN8b0b5oQRURERESktmpdomOM6QrMB7oAa4BBOBfcApwEnAxc3sjxiYiIiIhIHdRlBP9JoBDoB4wEvK+O+Rpnhh0REREREQmgusyicwpwpbV2qzHGdxWHNKBz44UlIiIiIiL1UZcR/DCgqnWa44GShocjIiIiIiINUZcE/1fgrCq2TQQWNzwcERERERFpiLqU6DwOvGOchSn+424bYIyZAlwGTG7k2EREREREpI7qstDVLGPMH4FHgd+7m1/FKdu51lr7cRPEJyIiIiIidVCXEXystf80xrwGjAE6AHuBH6y1VdXmi4iIiIhIM6rLPPjtrLV7rbV5wOdNGJOIiIiIiNRTXS6yTTfGzDHGnGWMCWuyiEREREREpN7qkuDfA/QC3gYyjDH/NMYc0zRhiYiIiIhIfdQ6wbfW/tVaOwQYAbwCnAF8Y4zZaIyZbozp01RBioiIiIhI7dRlBB8Aa+0ya+0tQFec+e+/B24B1jRybCIiIiIiUkd1TvDLWGtdQB5QABQDprGCEhERERGR+qnTNJkAxpi+wG+BC4EeQBrwAvBao0YmIiIiIiJ1VpdpMq8FLgKOwBm5fxe4AvjKWmubJjwREREREamLuozgP40z//1vgdnW2oKmCUlEREREROqrLgl+F2vtriaLREREREREGqzWCX5Zcm+MaQ+MBtoBc621WcaYCKDIfeGtiIiIiIgESF1q8A3wV+A6IAywOPX4WcB7wHfAQ00QY5uTnZ1NZmYmxcXFgQ5FRAIsJCSEiIgIkpOTiYiICHQ4IiLSBtSlROdO4FrgQeAz4EevbXNxavOV4NcgOzubXbt20blzZyIjI3HOm0TkUGStpaSkhNzcXLZt20bHjh2Jj48PdFgiItLK1SXBvxx40Fr7F2NMsM+2DUDvxgur7crMzKRz585ERUUFOhQRCTBjDKGhoSQmJhIeHk5GRoYSfBERabC6LHTVGVhYxbYiILrh4bR9xcXFREZGBjoMEWlhIiMjKSwsDHQYIiLSBtQlwU8DBlWxbSiwueHhHBpUliMivvTvgoiINJa6JPhvA/cZY47xarPGmH7ALcD/GjUyERERERGps7ok+NOBNcA3wHp329vAcpwa/EcbNTIREREREamzWif47pVrTwQuBX7AWdX2Z+BK4GRrbVETxCcSEDNmzMAY47mFhYXRu3dv7rrrLg4ePNhkxzXGMH369Cbbv4iIiLR9dZlFB2ttKfCa++ZhjAk3xlxjrX2mMYMTCbS3336bLl26kJOTw+zZs/nLX/5CTk4Of//735vkeAsWLKBLly5Nsm8RERE5NNRloav2wF5rrfVqiwT+iFOD3xFQgi9tyrBhw+jTpw8Ap5xyCuvXr+fll1/mmWeeISioLhVutTN69OhG36eIiIgcWqrNUNwj888YY3KAXcBeY8zV7m0XAZuAx4HtwISmDlYk0EaMGEF+fj579uwBID8/n9tvv52ePXsSFhZGz549eeSRR3C5XBWet2TJEo477jgiIyPp2rUrf/7zn7n//vsrzZzir0Tn448/ZsyYMURGRhIfH8/UqVNZu3ZthT4nnngixx57LJ9//jkjRowgKiqKQYMGMXv27MZ/E0RERKRFq2kE/z7gOpx6+yVAT+AZY8wA4BpgHXCltXZuk0Yp0kJs2bKF+Ph42rVrR0lJCePHj2fVqlXce++9DB48mIULF/LQQw+RlZXFk08+CcCePXsYN24cnTp1YubMmYSFhfH000+zZcuWGo/38ccfM2nSJMaOHcubb75Jbm4u9913H8ceeyzLli2jc+fOnr4bN27khhtu4M4776R9+/Y8+eSTnHPOOaxZs8bzLYSIiIi0fTUl+OcBz1lrry1rMMb8HngJ+Aw4QxfXSltWWlpKSUmJpwb/3Xff5W9/+xvBwcG89tprfPfdd3z99dccf/zxAIwbNw6ABx54gNtvv50OHTrw1FNPkZ+fzyeffOKprx8/fjw9evSo8fj33HMPvXr14qOPPiIkxPlzHTNmDP369ePJJ5/kqaee8vTds2cP33zzDX379gWcbxtSU1N56623uOuuuxrzbREREZEWrKYi4q6A73f8s9z3Tym5l7auf//+hIaGkpSUxGWXXcZVV13Ftdc657sff/wx3bt35+ijj6akpMRzO/XUUykuLmbhQmfh54ULFzJ69OgKF89GRkYyadKkao+dl5fHkiVLOO+88zzJPUDPnj055phj+Prrryv079u3rye5B+jQoQMdOnRg27ZtDX4fREREpPWoaQQ/FMjxaSt7vLvxwxFpWWbPnk2XLl3YvXs3Tz31FM899xxHHXUUF198MZmZmWzdupXQ0FC/z927dy8A6enpDBpUeRHojh07Vnvsffv2Ya0lNTW10raUlBS2bt1aoS0pKalSv/Dw8Cad1lNERERantrMotPZGNPL63GwV/t+747W2k2NFZhISzBo0CBP/frYsWMZMmQIt956K2eddRbt2rWjZ8+evPXWW36fW1aCk5qaSmZmZqXtu3btqvbYiYmJGGPIyMiotC0jI8NvQi8iIiJSm3n+3sFZubbstsbdPsenfb2/J4u0FeHh4Tz++ONkZmby3HPPMWHCBLZv305MTAyjRo2qdGvfvj3gTH25YMECduzY4dlXQUEBH374YbXHi46OZuTIkbz99tuUlpZ62rdu3coPP/zAiSee2CSvU0RERFq3mkbwf9csUYi0EpMnT+aII47gySefZP369bzyyiuMGzeOW265haFDh1JUVMTGjRt5//33mTNnDlFRUdx88808//zzjB8/nvvvv5/w8HCeeuopwsPDK02T6euhhx5i0qRJnH766fzxj38kNzeX+++/n/j4eG655ZZmetUiIiLSmlSb4FtrZzZXICKtxcMPP8z48eN56aWX+OSTT3j00Ud58cUX2bx5M9HR0fTu3ZtJkyYRFhYGQPv27fniiy+4/vrrufjii2nXrh1/+MMf2LNnD6+++mq1x5owYQIffvghDzzwAOeeey5hYWGceOKJ/PWvf6VTp07N8XJFRESklTFeC9NKIxg1apRdtGhRldtXr17N4Ycf3owRSUtUWlrKiBEjPMm/COjfBxERqRtjzGJr7Sjf9tpcZCsiDXTvvffSp08funfvzt69e3nppZf49ddfmTdvXqBDExERkTZGCb5IMzDG8OCDD7Jz506MMQwZMoQ5c+YwceLEQIcmIiIibYwSfJFm8OCDD/Lggw8GOgwRERE5BNRmmkwREREREWklmj3BN8Z0Mcb83RizwBiTb4yxxpgefvpFGGMeN8akG2MK3P2P99MvyBhzpzFmizHmoDHmF2PMWVUc+wpjzBpjTKExZq0x5g9V9JtqjFnq3t9WY8w9xphgf31FRERERFqSQIzg9wHOBfYB31bT79/AFcB9wOlAOvCJMWaYT7+HgOnAs8BEYCHwtjHmNO9OxpgrgBeAd4EJwNvAc8aYq336jXf3+dm9v2eAe4A/1+1lioiIiIg0v0DU4H9jre0IYIy5HDjVt4MxZijwG+D31tpX3G1fAyuBB4HJ7rYOwJ+AR621T7if/pUxpg/wKDDP3S8EeAR4zVp7t1e/TsBDxpiXrLXF7vZHge+stVd69YsB7jHGPG2tzWi0d0JEREREpJE1+wi+tdZVi26TgWLgTa/nlQD/A8YbY8LdzeOBMOB1n+e/Dgw2xvR0Px4DJPvp9xrQDjgWwBjTFRhWRb9QnBF9EREREZEWq6VeZDsQ2GytzfdpX4mT0Pfx6lcIbPDTD2CAVz+AFfXpZ63dDOR79RMRERERaZFaaoKfhFOj7yvLa3vZ/X5beTlef/3ws8/a9itrS/LTjjHmSmPMImPMot27d/vrIiIiIiLSLFpqgt+qWGtftNaOstaOSk5ODnQ4AXPFFVdgjOGmm24KdCiVzJ8/H2MM8+fPD3QoIiIiIk2qpSb4+4BEP+1lI+hZXv0SjDGmFv3ws8/a9itry/LTLkBBQQFvvfUWAP/5z38oKSkJcEQVjRgxggULFjBixIhAhyIiIiLSpFpqgr8S6GmMifJpHwAUUV5zvxIIB3r76QewyqsflNfY16mfe57+KK9+4mPOnDlkZ2dz2mmnkZmZyccffxzokAAoLS2lpKSEuLg4Ro8eTVxcXKBDEhEREWlSLTXBn4sza805ZQ3uqS7PAz611ha6mz/GmW3nQp/nXwSscF8cC7AA2FNFvyzgewBr7Tbglyr6FQMf1f8ltW0zZ84kMTGRGTNmEBkZycyZMytsnz59OsYY1qxZw/jx44mOjqZbt2688sorALz22mv079+fmJgYTjrpJDZu3FjpGC+++CJDhw4lIiKC9u3bc9lll5GVVfFLFWMMd999N48++ig9e/YkLCyM5cuXV1miM3v2bI455hhiYmKIi4vjyCOP5P333/dsf/bZZxkzZgxJSUkkJCQwevRoPvzwwwr7KCkp4d5776V3796e2I499li+++67hrylIiIiIvUSiHnwMcac7f5xpPt+ojFmN7DbWvu1tXapMeZN4G/GmFBgM3A10BOv5Ntam2mMeQq40xiTAyzBOQkYi3uufHe/YmPMvTgLW6UBn7v7/B64zlpb5BXeXcAHxpgXgP8Cw3EWunpGc+D7t3PnTj7//HOuuOIKkpOTmTp1KrNmzWLfvn0kJlasdjrnnHO44oor+NOf/sRzzz3H73//e9avX8/8+fN59NFHKS4u5oYbbuA3v/kNP/74o+d5d9xxB08++STXX389jz/+OGlpadxzzz2sWLGCH374geDg8oWGZ8yYQa9evXjiiSeIjo6mU6dOHDhwoFLcf//737n++uuZOnUqM2fOJCYmhiVLlrBlyxZPny1btnD55ZfTo0cPSkpKmDt3LqeffjofffQREyZMAOCxxx7j6aef5pFHHmHYsGFkZ2ezaNGiSicfIiIiIs0hIAk+ziqy3p5z338NnOj++Xc4i1M9DCTgjKxPsNYu8Xnu3UAucAOQAqwFzrXWfuDdyVr7T2OMBW4BbgW2Addaa5/z6TfPfQJyP3ApsAtnFdtH6vE6a+WBuStZtTO7qXZfKwM6xXH/Gb4VTLXz+uuvU1paysUXXwzAJZdcwn//+1/efPNN/vCHP1Toe+utt3r6jRo1irlz5/LCCy+wefNmT/lMeno6N9xwA1u3bqV79+5s2bKFxx9/nPvvv5/77rvPs69+/fpx7LHHMnfuXKZOneppt9by6aefEhkZ6WlbvXp1hTiys7O56667mDZtGrNmzfK0jx8/vkK/J554wvOzy+Vi3LhxrFu3jueff96T4C9YsIBTTz2VG264wdP3jDPOqP0bKCIiItKIAlKiY601VdxO9OpTYK292VqbYq2NsNYeZa2d72dfpdbah6213a214dbaIdbad6o47gvW2n7ufn19k3uvfrOstUPd/bpZax+01pY21utva2bOnEnfvn0ZM2YMACeffDKdOnWqVKYDMHFi+VphiYmJdOjQoVJtfP/+/QHYvn07AJ999hkul4sLL7yQkpISz+2oo44iNjaWb775psIxJkyYUCG59+eHH34gNzeXK6+8stp+ixcv5vTTT6djx46EhIQQGhrKZ599xtq1az19jjjiCObNm8fdd9/Nd999R1FRUTV7FBEREWlagRrBFy/1HTlvCRYtWsSqVau4/fbb2b9/v6f9zDPP5Nlnn2XdunX069fP0+5bshMWFua3DeDgwYMAZGZmAtCnTx/82bt3b4XHqampNcZd9pwuXbpU2Wf79u2MGzeOAQMG8Pe//51u3boREhLCvffeW+EbgbvuuouIiAhef/11/vznPxMTE8PZZ5/N448/Tvv27WuMRURERKQxKcGXBikbpX/sscd47LHHKm1/9dVXefjhhxt0jHbt2gHw6aefVjoZ8N5epvKsqZWVJd5paWkMGjTIb5+PP/6YAwcO8NZbb1U4EcjPr7jAcmhoKLfffju33347GRkZfPDBB9x8883k5+fz5ptv1hiLiIiISGNSgi/1VlRUxH//+1+OOuooHn300Urbb7rpJl577TUeeuihBh3nlFNOISgoiG3btnHKKac0aF9ljj76aGJiYnjxxRcr1d2XKUvkQ0NDPW3r1q3j+++/r3LkPyUlhcsvv5x58+axYsWKRolVREREpC6U4Eu9ffjhh+zdu5cnn3ySE088sdL2q666iquvvrrBq8f27t2b22+/nWuvvZa1a9dywgknEBERwfbt2/nss8+4/PLLOemkk+q0z9jYWP7yl79w3XXXcdZZZ3HhhRcSGxvLsmXLiIiI4LrrruPkk08mJCSEiy++mFtuuYX09HTuv/9+unXrhsvl8uxrypQpDB06lBEjRpCYmMjSpUv5+OOPueqqqxr0ukVERETqo6XOgy+twMyZM4mNjeWcc87xu/2CCy7wOyd+ffz5z3/mxRdf5JtvvuHcc89lypQpPPbYYyQmJtK3b9967fPaa6/l7bffZseOHVx44YWcddZZvPPOO/Ts2ROAgQMH8sYbb7B161YmT57MX//6Vx599FGOP/74Cvs5/vjj+fTTT7nsssuYMGECzz//PLfddht//etfG/y6RUREROrKWGsDHUObMmrUKLto0aIqt69evZrDDz+8GSMSkdZC/z6IiEhdGGMWW2tH+bZrBF9EREREpA1Rgi8iIiIi0oYowRcRERERaUOU4IuIiIiItCFK8EVERERE2hAl+CIiIiIibYgSfBERERGRNkQJvoiIiIhIG6IEX0RERESkDVGCLyIiIiLShijBl0ZzxRVXYIzhpptuqtfz58+fjzGG+fPnN0o8l156KcYYzy05OZnjjz+ejz/+uFH2781f7C6XixtvvJHU1FSCgoKYOnUqW7ZswRjDjBkzGj0GEREREVCCL42koKCAt956C4D//Oc/lJSU1HkfI0aMYMGCBYwYMaLR4kpOTmbBggUsWLCAf/3rX1hrOe200/jiiy8a7RjgP/Z33nmHZ555hltvvZXvv/+ev/71r6SmprJgwQImTZrUqMcXERERKRMS6ACkbZgzZw7Z2dmcdtppzJs3j48//pjTTz+9TvuIi4tj9OjRjRpXWFhYhX2OHTuWbt268cwzzzBu3LhGO46/2FevXg3AjTfeSFBQ+bl0Y79GEREREW8awZdGMXPmTBITE5kxYwaRkZHMnDmzUp9169Yxbdo0OnToQEREBN26deOcc87xjPb7K3P59NNPOe2000hNTSUqKopBgwbx5JNPUlpaWq844+Li6NevHxs2bADgf//7H2PHjiU5OZmYmBiGDx/uN/aSkhIee+wxBgwYQEREBMnJyUyYMIE1a9b4jb1Hjx5Mnz4dgODgYE9ZTlUlOl9//TWnnHIK8fHxREdHM3ToUP7973/X6zWKiIjIoU0j+NJgO3fu5PPPP+eKK64gOTmZqVOnMmvWLPbt20diYqKn36RJk0hMTOT555+nffv2pKWlMW/ePFwuV5X73rRpE+PGjeO6664jIiKCRYsWMX36dHbv3s2jjz5a51hLSkrYvn07PXv29Oz/7LPP5o477iAoKIhvvvmGyy+/nIKCAv7whz94nnf++eczZ84cbrzxRk4++WQOHjzIN998Q3p6Ov379690nNmzZ/N///d/zJgxgwULFgDQu3dv8vLyKvV97733OOusszjmmGN44YUXaN++PStXrmTr1q11fn0iIiIiSvBbgo/ugIzlgY0hZTBMrHvCDPD6669TWlrKxRdfDMAll1zCf//7X958801Pkrxnzx42bNjAe++9x+TJkz3P/c1vflPtvr2TbGstxx13HEVFRTzxxBP8+c9/rlD6UpWybwgyMjJ46KGHyMjI4Pbbbwfgrrvu8vRzuVyceOKJpKen8/zzz3uO/eWXX/Luu+/yzDPPcP3113v6T506tcpjDh8+nM6dOwMVS3J8E3xrLTfccAPDhg3jq6++8ryek08+ucbXJSIiIuKPEnxpsJkzZ9K3b1/GjBkDOMlpp06dmDlzpidJbteuHb169eKOO+5g165dnHjiifTt27fGfaenpzN9+nQ+/vhjdu7cWeHi3czMTFJSUqp9flpaGqGhoZ7HMTExPPjgg55Eff369dx333188803ZGRkeL5NCA8P9zzn008/xRjDFVdcUct3pPbWrl3L1q1bPd8giIiIiDSUEvyWoJ4j5y3BokWLWLVqFbfffjv79+/3tJ955pk8++yzrFu3jn79+mGM4bPPPmP69Onceeed7N27l549e3Lrrbdy9dVX+923y+Vi8uTJ7Ny5k+nTp9O/f38iIyOZM2cOjzzyCAcPHqwxvg4dOvDhhx9ijKFdu3Z07dqV4OBgAHJzcznllFOIiori0UcfpXfv3oSFhfH888/z8ssve/axd+9ekpKSiIyMbNib5cfevXsB6NKlS6PvW0RERA5NSvClQcouSH3sscd47LHHKm1/9dVXefjhhwHo1asXr776KtZafvnlF5599ln++Mc/0qNHDyZOnFjpuRs3bmTRokW89tprXHTRRZ72uXPn1jq+0NBQRo0a5XfbggUL2Lp1K99++y3HHnusp913is/27duTlZVFQUFBoyf57du3B5xvGkREREQag2oCpN6Kior473//y1FHHcVXX31V6TZs2DBee+01rLUVnmeMYdiwYTz11FMArFixwu/+8/PzASqU2BQXF/PGG280Svz+9r9v3z7ee++9Cv1OPfVUrLW89NJLjXJcb/369aNHjx689NJLld4nERERkfrQCL7U24cffsjevXt58sknOfHEEyttv+qqq7j66quZP38+7dq144YbbuC8886jT58+lJaWMmPGDEJCQhg7dqzf/R9++OF0796du+++m+DgYEJDQ3n66acbLf6jjz6auLg4rrnmGh544AHy8vJ4+OGHad++PQcOHPD0O+mkkzjrrLO4+eab2b59O2PHjqW4uJhvvvmGSZMm+X3ttWWM4W9/+xtnnnkmY8eO5Q9/+APJycmsXr2azMxMHnjggUZ4pSIiInIo0Qi+1NvMmTOJjY3lnHPO8bv9ggsu8MyJn5KSQrdu3XjqqaeYPHkyF1xwATt37uSDDz5g5MiRfp8fFhbGnDlzSElJ4eKLL+aaa67h+OOP54477miU+JOTk5k9ezalpaWcffbZ3HnnnVx++eUVyoHK/O9//2P69OnMmTOHyZMn8/vf/56VK1eSmpra4DimTJnCZ599BsBll13G5MmTefHFF+nRo0eD9y0iIiKHHqOygMY1atQou2jRoiq3r169msMPP7wZIxKR1kL/PoiISF0YYxZbaytdbKgRfBERERGRNkQJvoiIiIhIG6IEX0RERESkDVGCLyIiIiLShijBFxERERFpQ5Tgi4iIiIi0IUrwRURERETaECX4IiIiIiJtiBJ8EREREZE2RAm+iIiIiEgbogRfGmTGjBkYY/zeEhISAh1eo3r00Ufp27cvUPF1r1u3rlLfr7/+2rP9888/97Rfeuml9OjRo8Zjle1/y5Yt1fbbsmULxhhmzJhR52PUVlWfrzGGOXPmNNpxykyfPp0vv/yyyu0LFy7EGMOOHTsqtL/xxhsYYxg+fLjf5+Xl5fHb3/6WDh06YIzhxhtvrDEOY0yd4xcREQm0kEAHIG3D22+/TZcuXSq0hYS0rV+vOXPmMGXKlAptsbGxvPbaazz00EMV2mfOnElsbCw5OTkV2u+9915uuOGGJo+1sV166aVcddVVldoPO+ywRj/WAw88wN13383YsWP9bp8zZw4jR46s9Ps2c+ZMAJYtW8by5csZPHhwhe3/+Mc/+O9//8vLL79Mv379SE1NrTaOyy+/nAkTJjTglYiIiARG28rAJGCGDRtGnz59mmTfxcXFhISEBHQ0NT09nZ9++oknnniiQvuZZ57J66+/zoMPPuiJr6CggHfeeYezzjqrwsg6QO/evZsr5EbVuXNnRo8eHegwACfBv+iiiyq0paWl8cUXXzBx4kQ++ugjZs6cWemzWr16NZ06deLiiy+udv+FhYWEh4fTpUuXSicRIiIirYFKdKRZVFXu4FtOUlZy8txzz3HbbbfRqVMnwsPD2b9/P9Zann76aQ477DDCwsJITU3l2muvJTs7u8I+jTHcfffdPPLII3Tp0oXIyEiOP/54li1bVun4s2bNYvTo0URFRZGQkMA555zDtm3bKvV77733SE5O5uijj67Q/tvf/patW7fy3Xffedpmz56Ny+XirLPOqvH1AmzatIlJkyYRFRVFcnIyN9xwA4WFhZWem5+fzx//+EfatWtHTEwMkydPrlSmUpX8/Hxuv/12evbsSVhYGD179uSRRx7B5XLV6vm18eyzzzJmzBiSkpJISEhg9OjRfPjhhxX6lJSUcO+999K7d28iIiJo3749xx57rOf9K/sdeeSRRzxlQNOnT/c8f82aNaxdu5apU6dW2O9rr72Gy+XigQce4JhjjuGNN96gtLTUs72sjGn79u2e/c6fP5/58+djjGHWrFlcccUVJCcn07FjR8D/72xJSQmPPfYYAwYMICIiguTkZCZMmMCaNWsAOHjwIDfddBODBg0iJiaGlJQUzjjjDM92ERGR5qARfGkUpaWllJSUVGgLCgoiKKh+55CPPPIIRxxxBC+++CKlpaVERERw991385e//IVrrrmGM844g1WrVnHvvffyyy+/8PXXX1c41quvvkq3bt149tlnKSws5L777mPcuHGsX7+epKQkAP75z39y9dVX87vf/Y777ruPnJwcpk+fzgknnMCvv/5KbGysZ39z5szhjDPOqPR6unfvzvHHH89rr73Gcccd5zn2tGnTiImJqfF1FhUVccopp1BQUMA//vEPOnTowAsvvMCsWbMq9b3qqqt48803uf/++zniiCP47LPP+M1vflPjMUpKShg/frzn/Ro8eDALFy7koYceIisriyeffLLGfVhrK32+ULEMa8uWLVx++eX06NGDkpIS5s6dy+mnn85HH33kKXV57LHHePrpp3nkkUcYNmwY2dnZLFq0iKysLAAWLFjAmDFjKpQEeY+iz5kzhz59+jBo0KAKccycOZPDDz+cI444gosvvpirrrqKTz/9lIkTJ3r2O336dH755Rdmz54NwIABA1iyZAkA1113HRMnTuS1117j4MGDVb4P559/PnPmzOHGG2/k5JNP5uDBg3zzzTekp6fTv39/CgsLycnJ4Z577iE1NZWsrCyee+45xowZw+rVq0lJSanxvRYREWkoJfgtwGM/PcaarMCO8PVP6s/tR95e/+f371+pbdKkSf/f3r2HV1Xd+R9/fzEJicglEC4phaAjYLEzzmiUS/0hCnJHbFV0REV8xDo61VHGBwe1KRVoR6w3dMqMWERULqMDAgIFhAGhYKHVTkVERbQREZBrgZQE8v39sfc5TU5OQggnOXjyeT3PfjZZe+21196Lc873rLP22ixcuLBG5bVu3Zq5c+dGe1AjgeiIESN49tlnAejXrx8tW7bk5ptvZuHChVx11VXR/YuKili6dCmNGjUCoGvXrnTs2JEnn3ySRx99lEOHDjFmzBhGjhzJr371q+h+l1xyCZ07d+aFF16I3oR58OBBVq5cyeuvvx63rrfccgujR4/mmWeeYd++fSxfvpzFixdX6zynT5/Op59+yrp166JDYAYMGFBh/PiWLVt49dVXmTBhAg8++CAAffv25dChQ0yZMqXKY8ycOZM1a9awatUqevbsCUDv3r2BYLz7mDFjaNWqVZVlTJw4kYkTJ1ZI3717Nzk5OQDlhsSUlpbSu3dvPvroI375y19GA/x169bRt2/fcvchDBkyJPrvyDWobEhQvPsgfvvb3/Lhhx9G6zds2DDuvfdepk+fHg3wu3XrRk5ODg0bNoxb7iWXXMLUqVOrvAYrVqzg9ddf5+mnn+aee+6Jppf9NaFp06blyjl+/Dj9+vWjdevWzJw5k/vuu6/K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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.rcParams.update({'font.size': 16})\n", + "revenue_table.transpose().plot(title=\"Revenue by Geographic Region\",\n", + " ylabel=\"Revenue (Millions of US$)\",\n", + " figsize=(12, 7), ylim=(0, 50000))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now Allison has a clear picture of the detailed revenue data that was hidden inside those 1500 pages of PDF\n", + "files. As she works on her analyst report, Allison can use the same process to extract DataFrames for\n", + "other financial metrics too!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "text-estensions-for-pandas", + "language": "python", + "name": "text-estensions-for-pandas" + }, + "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.9.1" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/Understand_Tables_Tooling.ipynb b/notebooks/Understand_Tables_Tooling.ipynb new file mode 100644 index 00000000..cc6a6a5a --- /dev/null +++ b/notebooks/Understand_Tables_Tooling.ipynb @@ -0,0 +1,3190 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Understand_Tables_Tooling.ipynb:\n", + "

\n", + "Extract Structured Information from Tables in PDF Documents\n", + " using IBM Watson Discovery Tooling and Text Extensions for Pandas\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Introduction\n", + "\n", + "Many organizations have valuable information hidden in tables inside human-readable documents like PDF files and web pages. Table identification and extraction technology can turn this human-readable information into a format that data science tools can import and use. Text Extensions for Pandas and Watson Discovery make this process much easier.\n", + "\n", + "In this notebook, we'll follow the journey of Allison, an analyst at an investment bank. Allison's employer has assigned her to cover several different companies, one of which is IBM. As part of her analysis, Allison wants to track IBM's revenue over time, broken down by geographical region. That detailed revenue information is all there in IBM's filings with the U.S. Securities and Exchange Commission (SEC). For example, here's IBM's 2019 annual report:\n", + "\n", + "![IBM Annual Report for 2019 (146 pages)](images/IBM_Annual_Report_2019.png)\n", + "\n", + "Did you see the table of revenue by geography? It's here, on page 39:\n", + "\n", + "![Page 39 of IBM Annual Report for 2019](images/IBM_Annual_Report_2019_page_39.png)\n", + "\n", + "Here's what that table looks like close up:\n", + "\n", + "![Table: Geographic Revenue (from IBM 2019 annual report)](images/screenshot_table_2019.png)\n", + "\n", + "But this particular table only gives two years' revenue figures. Allison needs to have enough data to draw a meaningful chart of revenue over time. 10 years of annual revenue figures would be a good starting point. \n", + "\n", + "Allison has a collection of IBM annual reports going back to 2009. In total, these documents contain about 1500 pages of financial information. Hidden inside those 1500 pages are the detailed revenue figures that Allison wants. She needs to find those figures, extract them from the documents, and import them into her data science tools.\n", + "\n", + "Fortunately, Allison has [Watson Discovery](https://www.ibm.com/cloud/watson-discovery), IBM's suite of tools for managing and extracting value from collections of human-readable documents.\n", + "\n", + "The cells that follow will show how Allison uses Text Extensions for Pandas and Watson Discovery to import the detailed revenue information from her PDF documents into a Pandas DataFrame...\n", + "\n", + "![Screenshot of a DataFrame from later in this notebook.](images/revenue_table.png)\n", + "\n", + "...that she then uses to generate a chart of revenue over time:\n", + "\n", + "![Chart of revenue over time, from later in this notebook.](images/revenue_over_time.png)\n", + "\n", + "But first, let's set your environment up so that you can run Allison's code yourself.\n", + "\n", + "(If you're just reading through the precomputed outputs of this notebook, you can skip ahead to the section labeled [\"Extract Tables with Watson Discovery\"](#watson_discovery))." + ] + }, + { + "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", + "* `matplotlib`\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**.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Core Python libraries\n", + "import json\n", + "import os\n", + "import sys\n", + "import requests\n", + "import glob\n", + "from typing import *\n", + "import pandas as pd\n", + "from matplotlib import pyplot as plt\n", + "from IPython.display import HTML\n", + "from base64 import b64encode\n", + "\n", + "# IBM libraries\n", + "from ibm_watson import DiscoveryV2\n", + "from ibm_cloud_sdk_core.authenticators import IAMAuthenticator\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": [ + "

\n", + "\n", + "# Extract Tables with Watson Discovery\n", + "\n", + "Allison connects to the [Watson Discovery](https://cloud.ibm.com/docs/discovery-data?topic=discovery-data-install) component of her firm's [IBM Cloud Pak for Data](https://www.ibm.com/products/cloud-pak-for-data) installation on their [OpenShift](https://www.openshift.com/) cluster and uploads her stack of IBM annual reports to her project. Then she uses the Watson Discovery's [Table Understanding enrichment](https://cloud.ibm.com/docs/discovery-data?topic=discovery-data-understanding_tables) to identify tables in the PDF documents and to extract detailed information about the cells and headers that make up each table.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "# Create a Project & Collection of Documents & Apply Table Understanding using Watson Discovery Tooling\n", + "This notebook shows you how Allison accomplishes her whole task end to end using the Watson Discovery tooling and python API. Allison first creates a project of type `Document Retreival` named \"TextExtensionsForPandasTableUnderstanding\", then she creates a collection named `IBM-10K` and upload the PDF documents into that collection. Before Allison can apply the table understanding to her collection, she needs to first enable the pre-trained-models option for her collection to get the tables annotated for her automatically using the pre-trained Smart Document Understanding algorithm. It would take some time for all the documents to get ingested and she can query the documents." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note: Alternatively, Allison could use the Watson Discovery's Python SDK to do the same programatically as shown in this [additional notebook](./Understand_Tables_API.ipynb#watson_discovery_create_project).\n", + "\n", + "If you're using the Watson Discovery tooling, you can follow the steps in the following video and then go ahead to the section labeled [\"Query the Project\"](#watson_discovery_qury_project). Otherwise you can simply follow the steps in [Create a Project through the Watson Discovery Service API](./Understand_Tables_API.ipynb#watson_discovery_create_project) to see how you can do the same using the Python SDK.)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def play(filename):\n", + " html = ''\n", + " video = open(filename,'rb').read()\n", + " src = 'data:video/mp4;base64,' + b64encode(video).decode()\n", + " html += '' % src \n", + " return HTML(html)\n", + "\n", + "play('./images/Table_Understanding.mp4')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Connect to the Watson Discovery Python API" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Allison needs two pieces of information to access her instance of Watson Discovery: An **API key** and a **service URL**. \n", + "\n", + "If you're using Watson Discovery 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 Watson Discovery 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", + "Alternately, you can uncomment the first two lines of code below to set the `IBM_API_KEY` and `IBM_SERVICE_URL` environment variables directly. **Be careful not to store your API key in any publicly-accessible location!**" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "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", + "# Retrieve the API key for your Watson Discovery service instance\n", + "if \"IBM_API_KEY\" not in os.environ:\n", + " raise ValueError(\"Expected Watson Discovery api key in the environment variable 'IBM_API_KEY'\")\n", + "api_key = os.environ.get(\"IBM_API_KEY\") \n", + "# Retrieve the service URL for your Watson Discovry service instance\n", + "if \"IBM_SERVICE_URL\" not in os.environ:\n", + " raise ValueError(\"Expected Watson Discovery service URL in the environment variable 'IBM_SERVICE_URL'\")\n", + "service_url = os.environ.get(\"IBM_SERVICE_URL\") " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Allison starts by using the API key and service URL from the previous cell to create an instance of the\n", + "Python API for her Watson Discovery." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "authenticator = IAMAuthenticator(api_key)\n", + "version='2020-08-30'\n", + "discovery = DiscoveryV2(\n", + " version=version,\n", + " authenticator=authenticator\n", + ")\n", + "\n", + "discovery.set_service_url(service_url)\n", + "discovery.set_disable_ssl_verification(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "\n", + "# Query the Project\n", + "She can then query the project with an empty string to retreive all the documents:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "for project in discovery.list_projects().get_result()['projects']:\n", + " if project['name'] == \"TextExtensionsForPandasTableUnderstanding\":\n", + " project_id = project['project_id']" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "collection_id = discovery.list_collections(project_id).get_result()['collections'][0]['collection_id']" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/monireh/opt/anaconda3/envs/text-extensions-for-pandas/lib/python3.9/site-packages/urllib3/connectionpool.py:1013: InsecureRequestWarning: Unverified HTTPS request is being made to host 'api.us-south.discovery.watson.cloud.ibm.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "geograpies_results = discovery.query(\n", + " project_id = project_id,\n", + " collection_ids = [collection_id],\n", + " natural_language_query= \"\" \n", + " ).get_result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "She then searches through all the tables and keeps thoese tables which are under the \"Geographic Revenue\" section in the documents." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'section_title': {'location': {'end': 651156, 'begin': 651138}, 'text': 'Geographic Revenue'}, 'row_headers': [{'column_index_begin': 0, 'row_index_begin': 4, 'location': {'end': 656019, 'begin': 656005}, 'text': 'Total revenue', 'row_index_end': 4, 'cell_id': 'rowHeader-656005-656019', 'column_index_end': 0, 'text_normalized': 'Total revenue'}, {'column_index_begin': 0, 'row_index_begin': 5, 'location': {'end': 657316, 'begin': 657304}, 'text': 'Geographies', 'row_index_end': 5, 'cell_id': 'rowHeader-657304-657316', 'column_index_end': 0, 'text_normalized': 'Geographies'}, {'column_index_begin': 0, 'row_index_begin': 6, 'location': {'end': 658609, 'begin': 658600}, 'text': 'Americas', 'row_index_end': 6, 'cell_id': 'rowHeader-658600-658609', 'column_index_end': 0, 'text_normalized': 'Americas'}, {'column_index_begin': 0, 'row_index_begin': 7, 'location': {'end': 659908, 'begin': 659882}, 'text': 'Europe/Middle East/Africa', 'row_index_end': 7, 'cell_id': 'rowHeader-659882-659908', 'column_index_end': 0, 'text_normalized': 'Europe/Middle East/Africa'}, {'column_index_begin': 0, 'row_index_begin': 8, 'location': {'end': 661196, 'begin': 661183}, 'text': 'Asia Pacific', 'row_index_end': 8, 'cell_id': 'rowHeader-661183-661196', 'column_index_end': 0, 'text_normalized': 'Asia Pacific'}], 'table_headers': [], 'location': {'end': 662216, 'begin': 652301}, 'text': ' Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent Adjusted for For the year ended December 31: 2017 2016\\nChange\\n Currency\\nTotal revenue $79,139 $79,919 (1.0)% (1.3)%\\nGeographies $78,793 $79,594 (1.0)% (1.4)%\\nAmericas 37,479 37,513 (0.1) (0.6)\\nEurope/Middle East/Africa 24,345 24,769 (1.7) (2.8)\\nAsia Pacific 16,970 17,313 (2.0) (1.1)\\n', 'body_cells': [{'row_header_ids': ['rowHeader-656005-656019'], 'column_index_begin': 1, 'row_index_begin': 4, 'row_header_texts': ['Total revenue'], 'column_header_texts': ['', '', '', '2017'], 'column_index_end': 1, 'column_header_ids': ['colHeader-652366-652367', 'colHeader-652910-652911', 'colHeader-653663-653664', 'colHeader-654875-654880'], 'column_header_texts_normalized': ['', '', '', '2017'], 'location': {'end': 656281, 'begin': 656273}, 'attributes': [{'location': {'end': 656280, 'begin': 656273}, 'text': '$79,139', 'type': 'Currency'}], 'text': '$79,139', 'row_index_end': 4, 'row_header_texts_normalized': ['Total revenue'], 'cell_id': 'bodyCell-656273-656281'}, {'row_header_ids': ['rowHeader-656005-656019'], 'column_index_begin': 2, 'row_index_begin': 4, 'row_header_texts': ['Total revenue'], 'column_header_texts': ['', '', '', '2016'], 'column_index_end': 2, 'column_header_ids': ['colHeader-652431-652432', 'colHeader-652975-652976', 'colHeader-653728-653729', 'colHeader-655140-655145'], 'column_header_texts_normalized': ['', '', '', '2016'], 'location': {'end': 656537, 'begin': 656529}, 'attributes': [{'location': {'end': 656536, 'begin': 656529}, 'text': '$79,919', 'type': 'Currency'}], 'text': '$79,919', 'row_index_end': 4, 'row_header_texts_normalized': ['Total revenue'], 'cell_id': 'bodyCell-656529-656537'}, {'row_header_ids': ['rowHeader-656005-656019'], 'column_index_begin': 3, 'row_index_begin': 4, 'row_header_texts': ['Total revenue'], 'column_header_texts': ['', 'Yr.-to-Yr.', 'Yr.-to-Yr.', 'Percent Change', 'Percent', '', 'Change', ''], 'column_index_end': 4, 'column_header_ids': ['colHeader-652496-652497', 'colHeader-652748-652759', 'colHeader-653225-653236', 'colHeader-653497-653512', 'colHeader-653974-653982', 'colHeader-654059-654060', 'colHeader-655408-655415', 'colHeader-655492-655493'], 'column_header_texts_normalized': ['', 'Yr.-to-Yr.', 'Yr.-to-Yr.', 'Percent Change', 'Percent', '', 'Change', ''], 'location': {'end': 656792, 'begin': 656785}, 'attributes': [{'location': {'end': 656789, 'begin': 656786}, 'text': '1.0', 'type': 'Number'}], 'text': '(1.0)%', 'row_index_end': 4, 'row_header_texts_normalized': ['Total revenue'], 'cell_id': 'bodyCell-656785-656792'}, {'row_header_ids': ['rowHeader-656005-656019'], 'column_index_begin': 5, 'row_index_begin': 4, 'row_header_texts': ['Total revenue'], 'column_header_texts': ['Yr.-to-Yr.', 'Percent Change', 'Adjusted for', 'Currency'], 'column_index_end': 5, 'column_header_ids': ['colHeader-652748-652759', 'colHeader-653497-653512', 'colHeader-654309-654322', 'colHeader-655738-655747'], 'column_header_texts_normalized': ['Yr.-to-Yr.', 'Percent Change', 'Adjusted for', 'Currency'], 'location': {'end': 657047, 'begin': 657040}, 'attributes': [{'location': {'end': 657044, 'begin': 657041}, 'text': '1.3', 'type': 'Number'}], 'text': '(1.3)%', 'row_index_end': 4, 'row_header_texts_normalized': ['Total revenue'], 'cell_id': 'bodyCell-657040-657047'}, {'row_header_ids': ['rowHeader-657304-657316'], 'column_index_begin': 1, 'row_index_begin': 5, 'row_header_texts': ['Geographies'], 'column_header_texts': ['', '', '', '2017'], 'column_index_end': 1, 'column_header_ids': ['colHeader-652366-652367', 'colHeader-652910-652911', 'colHeader-653663-653664', 'colHeader-654875-654880'], 'column_header_texts_normalized': ['', '', '', '2017'], 'location': {'end': 657579, 'begin': 657571}, 'attributes': [{'location': {'end': 657578, 'begin': 657571}, 'text': '$78,793', 'type': 'Currency'}], 'text': '$78,793', 'row_index_end': 5, 'row_header_texts_normalized': ['Geographies'], 'cell_id': 'bodyCell-657571-657579'}, {'row_header_ids': ['rowHeader-657304-657316'], 'column_index_begin': 2, 'row_index_begin': 5, 'row_header_texts': ['Geographies'], 'column_header_texts': ['', '', '', '2016'], 'column_index_end': 2, 'column_header_ids': ['colHeader-652431-652432', 'colHeader-652975-652976', 'colHeader-653728-653729', 'colHeader-655140-655145'], 'column_header_texts_normalized': ['', '', '', '2016'], 'location': {'end': 657834, 'begin': 657826}, 'attributes': [{'location': {'end': 657833, 'begin': 657826}, 'text': '$79,594', 'type': 'Currency'}], 'text': '$79,594', 'row_index_end': 5, 'row_header_texts_normalized': ['Geographies'], 'cell_id': 'bodyCell-657826-657834'}, {'row_header_ids': ['rowHeader-657304-657316'], 'column_index_begin': 3, 'row_index_begin': 5, 'row_header_texts': ['Geographies'], 'column_header_texts': ['', 'Yr.-to-Yr.', 'Yr.-to-Yr.', 'Percent Change', 'Percent', '', 'Change', ''], 'column_index_end': 4, 'column_header_ids': ['colHeader-652496-652497', 'colHeader-652748-652759', 'colHeader-653225-653236', 'colHeader-653497-653512', 'colHeader-653974-653982', 'colHeader-654059-654060', 'colHeader-655408-655415', 'colHeader-655492-655493'], 'column_header_texts_normalized': ['', 'Yr.-to-Yr.', 'Yr.-to-Yr.', 'Percent Change', 'Percent', '', 'Change', ''], 'location': {'end': 658087, 'begin': 658080}, 'attributes': [{'location': {'end': 658084, 'begin': 658081}, 'text': '1.0', 'type': 'Number'}], 'text': '(1.0)%', 'row_index_end': 5, 'row_header_texts_normalized': ['Geographies'], 'cell_id': 'bodyCell-658080-658087'}, {'row_header_ids': ['rowHeader-657304-657316'], 'column_index_begin': 5, 'row_index_begin': 5, 'row_header_texts': ['Geographies'], 'column_header_texts': ['Yr.-to-Yr.', 'Percent Change', 'Adjusted for', 'Currency'], 'column_index_end': 5, 'column_header_ids': ['colHeader-652748-652759', 'colHeader-653497-653512', 'colHeader-654309-654322', 'colHeader-655738-655747'], 'column_header_texts_normalized': ['Yr.-to-Yr.', 'Percent Change', 'Adjusted for', 'Currency'], 'location': {'end': 658343, 'begin': 658336}, 'attributes': [{'location': {'end': 658340, 'begin': 658337}, 'text': '1.4', 'type': 'Number'}], 'text': '(1.4)%', 'row_index_end': 5, 'row_header_texts_normalized': ['Geographies'], 'cell_id': 'bodyCell-658336-658343'}, {'row_header_ids': ['rowHeader-658600-658609'], 'column_index_begin': 1, 'row_index_begin': 6, 'row_header_texts': ['Americas'], 'column_header_texts': ['', '', '', '2017'], 'column_index_end': 1, 'column_header_ids': ['colHeader-652366-652367', 'colHeader-652910-652911', 'colHeader-653663-653664', 'colHeader-654875-654880'], 'column_header_texts_normalized': ['', '', '', '2017'], 'location': {'end': 658868, 'begin': 658861}, 'attributes': [{'location': {'end': 658867, 'begin': 658861}, 'text': '37,479', 'type': 'Number'}], 'text': '37,479', 'row_index_end': 6, 'row_header_texts_normalized': ['Americas'], 'cell_id': 'bodyCell-658861-658868'}, {'row_header_ids': ['rowHeader-658600-658609'], 'column_index_begin': 2, 'row_index_begin': 6, 'row_header_texts': ['Americas'], 'column_header_texts': ['', '', '', '2016'], 'column_index_end': 2, 'column_header_ids': ['colHeader-652431-652432', 'colHeader-652975-652976', 'colHeader-653728-653729', 'colHeader-655140-655145'], 'column_header_texts_normalized': ['', '', '', '2016'], 'location': {'end': 659120, 'begin': 659113}, 'attributes': [{'location': {'end': 659119, 'begin': 659113}, 'text': '37,513', 'type': 'Number'}], 'text': '37,513', 'row_index_end': 6, 'row_header_texts_normalized': ['Americas'], 'cell_id': 'bodyCell-659113-659120'}, {'row_header_ids': ['rowHeader-658600-658609'], 'column_index_begin': 3, 'row_index_begin': 6, 'row_header_texts': ['Americas'], 'column_header_texts': ['', 'Yr.-to-Yr.', 'Yr.-to-Yr.', 'Percent Change', 'Percent', '', 'Change', ''], 'column_index_end': 4, 'column_header_ids': ['colHeader-652496-652497', 'colHeader-652748-652759', 'colHeader-653225-653236', 'colHeader-653497-653512', 'colHeader-653974-653982', 'colHeader-654059-654060', 'colHeader-655408-655415', 'colHeader-655492-655493'], 'column_header_texts_normalized': ['', 'Yr.-to-Yr.', 'Yr.-to-Yr.', 'Percent Change', 'Percent', '', 'Change', ''], 'location': {'end': 659374, 'begin': 659368}, 'attributes': [{'location': {'end': 659372, 'begin': 659369}, 'text': '0.1', 'type': 'Number'}], 'text': '(0.1)', 'row_index_end': 6, 'row_header_texts_normalized': ['Americas'], 'cell_id': 'bodyCell-659368-659374'}, {'row_header_ids': ['rowHeader-658600-658609'], 'column_index_begin': 5, 'row_index_begin': 6, 'row_header_texts': ['Americas'], 'column_header_texts': ['Yr.-to-Yr.', 'Percent Change', 'Adjusted for', 'Currency'], 'column_index_end': 5, 'column_header_ids': ['colHeader-652748-652759', 'colHeader-653497-653512', 'colHeader-654309-654322', 'colHeader-655738-655747'], 'column_header_texts_normalized': ['Yr.-to-Yr.', 'Percent Change', 'Adjusted for', 'Currency'], 'location': {'end': 659625, 'begin': 659619}, 'attributes': [{'location': {'end': 659623, 'begin': 659620}, 'text': '0.6', 'type': 'Number'}], 'text': '(0.6)', 'row_index_end': 6, 'row_header_texts_normalized': ['Americas'], 'cell_id': 'bodyCell-659619-659625'}, {'row_header_ids': ['rowHeader-659882-659908'], 'column_index_begin': 1, 'row_index_begin': 7, 'row_header_texts': ['Europe/Middle East/Africa'], 'column_header_texts': ['', '', '', '2017'], 'column_index_end': 1, 'column_header_ids': ['colHeader-652366-652367', 'colHeader-652910-652911', 'colHeader-653663-653664', 'colHeader-654875-654880'], 'column_header_texts_normalized': ['', '', '', '2017'], 'location': {'end': 660166, 'begin': 660159}, 'attributes': [{'location': {'end': 660165, 'begin': 660159}, 'text': '24,345', 'type': 'Number'}], 'text': '24,345', 'row_index_end': 7, 'row_header_texts_normalized': ['Europe/Middle East/Africa'], 'cell_id': 'bodyCell-660159-660166'}, {'row_header_ids': ['rowHeader-659882-659908'], 'column_index_begin': 2, 'row_index_begin': 7, 'row_header_texts': ['Europe/Middle East/Africa'], 'column_header_texts': ['', '', '', '2016'], 'column_index_end': 2, 'column_header_ids': ['colHeader-652431-652432', 'colHeader-652975-652976', 'colHeader-653728-653729', 'colHeader-655140-655145'], 'column_header_texts_normalized': ['', '', '', '2016'], 'location': {'end': 660420, 'begin': 660413}, 'attributes': [{'location': {'end': 660419, 'begin': 660413}, 'text': '24,769', 'type': 'Number'}], 'text': '24,769', 'row_index_end': 7, 'row_header_texts_normalized': ['Europe/Middle East/Africa'], 'cell_id': 'bodyCell-660413-660420'}, {'row_header_ids': ['rowHeader-659882-659908'], 'column_index_begin': 3, 'row_index_begin': 7, 'row_header_texts': ['Europe/Middle East/Africa'], 'column_header_texts': ['', 'Yr.-to-Yr.', 'Yr.-to-Yr.', 'Percent Change', 'Percent', '', 'Change', ''], 'column_index_end': 4, 'column_header_ids': ['colHeader-652496-652497', 'colHeader-652748-652759', 'colHeader-653225-653236', 'colHeader-653497-653512', 'colHeader-653974-653982', 'colHeader-654059-654060', 'colHeader-655408-655415', 'colHeader-655492-655493'], 'column_header_texts_normalized': ['', 'Yr.-to-Yr.', 'Yr.-to-Yr.', 'Percent Change', 'Percent', '', 'Change', ''], 'location': {'end': 660673, 'begin': 660667}, 'attributes': [{'location': {'end': 660671, 'begin': 660668}, 'text': '1.7', 'type': 'Number'}], 'text': '(1.7)', 'row_index_end': 7, 'row_header_texts_normalized': ['Europe/Middle East/Africa'], 'cell_id': 'bodyCell-660667-660673'}, {'row_header_ids': ['rowHeader-659882-659908'], 'column_index_begin': 5, 'row_index_begin': 7, 'row_header_texts': ['Europe/Middle East/Africa'], 'column_header_texts': ['Yr.-to-Yr.', 'Percent Change', 'Adjusted for', 'Currency'], 'column_index_end': 5, 'column_header_ids': ['colHeader-652748-652759', 'colHeader-653497-653512', 'colHeader-654309-654322', 'colHeader-655738-655747'], 'column_header_texts_normalized': ['Yr.-to-Yr.', 'Percent Change', 'Adjusted for', 'Currency'], 'location': {'end': 660928, 'begin': 660922}, 'attributes': [{'location': {'end': 660926, 'begin': 660923}, 'text': '2.8', 'type': 'Number'}], 'text': '(2.8)', 'row_index_end': 7, 'row_header_texts_normalized': ['Europe/Middle East/Africa'], 'cell_id': 'bodyCell-660922-660928'}, {'row_header_ids': ['rowHeader-661183-661196'], 'column_index_begin': 1, 'row_index_begin': 8, 'row_header_texts': ['Asia Pacific'], 'column_header_texts': ['', '', '', '2017'], 'column_index_end': 1, 'column_header_ids': ['colHeader-652366-652367', 'colHeader-652910-652911', 'colHeader-653663-653664', 'colHeader-654875-654880'], 'column_header_texts_normalized': ['', '', '', '2017'], 'location': {'end': 661455, 'begin': 661448}, 'attributes': [{'location': {'end': 661454, 'begin': 661448}, 'text': '16,970', 'type': 'Number'}], 'text': '16,970', 'row_index_end': 8, 'row_header_texts_normalized': ['Asia Pacific'], 'cell_id': 'bodyCell-661448-661455'}, {'row_header_ids': ['rowHeader-661183-661196'], 'column_index_begin': 2, 'row_index_begin': 8, 'row_header_texts': ['Asia Pacific'], 'column_header_texts': ['', '', '', '2016'], 'column_index_end': 2, 'column_header_ids': ['colHeader-652431-652432', 'colHeader-652975-652976', 'colHeader-653728-653729', 'colHeader-655140-655145'], 'column_header_texts_normalized': ['', '', '', '2016'], 'location': {'end': 661708, 'begin': 661701}, 'attributes': [{'location': {'end': 661707, 'begin': 661701}, 'text': '17,313', 'type': 'Number'}], 'text': '17,313', 'row_index_end': 8, 'row_header_texts_normalized': ['Asia Pacific'], 'cell_id': 'bodyCell-661701-661708'}, {'row_header_ids': ['rowHeader-661183-661196'], 'column_index_begin': 3, 'row_index_begin': 8, 'row_header_texts': ['Asia Pacific'], 'column_header_texts': ['', 'Yr.-to-Yr.', 'Yr.-to-Yr.', 'Percent Change', 'Percent', '', 'Change', ''], 'column_index_end': 4, 'column_header_ids': ['colHeader-652496-652497', 'colHeader-652748-652759', 'colHeader-653225-653236', 'colHeader-653497-653512', 'colHeader-653974-653982', 'colHeader-654059-654060', 'colHeader-655408-655415', 'colHeader-655492-655493'], 'column_header_texts_normalized': ['', 'Yr.-to-Yr.', 'Yr.-to-Yr.', 'Percent Change', 'Percent', '', 'Change', ''], 'location': {'end': 661962, 'begin': 661956}, 'attributes': [{'location': {'end': 661960, 'begin': 661957}, 'text': '2.0', 'type': 'Number'}], 'text': '(2.0)', 'row_index_end': 8, 'row_header_texts_normalized': ['Asia Pacific'], 'cell_id': 'bodyCell-661956-661962'}, {'row_header_ids': ['rowHeader-661183-661196'], 'column_index_begin': 5, 'row_index_begin': 8, 'row_header_texts': ['Asia Pacific'], 'column_header_texts': ['Yr.-to-Yr.', 'Percent Change', 'Adjusted for', 'Currency'], 'column_index_end': 5, 'column_header_ids': ['colHeader-652748-652759', 'colHeader-653497-653512', 'colHeader-654309-654322', 'colHeader-655738-655747'], 'column_header_texts_normalized': ['Yr.-to-Yr.', 'Percent Change', 'Adjusted for', 'Currency'], 'location': {'end': 662216, 'begin': 662210}, 'attributes': [{'location': {'end': 662214, 'begin': 662211}, 'text': '1.1', 'type': 'Number'}], 'text': '(1.1)', 'row_index_end': 8, 'row_header_texts_normalized': ['Asia Pacific'], 'cell_id': 'bodyCell-662210-662216'}], 'contexts': [{'location': {'end': 651629, 'begin': 651345}, 'text': 'In addition to the revenue presentation by reportable segment, the company also measures revenue performance on a geographic basis.'}, {'location': {'end': 651727, 'begin': 651630}, 'text': 'The following geographic, regional and country-specific revenue performance excludes OEM revenue.'}, {'location': {'end': 651925, 'begin': 651910}, 'text': '($ in millions)'}, {'location': {'end': 662716, 'begin': 662417}, 'text': 'Total geographic revenue of $78,793 million in 2017 decreased 1.0 percent as reported (1 percent adjusted for currency) compared to the prior year.'}, {'location': {'end': 663593, 'begin': 662900}, 'text': 'Americas revenue was essentially flat year to year as reported, but decreased 1 percent adjusted for currency with a decline in North America partially offset by growth in Latin America, both as reported and adjusted for currency.'}, {'location': {'end': 664024, 'begin': 663594}, 'text': 'Within North America, the U.S. decreased 1.4 percent and Canada increased 4.9 percent (3 percent adjusted for currency).'}], 'key_value_pairs': [], 'title': {}, 'column_headers': [{'column_index_begin': 0, 'row_index_begin': 0, 'location': {'end': 652302, 'begin': 652301}, 'text': '', 'row_index_end': 0, 'cell_id': 'colHeader-652301-652302', 'column_index_end': 0, 'text_normalized': ''}, {'column_index_begin': 1, 'row_index_begin': 0, 'location': {'end': 652367, 'begin': 652366}, 'text': '', 'row_index_end': 0, 'cell_id': 'colHeader-652366-652367', 'column_index_end': 1, 'text_normalized': ''}, {'column_index_begin': 2, 'row_index_begin': 0, 'location': {'end': 652432, 'begin': 652431}, 'text': '', 'row_index_end': 0, 'cell_id': 'colHeader-652431-652432', 'column_index_end': 2, 'text_normalized': ''}, {'column_index_begin': 3, 'row_index_begin': 0, 'location': {'end': 652497, 'begin': 652496}, 'text': '', 'row_index_end': 0, 'cell_id': 'colHeader-652496-652497', 'column_index_end': 3, 'text_normalized': ''}, {'column_index_begin': 4, 'row_index_begin': 0, 'location': {'end': 652759, 'begin': 652748}, 'text': 'Yr.-to-Yr.', 'row_index_end': 0, 'cell_id': 'colHeader-652748-652759', 'column_index_end': 5, 'text_normalized': 'Yr.-to-Yr.'}, {'column_index_begin': 0, 'row_index_begin': 1, 'location': {'end': 652846, 'begin': 652845}, 'text': '', 'row_index_end': 1, 'cell_id': 'colHeader-652845-652846', 'column_index_end': 0, 'text_normalized': ''}, {'column_index_begin': 1, 'row_index_begin': 1, 'location': {'end': 652911, 'begin': 652910}, 'text': '', 'row_index_end': 1, 'cell_id': 'colHeader-652910-652911', 'column_index_end': 1, 'text_normalized': ''}, {'column_index_begin': 2, 'row_index_begin': 1, 'location': {'end': 652976, 'begin': 652975}, 'text': '', 'row_index_end': 1, 'cell_id': 'colHeader-652975-652976', 'column_index_end': 2, 'text_normalized': ''}, {'column_index_begin': 3, 'row_index_begin': 1, 'location': {'end': 653236, 'begin': 653225}, 'text': 'Yr.-to-Yr.', 'row_index_end': 1, 'cell_id': 'colHeader-653225-653236', 'column_index_end': 3, 'text_normalized': 'Yr.-to-Yr.'}, {'column_index_begin': 4, 'row_index_begin': 1, 'location': {'end': 653512, 'begin': 653497}, 'text': 'Percent Change', 'row_index_end': 1, 'cell_id': 'colHeader-653497-653512', 'column_index_end': 5, 'text_normalized': 'Percent Change'}, {'column_index_begin': 0, 'row_index_begin': 2, 'location': {'end': 653599, 'begin': 653598}, 'text': '', 'row_index_end': 2, 'cell_id': 'colHeader-653598-653599', 'column_index_end': 0, 'text_normalized': ''}, {'column_index_begin': 1, 'row_index_begin': 2, 'location': {'end': 653664, 'begin': 653663}, 'text': '', 'row_index_end': 2, 'cell_id': 'colHeader-653663-653664', 'column_index_end': 1, 'text_normalized': ''}, {'column_index_begin': 2, 'row_index_begin': 2, 'location': {'end': 653729, 'begin': 653728}, 'text': '', 'row_index_end': 2, 'cell_id': 'colHeader-653728-653729', 'column_index_end': 2, 'text_normalized': ''}, {'column_index_begin': 3, 'row_index_begin': 2, 'location': {'end': 653982, 'begin': 653974}, 'text': 'Percent', 'row_index_end': 2, 'cell_id': 'colHeader-653974-653982', 'column_index_end': 3, 'text_normalized': 'Percent'}, {'column_index_begin': 4, 'row_index_begin': 2, 'location': {'end': 654060, 'begin': 654059}, 'text': '', 'row_index_end': 2, 'cell_id': 'colHeader-654059-654060', 'column_index_end': 4, 'text_normalized': ''}, {'column_index_begin': 5, 'row_index_begin': 2, 'location': {'end': 654322, 'begin': 654309}, 'text': 'Adjusted for', 'row_index_end': 2, 'cell_id': 'colHeader-654309-654322', 'column_index_end': 5, 'text_normalized': 'Adjusted for'}, {'column_index_begin': 0, 'row_index_begin': 3, 'location': {'end': 654611, 'begin': 654579}, 'text': 'For the year ended December 31:', 'row_index_end': 3, 'cell_id': 'colHeader-654579-654611', 'column_index_end': 0, 'text_normalized': 'For the year ended December 31:'}, {'column_index_begin': 1, 'row_index_begin': 3, 'location': {'end': 654880, 'begin': 654875}, 'text': '2017', 'row_index_end': 3, 'cell_id': 'colHeader-654875-654880', 'column_index_end': 1, 'text_normalized': '2017'}, {'column_index_begin': 2, 'row_index_begin': 3, 'location': {'end': 655145, 'begin': 655140}, 'text': '2016', 'row_index_end': 3, 'cell_id': 'colHeader-655140-655145', 'column_index_end': 2, 'text_normalized': '2016'}, {'column_index_begin': 3, 'row_index_begin': 3, 'location': {'end': 655415, 'begin': 655408}, 'text': 'Change', 'row_index_end': 3, 'cell_id': 'colHeader-655408-655415', 'column_index_end': 3, 'text_normalized': 'Change'}, {'column_index_begin': 4, 'row_index_begin': 3, 'location': {'end': 655493, 'begin': 655492}, 'text': '', 'row_index_end': 3, 'cell_id': 'colHeader-655492-655493', 'column_index_end': 4, 'text_normalized': ''}, {'column_index_begin': 5, 'row_index_begin': 3, 'location': {'end': 655747, 'begin': 655738}, 'text': 'Currency', 'row_index_end': 3, 'cell_id': 'colHeader-655738-655747', 'column_index_end': 5, 'text_normalized': 'Currency'}]}\n", + ".\n", + ".\n", + ".\n", + " 41 more tables\n" + ] + } + ], + "source": [ + "key = \"enriched_html\"\n", + "tables = [table for result in geograpies_results['results'] \n", + " if key in result for enriched_html in result ['enriched_html']\n", + " for table in enriched_html['tables']\n", + " if table['section_title']['text']==\"Geographic Revenue\"]\n", + "\n", + "print(tables[0])\n", + "print(\".\\n.\\n.\\n %d more tables\"%(len(tables)-1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That raw output contains everything Allison needs to extract the revenue figures from this document, but it's in a format that's cumbersome to deal with. So Allison uses Text Extensions for Pandas to convert the output into a collection of Pandas DataFrames. These DataFrames encode information about the row headers, column headers, and cells that make up the table." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['row_headers', 'col_headers', 'body_cells', 'given_loc'])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "table_data = tp.io.watson.tables.parse_response({\"tables\":tables})\n", + "table_data.keys()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "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", + "
textcolumn_index_begincolumn_index_endrow_index_beginrow_index_endcell_idcolumn_header_idscolumn_header_textsrow_header_idsrow_header_textsattributes.textattributes.type
0$79,1391144bodyCell-656273-656281[colHeader-652366-652367, colHeader-652910-652...[2017][rowHeader-656005-656019][Total revenue][$79,139][Currency]
1$79,9192244bodyCell-656529-656537[colHeader-652431-652432, colHeader-652975-652...[2016][rowHeader-656005-656019][Total revenue][$79,919][Currency]
2(1.0)%3444bodyCell-656785-656792[colHeader-652496-652497, colHeader-652748-652...[Yr.-to-Yr., Yr.-to-Yr., Percent Change, Perce...[rowHeader-656005-656019][Total revenue][1.0][Number]
3(1.3)%5544bodyCell-657040-657047[colHeader-652748-652759, colHeader-653497-653...[Yr.-to-Yr., Percent Change, Adjusted for, Cur...[rowHeader-656005-656019][Total revenue][1.3][Number]
4$78,7931155bodyCell-657571-657579[colHeader-652366-652367, colHeader-652910-652...[2017][rowHeader-657304-657316][Geographies][$78,793][Currency]
\n", + "
" + ], + "text/plain": [ + " text column_index_begin column_index_end row_index_begin \\\n", + "0 $79,139 1 1 4 \n", + "1 $79,919 2 2 4 \n", + "2 (1.0)% 3 4 4 \n", + "3 (1.3)% 5 5 4 \n", + "4 $78,793 1 1 5 \n", + "\n", + " row_index_end cell_id \\\n", + "0 4 bodyCell-656273-656281 \n", + "1 4 bodyCell-656529-656537 \n", + "2 4 bodyCell-656785-656792 \n", + "3 4 bodyCell-657040-657047 \n", + "4 5 bodyCell-657571-657579 \n", + "\n", + " column_header_ids \\\n", + "0 [colHeader-652366-652367, colHeader-652910-652... \n", + "1 [colHeader-652431-652432, colHeader-652975-652... \n", + "2 [colHeader-652496-652497, colHeader-652748-652... \n", + "3 [colHeader-652748-652759, colHeader-653497-653... \n", + "4 [colHeader-652366-652367, colHeader-652910-652... \n", + "\n", + " column_header_texts \\\n", + "0 [2017] \n", + "1 [2016] \n", + "2 [Yr.-to-Yr., Yr.-to-Yr., Percent Change, Perce... \n", + "3 [Yr.-to-Yr., Percent Change, Adjusted for, Cur... \n", + "4 [2017] \n", + "\n", + " row_header_ids row_header_texts attributes.text attributes.type \n", + "0 [rowHeader-656005-656019] [Total revenue] [$79,139] [Currency] \n", + "1 [rowHeader-656005-656019] [Total revenue] [$79,919] [Currency] \n", + "2 [rowHeader-656005-656019] [Total revenue] [1.0] [Number] \n", + "3 [rowHeader-656005-656019] [Total revenue] [1.3] [Number] \n", + "4 [rowHeader-657304-657316] [Geographies] [$78,793] [Currency] " + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "table_data[\"body_cells\"].head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Text Extensions for Pandas can convert these DataFrames into a single Pandas DataFrame that matches the layout of the original table in the document. Allison calls the `make_table()` function to perform that conversion and inspects the output." + ] + }, + { + "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", + " \n", + " \n", + " \n", + "
Yr.-to-Yr.
Yr.-to-Yr.Percent Change
Yr.-to-Yr.Adjusted for
20172016Percent ChangeCurrency
Percent
Change
Total revenue$79,139$79,919(1.0)%(1.3)%
Geographies$78,793$79,594(1.0)%(1.4)%
Americas3747937513-0.1-0.6
Europe/Middle East/Africa2434524769-1.7-2.8
Asia Pacific1697017313-2-1.1
\n", + "
" + ], + "text/plain": [ + " Yr.-to-Yr.\n", + " Yr.-to-Yr. Percent Change\n", + " Yr.-to-Yr. Adjusted for\n", + " 2017 2016 Percent Change Currency\n", + " Percent \n", + " \n", + " Change \n", + " \n", + "Total revenue $79,139 $79,919 (1.0)% (1.3)%\n", + "Geographies $78,793 $79,594 (1.0)% (1.4)%\n", + "Americas 37479 37513 -0.1 -0.6\n", + "Europe/Middle East/Africa 24345 24769 -1.7 -2.8\n", + "Asia Pacific 16970 17313 -2 -1.1" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "revenue_2017_df = tp.io.watson.tables.make_table(table_data)\n", + "revenue_2017_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "  \n", + "\n", + "The reconstructed dataframe looks good! Here's what the original table in the PDF document looked like:\n", + "![Table: Geographic Revenue (from IBM 2017 annual report)](images/screenshot_table_2017.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If Allison just wanted to create a DataFrame of 2016/2017 revenue figures, her task would be done. But Allison wants to reconstruct ten years of revenue by geographic region. To do that, she will need to combine information from multiple documents. For tables like this one that have multiple levels of header information, this kind of integration is easier to perform over the \"exploded\" version of the table, where each cell in the table is represented a single row containing all the corresponding header values.\n", + "\n", + "Allison passes the same table data from the 2017 report through the Text Extensions for Pandas function `make_exploded_df()` to produce the exploded represention of the table:" + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
textrow_header_texts_0column_header_textsattributes.type
0$79,139Total revenue2017[Currency]
1$79,919Total revenue2016[Currency]
2(1.0)%Total revenueYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]
3(1.3)%Total revenueYr.-to-Yr. Percent Change Adjusted for Currency[Number]
4$78,793Geographies2017[Currency]
5$79,594Geographies2016[Currency]
6(1.0)%GeographiesYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]
7(1.4)%GeographiesYr.-to-Yr. Percent Change Adjusted for Currency[Number]
837,479Americas2017[Number]
937,513Americas2016[Number]
10(0.1)AmericasYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]
11(0.6)AmericasYr.-to-Yr. Percent Change Adjusted for Currency[Number]
1224,345Europe/Middle East/Africa2017[Number]
1324,769Europe/Middle East/Africa2016[Number]
14(1.7)Europe/Middle East/AfricaYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]
15(2.8)Europe/Middle East/AfricaYr.-to-Yr. Percent Change Adjusted for Currency[Number]
1616,970Asia Pacific2017[Number]
1717,313Asia Pacific2016[Number]
18(2.0)Asia PacificYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]
19(1.1)Asia PacificYr.-to-Yr. Percent Change Adjusted for Currency[Number]
\n", + "
" + ], + "text/plain": [ + " text row_header_texts_0 \\\n", + "0 $79,139 Total revenue \n", + "1 $79,919 Total revenue \n", + "2 (1.0)% Total revenue \n", + "3 (1.3)% Total revenue \n", + "4 $78,793 Geographies \n", + "5 $79,594 Geographies \n", + "6 (1.0)% Geographies \n", + "7 (1.4)% Geographies \n", + "8 37,479 Americas \n", + "9 37,513 Americas \n", + "10 (0.1) Americas \n", + "11 (0.6) Americas \n", + "12 24,345 Europe/Middle East/Africa \n", + "13 24,769 Europe/Middle East/Africa \n", + "14 (1.7) Europe/Middle East/Africa \n", + "15 (2.8) Europe/Middle East/Africa \n", + "16 16,970 Asia Pacific \n", + "17 17,313 Asia Pacific \n", + "18 (2.0) Asia Pacific \n", + "19 (1.1) Asia Pacific \n", + "\n", + " column_header_texts attributes.type \n", + "0 2017 [Currency] \n", + "1 2016 [Currency] \n", + "2 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] \n", + "3 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] \n", + "4 2017 [Currency] \n", + "5 2016 [Currency] \n", + "6 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] \n", + "7 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] \n", + "8 2017 [Number] \n", + "9 2016 [Number] \n", + "10 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] \n", + "11 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] \n", + "12 2017 [Number] \n", + "13 2016 [Number] \n", + "14 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] \n", + "15 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] \n", + "16 2017 [Number] \n", + "17 2016 [Number] \n", + "18 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] \n", + "19 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] " + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exploded_df, row_header_names, col_header_names = (\n", + " tp.io.watson.tables.make_exploded_df(table_data, col_explode_by=\"concat\"))\n", + "exploded_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This exploded version of the table is the exact same data, just represented in a different way. If she wants, Allison can convert it back to the format from the original document by calling `pandas.DataFrame.pivot()`:" + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
column_header_texts20162017Yr.-to-Yr. Percent Change Adjusted for CurrencyYr.-to-Yr. Yr.-to-Yr. Percent Change Percent Change
row_header_texts_0
Americas37,51337,479(0.6)(0.1)
Asia Pacific17,31316,970(1.1)(2.0)
Europe/Middle East/Africa24,76924,345(2.8)(1.7)
Geographies$79,594$78,793(1.4)%(1.0)%
Total revenue$79,919$79,139(1.3)%(1.0)%
\n", + "
" + ], + "text/plain": [ + "column_header_texts 2016 2017 \\\n", + "row_header_texts_0 \n", + "Americas 37,513 37,479 \n", + "Asia Pacific 17,313 16,970 \n", + "Europe/Middle East/Africa 24,769 24,345 \n", + "Geographies $79,594 $78,793 \n", + "Total revenue $79,919 $79,139 \n", + "\n", + "column_header_texts Yr.-to-Yr. Percent Change Adjusted for Currency \\\n", + "row_header_texts_0 \n", + "Americas (0.6) \n", + "Asia Pacific (1.1) \n", + "Europe/Middle East/Africa (2.8) \n", + "Geographies (1.4)% \n", + "Total revenue (1.3)% \n", + "\n", + "column_header_texts Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent Change \n", + "row_header_texts_0 \n", + "Americas (0.1) \n", + "Asia Pacific (2.0) \n", + "Europe/Middle East/Africa (1.7) \n", + "Geographies (1.0)% \n", + "Total revenue (1.0)% " + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exploded_df.pivot(index=\"row_header_texts_0\", columns=\"column_header_texts\", values=\"text\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But because she is about to merge this DataFrame with similar data from other documents, Allison keeps the data in exploded format for now. \n", + "\n", + "Allison's next task is to write some Pandas transformations that will clean and reformat the DataFrame for each source table prior to merging them all together. She uses the 2017 report's data as a test case for creating this code. The first step is to convert the cell values in the Watson Discovery output from text to numeric values. Text Extensions for Pandas includes a more robust version of [`pandas.to_numeric()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_numeric.html) that can handle common idioms for representing currencies and percentages. Allison uses this function, called `convert_cols_to_numeric()`, to convert all the cell values to numbers. She adds a new column \"value\" to her DataFrame to hold these numbers." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
textrow_header_texts_0column_header_textsattributes.typevalue
0$79,139Total revenue2017[Currency]79139.0
1$79,919Total revenue2016[Currency]79919.0
2(1.0)%Total revenueYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]-1.0
3(1.3)%Total revenueYr.-to-Yr. Percent Change Adjusted for Currency[Number]-1.3
4$78,793Geographies2017[Currency]78793.0
5$79,594Geographies2016[Currency]79594.0
6(1.0)%GeographiesYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]-1.0
7(1.4)%GeographiesYr.-to-Yr. Percent Change Adjusted for Currency[Number]-1.4
837,479Americas2017[Number]37479.0
937,513Americas2016[Number]37513.0
10(0.1)AmericasYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]-0.1
11(0.6)AmericasYr.-to-Yr. Percent Change Adjusted for Currency[Number]-0.6
1224,345Europe/Middle East/Africa2017[Number]24345.0
1324,769Europe/Middle East/Africa2016[Number]24769.0
14(1.7)Europe/Middle East/AfricaYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]-1.7
15(2.8)Europe/Middle East/AfricaYr.-to-Yr. Percent Change Adjusted for Currency[Number]-2.8
1616,970Asia Pacific2017[Number]16970.0
1717,313Asia Pacific2016[Number]17313.0
18(2.0)Asia PacificYr.-to-Yr. Yr.-to-Yr. Percent Change Percent C...[Number]-2.0
19(1.1)Asia PacificYr.-to-Yr. Percent Change Adjusted for Currency[Number]-1.1
\n", + "
" + ], + "text/plain": [ + " text row_header_texts_0 \\\n", + "0 $79,139 Total revenue \n", + "1 $79,919 Total revenue \n", + "2 (1.0)% Total revenue \n", + "3 (1.3)% Total revenue \n", + "4 $78,793 Geographies \n", + "5 $79,594 Geographies \n", + "6 (1.0)% Geographies \n", + "7 (1.4)% Geographies \n", + "8 37,479 Americas \n", + "9 37,513 Americas \n", + "10 (0.1) Americas \n", + "11 (0.6) Americas \n", + "12 24,345 Europe/Middle East/Africa \n", + "13 24,769 Europe/Middle East/Africa \n", + "14 (1.7) Europe/Middle East/Africa \n", + "15 (2.8) Europe/Middle East/Africa \n", + "16 16,970 Asia Pacific \n", + "17 17,313 Asia Pacific \n", + "18 (2.0) Asia Pacific \n", + "19 (1.1) Asia Pacific \n", + "\n", + " column_header_texts attributes.type value \n", + "0 2017 [Currency] 79139.0 \n", + "1 2016 [Currency] 79919.0 \n", + "2 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] -1.0 \n", + "3 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] -1.3 \n", + "4 2017 [Currency] 78793.0 \n", + "5 2016 [Currency] 79594.0 \n", + "6 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] -1.0 \n", + "7 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] -1.4 \n", + "8 2017 [Number] 37479.0 \n", + "9 2016 [Number] 37513.0 \n", + "10 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] -0.1 \n", + "11 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] -0.6 \n", + "12 2017 [Number] 24345.0 \n", + "13 2016 [Number] 24769.0 \n", + "14 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] -1.7 \n", + "15 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] -2.8 \n", + "16 2017 [Number] 16970.0 \n", + "17 2016 [Number] 17313.0 \n", + "18 Yr.-to-Yr. Yr.-to-Yr. Percent Change Percent C... [Number] -2.0 \n", + "19 Yr.-to-Yr. Percent Change Adjusted for Currency [Number] -1.1 " + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exploded_df[\"value\"] = \\\n", + " tp.io.watson.tables.convert_cols_to_numeric(exploded_df[[\"text\"]])\n", + "exploded_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now all the cell values have been converted to floating-point numbers, but only some of these numbers represent revenue. Looking at the 2017 data, Allison can see that the revenue numbers have 4-digit years in their column headers. So she filters the DataFrame down to just those rows with 4-digit numbers in the \"column_header_texts\" column." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "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", + "
textrow_header_texts_0column_header_textsattributes.typevalue
0$79,139Total revenue2017[Currency]79139.0
1$79,919Total revenue2016[Currency]79919.0
4$78,793Geographies2017[Currency]78793.0
5$79,594Geographies2016[Currency]79594.0
837,479Americas2017[Number]37479.0
937,513Americas2016[Number]37513.0
1224,345Europe/Middle East/Africa2017[Number]24345.0
1324,769Europe/Middle East/Africa2016[Number]24769.0
1616,970Asia Pacific2017[Number]16970.0
1717,313Asia Pacific2016[Number]17313.0
\n", + "
" + ], + "text/plain": [ + " text row_header_texts_0 column_header_texts attributes.type \\\n", + "0 $79,139 Total revenue 2017 [Currency] \n", + "1 $79,919 Total revenue 2016 [Currency] \n", + "4 $78,793 Geographies 2017 [Currency] \n", + "5 $79,594 Geographies 2016 [Currency] \n", + "8 37,479 Americas 2017 [Number] \n", + "9 37,513 Americas 2016 [Number] \n", + "12 24,345 Europe/Middle East/Africa 2017 [Number] \n", + "13 24,769 Europe/Middle East/Africa 2016 [Number] \n", + "16 16,970 Asia Pacific 2017 [Number] \n", + "17 17,313 Asia Pacific 2016 [Number] \n", + "\n", + " value \n", + "0 79139.0 \n", + "1 79919.0 \n", + "4 78793.0 \n", + "5 79594.0 \n", + "8 37479.0 \n", + "9 37513.0 \n", + "12 24345.0 \n", + "13 24769.0 \n", + "16 16970.0 \n", + "17 17313.0 " + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rows_to_retain = exploded_df[((exploded_df[\"column_header_texts\"].str.fullmatch(\"\\d{4}\"))) &\n", + " (exploded_df[\"text\"].str.len() > 0)].copy()\n", + "rows_to_retain" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That's looking good! Now Allison drops the unnecessary columns and gives some more friendly names to the columns that remain." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "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", + "
YearRegionRevenue
02017Total revenue79139.0
12016Total revenue79919.0
42017Geographies78793.0
52016Geographies79594.0
82017Americas37479.0
92016Americas37513.0
122017Europe/Middle East/Africa24345.0
132016Europe/Middle East/Africa24769.0
162017Asia Pacific16970.0
172016Asia Pacific17313.0
\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "0 2017 Total revenue 79139.0\n", + "1 2016 Total revenue 79919.0\n", + "4 2017 Geographies 78793.0\n", + "5 2016 Geographies 79594.0\n", + "8 2017 Americas 37479.0\n", + "9 2016 Americas 37513.0\n", + "12 2017 Europe/Middle East/Africa 24345.0\n", + "13 2016 Europe/Middle East/Africa 24769.0\n", + "16 2017 Asia Pacific 16970.0\n", + "17 2016 Asia Pacific 17313.0" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rows_to_retain.rename(\n", + " columns={\n", + " \"row_header_texts_0\": \"Region\",\n", + " \"column_header_texts\": \"Year\",\n", + " \"value\": \"Revenue\"\n", + " })[[\"Year\", \"Region\", \"Revenue\"]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The code from the last few cells worked to clean up the 2017 data, so Allison copies and pastes that code into a Python function:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "def dataframe_for_table(table: str):\n", + "\n", + " table_data = tp.io.watson.tables.parse_response({\"tables\":[table]})\n", + " exploded_df, _, _ = tp.io.watson.tables.make_exploded_df(\n", + " table_data, col_explode_by=\"concat\")\n", + " \n", + " if \"column_header_texts\" in exploded_df.columns and \"row_header_texts_0\" in exploded_df.columns: \n", + " rows_to_retain = exploded_df[((exploded_df[\"column_header_texts\"].str.fullmatch(\"\\d{4}\"))\n", + " & (exploded_df[\"text\"].str.len() > 0)\n", + " )].copy()\n", + " rows_to_retain[\"value\"] = tp.io.watson.tables.convert_cols_to_numeric(\n", + " rows_to_retain[[\"text\"]])\n", + " rows_to_retain[\"file\"] = \"filename\"\n", + " return (\n", + " rows_to_retain.rename(columns={\n", + " \"row_header_texts_0\": \"Region\", \"column_header_texts\": \"Year\", \"value\": \"Revenue\"})\n", + " [[\"Year\", \"Region\", \"Revenue\"]]\n", + " )\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then she calls that function on the Watson Discovery output from the 2017 annual report to verify that it produces the same answer. " + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "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", + "
YearRegionRevenue
02017Total revenue79139.0
12016Total revenue79919.0
42017Geographies78793.0
52016Geographies79594.0
82017Americas37479.0
92016Americas37513.0
122017Europe/Middle East/Africa24345.0
132016Europe/Middle East/Africa24769.0
162017Asia Pacific16970.0
172016Asia Pacific17313.0
\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "0 2017 Total revenue 79139.0\n", + "1 2016 Total revenue 79919.0\n", + "4 2017 Geographies 78793.0\n", + "5 2016 Geographies 79594.0\n", + "8 2017 Americas 37479.0\n", + "9 2016 Americas 37513.0\n", + "12 2017 Europe/Middle East/Africa 24345.0\n", + "13 2016 Europe/Middle East/Africa 24769.0\n", + "16 2017 Asia Pacific 16970.0\n", + "17 2016 Asia Pacific 17313.0" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dataframe_for_table(tables[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Looks good! Time to run the same function over an entire stack of reports." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Allison calls her `dataframe_for_table()` function on each of the tables, then concatenates all of the resulting Pandas DataFrames into a single large DataFrame." + ] + }, + { + "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", + "
YearRegionRevenue
02017Total revenue79139.0
12016Total revenue79919.0
42017Geographies78793.0
52016Geographies79594.0
82017Americas37479.0
............
172009Asia Pacific20710.0
02018Total revenue79591.0
52018Americas36994.0
92018Europe/Middle East/Africa25491.0
132018Asia Pacific17106.0
\n", + "

170 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "0 2017 Total revenue 79139.0\n", + "1 2016 Total revenue 79919.0\n", + "4 2017 Geographies 78793.0\n", + "5 2016 Geographies 79594.0\n", + "8 2017 Americas 37479.0\n", + ".. ... ... ...\n", + "17 2009 Asia Pacific 20710.0\n", + "0 2018 Total revenue 79591.0\n", + "5 2018 Americas 36994.0\n", + "9 2018 Europe/Middle East/Africa 25491.0\n", + "13 2018 Asia Pacific 17106.0\n", + "\n", + "[170 rows x 3 columns]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "revenue_df = pd.concat([dataframe_for_table(t) for t in tables])\n", + "revenue_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Allison can see that the first four lines of this DataFrame contain total worldwide revenue; and that this total occurred\n", + "under different names in different documents. Allison is interested in the fine-grained revenue figures, not\n", + "the totals, so she needs to filter out all these rows with worldwide revenue.\n", + "\n", + "What are all the names of geographic regions that IBM annual reports have used over the last ten years?" + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Region
0Total revenue
4Geographies
8Americas
12Europe/Middle East/Africa
16Asia Pacific
0Total revenue:
4Geographies:
92009. The increase in total expense and other ...
25Examples of the company's investments include:
29NaN
33• Industry sales skills to support Smarter Planet
16Asia Pacifi c
3of intellectual property
6Licensing/royalty-based fees
9Custom development income
12Total
3other (income)
\n", + "
" + ], + "text/plain": [ + " Region\n", + "0 Total revenue\n", + "4 Geographies\n", + "8 Americas\n", + "12 Europe/Middle East/Africa\n", + "16 Asia Pacific\n", + "0 Total revenue:\n", + "4 Geographies:\n", + "9 2009. The increase in total expense and other ...\n", + "25 Examples of the company's investments include:\n", + "29 NaN\n", + "33 • Industry sales skills to support Smarter Planet\n", + "16 Asia Pacifi c\n", + "3 of intellectual property\n", + "6 Licensing/royalty-based fees\n", + "9 Custom development income\n", + "12 Total\n", + "3 other (income)" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "revenue_df[[\"Region\"]].drop_duplicates()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It looks like all the worldwide revenue figures are under some variation of \"Geographies\" or \"Total revenue\". \n", + "Allison uses Pandas' string matching facilities to keep the rows whose \"Region\" column contains the \n", + "name of the regions in the world." + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
YearRegionRevenue
82017Americas37479.0
92016Americas37513.0
122017Europe/Middle East/Africa24345.0
132016Europe/Middle East/Africa24769.0
162017Asia Pacific16970.0
............
162010Asia Pacific23150.0
172009Asia Pacific20710.0
52018Americas36994.0
92018Europe/Middle East/Africa25491.0
132018Asia Pacific17106.0
\n", + "

93 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "8 2017 Americas 37479.0\n", + "9 2016 Americas 37513.0\n", + "12 2017 Europe/Middle East/Africa 24345.0\n", + "13 2016 Europe/Middle East/Africa 24769.0\n", + "16 2017 Asia Pacific 16970.0\n", + ".. ... ... ...\n", + "16 2010 Asia Pacific 23150.0\n", + "17 2009 Asia Pacific 20710.0\n", + "5 2018 Americas 36994.0\n", + "9 2018 Europe/Middle East/Africa 25491.0\n", + "13 2018 Asia Pacific 17106.0\n", + "\n", + "[93 rows x 3 columns]" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "geo_revenue_df = (\n", + " revenue_df[(\n", + " (revenue_df[\"Region\"].str.contains(\"Americas\", case=False))\n", + " | (revenue_df[\"Region\"].str.contains(\"Europe/Middle East/Africa\", case=False))\n", + " | (revenue_df[\"Region\"].str.contains(\"Asia Pacifi\", case=False))\n", + " )]).copy()\n", + "geo_revenue_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now every row contains a regional revenue figure. What are the regions represented? " + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "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", + "
Region
8Americas
12Europe/Middle East/Africa
16Asia Pacific
16Asia Pacifi c
\n", + "
" + ], + "text/plain": [ + " Region\n", + "8 Americas\n", + "12 Europe/Middle East/Africa\n", + "16 Asia Pacific\n", + "16 Asia Pacifi c" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "geo_revenue_df[[\"Region\"]].drop_duplicates()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That's strange — one of the regions is \"Asia Pacifi c\", with a space before the last \"c\". It looks like the PDF conversion on the 2016 annual report added an extra space. Allison uses the function `pandas.Series.replace()` to correct that issue." + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
YearRegionRevenue
82017Americas37479.0
92016Americas37513.0
122017Europe/Middle East/Africa24345.0
132016Europe/Middle East/Africa24769.0
162017Asia Pacific16970.0
............
162010Asia Pacific23150.0
172009Asia Pacific20710.0
52018Americas36994.0
92018Europe/Middle East/Africa25491.0
132018Asia Pacific17106.0
\n", + "

93 rows × 3 columns

\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "8 2017 Americas 37479.0\n", + "9 2016 Americas 37513.0\n", + "12 2017 Europe/Middle East/Africa 24345.0\n", + "13 2016 Europe/Middle East/Africa 24769.0\n", + "16 2017 Asia Pacific 16970.0\n", + ".. ... ... ...\n", + "16 2010 Asia Pacific 23150.0\n", + "17 2009 Asia Pacific 20710.0\n", + "5 2018 Americas 36994.0\n", + "9 2018 Europe/Middle East/Africa 25491.0\n", + "13 2018 Asia Pacific 17106.0\n", + "\n", + "[93 rows x 3 columns]" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "geo_revenue_df[\"Region\"] = geo_revenue_df[\"Region\"].replace(\"Asia Pacifi c\", \"Asia Pacific\")\n", + "geo_revenue_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Allison inspects the time series of revenue for the \"Americas\" region:" + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
YearRegionRevenue
92008Americas42807.0
92009Americas40184.0
82009Americas40184.0
92009Americas40184.0
82010Americas42044.0
92010Americas42044.0
82010Americas42044.0
92010Americas42044.0
82011Americas44944.0
82011Americas44944.0
92011Americas44944.0
92011Americas44944.0
82012Americas44556.0
82012Americas44556.0
92012Americas44556.0
92013Americas43249.0
92013Americas43249.0
82013Americas43249.0
82014Americas41410.0
82014Americas41410.0
92014Americas41410.0
92014Americas41410.0
92015Americas38486.0
82015Americas38486.0
82015Americas38486.0
92015Americas38486.0
82016Americas37513.0
82016Americas37513.0
92016Americas37513.0
82017Americas37479.0
52018Americas36994.0
\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "9 2008 Americas 42807.0\n", + "9 2009 Americas 40184.0\n", + "8 2009 Americas 40184.0\n", + "9 2009 Americas 40184.0\n", + "8 2010 Americas 42044.0\n", + "9 2010 Americas 42044.0\n", + "8 2010 Americas 42044.0\n", + "9 2010 Americas 42044.0\n", + "8 2011 Americas 44944.0\n", + "8 2011 Americas 44944.0\n", + "9 2011 Americas 44944.0\n", + "9 2011 Americas 44944.0\n", + "8 2012 Americas 44556.0\n", + "8 2012 Americas 44556.0\n", + "9 2012 Americas 44556.0\n", + "9 2013 Americas 43249.0\n", + "9 2013 Americas 43249.0\n", + "8 2013 Americas 43249.0\n", + "8 2014 Americas 41410.0\n", + "8 2014 Americas 41410.0\n", + "9 2014 Americas 41410.0\n", + "9 2014 Americas 41410.0\n", + "9 2015 Americas 38486.0\n", + "8 2015 Americas 38486.0\n", + "8 2015 Americas 38486.0\n", + "9 2015 Americas 38486.0\n", + "8 2016 Americas 37513.0\n", + "8 2016 Americas 37513.0\n", + "9 2016 Americas 37513.0\n", + "8 2017 Americas 37479.0\n", + "5 2018 Americas 36994.0" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "geo_revenue_df[geo_revenue_df[\"Region\"] == \"Americas\"].sort_values(\"Year\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Every year from 2008 to 2019 is present, but many of the years appear multiple times. That's to be expected, \n", + "since each of the annual reports contains two years of geographical revenue figures and some of the annual reports contain two tables.\n", + "Allison drops the duplicate values using `pandas.DataFrame.drop_duplicates()`." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
YearRegionRevenue
82017Americas37479.0
92016Americas37513.0
122017Europe/Middle East/Africa24345.0
132016Europe/Middle East/Africa24769.0
162017Asia Pacific16970.0
172016Asia Pacific17313.0
92015Americas38486.0
132015Europe/Middle East/Africa26073.0
172015Asia Pacific16871.0
82010Americas42044.0
92009Americas40184.0
122010Europe/Middle East/Africa31866.0
132009Europe/Middle East/Africa32583.0
162010Asia Pacific23150.0
172009Asia Pacific20710.0
92008Americas42807.0
132008Europe/Middle East/Africa37020.0
172008Asia Pacific21111.0
92014Americas41410.0
132014Europe/Middle East/Africa30700.0
172014Asia Pacific20216.0
92013Americas43249.0
132013Europe/Middle East/Africa31628.0
172013Asia Pacific22923.0
92012Americas44556.0
132012Europe/Middle East/Africa31775.0
172012Asia Pacific25937.0
92011Americas44944.0
132011Europe/Middle East/Africa33952.0
172011Asia Pacific25273.0
52018Americas36994.0
92018Europe/Middle East/Africa25491.0
132018Asia Pacific17106.0
\n", + "
" + ], + "text/plain": [ + " Year Region Revenue\n", + "8 2017 Americas 37479.0\n", + "9 2016 Americas 37513.0\n", + "12 2017 Europe/Middle East/Africa 24345.0\n", + "13 2016 Europe/Middle East/Africa 24769.0\n", + "16 2017 Asia Pacific 16970.0\n", + "17 2016 Asia Pacific 17313.0\n", + "9 2015 Americas 38486.0\n", + "13 2015 Europe/Middle East/Africa 26073.0\n", + "17 2015 Asia Pacific 16871.0\n", + "8 2010 Americas 42044.0\n", + "9 2009 Americas 40184.0\n", + "12 2010 Europe/Middle East/Africa 31866.0\n", + "13 2009 Europe/Middle East/Africa 32583.0\n", + "16 2010 Asia Pacific 23150.0\n", + "17 2009 Asia Pacific 20710.0\n", + "9 2008 Americas 42807.0\n", + "13 2008 Europe/Middle East/Africa 37020.0\n", + "17 2008 Asia Pacific 21111.0\n", + "9 2014 Americas 41410.0\n", + "13 2014 Europe/Middle East/Africa 30700.0\n", + "17 2014 Asia Pacific 20216.0\n", + "9 2013 Americas 43249.0\n", + "13 2013 Europe/Middle East/Africa 31628.0\n", + "17 2013 Asia Pacific 22923.0\n", + "9 2012 Americas 44556.0\n", + "13 2012 Europe/Middle East/Africa 31775.0\n", + "17 2012 Asia Pacific 25937.0\n", + "9 2011 Americas 44944.0\n", + "13 2011 Europe/Middle East/Africa 33952.0\n", + "17 2011 Asia Pacific 25273.0\n", + "5 2018 Americas 36994.0\n", + "9 2018 Europe/Middle East/Africa 25491.0\n", + "13 2018 Asia Pacific 17106.0" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "geo_revenue_df.drop_duplicates([\"Region\", \"Year\"], inplace=True)\n", + "geo_revenue_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now Allison has a clean and complete set of revenue figures by geographical region for the years 2008-2019.\n", + "She uses Pandas' `pandas.DataFrame.pivot()` method to convert this data into a compact table." + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Year20082009201020112012201320142015201620172018
Region
Americas42807.040184.042044.044944.044556.043249.041410.038486.037513.037479.036994.0
Asia Pacific21111.020710.023150.025273.025937.022923.020216.016871.017313.016970.017106.0
Europe/Middle East/Africa37020.032583.031866.033952.031775.031628.030700.026073.024769.024345.025491.0
\n", + "
" + ], + "text/plain": [ + "Year 2008 2009 2010 2011 2012 \\\n", + "Region \n", + "Americas 42807.0 40184.0 42044.0 44944.0 44556.0 \n", + "Asia Pacific 21111.0 20710.0 23150.0 25273.0 25937.0 \n", + "Europe/Middle East/Africa 37020.0 32583.0 31866.0 33952.0 31775.0 \n", + "\n", + "Year 2013 2014 2015 2016 2017 \\\n", + "Region \n", + "Americas 43249.0 41410.0 38486.0 37513.0 37479.0 \n", + "Asia Pacific 22923.0 20216.0 16871.0 17313.0 16970.0 \n", + "Europe/Middle East/Africa 31628.0 30700.0 26073.0 24769.0 24345.0 \n", + "\n", + "Year 2018 \n", + "Region \n", + "Americas 36994.0 \n", + "Asia Pacific 17106.0 \n", + "Europe/Middle East/Africa 25491.0 " + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "revenue_table = geo_revenue_df.pivot(index=\"Region\", columns=\"Year\", values=\"Revenue\")\n", + "revenue_table" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then she uses that table to produce a plot of revenue by region over that 11-year period." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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F198MZmZmWzdupXQ0FC/z927dy8A6enpDBpUeRHojh07Vnvsffv2Ya0lNTW10raUlBS2bt1aoS0pKalSv/Dw8Cad1lNERERantrMotPZGNPL63GwV/t+747W2k2NFZhISzBo0CBP/frYsWMZMmQIt956K2eddRbt2rWjZ8+evPXWW36fW1aCk5qaSmZmZqXtu3btqvbYiYmJGGPIyMiotC0jI8NvQi8iIiJSm3n+3sFZubbstsbdPsenfb2/J4u0FeHh4Tz++ONkZmby3HPPMWHCBLZv305MTAyjRo2qdGvfvj3gTH25YMECduzY4dlXQUEBH374YbXHi46OZuTIkbz99tuUlpZ62rdu3coPP/zAiSee2CSvU0RERFq3mkbwf9csUYi0EpMnT+aII47gySefZP369bzyyiuMGzeOW265haFDh1JUVMTGjRt5//33mTNnDlFRUdx88808//zzjB8/nvvvv5/w8HCeeuopwsPDK02T6euhhx5i0qRJnH766fzxj38kNzeX+++/n/j4eG655ZZmetUiIiLSmlSb4FtrZzZXICKtxcMPP8z48eN56aWX+OSTT3j00Ud58cUX2bx5M9HR0fTu3ZtJkyYRFhYGQPv27fniiy+4/vrrufjii2nXrh1/+MMf2LNnD6+++mq1x5owYQIffvghDzzwAOeeey5hYWGceOKJ/PWvf6VTp07N8XJFRESklTFeC9NKIxg1apRdtGhRldtXr17N4Ycf3owRSUtUWlrKiBEjPMm/COjfBxERqRtjzGJr7Sjf9tpcZCsiDXTvvffSp08funfvzt69e3nppZf49ddfmTdvXqBDExERkTZGCb5IMzDG8OCDD7Jz506MMQwZMoQ5c+YwceLEQIcmIiIibYwSfJFm8OCDD/Lggw8GOgwRERE5BNRmmkwREREREWklmj3BN8Z0Mcb83RizwBiTb4yxxpgefvpFGGMeN8akG2MK3P2P99MvyBhzpzFmizHmoDHmF2PMWVUc+wpjzBpjTKExZq0x5g9V9JtqjFnq3t9WY8w9xphgf31FRERERFqSQIzg9wHOBfYB31bT79/AFcB9wOlAOvCJMWaYT7+HgOnAs8BEYCHwtjHmNO9OxpgrgBeAd4EJwNvAc8aYq336jXf3+dm9v2eAe4A/1+1lioiIiIg0v0DU4H9jre0IYIy5HDjVt4MxZijwG+D31tpX3G1fAyuBB4HJ7rYOwJ+AR621T7if/pUxpg/wKDDP3S8EeAR4zVp7t1e/TsBDxpiXrLXF7vZHge+stVd69YsB7jHGPG2tzWi0d0JEREREpJE1+wi+tdZVi26TgWLgTa/nlQD/A8YbY8LdzeOBMOB1n+e/Dgw2xvR0Px4DJPvp9xrQDjgWwBjTFRhWRb9QnBF9EREREZEWq6VeZDsQ2GytzfdpX4mT0Pfx6lcIbPDTD2CAVz+AFfXpZ63dDOR79RMRERERaZFaaoKfhFOj7yvLa3vZ/X5beTlef/3ws8/a9itrS/LTjjHmSmPMImPMot27d/vrIiIiIiLSLFpqgt+qWGtftNaOstaOSk5ODnQ4AXPFFVdgjOGmm24KdCiVzJ8/H2MM8+fPD3QoIiIiIk2qpSb4+4BEP+1lI+hZXv0SjDGmFv3ws8/a9itry/LTLkBBQQFvvfUWAP/5z38oKSkJcEQVjRgxggULFjBixIhAhyIiIiLSpFpqgr8S6GmMifJpHwAUUV5zvxIIB3r76QewyqsflNfY16mfe57+KK9+4mPOnDlkZ2dz2mmnkZmZyccffxzokAAoLS2lpKSEuLg4Ro8eTVxcXKBDEhEREWlSLTXBn4sza805ZQ3uqS7PAz611ha6mz/GmW3nQp/nXwSscF8cC7AA2FNFvyzgewBr7Tbglyr6FQMf1f8ltW0zZ84kMTGRGTNmEBkZycyZMytsnz59OsYY1qxZw/jx44mOjqZbt2688sorALz22mv079+fmJgYTjrpJDZu3FjpGC+++CJDhw4lIiKC9u3bc9lll5GVVfFLFWMMd999N48++ig9e/YkLCyM5cuXV1miM3v2bI455hhiYmKIi4vjyCOP5P333/dsf/bZZxkzZgxJSUkkJCQwevRoPvzwwwr7KCkp4d5776V3796e2I499li+++67hrylIiIiIvUSiHnwMcac7f5xpPt+ojFmN7DbWvu1tXapMeZN4G/GmFBgM3A10BOv5Ntam2mMeQq40xiTAyzBOQkYi3uufHe/YmPMvTgLW6UBn7v7/B64zlpb5BXeXcAHxpgXgP8Cw3EWunpGc+D7t3PnTj7//HOuuOIKkpOTmTp1KrNmzWLfvn0kJlasdjrnnHO44oor+NOf/sRzzz3H73//e9avX8/8+fN59NFHKS4u5oYbbuA3v/kNP/74o+d5d9xxB08++STXX389jz/+OGlpadxzzz2sWLGCH374geDg8oWGZ8yYQa9evXjiiSeIjo6mU6dOHDhwoFLcf//737n++uuZOnUqM2fOJCYmhiVLlrBlyxZPny1btnD55ZfTo0cPSkpKmDt3LqeffjofffQREyZMAOCxxx7j6aef5pFHHmHYsGFkZ2ezaNGiSicfIiIiIs0hIAk+ziqy3p5z338NnOj++Xc4i1M9DCTgjKxPsNYu8Xnu3UAucAOQAqwFzrXWfuDdyVr7T2OMBW4BbgW2Addaa5/z6TfPfQJyP3ApsAtnFdtH6vE6a+WBuStZtTO7qXZfKwM6xXH/Gb4VTLXz+uuvU1paysUXXwzAJZdcwn//+1/efPNN/vCHP1Toe+utt3r6jRo1irlz5/LCCy+wefNmT/lMeno6N9xwA1u3bqV79+5s2bKFxx9/nPvvv5/77rvPs69+/fpx7LHHMnfuXKZOneppt9by6aefEhkZ6WlbvXp1hTiys7O56667mDZtGrNmzfK0jx8/vkK/J554wvOzy+Vi3LhxrFu3jueff96T4C9YsIBTTz2VG264wdP3jDPOqP0bKCIiItKIAlKiY601VdxO9OpTYK292VqbYq2NsNYeZa2d72dfpdbah6213a214dbaIdbad6o47gvW2n7ufn19k3uvfrOstUPd/bpZax+01pY21utva2bOnEnfvn0ZM2YMACeffDKdOnWqVKYDMHFi+VphiYmJdOjQoVJtfP/+/QHYvn07AJ999hkul4sLL7yQkpISz+2oo44iNjaWb775psIxJkyYUCG59+eHH34gNzeXK6+8stp+ixcv5vTTT6djx46EhIQQGhrKZ599xtq1az19jjjiCObNm8fdd9/Nd999R1FRUTV7FBEREWlagRrBFy/1HTlvCRYtWsSqVau4/fbb2b9/v6f9zDPP5Nlnn2XdunX069fP0+5bshMWFua3DeDgwYMAZGZmAtCnTx/82bt3b4XHqampNcZd9pwuXbpU2Wf79u2MGzeOAQMG8Pe//51u3boREhLCvffeW+EbgbvuuouIiAhef/11/vznPxMTE8PZZ5/N448/Tvv27WuMRURERKQxKcGXBikbpX/sscd47LHHKm1/9dVXefjhhxt0jHbt2gHw6aefVjoZ8N5epvKsqZWVJd5paWkMGjTIb5+PP/6YAwcO8NZbb1U4EcjPr7jAcmhoKLfffju33347GRkZfPDBB9x8883k5+fz5ptv1hiLiIiISGNSgi/1VlRUxH//+1+OOuooHn300Urbb7rpJl577TUeeuihBh3nlFNOISgoiG3btnHKKac0aF9ljj76aGJiYnjxxRcr1d2XKUvkQ0NDPW3r1q3j+++/r3LkPyUlhcsvv5x58+axYsWKRolVREREpC6U4Eu9ffjhh+zdu5cnn3ySE088sdL2q666iquvvrrBq8f27t2b22+/nWuvvZa1a9dywgknEBERwfbt2/nss8+4/PLLOemkk+q0z9jYWP7yl79w3XXXcdZZZ3HhhRcSGxvLsmXLiIiI4LrrruPkk08mJCSEiy++mFtuuYX09HTuv/9+unXrhsvl8uxrypQpDB06lBEjRpCYmMjSpUv5+OOPueqqqxr0ukVERETqo6XOgy+twMyZM4mNjeWcc87xu/2CCy7wOyd+ffz5z3/mxRdf5JtvvuHcc89lypQpPPbYYyQmJtK3b9967fPaa6/l7bffZseOHVx44YWcddZZvPPOO/Ts2ROAgQMH8sYbb7B161YmT57MX//6Vx599FGOP/74Cvs5/vjj+fTTT7nsssuYMGECzz//PLfddht//etfG/y6RUREROrKWGsDHUObMmrUKLto0aIqt69evZrDDz+8GSMSkdZC/z6IiEhdGGMWW2tH+bZrBF9EREREpA1Rgi8iIiIi0oYowRcRERERaUOU4IuIiIiItCFK8EVERERE2hAl+CIiIiIibYgSfBERERGRNkQJvoiIiIhIG6IEX0RERESkDVGCLyIiIiLShijBl0ZzxRVXYIzhpptuqtfz58+fjzGG+fPnN0o8l156KcYYzy05OZnjjz+ejz/+uFH2781f7C6XixtvvJHU1FSCgoKYOnUqW7ZswRjDjBkzGj0GEREREVCCL42koKCAt956C4D//Oc/lJSU1HkfI0aMYMGCBYwYMaLR4kpOTmbBggUsWLCAf/3rX1hrOe200/jiiy8a7RjgP/Z33nmHZ555hltvvZXvv/+ev/71r6SmprJgwQImTZrUqMcXERERKRMS6ACkbZgzZw7Z2dmcdtppzJs3j48//pjTTz+9TvuIi4tj9OjRjRpXWFhYhX2OHTuWbt268cwzzzBu3LhGO46/2FevXg3AjTfeSFBQ+bl0Y79GEREREW8awZdGMXPmTBITE5kxYwaRkZHMnDmzUp9169Yxbdo0OnToQEREBN26deOcc87xjPb7K3P59NNPOe2000hNTSUqKopBgwbx5JNPUlpaWq844+Li6NevHxs2bADgf//7H2PHjiU5OZmYmBiGDx/uN/aSkhIee+wxBgwYQEREBMnJyUyYMIE1a9b4jb1Hjx5Mnz4dgODgYE9ZTlUlOl9//TWnnHIK8fHxREdHM3ToUP7973/X6zWKiIjIoU0j+NJgO3fu5PPPP+eKK64gOTmZqVOnMmvWLPbt20diYqKn36RJk0hMTOT555+nffv2pKWlMW/ePFwuV5X73rRpE+PGjeO6664jIiKCRYsWMX36dHbv3s2jjz5a51hLSkrYvn07PXv29Oz/7LPP5o477iAoKIhvvvmGyy+/nIKCAv7whz94nnf++eczZ84cbrzxRk4++WQOHjzIN998Q3p6Ov379690nNmzZ/N///d/zJgxgwULFgDQu3dv8vLyKvV97733OOusszjmmGN44YUXaN++PStXrmTr1q11fn0iIiIiSvBbgo/ugIzlgY0hZTBMrHvCDPD6669TWlrKxRdfDMAll1zCf//7X958801Pkrxnzx42bNjAe++9x+TJkz3P/c1vflPtvr2TbGstxx13HEVFRTzxxBP8+c9/rlD6UpWybwgyMjJ46KGHyMjI4Pbbbwfgrrvu8vRzuVyceOKJpKen8/zzz3uO/eWXX/Luu+/yzDPPcP3113v6T506tcpjDh8+nM6dOwMVS3J8E3xrLTfccAPDhg3jq6++8ryek08+ucbXJSIiIuKPEnxpsJkzZ9K3b1/GjBkDOMlpp06dmDlzpidJbteuHb169eKOO+5g165dnHjiifTt27fGfaenpzN9+nQ+/vhjdu7cWeHi3czMTFJSUqp9flpaGqGhoZ7HMTExPPjgg55Eff369dx333188803ZGRkeL5NCA8P9zzn008/xRjDFVdcUct3pPbWrl3L1q1bPd8giIiIiDSUEvyWoJ4j5y3BokWLWLVqFbfffjv79+/3tJ955pk8++yzrFu3jn79+mGM4bPPPmP69Onceeed7N27l549e3Lrrbdy9dVX+923y+Vi8uTJ7Ny5k+nTp9O/f38iIyOZM2cOjzzyCAcPHqwxvg4dOvDhhx9ijKFdu3Z07dqV4OBgAHJzcznllFOIiori0UcfpXfv3oSFhfH888/z8ssve/axd+9ekpKSiIyMbNib5cfevXsB6NKlS6PvW0RERA5NSvClQcouSH3sscd47LHHKm1/9dVXefjhhwHo1asXr776KtZafvnlF5599ln++Mc/0qNHDyZOnFjpuRs3bmTRokW89tprXHTRRZ72uXPn1jq+0NBQRo0a5XfbggUL2Lp1K99++y3HHnusp913is/27duTlZVFQUFBoyf57du3B5xvGkREREQag2oCpN6Kior473//y1FHHcVXX31V6TZs2DBee+01rLUVnmeMYdiwYTz11FMArFixwu/+8/PzASqU2BQXF/PGG280Svz+9r9v3z7ee++9Cv1OPfVUrLW89NJLjXJcb/369aNHjx689NJLld4nERERkfrQCL7U24cffsjevXt58sknOfHEEyttv+qqq7j66quZP38+7dq144YbbuC8886jT58+lJaWMmPGDEJCQhg7dqzf/R9++OF0796du+++m+DgYEJDQ3n66acbLf6jjz6auLg4rrnmGh544AHy8vJ4+OGHad++PQcOHPD0O+mkkzjrrLO4+eab2b59O2PHjqW4uJhvvvmGSZMm+X3ttWWM4W9/+xtnnnkmY8eO5Q9/+APJycmsXr2azMxMHnjggUZ4pSIiInIo0Qi+1NvMmTOJjY3lnHPO8bv9ggsu8MyJn5KSQrdu3XjqqaeYPHkyF1xwATt37uSDDz5g5MiRfp8fFhbGnDlzSElJ4eKLL+aaa67h+OOP54477miU+JOTk5k9ezalpaWcffbZ3HnnnVx++eUVyoHK/O9//2P69OnMmTOHyZMn8/vf/56VK1eSmpra4DimTJnCZ599BsBll13G5MmTefHFF+nRo0eD9y0iIiKHHqOygMY1atQou2jRoiq3r169msMPP7wZIxKR1kL/PoiISF0YYxZbaytdbKgRfBERERGRNkQJvoiIiIhIG6IEX0RERESkDVGCLyIiIiLShijBFxERERFpQ5Tgi4iIiIi0IUrwRURERETaECX4IiIiIiJtiBJ8EREREZE2RAm+iIiIiEgbogRfGmTGjBkYY/zeEhISAh1eo3r00Ufp27cvUPF1r1u3rlLfr7/+2rP9888/97Rfeuml9OjRo8Zjle1/y5Yt1fbbsmULxhhmzJhR52PUVlWfrzGGOXPmNNpxykyfPp0vv/yyyu0LFy7EGMOOHTsqtL/xxhsYYxg+fLjf5+Xl5fHb3/6WDh06YIzhxhtvrDEOY0yd4xcREQm0kEAHIG3D22+/TZcuXSq0hYS0rV+vOXPmMGXKlAptsbGxvPbaazz00EMV2mfOnElsbCw5OTkV2u+9915uuOGGJo+1sV166aVcddVVldoPO+ywRj/WAw88wN13383YsWP9bp8zZw4jR46s9Ps2c+ZMAJYtW8by5csZPHhwhe3/+Mc/+O9//8vLL79Mv379SE1NrTaOyy+/nAkTJjTglYiIiARG28rAJGCGDRtGnz59mmTfxcXFhISEBHQ0NT09nZ9++oknnniiQvuZZ57J66+/zoMPPuiJr6CggHfeeYezzjqrwsg6QO/evZsr5EbVuXNnRo8eHegwACfBv+iiiyq0paWl8cUXXzBx4kQ++ugjZs6cWemzWr16NZ06deLiiy+udv+FhYWEh4fTpUuXSicRIiIirYFKdKRZVFXu4FtOUlZy8txzz3HbbbfRqVMnwsPD2b9/P9Zann76aQ477DDCwsJITU3l2muvJTs7u8I+jTHcfffdPPLII3Tp0oXIyEiOP/54li1bVun4s2bNYvTo0URFRZGQkMA555zDtm3bKvV77733SE5O5uijj67Q/tvf/patW7fy3Xffedpmz56Ny+XirLPOqvH1AmzatIlJkyYRFRVFcnIyN9xwA4WFhZWem5+fzx//+EfatWtHTEwMkydPrlSmUpX8/Hxuv/12evbsSVhYGD179uSRRx7B5XLV6vm18eyzzzJmzBiSkpJISEhg9OjRfPjhhxX6lJSUcO+999K7d28iIiJo3749xx57rOf9K/sdeeSRRzxlQNOnT/c8f82aNaxdu5apU6dW2O9rr72Gy+XigQce4JhjjuGNN96gtLTUs72sjGn79u2e/c6fP5/58+djjGHWrFlcccUVJCcn07FjR8D/72xJSQmPPfYYAwYMICIiguTkZCZMmMCaNWsAOHjwIDfddBODBg0iJiaGlJQUzjjjDM92ERGR5qARfGkUpaWllJSUVGgLCgoiKKh+55CPPPIIRxxxBC+++CKlpaVERERw991385e//IVrrrmGM844g1WrVnHvvffyyy+/8PXXX1c41quvvkq3bt149tlnKSws5L777mPcuHGsX7+epKQkAP75z39y9dVX87vf/Y777ruPnJwcpk+fzgknnMCvv/5KbGysZ39z5szhjDPOqPR6unfvzvHHH89rr73Gcccd5zn2tGnTiImJqfF1FhUVccopp1BQUMA//vEPOnTowAsvvMCsWbMq9b3qqqt48803uf/++zniiCP47LPP+M1vflPjMUpKShg/frzn/Ro8eDALFy7koYceIisriyeffLLGfVhrK32+ULEMa8uWLVx++eX06NGDkpIS5s6dy+mnn85HH33kKXV57LHHePrpp3nkkUcYNmwY2dnZLFq0iKysLAAWLFjAmDFjKpQEeY+iz5kzhz59+jBo0KAKccycOZPDDz+cI444gosvvpirrrqKTz/9lIkTJ3r2O336dH755Rdmz54NwIABA1iyZAkA1113HRMnTuS1117j4MGDVb4P559/PnPmzOHGG2/k5JNP5uDBg3zzzTekp6fTv39/CgsLycnJ4Z577iE1NZWsrCyee+45xowZw+rVq0lJSanxvRYREWkoJfgtwGM/PcaarMCO8PVP6s/tR95e/+f371+pbdKkSf/f3r2HV1Xd+R9/fzEJicglEC4phaAjYLEzzmiUS/0hCnJHbFV0REV8xDo61VHGBwe1KRVoR6w3dMqMWERULqMDAgIFhAGhYKHVTkVERbQREZBrgZQE8v39sfc5TU5OQggnOXjyeT3PfjZZe+21196Lc873rLP22ixcuLBG5bVu3Zq5c+dGe1AjgeiIESN49tlnAejXrx8tW7bk5ptvZuHChVx11VXR/YuKili6dCmNGjUCoGvXrnTs2JEnn3ySRx99lEOHDjFmzBhGjhzJr371q+h+l1xyCZ07d+aFF16I3oR58OBBVq5cyeuvvx63rrfccgujR4/mmWeeYd++fSxfvpzFixdX6zynT5/Op59+yrp166JDYAYMGFBh/PiWLVt49dVXmTBhAg8++CAAffv25dChQ0yZMqXKY8ycOZM1a9awatUqevbsCUDv3r2BYLz7mDFjaNWqVZVlTJw4kYkTJ1ZI3717Nzk5OQDlhsSUlpbSu3dvPvroI375y19GA/x169bRt2/fcvchDBkyJPrvyDWobEhQvPsgfvvb3/Lhhx9G6zds2DDuvfdepk+fHg3wu3XrRk5ODg0bNoxb7iWXXMLUqVOrvAYrVqzg9ddf5+mnn+aee+6Jppf9NaFp06blyjl+/Dj9+vWjdevWzJw5k/vuu6/KY4iIiCSChuhIQsydO5cNGzaUW5566qkal3f11VeXGx6xfv16iouLK4y9vuGGG0hLS2PVqlXl0gcOHBgN7gE6dOhAt27dWLduHRAEmgcPHmT48OEcO3YsurRr147zzjuP1atXR/ddtGgRGRkZ9OnTJ25dr7vuOo4ePcqCBQt45ZVXaNOmTTSAPpF169bRrl27ckFngwYNGDZsWLl877zzDqWlpRXSb7jhhhMeY8mSJeTl5dGjR49y59q3b19KSkpYv379Ccu47bbbKrTvhg0bys2U9Lvf/Y7BgwfTunVr0tLSSE9PZ9myZWzZsiWa5+KLL2bRokU89NBDrFmzhuLi4hMeOyJyH0Ts8Jzp06fToEGD6P+NZs2aMXToUN544w0OHDhQrbK///3vnzDP0qVLMTNGjRpVZb45c+bQtWtXmjVrRlpaGo0aNeLQoUPlroOIiEhtUg/+aeBUes5PF9/97ncTepNt7AwnkSEcselpaWm0aNEiuj0iMo46Nm3Tpk0A7Nq1C6DSoD07Ozv673nz5tGvXz8yMzPj5m3cuDFXX301M2bM4LPPPmP48OHVHpq0Y8eOSusamy9eerx9Y+3atYvPP/+c9PT0uNv37NlzwjJyc3PJz8+vdHthYSG9e/emS5cuTJ48mfbt25OWlsYjjzzC5s2bo/nGjh1LZmYmL7/8MhMnTuSss87i2muvZdKkSdFfAioT7z6I4uJiZs2aRffu3WncuDH79+8HgoB99uzZzJkz54QBeeT8TmTPnj00b96crKysSvMsWLCA66+/nhEjRlBQUEBOTg4NGjRg4MCBVQ79ERERSSQF+FInIsFxcXExGRkZ0fTKgsvYmxsj4+a/+uorzj///Gj6sWPHooFXWTt37qxQ5s6dO2nbti0ALVq0AIL55suWFxEZf19cXMzixYt57rnnqjy/W265hUGDBlFaWsrMmTOrzFtWbm5u9EtHVfWPBKA7d+7knHPOqTRfPC1atODss89mzpw5cbcnYs78JUuWcODAAebMmVNuzPyRI0fK5UtPT2fMmDGMGTOGr776ioULF3L//fdz5MgRZs+eXeUx4t0HsWDBAvbu3cvatWvLfSmLmD59erUC/OrM0JSTk8PevXspKiqqNMifNWsW5557brnZk0pKSip8ARUREalNGqIjdSIvLw+A999/P5q2f/9+fvOb31Rr/27dupGRkcGsWbPKpc+ePZtjx47Rq1evcumLFi3i8OHD0b8/++wz1q9fT/fu3QHo0aMHjRs35pNPPiE/P7/CEpnffcWKFRw5coTBgwdXWb8rr7ySYcOGceedd8b9wlCZ7t27U1hYWG6YTGlpaYVgvGvXrjRo0KBCeuz1iKd///4UFhZy1llnxT3XE/WcV0ckkC/7K8FHH33E2rVrK92nTZs23H777fTp06fc/4uMjAyKiorK5Y3cBxFveE6jRo1Yvnw5K1euLLfceuutrF27lq1bt57y+UFwz4O7VzlW/8iRIxWe/zBjxoxyM/qIiIjUNvXgS0K89957fP311xXS8/PzSUtLY8CAATRt2pRRo0Yxbtw4jh49ymOPPVatmWYg6MEfPXo0P/vZz2jUqBEDBw5k8+bNPPzww1x66aUMGjSoXP6srCz69u3LAw88wNGjRykoKKBJkybRmxybNGnCpEmTuPvuu9m9e3e0ftu3b2fVqlX06tWLG2+8kXnz5nHZZZed8Km8Z5xxxkn13EeMGDGCn//85/zgBz9g4sSJtGrViilTplSY+rNz587ceOON/PjHP6a0tJSLL76YpUuXsmjRohMeY/jw4UybNo3evXszevRoLrjgAoqLi9m6dSvz589n3rx5nHnmmVWWsX379rhj9fPy8sjNzaVPnz6kpaVFbzjesWMHBQUFtG/fvtxUnEOHDuWCCy7gwgsvJDs7m3fffZclS5aUe4hWly5dePPNN+nfvz/Z2dl861vfYvXq1RXug9i1axeLFy/mpptuinvPQ5s2bXjxxRd56aWXGDdu3Amv04lcfvnlXHPNNdx///0UFhZyxRVXUFJSwurVqxk0aBC9evWif//+zJs3j/vuu4/BgwezceNGJk+enHJPdRYRkdOcu2tJ4HLRRRd5VT744IMqt3/TTJs2zYFKl927d0fzvv32256fn+9ZWVnesWNHnzFjho8YMcLz8vKiebZt2+aAP//88xWOVVpa6k888YR36tTJ09PTvU2bNn7XXXf5gQMHyuUDfOzYsT5hwgRv27atN2zY0C+99FJ/9913K5T55ptveq9evbxx48aelZXl5557ro8cOdI3bdrkpaWlnpub65MnT670vD/++ONKr83KlSsd8GXLlkXTYs/X3X3r1q0+YMAAz8rK8pycHL/nnnt8ypQpDvi2bdui+Q4fPux33nmnZ2dne6NGjXzIkCG+Zs0aB3zatGlVHqOoqMgLCgq8c+fOnpGR4dnZ2Z6fn+8FBQVeUlJS6TlErmdly6RJk6L5Zs+e7Z07d/aGDRt6ly5dfObMmRXq8vjjj3vXrl29efPmnpmZ6Z06dfKCggIvLi6O5lmzZo1feOGF3rBhQwe8oKDAr7/+er/mmmvK1evJJ590wFevXl1p3Xv06OEdOnTw0tJSHz58eIXrEq+NIgoKCjx4i/yrkpISHz9+vHfs2NHT09M9JyfHBwwY4B9++KG7ux8/ftwfeughz83N9aysLO/Zs6f//ve/97y8PB8xYkSV19k99d4fRESkdgEbPU48asE2SZT8/HzfuHFjpds3b97Md77znTqsUf0TedDV+PHjT6mcyJCewsJCPdE0iYqLi2nZsiXPPfdchVmUUo3eH0RE5GSY2e/cvcIsGBqiI1KJbt26oS/AyZeRkVHt6S5FREREN9mKiIiIiKQU9eBLylGvu4iIiNRn6sEXEREREUkhCvBFRERERFKIAvwk0BASEYml9wUREUkUBfh1LD09vcJTOkVEioqKaNiwYbKrISIiKUABfh1r1aoV27dv58iRI+qxE6nn3J2SkhL27t3LF198QYsWLZJdJRERSQGaRaeONWnSBIAvv/ySkpKSJNdGRJItLS2NzMxM2rdvT2ZmZrKrIyIiKUABfhI0adIkGuiLiIiIiCSShuiIiIiIiKQQBfhxmFk7M3vNzA6Y2UEz+x8za5/seomIiIiInIgC/BhmdiawAjgPGAHcDHQEVppZo2TWTURERETkRDQGv6JRwDlAZ3f/BMDM/g/4GPgh8EQS6yYiIiIiUiX14Fd0FbA+EtwDuPs2YC0wNGm1EhERERGpBgX4FZ0PvB8nfRPQpY7rIiIiIiJyUjREp6LmwL446XuB7Hg7mNkdwB3hn4fMbEst1a0qOcDXSTiu1C21c/2gdq4f1M6pT21cPySznfPiJSrATwB3/y/gv5JZBzPb6O75yayD1D61c/2gdq4f1M6pT21cP5yO7awhOhXtI35PfWU9+yIiIiIipw0F+BVtIhiHH6sL8EEd10VERERE5KQowK9oPtDNzM6JJJhZB+B74bbTVVKHCEmdUTvXD2rn+kHtnPrUxvXDadfO5u7JrsNpJXyY1R+AIuBhwIFHgcbA37n7oSRWT0RERESkSurBj+Huh4ErgI+AGcArwDbgCgX3IiIiInK6Uw++iIiIiEgKUQ9+EpnZtWb2upl9bmZFZrbFzH5mZo1j8mWb2VQz+9rMDpvZcjP72zjlZZrZJDPbEZa3zsx6xsnXwsyeNrNPw3zbzOxZM2tZm+dbX9VCO080s6VmtsfM3MxureLYo8zsQzM7Gh73zlo4RSE57WxmueExNprZfjPbbWZvxXvdy6lL5mu5zD49zKw0zK+prmtBkt+zs83sKTP7U/i+/YWZvZj4s5RktbOZnWlm48zso/C4hWb2kgX3eyaMAvzk+lfgODAW6A/8EvgnYJmZNQAwMwMWhNt/BFwDpAMrzezbMeW9AIwCfgwMBnYAvzazv49kCMubD9wITAIGhOsbgAXhdkmsRLfzj4AsYGFVBzWzUcB/Aq+H5f438B9m9k+JOS2JkYx2vgi4HngDuA64FfgL8L9mNjghZyVlJeW1HGFm6QSv6Z2nfCZSlWS9Z2cDa4A+BPcAXhnW5c8JOSuJlazX81TgAeB5YCBBW/cE3jKzs079tELuriVJC9AyTtotBDf2XhH+PTT8+/IyeZoSPFn3mTJpF4T5RpZJSwO2APPLpHUK890Rc9w7w/TOyb4uqbYksp3D9Abh+txwn1vjlJ8G7AKmx6T/iuBpe+nJvi6ptiSpnZsBaXHafguwOtnXJNWWZLRxTP6xwPvAhDB/Wk3PRcvp187AFOBzoEmyr0F9WJL0nn0mcAyYGJPeP9ynX6LOTz34SeTuu+MkbwjXbcP1VcCX7r6yzH4HCL5RDi2z31VACTC7TL5jwCygn5k1DJMzwvXBmOPuD9f6P5FgCW5n3L20GoftDrQEXo5JnwG0AC6tRhlyEpLRzu6+P3ydl007BrxX5piSIEl6LQNgZn9D0NN3F8F7vdSSZLSzBTP43QJMdffYz2epBUl6PZ8RLrUegymYO/1cFq43h+vzCXpsYm0C2pf5Oed8YJu7H4mTL4PgG2Xk79XAI2aWb2ZnmdklBMN6Frv7ZqQu1LSdqyvysLbYMjeF6y4nWZ7UTG23cwVmlkHwBU+v5bpRV208Bfhvd19dw/3l1NR2O19EMLxjp5m9Fo7NPmRm88zs7JpVWWqgVtvZ3f9M0NF2j5ldHsZg5xMMlf4D8FbNql2RAvzTiJm1BX4KLHf3jWFyc2BfnOx7w3V2NfM1B/Dgt6CBBD/hbyAY2/cO8CnB2DKpZafYztXVPFzHlrk3ZrvUkjpq53h+Anwb+PcElCVVqKs2NrObCALAB2pSTzk1ddTO3wrXjxOMC78KuAP4B4J7ahpXtqMkRh2+Z48E5gIrCGKw9wnG9V/p7sU1KC8uBfinifBb4BsEY7NG1vLhnge6EYy7vyxc5wOvRW4skdpRx+0sSZKsdjazG4EHgUfd/e26Om59VFdtbGbNgSeAse6+q7aOI/HV4Ws58tn7KXCDuy9z91eBYUB74KZaPHa9V8fv2eMJ2vNfCWKwmwmGzi4Oh2olhKbYOg2YWRbBeK5zgMvc/Ysym/cR/xtibA/tPiCvinx7w2MNAv4R6OPukZ+CVpvZp8BSYAjBf3JJsAS1c3VF8mcTzKYUW95epFbUcTuXPe4Q4EXgBXcvqGk5cmJ13MbjCV7Dc8ysWZiWGa6bmtlfPHhAoyRYHbfznnD9VvhLOwDu/o6ZHSToyZdaUJftHA7HeRC43d1fKJP+DsEDVm8Hnj6ZMiuj3tokC6c9e42gB32gu/8xJssm/jqeuqwuwJ/8r0/X3QScbWZnxslXDHwS/h2Zu3VDTL7fhuvvnNwZSHUksJ2rKzLWPrbMyNj7D06yPKmGJLRz5Li9CaZBnQv8sCZlSPUkoY27AH9HEADuC5cx4bavCZ62LgmWxPfsylT7hmypviS0c9wYzN0/JrjRNmExmAL8JAqHw7wCXAFc7e7r42SbD7Q1s8vK7NeEoKd9fpl8CwjGcF1XJl8awRzZS939aJj8Vbi+JOY4XcP19pqdjVQmwe1cXesIPvyHx6TfRNB7v7YGZUoVktTOmFl3gl/d3gJuOpmZWeTkJKmN/wW4PGaZHm6LzJcuCZSMdg57jTcCV5Z9Hk34+m5CxU45OUVJej3HjcHMrBPBtMcJi8E0RCe5niMIyCcAh82sW5ltX4Qv+PkEwdrLZvYAQe/NvwEGPBbJ7O7vmtls4KnwG+k2ggc2nE35IO9/wuO9ZGaPAh8C5wEFQCFBD6AkVsLaGSB8o2kJtAmT8s3sEIC7vxauS8zsEYIHW20HlhO8id0G/CiRN/JIVJ23s5mdB7xJ8GVuEnBRmdiASj6wpOaS8Vp+L7YSZtYr/Oeq2GlSJSHqvJ1DDwK/Jrgfbmq4zwSCz+lXE3qGAslp57cJZsv5Rfhgs40E91g8DBzgr1/eT12iJtTXUqOHLHxG8GCDeMtPyuRrTvCAor3AEYKeugvilJdFcDPWVwRPs3wH6BUnXzuCp95uC/NtI7jxtm2yr0kqLrXQzv9bWXlx8v6QYFzfUeBj4K5kX49UXZLRzgRPrq3smBX+P2j55rVxJfX4CXrQVUq2M8HT5TcQfDbvAV4CWif7mqTikqx2Jrih9hcEn8lFBJ2rs0nwg0YtPJiIiIiIiKQAjcEXEREREUkhCvBFRERERFKIAnwRERERkRSiAF9EREREJIUowBcRERERSSEK8EVEREREUogCfBEROSVm9pqZ7TWz1nG29TKzUjO7Nxl1ExGpjzQPvoiInJIwsP8AWOHu15VJzwL+D9gF/D93L01SFUVE6hX14IuIyClx953AvcC1ZnZ1mU0/Ab4N3Fbbwb2ZpZuZ1eYxRES+KRTgi4jIKXP3l4E3gefMrKmZXQiMJnjk+xYAM7vDzP5gZn8xs6/N7AUza162HDP7ZzNbFw752W9m681sUEyeDmbmZnaXmT1mZl8CR4FmdXKyIiKnOQ3RERGRhDCztsAmYC7w98AxoJu7HzeznxME/M8AvwbaAuOBL4Ae7n48LONxYDPwGZAGDAHuBga4+5IwTwdgG/AlsAGYCpwBLHX3ojo4VRGR05oCfBERSRgzux14HigBLnL3P4YB+VZgnLv/tEze7wFrgO+7+7w4ZTUg+KV5EVDk7kPD9A4EAf674TH0QSYiUoaG6IiISMK4+1RgBzDP3f8YJl9J8HnzipmlRRbgHeDPQM/I/mZ2kZktNLOdBL8AlIT7d45zuHkK7kVEKlKALyIiiVYcLhGtwvUnBAF72aUx0ALAzNoBbwHNgR8BPYCLgSVAZpzj7KiFuouIfOOlJbsCIiKS8vaE677Aviq29weaAsPc/YvIRjM7s5Jy1XsvIhKHAnwREalty4BSoL27L6siXySQL4kkmFkn4HsEN+OKiEg1KMAXEZFa5e5bzezfgWfNrDOwCvgL0I5gfP1Ud18JLCcYd/+Smf0CyAXGAX9CQ0pFRKpNAb6IiNQ6dx9rZpsJpry8m2B4TSHBmPuPwzybzGw48FNgPsHMOw8SDN3plYRqi4h8I2maTBERERGRFKKfPEVEREREUogCfBERERGRFKIAX0REREQkhSjAFxERERFJIQrwRURERERSiAJ8EREREZEUogBfRERERCSFKMAXEREREUkh/x+396Y6/81dQAAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.rcParams.update({'font.size': 16})\n", + "revenue_table.transpose().plot(title=\"Revenue by Geographic Region\",\n", + " ylabel=\"Revenue (Millions of US$)\",\n", + " figsize=(12, 7), ylim=(0, 50000))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now Allison has a clear picture of the detailed revenue data that was hidden inside those 1500 pages of PDF\n", + "files. As she works on her analyst report, Allison can use the same process to extract DataFrames for\n", + "other financial metrics too!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "text-estensions-for-pandas", + "language": "python", + "name": "text-estensions-for-pandas" + }, + "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.9.1" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/images/Table_Understanding.mp4 b/notebooks/images/Table_Understanding.mp4 new file mode 100644 index 00000000..204a50a6 Binary files /dev/null and b/notebooks/images/Table_Understanding.mp4 differ diff --git a/notebooks/images/screenshot_table_2017.png b/notebooks/images/screenshot_table_2017.png new file mode 100644 index 00000000..25b6471b Binary files /dev/null and b/notebooks/images/screenshot_table_2017.png differ diff --git a/resources/IBM Annual Report/IBM_Annual_Report_2009.pdf b/resources/IBM Annual Report/IBM_Annual_Report_2009.pdf new file mode 100644 index 00000000..a232a650 Binary files /dev/null and b/resources/IBM Annual Report/IBM_Annual_Report_2009.pdf differ diff --git a/resources/IBM Annual Report/IBM_Annual_Report_2010.pdf b/resources/IBM Annual Report/IBM_Annual_Report_2010.pdf new file mode 100644 index 00000000..fb33570a Binary files /dev/null and b/resources/IBM Annual Report/IBM_Annual_Report_2010.pdf differ diff --git a/resources/IBM Annual Report/IBM_Annual_Report_2011.pdf b/resources/IBM Annual Report/IBM_Annual_Report_2011.pdf new file mode 100644 index 00000000..1b4e0885 Binary files /dev/null and b/resources/IBM Annual Report/IBM_Annual_Report_2011.pdf differ diff --git a/resources/IBM Annual Report/IBM_Annual_Report_2012.pdf b/resources/IBM Annual Report/IBM_Annual_Report_2012.pdf new file mode 100644 index 00000000..3176786b Binary files /dev/null and b/resources/IBM Annual Report/IBM_Annual_Report_2012.pdf differ diff --git a/resources/IBM Annual Report/IBM_Annual_Report_2013.pdf b/resources/IBM Annual Report/IBM_Annual_Report_2013.pdf new file mode 100644 index 00000000..d7aa6a64 Binary files /dev/null and b/resources/IBM Annual Report/IBM_Annual_Report_2013.pdf differ diff --git a/resources/IBM Annual Report/IBM_Annual_Report_2014.pdf b/resources/IBM Annual Report/IBM_Annual_Report_2014.pdf new file mode 100644 index 00000000..6e426568 Binary files /dev/null and b/resources/IBM Annual Report/IBM_Annual_Report_2014.pdf differ diff --git a/resources/IBM Annual Report/IBM_Annual_Report_2015.pdf b/resources/IBM Annual Report/IBM_Annual_Report_2015.pdf new file mode 100644 index 00000000..e8df6223 Binary files /dev/null and b/resources/IBM Annual Report/IBM_Annual_Report_2015.pdf differ diff --git a/resources/IBM Annual Report/IBM_Annual_Report_2016.pdf b/resources/IBM Annual Report/IBM_Annual_Report_2016.pdf new file mode 100644 index 00000000..f420239c Binary files /dev/null and b/resources/IBM Annual Report/IBM_Annual_Report_2016.pdf differ diff --git a/resources/IBM Annual Report/IBM_Annual_Report_2017.pdf b/resources/IBM Annual Report/IBM_Annual_Report_2017.pdf new file mode 100644 index 00000000..31d4f202 Binary files /dev/null and b/resources/IBM Annual Report/IBM_Annual_Report_2017.pdf differ diff --git a/resources/IBM Annual Report/IBM_Annual_Report_2018.pdf b/resources/IBM Annual Report/IBM_Annual_Report_2018.pdf new file mode 100644 index 00000000..80c3f95f Binary files /dev/null and b/resources/IBM Annual Report/IBM_Annual_Report_2018.pdf differ diff --git a/resources/IBM Annual Report/IBM_Annual_Report_2019.pdf b/resources/IBM Annual Report/IBM_Annual_Report_2019.pdf new file mode 100644 index 00000000..e262650d Binary files /dev/null and b/resources/IBM Annual Report/IBM_Annual_Report_2019.pdf differ