Skip to content

Instantly share code, notes, and snippets.

@jminas
Created August 28, 2015 01:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jminas/70968f75aa9d3f8939fe to your computer and use it in GitHub Desktop.
Save jminas/70968f75aa9d3f8939fe to your computer and use it in GitHub Desktop.
pyspark
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"''"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sc"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import numpy as np\n",
"from sklearn.datasets import fetch_20newsgroups\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib inline\n",
"from StringIO import StringIO\n",
"from datetime import datetime\n",
"from collections import namedtuple\n",
"from operator import add, itemgetter\n",
"from sklearn.linear_model import SGDClassifier\n",
"import csv\n",
"from pyspark import SparkConf, SparkContext\n",
"from pyspark.mllib.classification import LogisticRegressionWithSGD\n",
"from pyspark.mllib.regression import LabeledPoint"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"Spark\n",
"=====\n",
"***"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"##The Word Count Example\n",
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####A spark context is the main entry point for Spark functionality. It is the connection to the Spark cluster and can be used to creat RDDs, accumulators and broadcast variables on that cluster"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####RDD = Resilient Distributed Dataset. This is an immutable, partitioned collection of elements that can be operated upon in parallel"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"spark_home = os.environ.get('SPARK_HOME', None)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"/home/jenni/spark-1.2.0-bin-hadoop2.4/\n"
]
}
],
"source": [
"print spark_home"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Let's look at a simple text file and do some word counting"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"text_file = sc.textFile(\"../README.md\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####This produces a new RDD"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"pyspark.rdd.RDD"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"type(text_file)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"../README.md MappedRDD[1] at textFile at NativeMethodAccessorImpl.java:-2\n"
]
}
],
"source": [
"print text_file"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####We can apply a filter using an anonymous function"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"lines_not_empty = text_file.filter(lambda x: len(x) > 0)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"36"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"lines_not_empty.count()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####flatMap() \n",
"- flattens the return lists into a single list"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"words = text_file.flatMap(lambda x: x.split())"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[u'#',\n",
" u'DS_BOS_06',\n",
" u'The',\n",
" u'6th',\n",
" u'iteration',\n",
" u'of',\n",
" u'the',\n",
" u'Boston',\n",
" u'Data',\n",
" u'Science']"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"words.take(10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####The map function\n",
"- map returns a new RDD containing values created by applying the supplied lambda function to each value in the original RDD\n",
"- A map function utlizing the anonymous Pyhon function lambda"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"words = words.map(lambda x: x.replace('|', '').replace('.', '').\\\n",
" replace('-', '').replace(' ', '').replace('&', '').replace('#','').upper())"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[u'',\n",
" u'DS_BOS_06',\n",
" u'THE',\n",
" u'6TH',\n",
" u'ITERATION',\n",
" u'OF',\n",
" u'THE',\n",
" u'BOSTON',\n",
" u'DATA',\n",
" u'SCIENCE']"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"words.take(10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####A word counting mapper function"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"word_counts = words.map(lambda x: (x, 1))"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[(u'', 1),\n",
" (u'DS_BOS_06', 1),\n",
" (u'THE', 1),\n",
" (u'6TH', 1),\n",
" (u'ITERATION', 1),\n",
" (u'OF', 1),\n",
" (u'THE', 1),\n",
" (u'BOSTON', 1),\n",
" (u'DATA', 1),\n",
" (u'SCIENCE', 1)]"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"word_counts.take(10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Now do a reduction\n",
"#####The reduceByKey function\n",
"- input must be tuples of the form (key, value)\n",
"- creates a new RDD containing a tuple for each unique value of the key\n",
"- the value in the output depends upon the supplied lambda function"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"word_counts = word_counts.reduceByKey(lambda a, b: a + b)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[(u'', 45),\n",
" (u'PYTHON,', 1),\n",
" (u'[WEB', 1),\n",
" (u'LEARNING', 1),\n",
" (u'BEGINNERS](HTTP://WWWCYBERCITIBIZ/OPENSOURCE/LEARNINGBASHSCRIPTINGFORBEGINNERS/)',\n",
" 1),\n",
" (u'[PEER', 1),\n",
" (u'8/18:', 1),\n",
" (u'SUBMISSION:**', 1),\n",
" (u'<BR>**PROJECT', 4),\n",
" (u'**EXPERTINRESIDENCE:**', 1)]"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"word_counts.take(10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Now do another map and swap the key and the value in terms of their positions\n",
"#####Which will make the value the key"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"word_counts = word_counts.map(lambda x: (x[1], x[0]))"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"word_counts = word_counts.sortByKey(False)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[(45, u''),\n",
" (8, u'AND'),\n",
" (6, u'DATA'),\n",
" (5, u'SCIENCE'),\n",
" (4, u'<BR>**PROJECT'),\n",
" (4, u'THE'),\n",
" (4, u'PROJECT'),\n",
" (4, u'MILESTONE'),\n",
" (3, u'[DATA'),\n",
" (3, u'[INTRO')]"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"word_counts.take(10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"##A Second Work Count Example\n",
"---"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"lines = sc.parallelize(['Its fun to have fun,','but you have to know how.']) "
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"rd1 = lines.map(lambda x: x.replace('|', '').\\\n",
" replace('.', '').replace('-', '').replace('&', '').replace('#','').upper())"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"['ITS FUN TO HAVE FUN,', 'BUT YOU HAVE TO KNOW HOW']"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rd1.take(10)"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"rd2 = rd1.flatMap(lambda x: x.split())"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"['ITS', 'FUN', 'TO', 'HAVE', 'FUN,', 'BUT', 'YOU', 'HAVE', 'TO', 'KNOW', 'HOW']"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rd2.take(20)"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"#create the tuples required for the reduce step, 1 is the value and this will be counted by the reduce lamda function\n",
"rd3 = rd2.map(lambda x: (x, 1))"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"rd4 = rd3.reduceByKey(lambda a, b: a + b)"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[('TO', 2),\n",
" ('BUT', 1),\n",
" ('HOW', 1),\n",
" ('KNOW', 1),\n",
" ('HAVE', 2),\n",
" ('FUN', 1),\n",
" ('FUN,', 1),\n",
" ('YOU', 1),\n",
" ('ITS', 1)]"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rd4.take(20)"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"#use the another map function to swap the key, value positionally\n",
"rd5 = rd4.map(lambda x: (x[1], x[0]))"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[(2, 'TO'),\n",
" (1, 'BUT'),\n",
" (1, 'HOW'),\n",
" (1, 'KNOW'),\n",
" (2, 'HAVE'),\n",
" (1, 'FUN'),\n",
" (1, 'FUN,'),\n",
" (1, 'YOU'),\n",
" (1, 'ITS')]"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rd5.take(20)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####The function sortByKey does exactly what is says, and sorts the tuples using the key value"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"rd6 = rd5.sortByKey(ascending=False)"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[(2, 'TO'),\n",
" (2, 'HAVE'),\n",
" (1, 'BUT'),\n",
" (1, 'HOW'),\n",
" (1, 'KNOW'),\n",
" (1, 'FUN'),\n",
" (1, 'FUN,'),\n",
" (1, 'YOU'),\n",
" (1, 'ITS')]"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rd6.take(20)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"##Working a slightly more complicated example\n",
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Let's use the 20 news groups dataset"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"ngd = fetch_20newsgroups(shuffle = True, remove = (\"headers\", \"footers\", \"quotes\"), random_state = 6)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Create an RDD"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"mrd1 = sc.parallelize(ngd.data) "
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(11314,)\n"
]
}
],
"source": [
"print np.shape(ngd.data)"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[u'\\n\\n\\n\\n\\nTheir should be no difference in the drive itself between IBM-PC and Mac.\\nThe two main differences are the formatting of the disk itself (but with\\nthe correct software each can read the others) and maybe the cable\\n(depends on your SCSI board on IBM-PC).\\n\\nIf you get some Mac softawre to allow mounting of ANY IBM-formatted disk\\nand the correct cable you should br able to mount and read your IBM-PC\\nsyquest.\\n\\ngood luck,\\n\\n--Paul\\n\\n-- \\n +-------------------------------------------------------------------------+\\n | Paul Hardwick | Technical Consulting | InterNet: hardwick@panix.com |\\n | P.O. Box 1482 | for MVS (SP/XA/ESA) | Voice: (212) 535-0998 |\\n | NY, NY 10274 | and 3rd party addons | Fax: (212) Pending |\\n +-------------------------------------------------------------------------+']"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mrd1.take(1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####glom() allows you to treat a partition as an array rather than as a single row at a time"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"test = mrd1.glom()"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"#test.take(1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Chain commands\n",
"#####The aim here is to get a list of sentences\n",
"1. Replace 'the' with ''\n",
"2. Convert to lowercase and replace end of lines with ''\n",
"3. Split sentences on full stops\n",
"4. Join sentences separating each with a space\n",
"5. Put everything into a single array"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"mrd2 = mrd1.glom().map(lambda x: \" \".join(x)).flatMap(lambda x: x.split('.')).map(lambda x: x.replace('\\n', '').\\\n",
" lower()).map(lambda x: x.replace('the', ''))"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[u'ir should be no difference in drive itself between ibm-pc and mac',\n",
" u' two main differences are formatting of disk itself (but with correct software each can read ors) and maybe cable(depends on your scsi board on ibm-pc)']"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mrd2.take(2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Using the sentences write a mapping function to create bigrams\n",
"- x a sentence\n",
"- return a key value pair that consists of a bigram, and a count 1\n",
"- loop over the sentence and insert adjacent pairs of words to form the bigram"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"bigrams = mrd2.map(lambda x: x.split()).flatMap(lambda x: [((x[i], x[i+1]), 1) for i in range(0, len(x)-1)])"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[((u'ir', u'should'), 1),\n",
" ((u'should', u'be'), 1),\n",
" ((u'be', u'no'), 1),\n",
" ((u'no', u'difference'), 1),\n",
" ((u'difference', u'in'), 1),\n",
" ((u'in', u'drive'), 1),\n",
" ((u'drive', u'itself'), 1),\n",
" ((u'itself', u'between'), 1),\n",
" ((u'between', u'ibm-pc'), 1),\n",
" ((u'ibm-pc', u'and'), 1)]"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bigrams.take(10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Sort then swap the key value position and reduce by the key"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"f_bigrams = bigrams.reduceByKey(lambda a, b: a + b).map(lambda x: (x[1], x[0])).sortByKey(False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Here's the list of the top bigrams!!"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[(2954, (u'to', u'be')),\n",
" (2895, (u'it', u'is')),\n",
" (2508, (u'is', u'a')),\n",
" (2178, (u'i', u'have')),\n",
" (2170, (u'if', u'you')),\n",
" (1854, (u'this', u'is')),\n",
" (1591, (u'of', u'a')),\n",
" (1531, (u'in', u'a')),\n",
" (1529, (u'i', u'am')),\n",
" (1496, (u'is', u'not'))]"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f_bigrams.take(10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"####Spark supports the efficient parallel application of map and reduce operations by dividing data up into multiple partitions.\n",
"- Each partition is replicated across multiple workers running on different nodes in a cluster so that failure of a single worker should not cause the RDD to become unavailable.\n",
"- Many operations including map and flatMap can be applied independently to each partition, running as concurrent jobs based on the number of available cores. \n",
"- When processing reduceByKey, Spark will create a number of output partitions based on the *default* paralellism based on the numbers of nodes and cores available to Spark. \n",
"- Data is effectively reshuffled so that input data from different input partitions with the same key value is passed to the same output partition and combined there using the specified reduce function. \n",
"- sortByKey is another operation which transforms N input partitions to M output partitions.\n",
"- The number of partitions generated by the reduce stage can be controlled by supplying the desired number of partitions as an extra parameter to reduceByKey"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 45,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sc.defaultParallelism"
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"new1 = sc.parallelize(ngd.data).glom().map(lambda x: \" \".join(x)).flatMap(lambda x: x.split('.')).\\\n",
" map(lambda x: x.replace('\\n', '').lower()).map(lambda x: x.replace('the', '')).map(lambda x: x.split()).\\\n",
" flatMap(lambda x: [((x[i], x[i+1]), 1) for i in range(0, len(x)-1)]).\\\n",
" reduceByKey(lambda a, b: a + b, numPartitions = 12).\\\n",
" map(lambda x: (x[1], x[0])).sortByKey(False)"
]
},
{
"cell_type": "code",
"execution_count": 88,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def countPartitions(id, iterator): \n",
" c = 0 \n",
" for _ in iterator: \n",
" c += 1 \n",
" yield (id, c) "
]
},
{
"cell_type": "code",
"execution_count": 89,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"{0: 54600, 1: 29046, 2: 90094, 11: 706648}"
]
},
"execution_count": 89,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"new1.mapPartitionsWithSplit(countPartitions).collectAsMap()"
]
},
{
"cell_type": "code",
"execution_count": 91,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[(2954, (u'to', u'be')),\n",
" (2895, (u'it', u'is')),\n",
" (2508, (u'is', u'a')),\n",
" (2178, (u'i', u'have')),\n",
" (2170, (u'if', u'you')),\n",
" (1854, (u'this', u'is')),\n",
" (1591, (u'of', u'a')),\n",
" (1531, (u'in', u'a')),\n",
" (1529, (u'i', u'am')),\n",
" (1496, (u'is', u'not'))]"
]
},
"execution_count": 91,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"new1.take(10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"##A Numerical Example\n",
"---"
]
},
{
"cell_type": "code",
"execution_count": 92,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def isprime(n):\n",
" \"\"\"\n",
" check if integer n is a prime\n",
" \"\"\"\n",
" \n",
" # make sure n is a positive integer\n",
" n = abs(int(n))\n",
" \n",
" # 0 and 1 are not primes\n",
" if n < 2:\n",
" return False\n",
" \n",
" # 2 is the only even prime number\n",
" if n == 2:\n",
" return True\n",
" \n",
" # all other even numbers are not primes\n",
" if not n & 1:\n",
" return False\n",
" \n",
" # range starts with 3 and only needs to go up the square root of n\n",
" # for all odd numbers\n",
" for x in range(3, int(n**0.5)+1, 2):\n",
" if n % x == 0:\n",
" return False\n",
" return True"
]
},
{
"cell_type": "code",
"execution_count": 93,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
]
},
"execution_count": 93,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Create an RDD of numbers from 0 to 1,000,000\n",
"nums = sc.parallelize(xrange(1000000))\n",
"\n",
"nums.take(10)"
]
},
{
"cell_type": "code",
"execution_count": 95,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"primes = nums.filter(isprime)"
]
},
{
"cell_type": "code",
"execution_count": 97,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]"
]
},
"execution_count": 97,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"primes.take(10)"
]
},
{
"cell_type": "code",
"execution_count": 98,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"78498\n"
]
}
],
"source": [
"# Compute the number of primes in the RDD\n",
"print primes.count()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"##Airline Delay Example\n",
"---\n",
"\n",
"[Modified from \"Getting Started with Spark (in Python) by Benjamin Bengfort](https://districtdatalabs.silvrback.com/getting-started-with-spark-in-python)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Setup some definitions and declarations"
]
},
{
"cell_type": "code",
"execution_count": 111,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"fields = ('date', 'airline', 'flightnum', 'origin', 'dest', 'dep',\n",
" 'dep_delay', 'arv', 'arv_delay', 'airtime', 'distance')\n",
"Flight = namedtuple('Flight', fields)\n",
"DATE_FMT = \"%Y-%m-%d\"\n",
"TIME_FMT = \"%H%M\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####NB: the use of named tuples\n",
"[Named tuples](http://pymotw.com/2/collections/namedtuple.html)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Function to parse a row of the database and fill a row of a named tuple"
]
},
{
"cell_type": "code",
"execution_count": 112,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def parse(row):\n",
" '''Parses a row and returns a named tuple'''\n",
"\n",
" row[0] = datetime.strptime(row[0], DATE_FMT).date()\n",
" row[5] = datetime.strptime(row[5], TIME_FMT).time()\n",
" row[6] = float(row[6])\n",
" row[7] = datetime.strptime(row[7], TIME_FMT).time()\n",
" row[8] = float(row[8])\n",
" row[9] = float(row[9])\n",
" row[10] = float(row[10])\n",
" \n",
" #function returns a completed named tuple constructed from a row\n",
" return Flight(*row[:11])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Uses line as if it were a file (StringIO)\n",
"#####csv.reader breaks it into lines"
]
},
{
"cell_type": "code",
"execution_count": 113,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def split(line):\n",
" '''Operator function for splitting a line with csv module'''\n",
" \n",
" reader = csv.reader(StringIO(line))\n",
" return reader.next()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####NB: the use of the broadcast function"
]
},
{
"cell_type": "code",
"execution_count": 114,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<type 'dict'>\n",
"19719 Coastal Air Transport: CTL\n",
"19718 Cresent Medivac: CRH\n",
"19713 Alliance Airlines: ACN\n",
"19712 Business Express Airlines: BEA\n",
"19711 Eagle Airline: EGA\n",
"19710 Armstrong Air Service Inc.: AAP\n"
]
}
],
"source": [
"#this is an airlines lookup dictionary\n",
"airlines = dict(sc.textFile(\"../Data/ontime/airlines.csv\").map(split).collect())\n",
"\n",
"print type(airlines)\n",
"\n",
"for i, key in enumerate(airlines.keys()):\n",
" print key, airlines[key]\n",
" if i == 5:\n",
" break"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####The airline lookup converts airline index to airline string"
]
},
{
"cell_type": "code",
"execution_count": 162,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"pyspark.broadcast.Broadcast"
]
},
"execution_count": 162,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#broadcast the dictionary to the cluster\n",
"airline_lookup = sc.broadcast(airlines)\n",
"\n",
"type(airline_lookup)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Eyeball the data in regular Python"
]
},
{
"cell_type": "code",
"execution_count": 163,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>date</th>\n",
" <th>airline</th>\n",
" <th>flightnum</th>\n",
" <th>origin</th>\n",
" <th>dest</th>\n",
" <th>dep</th>\n",
" <th>dep_delay</th>\n",
" <th>arv</th>\n",
" <th>arv_delay</th>\n",
" <th>airtime</th>\n",
" <th>distance</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>2014-04-01</td>\n",
" <td>19805</td>\n",
" <td>2</td>\n",
" <td>LAX</td>\n",
" <td>JFK</td>\n",
" <td>944</td>\n",
" <td>14</td>\n",
" <td>1736</td>\n",
" <td>-29</td>\n",
" <td>269</td>\n",
" <td>2475</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2014-04-01</td>\n",
" <td>19805</td>\n",
" <td>3</td>\n",
" <td>JFK</td>\n",
" <td>LAX</td>\n",
" <td>1224</td>\n",
" <td>-6</td>\n",
" <td>1614</td>\n",
" <td>39</td>\n",
" <td>371</td>\n",
" <td>2475</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" date airline flightnum origin dest dep dep_delay arv \\\n",
"0 2014-04-01 19805 2 LAX JFK 944 14 1736 \n",
"1 2014-04-01 19805 3 JFK LAX 1224 -6 1614 \n",
"\n",
" arv_delay airtime distance \n",
"0 -29 269 2475 \n",
"1 39 371 2475 "
]
},
"execution_count": 163,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df = pd.read_csv(\"../Data/ontime/flights.csv\")\n",
"df.columns = fields\n",
"df.head(2)"
]
},
{
"cell_type": "code",
"execution_count": 127,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class 'pyspark.rdd.PipelinedRDD'>\n"
]
},
{
"data": {
"text/plain": [
"[Flight(date=datetime.date(2014, 4, 1), airline='19805', flightnum='1', origin='JFK', dest='LAX', dep=datetime.time(8, 54), dep_delay=-6.0, arv=datetime.time(12, 17), arv_delay=2.0, airtime=355.0, distance=2475.0)]"
]
},
"execution_count": 127,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#load the flights data into an RDD\n",
"#transfer a row of the data into the named tuple, and then split it on the fields of the tuple\n",
"flights = sc.textFile(\"../Data/ontime/flights.csv\").map(split).map(parse)\n",
"\n",
"print type(flights)\n",
"\n",
"flights.take(1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Here's the named tuple in use"
]
},
{
"cell_type": "code",
"execution_count": 146,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2014-04-01\n",
"19805\n",
"1\n",
"JFK\n",
"LAX\n",
"-6.0\n",
"2.0\n"
]
}
],
"source": [
"tt = flights.take(1)[0]\n",
"print tt.date\n",
"print tt.airline\n",
"print tt.flightnum\n",
"print tt.origin\n",
"print tt.dest\n",
"print tt.dep_delay\n",
"print tt.arv_delay"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####and the airline lookup"
]
},
{
"cell_type": "code",
"execution_count": 135,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"'American Airlines Inc.: AA'"
]
},
"execution_count": 135,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"airline_lookup.value['19805']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####map the delays into a key value pair"
]
},
{
"cell_type": "code",
"execution_count": 156,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"#map the total delay to the airline, joined using the broadcast value\n",
"delays = flights.map(lambda f: (airline_lookup.value[f.airline], add(f.dep_delay, f.arv_delay)))"
]
},
{
"cell_type": "code",
"execution_count": 157,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class 'pyspark.rdd.PipelinedRDD'>\n"
]
},
{
"data": {
"text/plain": [
"[('American Airlines Inc.: AA', -4.0)]"
]
},
"execution_count": 157,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"print type(delays)\n",
"delays.take(1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Sum up the delays"
]
},
{
"cell_type": "code",
"execution_count": 158,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<type 'list'>\n",
"('Envoy Air: MQ', 493527.0)\n"
]
}
],
"source": [
"#reduce to the total delay for the month\n",
"delays = delays.reduceByKey(add).collect()\n",
"\n",
"print type(delays)\n",
"print delays[0]"
]
},
{
"cell_type": "code",
"execution_count": 159,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"#and sort the list\n",
"delays = sorted(delays, key=itemgetter(1))"
]
},
{
"cell_type": "code",
"execution_count": 160,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"('Alaska Airlines Inc.: AS', -45442.0)\n"
]
}
],
"source": [
"print delays[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####This is a list of airlines by increasing delay times"
]
},
{
"cell_type": "code",
"execution_count": 161,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Airline Delay (mins) \n",
"Alaska Airlines Inc.: AS -45442\n",
"Hawaiian Airlines Inc.: HA -20654\n",
"AirTran Airways Corporation: FL 39247\n",
"Virgin America: VX 40841\n",
"Frontier Airlines Inc.: F9 108480\n",
"US Airways Inc.: US 177717\n",
"JetBlue Airways: B6 279981\n",
"United Air Lines Inc.: UA 390614\n",
"American Airlines Inc.: AA 431755\n",
"Delta Air Lines Inc.: DL 461753\n",
"Envoy Air: MQ 493527\n",
"SkyWest Airlines Inc.: OO 519867\n",
"ExpressJet Airlines Inc.: EV 1160058\n",
"Southwest Airlines Co.: WN 2181955\n"
]
}
],
"source": [
"print \"{:43s} {:15s}\".format(\"Airline\", \"Delay (mins)\")\n",
"for d in delays:\n",
" print \"{:35s} {:15.0f}\".format(d[0], d[1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####and a plot of same"
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.text.Text at 0x11074f990>"
]
},
"execution_count": 51,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x116c49490>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig = plt.figure(figsize = (7,7))\n",
"ax = plt.subplot(111)\n",
"\n",
"\n",
"airlines = [d[0] for d in delays]\n",
"minutes = [d[1] for d in delays]\n",
"index = list(xrange(len(airlines)))\n",
"\n",
"bars = ax.barh(index, minutes)\n",
"\n",
"for idx, air, mins in zip(index, airlines, minutes):\n",
" if mins > 0:\n",
" bars[idx].set_color(\"red\")\n",
" ax.annotate(\"{:0.0f} min\".format(mins), xy=(mins+1, idx+0.5), va='center')\n",
" else:\n",
" bars[idx].set_color(\"blue\")\n",
" ax.annotate(\"{:0.0f} min\".format(mins), xy=(mins+1, idx+0.5), va='center')\n",
" \n",
"ax.set_yticks([idx + 0.5 for idx in index])\n",
"ax.set_yticklabels(airlines)\n",
"xt = ax.get_xticks()\n",
"ax.set_xticklabels([' ']*len(xt))\n",
"ax.grid(axis='x', color='white', linestyle='-')\n",
"ax.set_title(\"Total Minutes Delayed per Airline\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"#Logistic Regression with Spark MLlib\n",
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##Python First"
]
},
{
"cell_type": "code",
"execution_count": 165,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"dat = pd.read_csv(\"../Data/sample_svm_data.txt\", delimiter = ' ', header = None)"
]
},
{
"cell_type": "code",
"execution_count": 166,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>0</th>\n",
" <th>1</th>\n",
" <th>2</th>\n",
" <th>3</th>\n",
" <th>4</th>\n",
" <th>5</th>\n",
" <th>6</th>\n",
" <th>7</th>\n",
" <th>8</th>\n",
" <th>9</th>\n",
" <th>10</th>\n",
" <th>11</th>\n",
" <th>12</th>\n",
" <th>13</th>\n",
" <th>14</th>\n",
" <th>15</th>\n",
" <th>16</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1</td>\n",
" <td>0</td>\n",
" <td>2.520784</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>2.004684</td>\n",
" <td>2.000347</td>\n",
" <td>0</td>\n",
" <td>2.228387</td>\n",
" <td>2.228387</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" 0 1 2 3 4 5 6 7 8 9 10 \\\n",
"0 1 0 2.520784 0 0 0 2.004684 2.000347 0 2.228387 2.228387 \n",
"\n",
" 11 12 13 14 15 16 \n",
"0 0 0 0 0 0 0 "
]
},
"execution_count": 166,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dat.head(1)"
]
},
{
"cell_type": "code",
"execution_count": 167,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16]\n"
]
}
],
"source": [
"predictors = dat.columns.values[1:]\n",
"print predictors"
]
},
{
"cell_type": "code",
"execution_count": 168,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"X = dat[predictors]\n",
"y = dat[0]"
]
},
{
"cell_type": "code",
"execution_count": 169,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.636645962733\n"
]
}
],
"source": [
"clf_pySGD = SGDClassifier(loss='log', alpha = 0.01, n_iter = 10000)\n",
"clf_pySGD.fit(X, y)\n",
"yhat = clf_pySGD.predict(X)\n",
"print clf_pySGD.score(X, y)"
]
},
{
"cell_type": "code",
"execution_count": 170,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Predicted</th>\n",
" <th>0</th>\n",
" <th>1</th>\n",
" </tr>\n",
" <tr>\n",
" <th>Actual</th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>101</td>\n",
" <td>59</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>58</td>\n",
" <td>104</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
"Predicted 0 1\n",
"Actual \n",
"0 101 59\n",
"1 58 104"
]
},
"execution_count": 170,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cm = pd.crosstab(y, yhat, rownames=[\"Actual\"], colnames=[\"Predicted\"])\n",
"cm"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"##Spark\n",
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####LabeledPoint is a built in Pyspark class (label, features)"
]
},
{
"cell_type": "code",
"execution_count": 175,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def parse_point(line):\n",
" values = [float(x) for x in line.split(' ')]\n",
" return LabeledPoint(values[0], values[1:])"
]
},
{
"cell_type": "code",
"execution_count": 176,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"data = sc.textFile(\"../Data/sample_svm_data.txt\")"
]
},
{
"cell_type": "code",
"execution_count": 177,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[u'1 0 2.52078447201548 0 0 0 2.004684436494304 2.000347299268466 0 2.228387042742021 2.228387042742023 0 0 0 0 0 0']"
]
},
"execution_count": 177,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"data.take(1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####Map into key value pairs"
]
},
{
"cell_type": "code",
"execution_count": 178,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"parsed_data = data.map(parse_point)"
]
},
{
"cell_type": "code",
"execution_count": 188,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class 'pyspark.rdd.PipelinedRDD'>\n"
]
},
{
"data": {
"text/plain": [
"LabeledPoint(1.0, [0.0,2.52078447202,0.0,0.0,0.0,2.00468443649,2.00034729927,0.0,2.22838704274,2.22838704274,0.0,0.0,0.0,0.0,0.0,0.0])"
]
},
"execution_count": 188,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"print type(parsed_data)\n",
"parsed_data.first()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"######Use the Spark logisitic regression model"
]
},
{
"cell_type": "code",
"execution_count": 180,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class 'pyspark.mllib.classification.LogisticRegressionModel'>\n"
]
}
],
"source": [
"spark_clf = LogisticRegressionWithSGD.train(parsed_data)\n",
"\n",
"print type(spark_clf)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#####p is a labelled point"
]
},
{
"cell_type": "code",
"execution_count": 216,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"labels_and_predictions = parsed_data.map(lambda p: (p.label, spark_clf.predict(p.features)))"
]
},
{
"cell_type": "code",
"execution_count": 217,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"<class 'pyspark.rdd.PipelinedRDD'>\n",
"[(1.0, 1), (0.0, 1), (0.0, 0), (1.0, 1), (1.0, 0), (0.0, 1), (1.0, 1), (1.0, 1), (0.0, 0), (0.0, 0)]\n"
]
}
],
"source": [
"print type(labels_and_predictions)\n",
"print labels_and_predictions.take(10)"
]
},
{
"cell_type": "code",
"execution_count": 236,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"322\n"
]
}
],
"source": [
"print parsed_data.count()"
]
},
{
"cell_type": "code",
"execution_count": 271,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[(0.0, 59), (1.0, 104)]\n",
"[(0, 58.0), (1, 104.0)]\n",
"117\n",
"205\n"
]
}
],
"source": [
"yyhat = labels_and_predictions.reduceByKey(lambda x, y: x + y).collect()\n",
"landp = labels_and_predictions.map(lambda x : (x[1], x[0]))\n",
"yyhat_1 = landp.reduceByKey(lambda x, y: x + y).collect()\n",
"\n",
"\n",
"print yyhat\n",
"print yyhat_1\n",
"print labels_and_predictions.filter(lambda (x, y): x != y).count()\n",
"print labels_and_predictions.filter(lambda (x, y): x == y).count()"
]
},
{
"cell_type": "code",
"execution_count": 264,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"results = list(labels_and_predictions.take(1000))\n",
"y = np.array([x[0] for x in results])\n",
"yhat = np.array([x[1] for x in results])"
]
},
{
"cell_type": "code",
"execution_count": 265,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th>Predicted</th>\n",
" <th>0</th>\n",
" <th>1</th>\n",
" </tr>\n",
" <tr>\n",
" <th>Actual</th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>101</td>\n",
" <td>59</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>58</td>\n",
" <td>104</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
"Predicted 0 1\n",
"Actual \n",
"0 101 59\n",
"1 58 104"
]
},
"execution_count": 265,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"cm = pd.crosstab(y, yhat, rownames=[\"Actual\"], colnames=[\"Predicted\"])\n",
"cm"
]
},
{
"cell_type": "code",
"execution_count": 266,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"training_error = labels_and_predictions.filter(lambda (v, p): v != p).count()/float(parsed_data.count())"
]
},
{
"cell_type": "code",
"execution_count": 267,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.363354037267\n"
]
}
],
"source": [
"print training_error"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.10"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment