Skip to content

Instantly share code, notes, and snippets.

@pschatzmann
Created December 12, 2018 20:05
Show Gist options
  • Save pschatzmann/f2a41c1abd7db327ca7bb09275662ed6 to your computer and use it in GitHub Desktop.
Save pschatzmann/f2a41c1abd7db327ca7bb09275662ed6 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{"cells":[{"metadata":{},"cell_type":"markdown","source":"# MLLib: Predicting the Stock Movement from the News\nIn this demo we will show how we can predict if a stock is going up or down based on the news headlines.\n\nThe solution consists of the following components\n- Spark _MLLib_ (Machine Learning)\n- My _News-Digest_ (which I have described in my last blogs)\n- _Investor_ (we determine the stock prices to calculate the labels )\n\n## Setup\nWe import the necessary libraries with the help of Maven"},{"metadata":{"trusted":true},"cell_type":"code","source":"%classpath config resolver maven-public1 http://nuc.local:8081/repository/maven-public/\n%%classpath add mvn \norg.apache.spark:spark-sql_2.11:2.3.2\norg.apache.spark:spark-mllib_2.11:2.3.2\nch.pschatzmann:news-digest:LATEST\nch.pschatzmann:investor:LATEST\ncom.github.habernal:confusion-matrix:1.0","execution_count":217,"outputs":[{"data":{"application/vnd.jupyter.widget-view+json":{"model_id":"","version_major":2,"version_minor":0},"method":"display_data"},"metadata":{},"output_type":"display_data"}]},{"metadata":{},"cell_type":"markdown","source":"And we import the necessary packages or Classes,"},{"metadata":{"trusted":true},"cell_type":"code","source":"import org.apache.spark.sql.SparkSession\nimport org.apache.spark.sql.functions._\nimport org.apache.spark.ml.feature.RegexTokenizer\nimport org.apache.spark.ml.feature.HashingTF\nimport org.apache.spark.ml.classification._\nimport org.apache.spark.ml.Pipeline\nimport org.apache.spark.ml.feature._\nimport org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator\n\nimport ch.pschatzmann.news._\nimport ch.pschatzmann.stocks._\nimport ch.pschatzmann.stocks.ta4j.indicator._\nimport ch.pschatzmann.stocks.integration.HistoricValues\nimport ch.pschatzmann.stocks.accounting._\n\nimport org.ta4j.core.indicators._;\nimport org.ta4j.core.indicators.helpers._;\nimport com.github.habernal.confusionmatrix._","execution_count":242,"outputs":[{"data":{"text/plain":"import org.apache.spark.sql.SparkSession\nimport org.apache.spark.sql.functions._\nimport org.apache.spark.ml.feature.RegexTokenizer\nimport org.apache.spark.ml.feature.HashingTF\nimport org.apache.spark.ml.classification._\nimport org.apache.spark.ml.Pipeline\nimport org.apache.spark.ml.feature._\nimport org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator\nimport ch.pschatzmann.news._\nimport ch.pschatzmann.stocks._\nimport ch.pschatzmann.stocks.ta4j.indicator._\nimport ch.pschatzmann.stocks.integration.HistoricValues\nimport ch.pschatzmann.stocks.accounting._\nimport org.ta4j.core.indicators._\nimport org.ta4j.core.indicators.helpers._\nimport com.github.habernal.confusionmatrix._\n"},"execution_count":242,"metadata":{},"output_type":"execute_result"}]},{"metadata":{},"cell_type":"markdown","source":"Finally we create a Spark session and context."},{"metadata":{"trusted":true},"cell_type":"code","source":"val spark = SparkSession.builder()\n .appName(\"SolrQuery\")\n .master(\"local[*]\")\n .config(\"spark.ui.enabled\", \"false\")\n .getOrCreate()\nval sc = spark.sparkContext","execution_count":219,"outputs":[{"data":{"text/plain":"org.apache.spark.SparkContext@e6b0028"},"execution_count":219,"metadata":{},"output_type":"execute_result"}]},{"metadata":{},"cell_type":"markdown","source":"## Features: Selecting News Articles\nIn order to build our features, we search for all news articles for the MSFT ticker: we select\n- the date and \n- the content."},{"metadata":{"trusted":true},"cell_type":"code","source":"import spark.implicits._\nimport scala.collection.JavaConverters._\n\nval ticker = \"MSFT\"\nval query = Utils.companyNameByTickerSearch(ticker)\nval store = new SolrDocumentStore()\nval rdd = sc.parallelize(store.pagedDocuments(query).asScala)\n .map(page => page.values.asScala)\n .flatMap(l => l)\nval df = spark.createDataFrame(rdd, classOf[Document])\nval newsDataset = df.withColumn(\"date\", expr(\"substring(publishDate_t, 1, 10)\"))\n .select(\"date\",\"content_t\")\n\nnewsDataset.show","execution_count":236,"outputs":[{"name":"stdout","output_type":"stream","text":"+----------+--------------------+\n| date| content_t|\n+----------+--------------------+\n|2000-01-15|Microsoft boss ov...|\n|2000-01-23|For your pleasure...|\n|2000-02-01|Microsoft finance...|\n|2000-02-02|365 Corporation p...|\n|2000-02-17|Windows open on a...|\n|2000-03-21|Microsoft to sell...|\n|2000-03-24|Judgment day fill...|\n|2000-03-25|Cisco eclipses Mi...|\n|2000-04-02|Microsoft talks c...|\n|2000-04-05|Markets swoon aft...|\n|2000-04-12|Microsoft hires B...|\n|2000-04-21|Microsoft victors...|\n|2000-04-25|Microsoft plunges...|\n|2000-04-25|Microsoft issue s...|\n|2000-05-01|Microsoft bounces...|\n|2000-05-11|World Online hire...|\n|2000-05-25|Microsoft falls o...|\n|2000-06-07|Judge orders Micr...|\n|2000-06-28|Microsoft and AT&...|\n|2000-07-06|Microsoft offers ...|\n+----------+--------------------+\nonly showing top 20 rows\n\n"},{"data":{"text/plain":"org.apache.spark.sql.SparkSession$implicits$@734f1aa9"},"execution_count":236,"metadata":{},"output_type":"execute_result"}]},{"metadata":{},"cell_type":"markdown","source":"## Labels: Descriptions\nWe plan to use the Investor SignIndicator: We create the String labels for the indicated numeric values."},{"metadata":{"trusted":true},"cell_type":"code","source":"val descriptions = Seq((0.0,\"neutral\"),(1.0 ,\"positive\"),(-1.0 ,\"negative\"))\nval descriptionsDF = sc.parallelize(descriptions).toDF(\"value\",\"labelDescr\")\ndescriptionsDF.show","execution_count":222,"outputs":[{"name":"stdout","output_type":"stream","text":"+-----+----------+\n|value|labelDescr|\n+-----+----------+\n| 0.0| neutral|\n| 1.0| positive|\n| -1.0| negative|\n+-----+----------+\n\n"},{"data":{"text/plain":"null"},"execution_count":222,"metadata":{},"output_type":"execute_result"}]},{"metadata":{},"cell_type":"markdown","source":"## Labels: Values\nWe determine the label values for all dates with the help of the SignIndicator which is calculated from the difference of the closing price and the closing price of the future. If the value is 1.0 it will go up in the future, if it is -1.0 it will go down. \n\nWe convert the data into a dataframe and join it with the labels"},{"metadata":{"trusted":true},"cell_type":"code","source":"var stockData = Context.getStockData(ticker)\nvar series = stockData.toTimeSeries()\n\nvar close = new ClosePriceIndicator(series)\nvar closePrior = new OffsetIndicator(close, -1)\nvar closeNext = new OffsetIndicator(close, +5)\nvar sentimentHistoricValues = new SignIndicator(new DifferenceIndicator(closeNext, closePrior)).toHistoricValues()\nval dateFormat = new java.text.SimpleDateFormat(\"yyyy-MM-dd\")\nvar sentimentDF = sc.parallelize(sentimentHistoricValues\n .list().asScala\n .map(r => (dateFormat.format(r.getDate), r.getValue)))\n .toDF(\"date\",\"value\")\n\nsentimentDF = sentimentDF.join(descriptionsDF,Seq(\"value\")) \nsentimentDF.printSchema\n\n","execution_count":223,"outputs":[{"name":"stdout","output_type":"stream","text":"root\n |-- value: double (nullable = true)\n |-- date: string (nullable = true)\n |-- labelDescr: string (nullable = true)\n\n"},{"data":{"text/plain":"null"},"execution_count":223,"metadata":{},"output_type":"execute_result"}]},{"metadata":{"trusted":true},"cell_type":"code","source":"sentimentDF.show","execution_count":237,"outputs":[{"name":"stdout","output_type":"stream","text":"+-----+----------+----------+\n|value| date|labelDescr|\n+-----+----------+----------+\n| 0.0|1986-03-06| neutral|\n| 0.0|1986-03-14| neutral|\n| 0.0|1986-03-24| neutral|\n| 0.0|1986-04-18| neutral|\n| 0.0|1986-04-25| neutral|\n| 0.0|1986-05-05| neutral|\n| 0.0|1986-06-20| neutral|\n| 0.0|1986-07-15| neutral|\n| 0.0|1986-08-20| neutral|\n| 0.0|1987-01-28| neutral|\n| 0.0|1987-03-25| neutral|\n| 0.0|1987-07-07| neutral|\n| 0.0|1987-07-14| neutral|\n| 0.0|1987-07-15| neutral|\n| 0.0|1988-01-19| neutral|\n| 0.0|1988-02-17| neutral|\n| 0.0|1988-06-16| neutral|\n| 0.0|1988-09-01| neutral|\n| 0.0|1988-11-25| neutral|\n| 0.0|1989-01-09| neutral|\n+-----+----------+----------+\nonly showing top 20 rows\n\n"}]},{"metadata":{},"cell_type":"markdown","source":"## Building the Dataset\nOur __final input dataset__ consists of the newsDataset which is joined with the sentimentDF which contains the following colums\n- date (as string)\n- content_t (news headines which will give the features)\n- labelDescr (which is our label)"},{"metadata":{"trusted":true},"cell_type":"code","source":"val resultDf = newsDataset.join(sentimentDF,Seq(\"date\")).select(\"date\",\"content_t\",\"labelDescr\") \n\nresultDf.show","execution_count":238,"outputs":[{"name":"stdout","output_type":"stream","text":"+----------+--------------------+----------+\n| date| content_t|labelDescr|\n+----------+--------------------+----------+\n|2000-11-13|WHEN online broke...| negative|\n|2001-11-16|Rebounding from a...| positive|\n|2001-11-16|Oil Prices Drop S...| positive|\n|2001-11-16|The General Motor...| positive|\n|2001-11-16|The Telstra Corpo...| positive|\n|2001-11-16|On a day when oil...| positive|\n|2001-11-16|To the Editor: La...| positive|\n|2001-11-16|A federal appeals...| positive|\n|2001-11-16|The Mitsubishi Co...| positive|\n|2002-05-13|The Securities an...| negative|\n|2002-05-13|IF anyone has des...| negative|\n|2002-05-13|The last of the w...| negative|\n|2002-05-13|In the Fortune 50...| negative|\n|2002-05-13|WERTHEIMER-Franc....| negative|\n|2002-06-21|The decision by a...| negative|\n|2002-06-21|FALKINBURG-John N...| negative|\n|2002-06-21|Verizon Communica...| negative|\n|2002-06-21|The board of the ...| negative|\n|2002-06-21|WHITWELL-Joseph E...| negative|\n|2002-06-21|The information-g...| negative|\n+----------+--------------------+----------+\nonly showing top 20 rows\n\n"},{"data":{"text/plain":"null"},"execution_count":238,"metadata":{},"output_type":"execute_result"}]},{"metadata":{},"cell_type":"markdown","source":"Now we can split the data into a __training and test__ frame."},{"metadata":{"trusted":true},"cell_type":"code","source":"val Array(training, test) = resultDf.randomSplit(Array(0.9, 0.1), seed = 12345)\n\ns\"training: ${training.count} / test: ${test.count}\"","execution_count":226,"outputs":[{"data":{"text/plain":"training: 12353 / test: 1397"},"execution_count":226,"metadata":{},"output_type":"execute_result"}]},{"metadata":{},"cell_type":"markdown","source":"## Machine Learning Model / Pipline\nWe user the StringIndexer to convert the label to a number and the HashingTF to convert our headlines to a vector.\nThe model is trained by calling __fit__ and we get a prediction by calling __transform__ on the model."},{"metadata":{"trusted":true},"cell_type":"code","source":"val indexer = new StringIndexer()\n .setInputCol(\"labelDescr\")\n .setOutputCol(\"label\")\n .fit(resultDf)\n\nval converter = new IndexToString()\n .setInputCol(\"prediction\")\n .setOutputCol(\"predictedLabelDescr\")\n .setLabels(indexer.labels)\n\nval tokenizer = new RegexTokenizer()\n .setInputCol(\"content_t\")\n .setOutputCol(\"words\")\n\nval hashingTF = new HashingTF()\n .setInputCol(tokenizer.getOutputCol) \n .setOutputCol(\"features\")\n .setNumFeatures(5000)\n\nval classifier = new LogisticRegression()\n .setMaxIter(20)\n .setRegParam(0.01)\n\nval pipeline = new Pipeline().setStages(Array(tokenizer, indexer, hashingTF, classifier, converter))\nval model = pipeline.fit(training)\n\nval trainPredictions = model.transform(training)\nval testPredictions = model.transform(test)\n\n","execution_count":274,"outputs":[{"data":{"text/plain":"[date: string, content_t: string ... 8 more fields]"},"execution_count":274,"metadata":{},"output_type":"execute_result"}]},{"metadata":{},"cell_type":"markdown","source":"Here is the result of the predictions from our test dataset:"},{"metadata":{"trusted":true},"cell_type":"code","source":"testPredictions.select(\"date\",\"labelDescr\",\"predictedLabelDescr\").show\n","execution_count":275,"outputs":[{"name":"stdout","output_type":"stream","text":"+----------+----------+-------------------+\n| date|labelDescr|predictedLabelDescr|\n+----------+----------+-------------------+\n|2001-11-16| positive| positive|\n|2001-11-16| positive| positive|\n|2009-12-04| positive| positive|\n|2000-07-11| negative| negative|\n|2000-10-25| positive| positive|\n|2000-10-25| positive| negative|\n|2000-10-25| positive| negative|\n|2004-12-03| negative| negative|\n|2005-02-01| negative| negative|\n|2006-03-28| negative| positive|\n|2007-02-02| negative| negative|\n|2009-10-01| positive| negative|\n|2001-05-04| negative| negative|\n|2002-08-28| negative| positive|\n|2002-12-31| positive| negative|\n|2004-12-02| negative| negative|\n|2006-03-30| negative| positive|\n|2008-02-29| positive| negative|\n|2008-09-25| negative| negative|\n|2010-06-15| negative| positive|\n+----------+----------+-------------------+\nonly showing top 20 rows\n\n"}]},{"metadata":{"trusted":true},"cell_type":"code","source":"testPredictions.printSchema","execution_count":276,"outputs":[{"name":"stdout","output_type":"stream","text":"root\n |-- date: string (nullable = true)\n |-- content_t: string (nullable = true)\n |-- labelDescr: string (nullable = true)\n |-- words: array (nullable = true)\n | |-- element: string (containsNull = true)\n |-- label: double (nullable = false)\n |-- features: vector (nullable = true)\n |-- rawPrediction: vector (nullable = true)\n |-- probability: vector (nullable = true)\n |-- prediction: double (nullable = false)\n |-- predictedLabelDescr: string (nullable = true)\n\n"}]},{"metadata":{},"cell_type":"markdown","source":"## Evaluation\nWe can calculate the accuracy..."},{"metadata":{"trusted":true},"cell_type":"code","source":"val evaluator = new MulticlassClassificationEvaluator()\n .setLabelCol(\"label\")\n .setPredictionCol(\"prediction\")\n .setMetricName(\"accuracy\")\nval accuracy = evaluator.evaluate(testPredictions)\n\ns\"Accuracy = $accuracy\"\n","execution_count":277,"outputs":[{"data":{"text/plain":"Accuracy = 0.5204008589835362"},"execution_count":277,"metadata":{},"output_type":"execute_result"}]},{"metadata":{},"cell_type":"markdown","source":"... and the Confusion Matrix"},{"metadata":{"trusted":true},"cell_type":"code","source":"val cm = new ConfusionMatrix()\ntestPredictions.collect.foreach(l => cm.increaseValue(l.getString(2),l.getString(9),1))\n\ncm\n","execution_count":278,"outputs":[{"data":{"text/plain":" ↓gold\\pred→ negative neutral positive\n negative 375 0 305\n neutral 1 0 3\n positive 361 0 352\n"},"execution_count":278,"metadata":{},"output_type":"execute_result"}]},{"metadata":{},"cell_type":"markdown","source":"## Accuracy Over Time Horizon\nWe calculate the accuracy of the prediction based on different number of days for the calculation if the stock goes up or down.\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"import scala.collection.JavaConverters._\nimport spark.implicits._\n\nclass StockPrediction(ticker:String, pipeline:Pipeline) {\n // calculate the accuracy\n def accuracy(days:Int):Double = {\n val query = Utils.companyNameByTickerSearch(ticker)\n val store = new SolrDocumentStore()\n val rdd = sc.parallelize(store.pagedDocuments(query).asScala)\n .map(page => page.values.asScala)\n .flatMap(l => l)\n val df = spark.createDataFrame(rdd, classOf[Document])\n val newsDataset = df.withColumn(\"date\", expr(\"substring(publishDate_t, 1, 10)\"))\n .select(\"date\",\"content_t\")\n\n var stockData = Context.getStockData(ticker)\n var series = stockData.toTimeSeries()\n var close = new ClosePriceIndicator(series)\n var closePrior = new OffsetIndicator(close, -1)\n var closeNext = new OffsetIndicator(close, +days)\n var sentimentHistoricValues = new SignIndicator(new DifferenceIndicator(closeNext, closePrior)).toHistoricValues()\n val dateFormat = new java.text.SimpleDateFormat(\"yyyy-MM-dd\")\n var sentimentDF = sc.parallelize(sentimentHistoricValues\n .list().asScala\n .map(r => (dateFormat.format(r.getDate), r.getValue)))\n .toDF(\"date\",\"value\")\n sentimentDF = sentimentDF.join(descriptionsDF,Seq(\"value\")) \n\n val resultDf = newsDataset.join(sentimentDF,Seq(\"date\")).select(\"date\",\"content_t\",\"labelDescr\") \n\n val Array(training, test) = resultDf.randomSplit(Array(0.9, 0.1), seed = 12345)\n val model = pipeline.fit(training)\n val testPredictions = model.transform(test)\n \n val evaluator = new MulticlassClassificationEvaluator()\n .setLabelCol(\"label\")\n .setPredictionCol(\"prediction\")\n .setMetricName(\"accuracy\")\n val accuracy = evaluator.evaluate(testPredictions)\n return accuracy\n }\n}\n","execution_count":294,"outputs":[{"data":{"text/plain":"org.apache.spark.sql.SparkSession$implicits$@734f1aa9"},"execution_count":294,"metadata":{},"output_type":"execute_result"}]},{"metadata":{"trusted":true},"cell_type":"code","source":"new StockPrediction(ticker, pipeline).accuracy(5)","execution_count":301,"outputs":[{"data":{"text/plain":"0.5204008589835362"},"execution_count":301,"metadata":{},"output_type":"execute_result"}]},{"metadata":{},"cell_type":"markdown","source":"We calculate the accuracy for 1 to 100 days and display the result as chart"},{"metadata":{"trusted":true},"cell_type":"code","source":"val days = (1 to 100)\nval prediction = new StockPrediction(ticker, pipeline)\nval accuracies = days.map(n => prediction.accuracy(n))","execution_count":298,"outputs":[{"data":{"text/plain":"[[0.5053686471009305, 0.5089477451682176, 0.5404438081603435, 0.521832498210451, 0.5204008589835362, 0.5397279885468862, 0.49964209019327127, 0.5189692197566214, 0.5497494631352899, 0.5318539727988547, 0.5311381531853973, 0.5404438081603435, 0.5161059413027917, 0.506084466714388, 0.513242662848962, 0.5304223335719399, 0.5518969219756621, 0.5375805297065139, 0.5289906943450251, 0.5561918396564066, 0.5583392984967788, 0.5590551181102362, 0.5547602004294918, 0.5497494631352899, 0.5390121689334287, 0.5497494631352899, 0.5390121689334287, 0.5340014316392269, 0.5325697924123121, 0.5068002863278454, 0.5211166785969935, 0.5246957766642806, 0.5425912670007158, 0.5447387258410881, 0.5340014316392269, 0.5497494631352899, 0.5576234788833214, 0.5590551181102362, 0.5626342161775233, 0.5447387258410881, 0.5576234788833214, 0.5318539727988547, 0.5390121689334287, 0.5476020042949177, 0.5461703650680029, 0.5476020042949177, 0.5612025769506085, 0.5669291338582677, 0.5511811023622047, 0.560486757337151, 0.5497494631352899, 0.560486757337151, 0.5483178239083751, 0.560486757337151, 0.5483178239083751, 0.5597709377236937, 0.56907659269864, 0.5612025769506085, 0.5447387258410881, 0.5597709377236937, 0.5404438081603435, 0.521832498210451, 0.5347172512526843, 0.5676449534717252, 0.5597709377236937, 0.5354330708661418, 0.5368647100930566, 0.5440229062276306, 0.5261274158911954, 0.5590551181102362, 0.5340014316392269, 0.5375805297065139, 0.5361488904795991, 0.5425912670007158, 0.5110952040085899, 0.5289906943450251, 0.5182534001431639, 0.541159627773801, 0.5504652827487473, 0.5418754473872585, 0.5239799570508232, 0.5368647100930566, 0.5361488904795991, 0.5433070866141733, 0.5347172512526843, 0.5175375805297066, 0.5547602004294918, 0.5447387258410881, 0.5361488904795991, 0.5297065139584825, 0.5425912670007158, 0.5418754473872585, 0.5382963493199714, 0.5368647100930566, 0.5397279885468862, 0.5476020042949177, 0.5518969219756621, 0.5425912670007158, 0.5397279885468862, 0.5533285612025769]]"},"execution_count":298,"metadata":{},"output_type":"execute_result"}]},{"metadata":{"trusted":true},"cell_type":"code","source":"val plot = new Plot\nplot.add(new Line { x = days; y = accuracies })\n","execution_count":302,"outputs":[{"output_type":"display_data","data":{"method":"display_data","application/vnd.jupyter.widget-view+json":{"version_minor":0,"model_id":"09c78a06-9515-46a3-ba23-e42b107f4eb8","version_major":2}},"metadata":{}}]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"name":"scala","display_name":"Scala","language":"scala"},"language_info":{"nbconverter_exporter":"","codemirror_mode":"text/x-scala","name":"Scala","mimetype":"","file_extension":".scala","version":"2.11.12"},"toc":{"nav_menu":{},"number_sections":false,"sideBar":false,"skip_h1_title":false,"base_numbering":1,"title_cell":"Table of Contents","title_sidebar":"Contents","toc_cell":false,"toc_position":{},"toc_section_display":false,"toc_window_display":false},"widgets":{"application/vnd.jupyter.widget-state+json":{"version_major":2,"version_minor":0,"state":{"09c78a06-9515-46a3-ba23-e42b107f4eb8":{"model_name":"PlotModel","model_module":"beakerx","model_module_version":"*","state":{"model":{"type":"Plot","init_width":640,"init_height":480,"chart_title":null,"show_legend":null,"use_tool_tip":true,"legend_position":{"type":"LegendPosition","position":"TOP_RIGHT"},"legend_layout":"VERTICAL","custom_styles":[],"element_styles":{},"domain_axis_label":null,"y_label":"","rangeAxes":[{"type":"YAxis","label":"","auto_range":true,"auto_range_includes_zero":false,"lower_margin":0,"upper_margin":0,"lower_bound":0,"upper_bound":0,"use_log":false,"log_base":10}],"x_lower_margin":0.05,"x_upper_margin":0.05,"y_auto_range":true,"y_auto_range_includes_zero":false,"y_lower_margin":0,"y_upper_margin":0,"y_lower_bound":0,"y_upper_bound":0,"log_y":false,"timezone":null,"crosshair":null,"omit_checkboxes":false,"auto_zoom":false,"graphics_list":[{"type":"line","uid":"4fa49529-73f5-4ff3-9f8e-98476726b37f","visible":true,"yAxis":null,"hasClickAction":false,"x":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100],"y":[0.5053686471009305,0.5089477451682176,0.5404438081603435,0.521832498210451,0.5204008589835362,0.5397279885468862,0.49964209019327127,0.5189692197566214,0.5497494631352899,0.5318539727988547,0.5311381531853973,0.5404438081603435,0.5161059413027917,0.506084466714388,0.513242662848962,0.5304223335719399,0.5518969219756621,0.5375805297065139,0.5289906943450251,0.5561918396564066,0.5583392984967788,0.5590551181102362,0.5547602004294918,0.5497494631352899,0.5390121689334287,0.5497494631352899,0.5390121689334287,0.5340014316392269,0.5325697924123121,0.5068002863278454,0.5211166785969935,0.5246957766642806,0.5425912670007158,0.5447387258410881,0.5340014316392269,0.5497494631352899,0.5576234788833214,0.5590551181102362,0.5626342161775233,0.5447387258410881,0.5576234788833214,0.5318539727988547,0.5390121689334287,0.5476020042949177,0.5461703650680029,0.5476020042949177,0.5612025769506085,0.5669291338582677,0.5511811023622047,0.560486757337151,0.5497494631352899,0.560486757337151,0.5483178239083751,0.560486757337151,0.5483178239083751,0.5597709377236937,0.56907659269864,0.5612025769506085,0.5447387258410881,0.5597709377236937,0.5404438081603435,0.521832498210451,0.5347172512526843,0.5676449534717252,0.5597709377236937,0.5354330708661418,0.5368647100930566,0.5440229062276306,0.5261274158911954,0.5590551181102362,0.5340014316392269,0.5375805297065139,0.5361488904795991,0.5425912670007158,0.5110952040085899,0.5289906943450251,0.5182534001431639,0.541159627773801,0.5504652827487473,0.5418754473872585,0.5239799570508232,0.5368647100930566,0.5361488904795991,0.5433070866141733,0.5347172512526843,0.5175375805297066,0.5547602004294918,0.5447387258410881,0.5361488904795991,0.5297065139584825,0.5425912670007158,0.5418754473872585,0.5382963493199714,0.5368647100930566,0.5397279885468862,0.5476020042949177,0.5518969219756621,0.5425912670007158,0.5397279885468862,0.5533285612025769],"width":1.5,"legend":"","color":"#1F77B4","color_opacity":1,"style":"solid","stroke_dasharray":"","interpolation":"linear","elements":[{"x":0.47619047619047616,"y":0.48011782032400596,"index":0,"_x":1,"_y":0.5053686471009305},{"x":0.4766714766714767,"y":0.48257241040746196,"index":1,"_x":2,"_y":0.5089477451682176},{"x":0.47715247715247716,"y":0.5041728031418753,"index":2,"_x":3,"_y":0.5404438081603435},{"x":0.47763347763347763,"y":0.49140893470790376,"index":3,"_x":4,"_y":0.521832498210451},{"x":0.4781144781144781,"y":0.49042709867452144,"index":4,"_x":5,"_y":0.5204008589835362},{"x":0.4785954785954786,"y":0.5036818851251841,"index":5,"_x":6,"_y":0.5397279885468862},{"x":0.4790764790764791,"y":0.4761904761904762,"index":6,"_x":7,"_y":0.49964209019327127},{"x":0.47955747955747957,"y":0.4894452626411389,"index":7,"_x":8,"_y":0.5189692197566214},{"x":0.48003848003848004,"y":0.510554737358861,"index":8,"_x":9,"_y":0.5497494631352899},{"x":0.4805194805194805,"y":0.49828178694158076,"index":9,"_x":10,"_y":0.5318539727988547},{"x":0.481000481000481,"y":0.4977908689248896,"index":10,"_x":11,"_y":0.5311381531853973},{"x":0.48148148148148145,"y":0.5041728031418753,"index":11,"_x":12,"_y":0.5404438081603435},{"x":0.481962481962482,"y":0.4874815905743741,"index":12,"_x":13,"_y":0.5161059413027917},{"x":0.48244348244348245,"y":0.4806087383406971,"index":13,"_x":14,"_y":0.506084466714388},{"x":0.4829244829244829,"y":0.4855179185076093,"index":14,"_x":15,"_y":0.513242662848962},{"x":0.4834054834054834,"y":0.4972999509081984,"index":15,"_x":16,"_y":0.5304223335719399},{"x":0.48388648388648386,"y":0.5120274914089348,"index":16,"_x":17,"_y":0.5518969219756621},{"x":0.4843674843674844,"y":0.5022091310751104,"index":17,"_x":18,"_y":0.5375805297065139},{"x":0.48484848484848486,"y":0.4963181148748159,"index":18,"_x":19,"_y":0.5289906943450251},{"x":0.48532948532948533,"y":0.514972999509082,"index":19,"_x":20,"_y":0.5561918396564066},{"x":0.4858104858104858,"y":0.5164457535591556,"index":20,"_x":21,"_y":0.5583392984967788},{"x":0.4862914862914863,"y":0.5169366715758469,"index":21,"_x":22,"_y":0.5590551181102362},{"x":0.48677248677248675,"y":0.5139911634756995,"index":22,"_x":23,"_y":0.5547602004294918},{"x":0.48725348725348727,"y":0.510554737358861,"index":23,"_x":24,"_y":0.5497494631352899},{"x":0.48773448773448774,"y":0.5031909671084929,"index":24,"_x":25,"_y":0.5390121689334287},{"x":0.4882154882154882,"y":0.510554737358861,"index":25,"_x":26,"_y":0.5497494631352899},{"x":0.4886964886964887,"y":0.5031909671084929,"index":26,"_x":27,"_y":0.5390121689334287},{"x":0.48917748917748916,"y":0.4997545409916544,"index":27,"_x":28,"_y":0.5340014316392269},{"x":0.4896584896584897,"y":0.4987727049582719,"index":28,"_x":29,"_y":0.5325697924123121},{"x":0.49013949013949015,"y":0.4810996563573883,"index":29,"_x":30,"_y":0.5068002863278454},{"x":0.4906204906204906,"y":0.4909180166912126,"index":30,"_x":31,"_y":0.5211166785969935},{"x":0.4911014911014911,"y":0.4933726067746686,"index":31,"_x":32,"_y":0.5246957766642806},{"x":0.49158249158249157,"y":0.5056455571919489,"index":32,"_x":33,"_y":0.5425912670007158},{"x":0.49206349206349204,"y":0.5071183112420226,"index":33,"_x":34,"_y":0.5447387258410881},{"x":0.49254449254449256,"y":0.4997545409916544,"index":34,"_x":35,"_y":0.5340014316392269},{"x":0.49302549302549303,"y":0.510554737358861,"index":35,"_x":36,"_y":0.5497494631352899},{"x":0.4935064935064935,"y":0.5159548355424644,"index":36,"_x":37,"_y":0.5576234788833214},{"x":0.493987493987494,"y":0.5169366715758469,"index":37,"_x":38,"_y":0.5590551181102362},{"x":0.49446849446849445,"y":0.5193912616593028,"index":38,"_x":39,"_y":0.5626342161775233},{"x":0.494949494949495,"y":0.5071183112420226,"index":39,"_x":40,"_y":0.5447387258410881},{"x":0.49543049543049544,"y":0.5159548355424644,"index":40,"_x":41,"_y":0.5576234788833214},{"x":0.4959114959114959,"y":0.49828178694158076,"index":41,"_x":42,"_y":0.5318539727988547},{"x":0.4963924963924964,"y":0.5031909671084929,"index":42,"_x":43,"_y":0.5390121689334287},{"x":0.49687349687349686,"y":0.5090819833087874,"index":43,"_x":44,"_y":0.5476020042949177},{"x":0.4973544973544973,"y":0.5081001472754051,"index":44,"_x":45,"_y":0.5461703650680029},{"x":0.49783549783549785,"y":0.5090819833087874,"index":45,"_x":46,"_y":0.5476020042949177},{"x":0.4983164983164983,"y":0.5184094256259205,"index":46,"_x":47,"_y":0.5612025769506085},{"x":0.4987974987974988,"y":0.5223367697594502,"index":47,"_x":48,"_y":0.5669291338582677},{"x":0.49927849927849927,"y":0.5115365733922436,"index":48,"_x":49,"_y":0.5511811023622047},{"x":0.49975949975949974,"y":0.5179185076092292,"index":49,"_x":50,"_y":0.560486757337151},{"x":0.5002405002405003,"y":0.510554737358861,"index":50,"_x":51,"_y":0.5497494631352899},{"x":0.5007215007215007,"y":0.5179185076092292,"index":51,"_x":52,"_y":0.560486757337151},{"x":0.5012025012025012,"y":0.5095729013254787,"index":52,"_x":53,"_y":0.5483178239083751},{"x":0.5016835016835017,"y":0.5179185076092292,"index":53,"_x":54,"_y":0.560486757337151},{"x":0.5021645021645021,"y":0.5095729013254787,"index":54,"_x":55,"_y":0.5483178239083751},{"x":0.5026455026455027,"y":0.5174275895925381,"index":55,"_x":56,"_y":0.5597709377236937},{"x":0.5031265031265031,"y":0.5238095238095238,"index":56,"_x":57,"_y":0.56907659269864},{"x":0.5036075036075036,"y":0.5184094256259205,"index":57,"_x":58,"_y":0.5612025769506085},{"x":0.5040885040885041,"y":0.5071183112420226,"index":58,"_x":59,"_y":0.5447387258410881},{"x":0.5045695045695046,"y":0.5174275895925381,"index":59,"_x":60,"_y":0.5597709377236937},{"x":0.5050505050505051,"y":0.5041728031418753,"index":60,"_x":61,"_y":0.5404438081603435},{"x":0.5055315055315055,"y":0.49140893470790376,"index":61,"_x":62,"_y":0.521832498210451},{"x":0.506012506012506,"y":0.5002454590083456,"index":62,"_x":63,"_y":0.5347172512526843},{"x":0.5064935064935064,"y":0.5228276877761414,"index":63,"_x":64,"_y":0.5676449534717252},{"x":0.506974506974507,"y":0.5174275895925381,"index":64,"_x":65,"_y":0.5597709377236937},{"x":0.5074555074555075,"y":0.5007363770250369,"index":65,"_x":66,"_y":0.5354330708661418},{"x":0.5079365079365079,"y":0.5017182130584192,"index":66,"_x":67,"_y":0.5368647100930566},{"x":0.5084175084175084,"y":0.5066273932253313,"index":67,"_x":68,"_y":0.5440229062276306},{"x":0.5088985088985089,"y":0.4943544428080511,"index":68,"_x":69,"_y":0.5261274158911954},{"x":0.5093795093795094,"y":0.5169366715758469,"index":69,"_x":70,"_y":0.5590551181102362},{"x":0.5098605098605099,"y":0.4997545409916544,"index":70,"_x":71,"_y":0.5340014316392269},{"x":0.5103415103415103,"y":0.5022091310751104,"index":71,"_x":72,"_y":0.5375805297065139},{"x":0.5108225108225108,"y":0.501227295041728,"index":72,"_x":73,"_y":0.5361488904795991},{"x":0.5113035113035113,"y":0.5056455571919489,"index":73,"_x":74,"_y":0.5425912670007158},{"x":0.5117845117845118,"y":0.4840451644575356,"index":74,"_x":75,"_y":0.5110952040085899},{"x":0.5122655122655123,"y":0.4963181148748159,"index":75,"_x":76,"_y":0.5289906943450251},{"x":0.5127465127465127,"y":0.48895434462444776,"index":76,"_x":77,"_y":0.5182534001431639},{"x":0.5132275132275133,"y":0.5046637211585666,"index":77,"_x":78,"_y":0.541159627773801},{"x":0.5137085137085137,"y":0.5110456553755522,"index":78,"_x":79,"_y":0.5504652827487473},{"x":0.5141895141895142,"y":0.5051546391752577,"index":79,"_x":80,"_y":0.5418754473872585},{"x":0.5146705146705147,"y":0.49288168875797744,"index":80,"_x":81,"_y":0.5239799570508232},{"x":0.5151515151515151,"y":0.5017182130584192,"index":81,"_x":82,"_y":0.5368647100930566},{"x":0.5156325156325157,"y":0.501227295041728,"index":82,"_x":83,"_y":0.5361488904795991},{"x":0.5161135161135161,"y":0.5061364752086402,"index":83,"_x":84,"_y":0.5433070866141733},{"x":0.5165945165945166,"y":0.5002454590083456,"index":84,"_x":85,"_y":0.5347172512526843},{"x":0.517075517075517,"y":0.4884634266077566,"index":85,"_x":86,"_y":0.5175375805297066},{"x":0.5175565175565175,"y":0.5139911634756995,"index":86,"_x":87,"_y":0.5547602004294918},{"x":0.5180375180375181,"y":0.5071183112420226,"index":87,"_x":88,"_y":0.5447387258410881},{"x":0.5185185185185185,"y":0.501227295041728,"index":88,"_x":89,"_y":0.5361488904795991},{"x":0.518999518999519,"y":0.4968090328915071,"index":89,"_x":90,"_y":0.5297065139584825},{"x":0.5194805194805194,"y":0.5056455571919489,"index":90,"_x":91,"_y":0.5425912670007158},{"x":0.51996151996152,"y":0.5051546391752577,"index":91,"_x":92,"_y":0.5418754473872585},{"x":0.5204425204425205,"y":0.5027000490918018,"index":92,"_x":93,"_y":0.5382963493199714},{"x":0.5209235209235209,"y":0.5017182130584192,"index":93,"_x":94,"_y":0.5368647100930566},{"x":0.5214045214045214,"y":0.5036818851251841,"index":94,"_x":95,"_y":0.5397279885468862},{"x":0.5218855218855218,"y":0.5090819833087874,"index":95,"_x":96,"_y":0.5476020042949177},{"x":0.5223665223665224,"y":0.5120274914089348,"index":96,"_x":97,"_y":0.5518969219756621},{"x":0.5228475228475229,"y":0.5056455571919489,"index":97,"_x":98,"_y":0.5425912670007158},{"x":0.5233285233285233,"y":0.5036818851251841,"index":98,"_x":99,"_y":0.5397279885468862},{"x":0.5238095238095238,"y":0.5130093274423171,"index":99,"_x":100,"_y":0.5533285612025769}],"yAxisLabel":null,"showItem":true,"useToolTip":true,"stroke_opacity":0,"index":0,"id":"i0"}],"constant_lines":[],"constant_bands":[],"rasters":[],"texts":[],"x_auto_range":true,"x_lower_bound":0,"x_upper_bound":0,"log_x":false,"x_log_base":10,"x_tickLabels_visible":true,"y_tickLabels_visible":true,"numberOfPoints":100,"outputPointsLimit":1000000,"outputPointsPreviewNumber":10000,"tips":{"i0_11":{"id":"i0_11","idx":0,"ele":{"x":0.48148148148148145,"y":0.5041728031418753,"index":11,"_x":12,"_y":0.5404438081603435},"isresp":true,"cx":167.9,"cy":196.8,"tooltip_cx":167.9,"tooltip_cy":196.8,"tooltip_r":5,"op":0,"sticking":false,"targetx":0.4814846479132193,"targety":0.5041758241758242,"datax":0.4822705518018018,"datay":0.5034817612942614,"scrx":175.67187499999994,"scry":203.48437499999963,"hidden":true}}}}}}}}},"nbformat":4,"nbformat_minor":2}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment