{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Producing and Consuming Messages to/from Kafka and plotting, using python producer and spark consumer\n", "\n", "To run this notebook you must already have created a Kafka topic " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Imports\n", "\n", "We use utility functions from the hops library to make Kafka configuration simple\n", "\n", "Dependencies: \n", "\n", "- hops-py-util\n", "- confluent-kafka" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Starting Spark application\n" ] }, { "data": { "text/html": [ "\n", "
IDYARN Application IDKindStateSpark UIDriver logCurrent session?
34application_1538483294796_0037pysparkidleLinkLink
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "SparkSession available as 'spark'.\n" ] } ], "source": [ "from hops import kafka\n", "from hops import tls\n", "from hops import hdfs\n", "from confluent_kafka import Producer, Consumer\n", "import numpy as np\n", "from pyspark.sql.types import StructType, StructField, FloatType, TimestampType" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Constants\n", "\n", "Update the `TOPIC_NAME` field to reflect the name of your Kafka topic that you want to read/write from/to\n", "Update the `OUTPUT_PATH` field to where the output data should be written" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "TOPIC_NAME = \"test\"\n", "OUTPUT_PATH = \"/Projects/\" + hdfs.project_name() + \"/Resources/data-csv\"\n", "CHECKPOINT_PATH = \"/Projects/\" + hdfs.project_name() + \"/Resources/checkpoint-csv\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Produce some Messages to the Topic" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Specify the configuration, using hops-py-util" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "config = {\n", " \"bootstrap.servers\": kafka.get_broker_endpoints(),\n", " \"security.protocol\": kafka.get_security_protocol(),\n", " \"ssl.ca.location\": tls.get_ca_chain_location(),\n", " \"ssl.certificate.location\": tls.get_client_certificate_location(),\n", " \"ssl.key.location\": tls.get_client_key_location(),\n", " \"group.id\": \"something\"\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create producer with kafka-confluent API" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "producer = Producer(config)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "producer.produce is an asychronous call so we create a callback to be notified when messages are delivered" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def delivery_callback(err, msg):\n", " \"\"\"\n", " Optional per-message delivery callback (triggered by poll() or flush())\n", " when a message has been successfully delivered or permanently\n", " failed delivery (after retries).\n", " \"\"\"\n", " if err:\n", " print(\"Message failed delivery: {}\".format(err))\n", " else:\n", " print('Message: {} delivered to topic: {}, partition: {}, offset: {}, timestamp: {}'.format(msg.value(), msg.topic(), msg.partition(), msg.offset(), msg.timestamp()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Produce 100 random number-messages to the topic" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Message: -0.113515966706715 delivered to topic: test, partition: 1, offset: 450, timestamp: (1, 1538570713617L)\n", "Message: -0.13372629136129505 delivered to topic: test, partition: 1, offset: 451, timestamp: (1, 1538570713617L)\n", "Message: -0.030109530115267787 delivered to topic: test, partition: 1, offset: 452, timestamp: (1, 1538570713617L)\n", "Message: 0.09914600908518259 delivered to topic: test, partition: 1, offset: 453, timestamp: (1, 1538570713617L)\n", "Message: -0.13942848544599754 delivered to topic: test, partition: 1, offset: 454, timestamp: (1, 1538570713617L)\n", "Message: 0.06244323355006787 delivered to topic: test, partition: 1, offset: 455, timestamp: (1, 1538570713617L)\n", "Message: 0.12423451639758504 delivered to topic: test, partition: 1, offset: 456, timestamp: (1, 1538570713617L)\n", "Message: 0.01979165601399879 delivered to topic: test, partition: 1, offset: 457, timestamp: (1, 1538570713617L)\n", "Message: 0.13896608728374968 delivered to topic: test, partition: 1, offset: 458, timestamp: (1, 1538570713617L)\n", "Message: -0.22441498120997885 delivered to topic: test, partition: 1, offset: 459, timestamp: (1, 1538570713617L)\n", "Message: 0.1099915077238914 delivered to topic: test, partition: 1, offset: 460, timestamp: (1, 1538570713617L)\n", "Message: 0.11565688359003806 delivered to topic: test, partition: 1, offset: 461, timestamp: (1, 1538570713617L)\n", "Message: 0.01708963484491049 delivered to topic: test, partition: 1, offset: 462, timestamp: (1, 1538570713617L)\n", "Message: -0.10090270372296102 delivered to topic: test, partition: 1, offset: 463, timestamp: (1, 1538570713617L)\n", "Message: -0.05870498166968106 delivered to topic: test, partition: 1, offset: 464, timestamp: (1, 1538570713617L)\n", "Message: -0.05802497535874125 delivered to topic: test, partition: 1, offset: 465, timestamp: (1, 1538570713617L)\n", "Message: 0.06852280438233449 delivered to topic: test, partition: 1, offset: 466, timestamp: (1, 1538570713617L)\n", "Message: -0.07384578426064682 delivered to topic: test, partition: 1, offset: 467, timestamp: (1, 1538570713617L)\n", "Message: -0.049791032528996144 delivered to topic: test, partition: 1, offset: 468, timestamp: (1, 1538570713617L)\n", "Message: -0.0812131295770192 delivered to topic: test, partition: 1, offset: 469, timestamp: (1, 1538570713617L)\n", "Message: -0.16636558959634237 delivered to topic: test, partition: 1, offset: 470, timestamp: (1, 1538570713617L)\n", "Message: -0.05029487377263785 delivered to topic: test, partition: 1, offset: 471, timestamp: (1, 1538570713617L)\n", "Message: -0.0634611358539206 delivered to topic: test, partition: 1, offset: 472, timestamp: (1, 1538570713617L)\n", "Message: 0.047822771574680845 delivered to topic: test, partition: 1, offset: 473, timestamp: (1, 1538570713617L)\n", "Message: -0.035862408830423124 delivered to topic: test, partition: 1, offset: 474, timestamp: (1, 1538570713617L)\n", "Message: -0.10771865791104468 delivered to topic: test, partition: 1, offset: 475, timestamp: (1, 1538570713617L)\n", "Message: -0.09621414290887964 delivered to topic: test, partition: 1, offset: 476, timestamp: (1, 1538570713617L)\n", "Message: -0.12471722823698611 delivered to topic: test, partition: 1, offset: 477, timestamp: (1, 1538570713617L)\n", "Message: -0.04144248818411699 delivered to topic: test, partition: 1, offset: 478, timestamp: (1, 1538570713617L)\n", "Message: -0.022852969314669194 delivered to topic: test, partition: 1, offset: 479, timestamp: (1, 1538570713617L)\n", "Message: 0.03812687911561363 delivered to topic: test, partition: 1, offset: 480, timestamp: (1, 1538570713617L)\n", "Message: 0.11600386213341707 delivered to topic: test, partition: 1, offset: 481, timestamp: (1, 1538570713617L)\n", "Message: 0.09175683466848605 delivered to topic: test, partition: 1, offset: 482, timestamp: (1, 1538570713617L)\n", "Message: -0.12798420753009673 delivered to topic: test, partition: 1, offset: 483, timestamp: (1, 1538570713617L)\n", "Message: 0.008424637864889025 delivered to topic: test, partition: 1, offset: 484, timestamp: (1, 1538570713617L)\n", "Message: 0.008146330017132953 delivered to topic: test, partition: 1, offset: 485, timestamp: (1, 1538570713617L)\n", "Message: -0.02340418188111429 delivered to topic: test, partition: 1, offset: 486, timestamp: (1, 1538570713617L)\n", "Message: -0.12962750536767612 delivered to topic: test, partition: 1, offset: 487, timestamp: (1, 1538570713617L)\n", "Message: -0.07628248175485523 delivered to topic: test, partition: 1, offset: 488, timestamp: (1, 1538570713617L)\n", "Message: -0.09094972953830724 delivered to topic: test, partition: 1, offset: 489, timestamp: (1, 1538570713617L)\n", "Message: 0.04784824141202446 delivered to topic: test, partition: 1, offset: 490, timestamp: (1, 1538570713617L)\n", "Message: -0.0008079695296911359 delivered to topic: test, partition: 1, offset: 491, timestamp: (1, 1538570713617L)\n", "Message: -0.12966562414756075 delivered to topic: test, partition: 1, offset: 492, timestamp: (1, 1538570713617L)\n", "Message: 0.15101829400472663 delivered to topic: test, partition: 1, offset: 493, timestamp: (1, 1538570713617L)\n", "Message: 0.07271092045856317 delivered to topic: test, partition: 1, offset: 494, timestamp: (1, 1538570713617L)\n", "Message: -0.14274124593717222 delivered to topic: test, partition: 1, offset: 495, timestamp: (1, 1538570713617L)\n", "Message: -0.07738435882850264 delivered to topic: test, partition: 1, offset: 496, timestamp: (1, 1538570713617L)\n", "Message: 0.017391910899179314 delivered to topic: test, partition: 1, offset: 497, timestamp: (1, 1538570713617L)\n", "Message: 0.11182130735642559 delivered to topic: test, partition: 1, offset: 498, timestamp: (1, 1538570713617L)\n", "Message: 0.17939618586484868 delivered to topic: test, partition: 1, offset: 499, timestamp: (1, 1538570713617L)\n", "Message: -0.030430503280300954 delivered to topic: test, partition: 1, offset: 500, timestamp: (1, 1538570713617L)\n", "Message: 0.010896595939977088 delivered to topic: test, partition: 1, offset: 501, timestamp: (1, 1538570713617L)\n", "Message: 0.07153886574524339 delivered to topic: test, partition: 1, offset: 502, timestamp: (1, 1538570713617L)\n", "Message: -0.027702479187509583 delivered to topic: test, partition: 1, offset: 503, timestamp: (1, 1538570713617L)\n", "Message: -0.025904346778860443 delivered to topic: test, partition: 1, offset: 504, timestamp: (1, 1538570713617L)\n", "Message: 0.054551892172761886 delivered to topic: test, partition: 1, offset: 505, timestamp: (1, 1538570713617L)\n", "Message: -0.027343347495906986 delivered to topic: test, partition: 1, offset: 506, timestamp: (1, 1538570713617L)\n", "Message: 0.14735111070401619 delivered to topic: test, partition: 1, offset: 507, timestamp: (1, 1538570713617L)\n", "Message: 0.03346639447277224 delivered to topic: test, partition: 1, offset: 508, timestamp: (1, 1538570713617L)\n", "Message: 0.1826423337750613 delivered to topic: test, partition: 1, offset: 509, timestamp: (1, 1538570713617L)\n", "Message: 0.04600033520055015 delivered to topic: test, partition: 1, offset: 510, timestamp: (1, 1538570713617L)\n", "Message: -0.3554881125189931 delivered to topic: test, partition: 1, offset: 511, timestamp: (1, 1538570713617L)\n", "Message: -0.01938300986377106 delivered to topic: test, partition: 1, offset: 512, timestamp: (1, 1538570713617L)\n", "Message: -0.06868591688742505 delivered to topic: test, partition: 1, offset: 513, timestamp: (1, 1538570713617L)\n", "Message: 0.04342203531972067 delivered to topic: test, partition: 1, offset: 514, timestamp: (1, 1538570713617L)\n", "Message: 0.026605428384062164 delivered to topic: test, partition: 1, offset: 515, timestamp: (1, 1538570713617L)\n", "Message: -0.23891058137348586 delivered to topic: test, partition: 1, offset: 516, timestamp: (1, 1538570713617L)\n", "Message: 0.063107528304626 delivered to topic: test, partition: 1, offset: 517, timestamp: (1, 1538570713617L)\n", "Message: -0.06384750369644372 delivered to topic: test, partition: 1, offset: 518, timestamp: (1, 1538570713617L)\n", "Message: 0.09966984328987485 delivered to topic: test, partition: 1, offset: 519, timestamp: (1, 1538570713617L)\n", "Message: 0.19078821436517882 delivered to topic: test, partition: 1, offset: 520, timestamp: (1, 1538570713617L)\n", "Message: 0.11490040429088477 delivered to topic: test, partition: 1, offset: 521, timestamp: (1, 1538570713617L)\n", "Message: 0.003344215541099674 delivered to topic: test, partition: 1, offset: 522, timestamp: (1, 1538570713617L)\n", "Message: 0.01482845788007928 delivered to topic: test, partition: 1, offset: 523, timestamp: (1, 1538570713617L)\n", "Message: -0.03389124274730744 delivered to topic: test, partition: 1, offset: 524, timestamp: (1, 1538570713618L)\n", "Message: -0.0017421597393584454 delivered to topic: test, partition: 1, offset: 525, timestamp: (1, 1538570713618L)\n", "Message: 0.02696093734246954 delivered to topic: test, partition: 1, offset: 526, timestamp: (1, 1538570713618L)\n", "Message: -0.0685537177874654 delivered to topic: test, partition: 1, offset: 527, timestamp: (1, 1538570713618L)\n", "Message: 0.008024171552555073 delivered to topic: test, partition: 1, offset: 528, timestamp: (1, 1538570713618L)\n", "Message: -0.007976413182145927 delivered to topic: test, partition: 1, offset: 529, timestamp: (1, 1538570713618L)\n", "Message: -0.1760613222741813 delivered to topic: test, partition: 1, offset: 530, timestamp: (1, 1538570713618L)\n", "Message: -0.04272057630253764 delivered to topic: test, partition: 1, offset: 531, timestamp: (1, 1538570713618L)\n", "Message: -0.08676547080431635 delivered to topic: test, partition: 1, offset: 532, timestamp: (1, 1538570713618L)\n", "Message: -0.005037102529253483 delivered to topic: test, partition: 1, offset: 533, timestamp: (1, 1538570713618L)\n", "Message: -0.026670469314402163 delivered to topic: test, partition: 1, offset: 534, timestamp: (1, 1538570713618L)\n", "Message: -0.07311083242634318 delivered to topic: test, partition: 1, offset: 535, timestamp: (1, 1538570713618L)\n", "Message: -0.023465465848581793 delivered to topic: test, partition: 1, offset: 536, timestamp: (1, 1538570713618L)\n", "Message: 0.11321026392440192 delivered to topic: test, partition: 1, offset: 537, timestamp: (1, 1538570713618L)\n", "Message: -0.15336795722952007 delivered to topic: test, partition: 1, offset: 538, timestamp: (1, 1538570713618L)\n", "Message: -0.2558586664453579 delivered to topic: test, partition: 1, offset: 539, timestamp: (1, 1538570713618L)\n", "Message: 0.008079556492722571 delivered to topic: test, partition: 1, offset: 540, timestamp: (1, 1538570713618L)\n", "Message: -0.0407650872978403 delivered to topic: test, partition: 1, offset: 541, timestamp: (1, 1538570713618L)\n", "Message: -0.03444415125912261 delivered to topic: test, partition: 1, offset: 542, timestamp: (1, 1538570713618L)\n", "Message: 0.05797445559620995 delivered to topic: test, partition: 1, offset: 543, timestamp: (1, 1538570713618L)\n", "Message: -0.05231783634896588 delivered to topic: test, partition: 1, offset: 544, timestamp: (1, 1538570713618L)\n", "Message: 0.11925067273119393 delivered to topic: test, partition: 1, offset: 545, timestamp: (1, 1538570713618L)\n", "Message: -0.050746734607947985 delivered to topic: test, partition: 1, offset: 546, timestamp: (1, 1538570713618L)\n", "Message: -0.12585044629192257 delivered to topic: test, partition: 1, offset: 547, timestamp: (1, 1538570713618L)\n", "Message: -0.20932999866560886 delivered to topic: test, partition: 1, offset: 548, timestamp: (1, 1538570713618L)\n", "Message: 0.07143653458156844 delivered to topic: test, partition: 1, offset: 549, timestamp: (1, 1538570713618L)\n", "0" ] } ], "source": [ "normal_rnd_dist = np.random.normal(0, 0.1, 100)\n", "for i in range(0, 100):\n", " producer.produce(TOPIC_NAME, str(normal_rnd_dist[i]), \"key\", callback=delivery_callback)\n", " \n", "# Trigger the sending of all messages to the brokers, 20sec timeout\n", "producer.flush(20) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Consume the Kafka Topic using Spark and Write to a Sink" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The below snippet creates a streaming DataFrame with Kafka as a data source. Spark is lazy so it will not start streaming the data from Kafka into the dataframe until we specify an output sink (which we do later on in this notebook)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "#lazy\n", "df = spark \\\n", " .readStream \\\n", " .format(\"kafka\") \\\n", " .option(\"kafka.bootstrap.servers\", kafka.get_broker_endpoints()) \\\n", " .option(\"kafka.security.protocol\",kafka.get_security_protocol()) \\\n", " .option(\"kafka.ssl.truststore.location\", tls.get_trust_store()) \\\n", " .option(\"kafka.ssl.truststore.password\", tls.get_key_store_pwd()) \\\n", " .option(\"kafka.ssl.keystore.location\", tls.get_key_store()) \\\n", " .option(\"kafka.ssl.keystore.password\", tls.get_key_store_pwd()) \\\n", " .option(\"kafka.ssl.key.password\", tls.get_trust_store_pwd()) \\\n", " .option(\"kafka.ssl.endpoint.identification.algorithm\", \"\") \\\n", " .option(\"subscribe\", TOPIC_NAME) \\\n", " .load()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When using Kafka as a data source, Spark gives us a default kafka schema as printed below" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "root\n", " |-- key: binary (nullable = true)\n", " |-- value: binary (nullable = true)\n", " |-- topic: string (nullable = true)\n", " |-- partition: integer (nullable = true)\n", " |-- offset: long (nullable = true)\n", " |-- timestamp: timestamp (nullable = true)\n", " |-- timestampType: integer (nullable = true)" ] } ], "source": [ "df.printSchema()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We are using the Spark structured streaming engine, which means that we can express stream queries just as we would do in batch jobs. \n", "\n", "Below we filter the input stream to select only the message values and their timestamp" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "messages = df.selectExpr(\"CAST(value AS STRING)\", \"timestamp\").selectExpr(\"CAST(value AS FLOAT)\", \"timestamp\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Specify the output query and the sink of the stream job to be a CSV file in HopsFS. \n", "\n", "By using checkpointing and a WAL, spark gives us end-to-end exactly-once fault-tolerance" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "query = messages \\\n", " .writeStream \\\n", " .format(\"csv\") \\\n", " .option(\"path\", OUTPUT_PATH) \\\n", " .option(\"checkpointLocation\", CHECKPOINT_PATH) \\\n", " .start()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Run the streaming job, in theory streaming jobs should run forever.\n", "\n", "However for this notebook example we are just going to read for 10 seconds and dump the output to the sink CSV file" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "query.awaitTermination(timeout=20) # 20 seconds timeout\n", "query.stop()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

\n", "Sometimes there is a delay before the spark job starts writing to the sink,

\n", "\n", "

before going on to the next step in this notebook, go to your HDFS `OUTPUT_PATH` \n", "and verify that the csv output is not empty.

\n", "\n", "

If it is empty, re-run the query above

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Read the Data from the Sink" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "schema = StructType([\n", " StructField(\"value\", FloatType(), True),\n", " StructField(\"timestamp\", TimestampType(), True)])\n", "\n", "df1 = spark.read \\\n", " .format(\"csv\") \\\n", " .option(\"header\", \"false\") \\\n", " .schema(schema) \\\n", " .load(OUTPUT_PATH)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "root\n", " |-- value: float (nullable = true)\n", " |-- timestamp: timestamp (nullable = true)" ] } ], "source": [ "df1.printSchema()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Visualize the DataFrame using SparkMagic\n", "\n", "

This visualization currenly only works in Python 2.*

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This command copies the spark dataframe from the cluster \n", "to the local machine and converts it to a pandas dataframe named \"df1\". \n", "This pandas dataframe is available in all cells started with the sparkmagic: %%local and can be used for \n", "visualizations and plotting." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "%%spark -o df1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Below is sparkmagics default plotting" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "bf3b24a36bee4e8ca5d9ea46c1389c10", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VkJveChjaGlsZHJlbj0oSEJveChjaGlsZHJlbj0oSFRNTCh2YWx1ZT11J1R5cGU6JyksIEJ1dHRvbihkZXNjcmlwdGlvbj11J1RhYmxlJywgbGF5b3V0PUxheW91dCh3aWR0aD11JzcwcHgnKSzigKY=\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "14d51278b25c4bc1b9e631c942bc63dd", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Output()" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%local\n", "df1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Install matplotlib on the local machine in case it is not already installed" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%bash\n", "pip install --user matplotlib" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAA3IAAAJcCAYAAAC8BpYTAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvIxREBQAAIABJREFUeJzt3XmQZWlZ5/HfQxf7LqTsRSEiBqCClCwuKIIKoqBGD8LYCopTOgrjMo7RqDMyGjqooKAwjj0OCIpsrSBCKCiKiizaDQjdLMPWrA3NKjQo0PDMH3nLSYqqrltUncx8Kj+fiIy8y7nnfTPrreVb5+S51d0BAABgjivs9AQAAAA4MUIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAnJSqurCqvmGn57GTquo7q+odVXVpVd3hFO73wVX14lO1PwBOH0IOgGOqqouq6p5HPPZZcdHdt+3uFx1nPweqqqtq30JT3WmPSvLQ7r5Gd79ypycDwOlPyAEw3i4IxJsnuXCH5wDAHiLkADgpW4/aVdWdquq8qvpIVb23qn59tdnfrj5/eHX64V2r6gpV9XNV9baquqSqnlxV196y3+9bPfeBqvqvR4zziKo6t6r+oKo+kuTBq7FfWlUfrqqLq+pxVXWlLfvrqvqRqnpjVX20qn6xqm5ZVS9ZzfcZW7c/4ms86lyr6spVdWmSM5L8U1W9+Siv/e2qetQRj/1JVf3k6vbZVfXm1ZxeW1XfeYw5fM5Rzap6UVX94Jb7P1BVr6uqD1XV86vq5qvHq6p+YzX3j1TVa6rqdsf8RQVg1xNyAJxKj03y2O6+VpJbJnnG6vG7rT5fZ3X64UuTPHj1cfckX5TkGkkelyRVdZsk/zPJ9yS5UZJrJ7nJEWPdL8m5Sa6T5ClJPp3kJ5JcP8ldk9wjyY8c8ZpvSXLHJHdJ8tNJzklyVpKbJbldkgce4+s66ly7+xPdfY3VNl/R3bc8ymufmuS7q6pWX9t1k3xzkqetnn9zkq9bfY3/PckfVNWNjjGPY6qq+yX5mSTflWQjyd+txs5qvLsl+ZLVOPdP8oETHQOA3UPIAXA8z14d5fpwVX04m4F1LJ9K8sVVdf3uvrS7X3Y5235Pkl/v7rd096VJHp7kAasjTmcm+dPufnF3fzLJf0vSR7z+pd397O7+THf/S3ef390v6+7LuvuiJL+T5OuPeM2vdvdHuvvCJBckecFq/H9O8mdJjnWhksub6/H83WruX7e6f+Zq7u9Oku5+Zne/e/V1PD3JG5PcaY39HumHk/yP7n5dd1+W5JeT3H51VO5TSa6Z5EuT1Gqbiz+PMQDYJYQcAMfzHd19ncMf+dyjXFs9JJtHfV5fVf9YVd92OdveOMnbttx/W5J9SW6weu4dh5/o7o/nc48gvWPrnar6kqp6blW9Z3W65S9n8+jcVu/dcvtfjnL/Gjm6y5vr5eruzubRt8NH+/59No8gHp7391XVq7aE8u2OMu913DzJY7fs54NJKslNuvuvsnm08/FJLqmqc6rqWp/HGADsEkIOgFOmu9/Y3Q9M8oVJfiXJuVV19Xzu0bQkeXc24+Ow/Ukuy2ZcXZzkpoefqKqrJrnekcMdcf+3k7w+ya1Wp3b+TDZD5lS4vLmu46lJzlwdHbtzkj9KktX9/53koUmutwrlC44x74+tPl9ty2M33HL7HUl+aGt0d/dVu/slSdLdv9ndd0xym2zG9n9Zc+4A7EJCDoBTpqrOqqqN7v5Mkg+vHv5MkvetPn/Rls2fmuQnquoWVXWNbB5Be/rqtMBzk3x7VX316gIkj8jxo+yaST6S5NKq+tIk//FUfV3Hmetxrd6S4P1JfjfJ87v78PfmcOS+L0mq6vuzeUTuaPt4X5J3JTmrqs6oqh/I5s8hHva/kjy8qm672te1q+rfrW5/VVXduaqumM0g/Nds/noAMJSQA+BUuleSC1dXcnxskgesfn7t40l+Kcnfr079u0uSJyT5/Wxe0fKt2YyLhyXJ6mfYHpbNUxIvTnJpkkuSfOJyxv6pbJ62+NFsHuV6+in8uo451xPwh0nuufqcJOnu1yZ5dJKXZvPo3pcl+fvL2cd/yOaRtA8kuW2Sl2zZ17OyeRT0aatTSy9Icu/V09fK5vfkQ9k8LfQDSX7tBOcPwC5Sm6fuA8DutToK9uFsnjb51p2eDwDsNEfkANiVqurbq+pqq5+xe1SS1yS5aGdnBQC7g5ADYLe6XzYvMvLuJLfK5mmaTiMBgDi1EgAAYBxH5AAAAIbZt9MT2Or6179+HzhwYKenAQAAsCPOP//893f3xvG221Uhd+DAgZx33nk7PQ0AAIAdUVVvW2c7p1YCAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMIuGXFX9RFVdWFUXVNVTq+oqS44HAACwFywWclV1kyT/KcnB7r5dkjOSPGCp8QAAAPaKpU+t3JfkqlW1L8nVkrx74fEAAABOe/uW2nF3v6uqHpXk7Un+JckLuvsFR25XVYeSHEqS/fv3LzUdAGBBB85+3trbXvTI+yw4E4C9YclTK6+b5H5JbpHkxkmuXlVnHbldd5/T3Qe7++DGxsZS0wEAADhtLHlq5T2TvLW739fdn0ryx0m+esHxAAAA9oQlQ+7tSe5SVVerqkpyjySvW3A8AACAPWGxkOvulyc5N8krkrxmNdY5S40HAACwVyx2sZMk6e6fT/LzS44BAACw1yz99gMAAACcYkIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhFgu5qrp1Vb1qy8dHqurHlxoPAABgr9i31I67+w1Jbp8kVXVGkncledZS4wEAAOwV23Vq5T2SvLm737ZN4wEAAJy2tivkHpDkqUd7oqoOVdV5VXXe+973vm2aDgAAwFyLh1xVXSnJfZM882jPd/c53X2wuw9ubGwsPR0AAIDxtuOI3L2TvKK737sNYwEAAJz2tiPkHphjnFYJAADAiVs05Krq6km+KckfLzkOAADAXrLY2w8kSXd/LMn1lhwDAABgr9muq1YCAABwigg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGWTTkquo6VXVuVb2+ql5XVXddcjwAAIC9YN/C+39skj/v7jOr6kpJrrbweAAAAKe9xUKuqq6d5G5JHpwk3f3JJJ9cajwAAIC9YskjcrdI8r4kT6yqr0hyfpIf6+6Pbd2oqg4lOZQk+/fvX3A6ALD7HTj7eWtve9Ej77PgTPYG329gqiV/Rm5fkq9M8tvdfYckH0ty9pEbdfc53X2wuw9ubGwsOB0AAIDTw5Ih984k7+zul6/un5vNsAMAAOAkLBZy3f2eJO+oqluvHrpHktcuNR4AAMBesfRVKx+W5CmrK1a+Jcn3LzweAADAaW/RkOvuVyU5uOQYAAAAe82ibwgOAADAqSfkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwzL4ld15VFyX5aJJPJ7msuw8uOR4AAMBesGjIrdy9u9+/DeMAAADsCU6tBAAAGGbpI3Kd5AVV1Ul+p7vPOXKDqjqU5FCS7N+/f+HpAMDxHTj7eWtve9Ej77PgTLbXul/36fQ1T+XXao69+ucJy1v6iNzXdvdXJrl3kh+tqrsduUF3n9PdB7v74MbGxsLTAQAAmG/RkOvud60+X5LkWUnutOR4AAAAe8FiIVdVV6+qax6+neSbk1yw1HgAAAB7xZI/I3eDJM+qqsPj/GF3//mC4wEAAOwJi4Vcd78lyVcstX8AAIC9ytsPAAAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDrBVyVfXCdR4DAABgefsu78mqukqSqyW5flVdN0mtnrpWkpssPDcAAACO4nJDLskPJfnxJDdOcn7+f8h9JMnjFpwXAAAAx3C5Idfdj03y2Kp6WHf/1jbNCQAAgMtxvCNySZLu/q2q+uokB7a+prufvNC8AAAAOIa1Qq6qfj/JLZO8KsmnVw93EiEHAACwzdYKuSQHk9ymu3vJyQAAAHB8676P3AVJbrjkRAAAAFjPukfkrp/ktVX1D0k+cfjB7r7vIrMCAADgmNYNuUcsOQkAAADWt+5VK/9m6YkAAACwnnWvWvnRbF6lMkmulOSKST7W3ddaamIAAAAc3bpH5K55+HZVVZL7JbnLUpMCAADg2Na9auW/6U3PTvItC8wHAACA41j31Mrv2nL3Ctl8X7l/XWRGAAAAXK51r1r57VtuX5bkomyeXgkAAMA2W/dn5L5/6YkAAACwnrV+Rq6qblpVz6qqS1Yff1RVN116cgAAAHyudS928sQkz0ly49XHn64eAwAAYJutG3Ib3f3E7r5s9fF7STYWnBcAAADHsG7IfaCqzqqqM1YfZyX5wJITAwAA4OjWDbkfSHL/JO9JcnGSM5M8eJ0XrsLvlVX13M9rhgAAAHyWdd9+4BeSPKi7P5QkVfUFSR6VzcA7nh9L8rok1/q8ZggAAMBnWfeI3Jcfjrgk6e4PJrnD8V60urLlfZL87uc3PQAAAI607hG5K1TVdY84IrfOax+T5KeTXPNYG1TVoSSHkmT//v1rTgeAE3Hg7Oetve1Fj7zPgjPZG9b9fvtenxo79f32+wrYSesekXt0kpdW1S9W1S8meUmSX728F1TVtyW5pLvPv7ztuvuc7j7Y3Qc3NlwIEwAA4HjWOiLX3U+uqvOSfOPqoe/q7tce52Vfk+S+VfWtSa6S5FpV9QfdfdbnP10AAADWPbUyq3A7Xrxt3f7hSR6eJFX1DUl+SsQBAACcvHVPrQQAAGCXWPuI3Mno7hcledF2jAUAAHC6c0QOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwi4VcVV2lqv6hqv6pqi6sqv++1FgAAAB7yb4F9/2JJN/Y3ZdW1RWTvLiq/qy7X7bgmAAAAKe9xUKuuzvJpau7V1x99FLjAQAA7BVLHpFLVZ2R5PwkX5zk8d398qNscyjJoSTZv3//ktMBgNPagbOft/a2Fz3yPgvOhNPVya6xdV+/29bn1Hlzelv0Yifd/enuvn2Smya5U1Xd7ijbnNPdB7v74MbGxpLTAQAAOC1sy1Uru/vDSf46yb22YzwAAIDT2ZJXrdyoquusbl81yTclef1S4wEAAOwVS/6M3I2SPGn1c3JXSPKM7n7uguMBAADsCUtetfLVSe6w1P4BAAD2qm35GTkAAABOHSEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhFgu5qrpZVf11Vb22qi6sqh9baiwAAIC9ZN+C+74syX/u7ldU1TWTnF9Vf9Hdr11wTAAAgNPeYkfkuvvi7n7F6vZHk7wuyU2WGg8AAGCvWPKI3L+pqgNJ7pDk5Ud57lCSQ0myf//+7ZgOwEgHzn7eWttd9Mj77Mi4Rxv7ZF57Iq8/mdce6/WwpJ1cn3vx98Ze/JpP1tTv2dR5fz4Wv9hJVV0jyR8l+fHu/siRz3f3Od19sLsPbmxsLD0dAACA8RYNuaq6YjYj7ind/cdLjgUAALBXLHnVykryf5K8rrt/falxAAAA9polj8h9TZLvTfKNVfWq1ce3LjgeAADAnrDYxU66+8VJaqn9AwAA7FWLX+wEAACAU0vIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwzGIhV1VPqKpLquqCpcYAAADYi5Y8Ivd7Se614P4BAAD2pMVCrrv/NskHl9o/AADAXrVvpydQVYeSHEqS/fv37/Bsju7A2c9ba7uLHnmfhWfCbrXuGkmOvk5OZo2d7NgnY+rvjZ36fu/krxXsZn5vsA7r5MTtxb+n95Idv9hJd5/T3Qe7++DGxsZOTwcAAGDX2/GQAwAA4MQIOQAAgGGWfPuBpyZ5aZJbV9U7q+ohS40FAACwlyx2sZPufuBS+wYAANjLnFoJAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcAADCMkAMAABhGyAEAAAwj5AAAAIYRcgAAAMMIOQAAgGGEHAAAwDBCDgAAYBghBwAAMIyQAwAAGEbIAQAADCPkAAAAhhFyAAAAwwg5AACAYYQcAADAMEIOAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGCYRUOuqu5VVW+oqjdV1dlLjgUAALBXLBZyVXVGkscnuXeS2yR5YFXdZqnxAAAA9oolj8jdKcmbuvst3f3JJE9Lcr8FxwMAANgTqruX2XHVmUnu1d0/uLr/vUnu3N0PPWK7Q0kOre7eOskbFpkQ2+X6Sd6/05NgDOuFE2G9cCKsF06UNcOJWHK93Ly7N4630b6FBl9bd5+T5JydngenRlWd190Hd3oezGC9cCKsF06E9cKJsmY4EbthvSx5auW7ktxsy/2brh4DAADgJCwZcv+Y5FZVdYuqulKSByR5zoLjAQAA7AmLnVrZ3ZdV1UOTPD/JGUme0N0XLjUeu4bTZDkR1gsnwnrhRFgvnChrhhOx4+tlsYudAAAAsIxF3xAcAACAU0/IAQAADCPkOClV9QVV9RdV9cbV5+seZZubV9UrqupVVXVhVf3wTsyVnbfmerl9Vb10tVZeXVXfvRNzZeets15W2/15VX24qp673XNk51XVvarqDVX1pqo6+yjPX7mqnr56/uVVdWD7Z8luscZ6udvq3yyXrd4TmT1sjfXyk1X12tW/V15YVTffzvkJOU7W2Ule2N23SvLC1f0jXZzkrt19+yR3TnJ2Vd14G+fI7rHOevl4ku/r7tsmuVeSx1TVdbZxjuwe66yXJPm1JN+7bbNi16iqM5I8Psm9k9wmyQOr6jZHbPaQJB/q7i9O8htJfmV7Z8luseZ6eXuSByf5w+2dHbvNmuvllUkOdveXJzk3ya9u5xyFHCfrfkmetLr9pCTfceQG3f3J7v7E6u6VY93tZeusl//b3W9c3X53kkuSbGzbDNlNjrtekqS7X5jko9s1KXaVOyV5U3e/pbs/meRp2Vw3W21dR+cmuUdV1TbOkd3juOuluy/q7lcn+cxOTJBdZZ318tfd/fHV3Zdl832zt41/UHOybtDdF69uvyfJDY62UVXdrKpeneQdSX5l9Q909p611sthVXWnJFdK8ualJ8audELrhT3pJtn8e+Wwd64eO+o23X1Zkn9Ocr1tmR27zTrrBQ470fXykCR/tuiMjrDY+8hx+qiqv0xyw6M89bNb73R3V9VR38+iu9+R5MtXp1Q+u6rO7e73nvrZstNOxXpZ7edGSX4/yYO62/+MnqZO1XoBgJ1SVWclOZjk67dzXCHHcXX3PY/1XFW9t6pu1N0Xr/7hfclx9vXuqrogyddl8xQXTjOnYr1U1bWSPC/Jz3b3yxaaKrvAqfzzhT3pXUlutuX+TVePHW2bd1bVviTXTvKB7Zkeu8w66wUOW2u9VNU9s/mfj1+/5UeJtoVTKzlZz0nyoNXtByX5kyM3qKqbVtVVV7evm+Rrk7xh22bIbrLOerlSkmcleXJ3i/297bjrhT3vH5Pcqqpusfqz4wHZXDdbbV1HZyb5q+52dHdvWme9wGHHXS9VdYckv5Pkvt297f/ZWP4s42RU1fWSPCPJ/iRvS3L/7v5gVR1M8sPd/YNV9U1JHp2kk1SSx3X3OTs2aXbMmuvlrCRPTHLhlpc+uLtftf0zZiets15W2/1dki9Nco1sHml5SHc/f4emzTarqm9N8pgkZyR5Qnf/UlX9QpLzuvs5VXWVbJ6mfYckH0zygO5+y87NmJ20xnr5qmz+Z+J1k/xrkvesrqLMHrTGevnLJF+WzSu0J8nbu/u+2zY/IQcAADCLUysBAACGEXIAAADDCDkAAIBhhBwAAMAwQg4AAGAYIQcASarq0p2eAwCsS8gBAAAMI+QAOC1V1SOr6ke33H9EVf1cVb2wql5RVa+pqvsd5XXfUFXP3XL/cVX14NXtO1bV31TV+VX1/Kq60bZ8MQBwBCEHwOnq6Unuv+X+/ZM8Kcl3dvdXJrl7kkdXVa2zs6q6YpLfSnJmd98xyROS/NKpnTIArGffTk8AAJbQ3a+sqi+sqhsn2UjyoSTvSfIbVXW3JJ9JcpMkN1g9fjy3TnK7JH+xar8zkly8xNwB4HiEHACns2cmOTPJDbN5hO57shl1d+zuT1XVRUmucsRrLstnn7Fy+PlKcmF333XRGQPeLrWyAAAAvklEQVTAGpxaCcDp7OlJHpDNmHtmkmsnuWQVcXdPcvOjvOZtSW5TVVeuquskucfq8Tck2aiquyabp1pW1W0X/woA4CgckQPgtNXdF1bVNZO8q7svrqqnJPnTqnpNkvOSvP4or3lHVT0jyQVJ3prklavHP1lVZyb5zaq6djb/Dn1Mkgu36csBgH9T3b3TcwAAAOAEOLUSAABgGCEHAAAwjJADAAAYRsgBAAAMI+QAAACGEXIAAADDCDkAAIBh/h+W0KhiY0JhWAAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "%%local\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "%matplotlib inline \n", "from pylab import rcParams\n", "rcParams['figure.figsize'] = 15, 10\n", "hist, bins = np.histogram(df1[\"value\"], bins=50)\n", "width = 0.7 * (bins[1] - bins[0])\n", "center = (bins[:-1] + bins[1:]) / 2\n", "plt.bar(center, hist, align='center', width=width)\n", "plt.title(\"Histogram of values\")\n", "plt.xlabel(\"value\")\n", "plt.ylabel(\"count\")\n", "plt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "PySpark", "language": "", "name": "pysparkkernel" }, "language_info": { "codemirror_mode": { "name": "python", "version": 2 }, "mimetype": "text/x-python", "name": "pyspark", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 2 }