Spark SQL¶

Agenda¶

  • DataFrame
  • Operations on DataFrames
  • Types
  • Column Expressions

DataFrame¶

Like an RDD, a DataFrame is an immutable distributed collection of data. Unlike an RDD, data is organized into named columns, like a table in a relational database. Designed to make large data sets processing even easier, DataFrame allows developers to impose a structure onto a distributed collection of data, allowing higher-level abstraction.

DataFrame¶

A DataFrame can informally be thought of as an RDD consisting of pyspark.sql.Row objects and a schema.

In [2]:
from pyspark.sql import Row
from pyspark.sql import types as T

rdd = sc.parallelize([
  Row(name='John Doe', age='48', height=190),
  Row(name='Jane Doe', age='20', height=188),
  Row(name='John Low', age='28', height=150),
])
schema = T.StructType([
  T.StructField(name='name', dataType=T.StringType(), nullable=True),
  T.StructField(name='age', dataType=T.StringType(), nullable=True),
  T.StructField(name='height', dataType=T.StringType(), nullable=True),
])

df = spark.createDataFrame(rdd, schema)
display(df)
Out[2]:
name age height
0 John Doe 48 190
1 Jane Doe 20 188
2 John Low 28 150

DataFrame¶

The underlying RDD is stored as an attribute of the DataFrame.

In [3]:
df.rdd
Out[3]:
MapPartitionsRDD[10] at javaToPython at NativeMethodAccessorImpl.java:0

DataFrames are immutable like RDDs, you can modify them through operations that create new, different DataFrames, with different columns.

Schemas¶

A schema defines the column names and types of a DataFrame. We can either let a data source define the schema (called schema-on-read) or we can define it explicitly ourselves. For ad hoc analysis, schema-on-read usually works just fine. When using Spark for production Extract, Transform, and Load (ETL), it is often a good idea to define your schemas manually, especially when working with untyped data sources like CSV and JSON because schema inference can vary depending on the type of data that you read in.

The schema is an attribute af a pyspark DataFrame as you can see below.

In [4]:
df.schema
Out[4]:
StructType([StructField('name', StringType(), True), StructField('age', StringType(), True), StructField('height', StringType(), True)])
In [5]:
df.printSchema()
root
 |-- name: string (nullable = true)
 |-- age: string (nullable = true)
 |-- height: string (nullable = true)

Schema¶

A schema is a StructType made up of a number of fields, StructFields, that have a name, type, a Boolean flag which specifies whether that column can contain missing or null values, and, finally, users can optionally specify associated metadata with that column. The metadata is a way of storing information about this column (Spark uses this in its machine learning library).

Here is how to create and enforce a specific schema on a DataFrame.

In [6]:
from pyspark.sql import types as T

manual_schema = T.StructType([
  T.StructField("carrier", T.StringType(), True),
  T.StructField("name", T.StringType(), True, metadata={"hello":"world"}),
])
airlines = (
    spark
    .read
    .format("csv")
    .schema(manual_schema)
    .option("header", "true")
    .option("mode", "FAILFAST")
    .load("/datasets/nycflights13/airlines.csv")
)
display(airlines)
Out[6]:
carrier name
0 9E Endeavor Air Inc.
1 AA American Airlines Inc.
2 AS Alaska Airlines Inc.
3 B6 JetBlue Airways
4 DL Delta Air Lines Inc.

Schema¶

If the types in the data (at runtime) do not match the schema, Spark can throw an error, depending on how the mode option is set.

In [7]:
from pyspark.sql import types as T

manual_schema = T.StructType([
    T.StructField("carrier", T.StringType(), False),
    T.StructField("name", T.ByteType(), True, metadata={"hello":"world"}),
])
airlines = (
    spark
    .read
    .format("csv")
    .schema(manual_schema)
    .option("header", "true")
    .option("mode", "FAILFAST")
    .load("/datasets/nycflights13/airlines.csv")
)
try:
    display(airlines)
except Exception as e:
    print(e)
An error occurred while calling o75.collectToPython.
: org.apache.spark.SparkException: Job aborted due to stage failure: Task 0 in stage 4.0 failed 1 times, most recent failure: Lost task 0.0 in stage 4.0 (TID 9) (172.20.10.3 executor driver): org.apache.spark.SparkException: Encountered error while reading file file:///datasets/nycflights13/airlines.csv. Details:
	at org.apache.spark.sql.errors.QueryExecutionErrors$.cannotReadFilesError(QueryExecutionErrors.scala:877)
	at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.nextIterator(FileScanRDD.scala:307)
	at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.hasNext(FileScanRDD.scala:125)
	at scala.collection.Iterator$$anon$10.hasNext(Iterator.scala:460)
	at org.apache.spark.sql.execution.SparkPlan.$anonfun$getByteArrayRdd$1(SparkPlan.scala:388)
	at org.apache.spark.rdd.RDD.$anonfun$mapPartitionsInternal$2(RDD.scala:888)
	at org.apache.spark.rdd.RDD.$anonfun$mapPartitionsInternal$2$adapted(RDD.scala:888)
	at org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:52)
	at org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:364)
	at org.apache.spark.rdd.RDD.iterator(RDD.scala:328)
	at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:92)
	at org.apache.spark.TaskContext.runTaskWithListeners(TaskContext.scala:161)
	at org.apache.spark.scheduler.Task.run(Task.scala:139)
	at org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$3(Executor.scala:554)
	at org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1529)
	at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:557)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.spark.SparkException: [MALFORMED_RECORD_IN_PARSING] Malformed records are detected in record parsing: [9E,null].
Parse Mode: FAILFAST. To process malformed records as null result, try setting the option 'mode' as 'PERMISSIVE'.
	at org.apache.spark.sql.errors.QueryExecutionErrors$.malformedRecordsDetectedInRecordParsingError(QueryExecutionErrors.scala:1764)
	at org.apache.spark.sql.catalyst.util.FailureSafeParser.parse(FailureSafeParser.scala:69)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser$.$anonfun$parseIterator$2(UnivocityParser.scala:456)
	at scala.collection.Iterator$$anon$11.nextCur(Iterator.scala:486)
	at scala.collection.Iterator$$anon$11.hasNext(Iterator.scala:492)
	at scala.collection.Iterator$$anon$10.hasNext(Iterator.scala:460)
	at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.hasNext(FileScanRDD.scala:125)
	at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.nextIterator(FileScanRDD.scala:297)
	... 17 more
Caused by: org.apache.spark.sql.catalyst.util.BadRecordException: java.lang.NumberFormatException: For input string: "Endeavor Air Inc."
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.org$apache$spark$sql$catalyst$csv$UnivocityParser$$convert(UnivocityParser.scala:365)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.$anonfun$parse$2(UnivocityParser.scala:307)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser$.$anonfun$parseIterator$1(UnivocityParser.scala:452)
	at org.apache.spark.sql.catalyst.util.FailureSafeParser.parse(FailureSafeParser.scala:60)
	... 23 more
Caused by: java.lang.NumberFormatException: For input string: "Endeavor Air Inc."
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:580)
	at java.lang.Byte.parseByte(Byte.java:149)
	at java.lang.Byte.parseByte(Byte.java:175)
	at scala.collection.immutable.StringLike.toByte(StringLike.scala:294)
	at scala.collection.immutable.StringLike.toByte$(StringLike.scala:294)
	at scala.collection.immutable.StringOps.toByte(StringOps.scala:33)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.$anonfun$makeConverter$2(UnivocityParser.scala:183)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.$anonfun$makeConverter$2$adapted(UnivocityParser.scala:183)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.nullSafeDatum(UnivocityParser.scala:291)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.$anonfun$makeConverter$1(UnivocityParser.scala:183)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.org$apache$spark$sql$catalyst$csv$UnivocityParser$$convert(UnivocityParser.scala:346)
	... 26 more

Driver stacktrace:
	at org.apache.spark.scheduler.DAGScheduler.failJobAndIndependentStages(DAGScheduler.scala:2785)
	at org.apache.spark.scheduler.DAGScheduler.$anonfun$abortStage$2(DAGScheduler.scala:2721)
	at org.apache.spark.scheduler.DAGScheduler.$anonfun$abortStage$2$adapted(DAGScheduler.scala:2720)
	at scala.collection.mutable.ResizableArray.foreach(ResizableArray.scala:62)
	at scala.collection.mutable.ResizableArray.foreach$(ResizableArray.scala:55)
	at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:49)
	at org.apache.spark.scheduler.DAGScheduler.abortStage(DAGScheduler.scala:2720)
	at org.apache.spark.scheduler.DAGScheduler.$anonfun$handleTaskSetFailed$1(DAGScheduler.scala:1206)
	at org.apache.spark.scheduler.DAGScheduler.$anonfun$handleTaskSetFailed$1$adapted(DAGScheduler.scala:1206)
	at scala.Option.foreach(Option.scala:407)
	at org.apache.spark.scheduler.DAGScheduler.handleTaskSetFailed(DAGScheduler.scala:1206)
	at org.apache.spark.scheduler.DAGSchedulerEventProcessLoop.doOnReceive(DAGScheduler.scala:2984)
	at org.apache.spark.scheduler.DAGSchedulerEventProcessLoop.onReceive(DAGScheduler.scala:2923)
	at org.apache.spark.scheduler.DAGSchedulerEventProcessLoop.onReceive(DAGScheduler.scala:2912)
	at org.apache.spark.util.EventLoop$$anon$1.run(EventLoop.scala:49)
	at org.apache.spark.scheduler.DAGScheduler.runJob(DAGScheduler.scala:971)
	at org.apache.spark.SparkContext.runJob(SparkContext.scala:2263)
	at org.apache.spark.SparkContext.runJob(SparkContext.scala:2284)
	at org.apache.spark.SparkContext.runJob(SparkContext.scala:2303)
	at org.apache.spark.sql.execution.SparkPlan.executeTake(SparkPlan.scala:530)
	at org.apache.spark.sql.execution.SparkPlan.executeTake(SparkPlan.scala:483)
	at org.apache.spark.sql.execution.CollectLimitExec.executeCollect(limit.scala:61)
	at org.apache.spark.sql.Dataset.$anonfun$collectToPython$1(Dataset.scala:3997)
	at org.apache.spark.sql.Dataset.$anonfun$withAction$2(Dataset.scala:4167)
	at org.apache.spark.sql.execution.QueryExecution$.withInternalError(QueryExecution.scala:526)
	at org.apache.spark.sql.Dataset.$anonfun$withAction$1(Dataset.scala:4165)
	at org.apache.spark.sql.execution.SQLExecution$.$anonfun$withNewExecutionId$6(SQLExecution.scala:118)
	at org.apache.spark.sql.execution.SQLExecution$.withSQLConfPropagated(SQLExecution.scala:195)
	at org.apache.spark.sql.execution.SQLExecution$.$anonfun$withNewExecutionId$1(SQLExecution.scala:103)
	at org.apache.spark.sql.SparkSession.withActive(SparkSession.scala:827)
	at org.apache.spark.sql.execution.SQLExecution$.withNewExecutionId(SQLExecution.scala:65)
	at org.apache.spark.sql.Dataset.withAction(Dataset.scala:4165)
	at org.apache.spark.sql.Dataset.collectToPython(Dataset.scala:3994)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:244)
	at py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:374)
	at py4j.Gateway.invoke(Gateway.java:282)
	at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132)
	at py4j.commands.CallCommand.execute(CallCommand.java:79)
	at py4j.ClientServerConnection.waitForCommands(ClientServerConnection.java:182)
	at py4j.ClientServerConnection.run(ClientServerConnection.java:106)
	at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.spark.SparkException: Encountered error while reading file file:///datasets/nycflights13/airlines.csv. Details:
	at org.apache.spark.sql.errors.QueryExecutionErrors$.cannotReadFilesError(QueryExecutionErrors.scala:877)
	at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.nextIterator(FileScanRDD.scala:307)
	at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.hasNext(FileScanRDD.scala:125)
	at scala.collection.Iterator$$anon$10.hasNext(Iterator.scala:460)
	at org.apache.spark.sql.execution.SparkPlan.$anonfun$getByteArrayRdd$1(SparkPlan.scala:388)
	at org.apache.spark.rdd.RDD.$anonfun$mapPartitionsInternal$2(RDD.scala:888)
	at org.apache.spark.rdd.RDD.$anonfun$mapPartitionsInternal$2$adapted(RDD.scala:888)
	at org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:52)
	at org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:364)
	at org.apache.spark.rdd.RDD.iterator(RDD.scala:328)
	at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:92)
	at org.apache.spark.TaskContext.runTaskWithListeners(TaskContext.scala:161)
	at org.apache.spark.scheduler.Task.run(Task.scala:139)
	at org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$3(Executor.scala:554)
	at org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1529)
	at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:557)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	... 1 more
Caused by: org.apache.spark.SparkException: [MALFORMED_RECORD_IN_PARSING] Malformed records are detected in record parsing: [9E,null].
Parse Mode: FAILFAST. To process malformed records as null result, try setting the option 'mode' as 'PERMISSIVE'.
	at org.apache.spark.sql.errors.QueryExecutionErrors$.malformedRecordsDetectedInRecordParsingError(QueryExecutionErrors.scala:1764)
	at org.apache.spark.sql.catalyst.util.FailureSafeParser.parse(FailureSafeParser.scala:69)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser$.$anonfun$parseIterator$2(UnivocityParser.scala:456)
	at scala.collection.Iterator$$anon$11.nextCur(Iterator.scala:486)
	at scala.collection.Iterator$$anon$11.hasNext(Iterator.scala:492)
	at scala.collection.Iterator$$anon$10.hasNext(Iterator.scala:460)
	at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.hasNext(FileScanRDD.scala:125)
	at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.nextIterator(FileScanRDD.scala:297)
	... 17 more
Caused by: org.apache.spark.sql.catalyst.util.BadRecordException: java.lang.NumberFormatException: For input string: "Endeavor Air Inc."
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.org$apache$spark$sql$catalyst$csv$UnivocityParser$$convert(UnivocityParser.scala:365)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.$anonfun$parse$2(UnivocityParser.scala:307)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser$.$anonfun$parseIterator$1(UnivocityParser.scala:452)
	at org.apache.spark.sql.catalyst.util.FailureSafeParser.parse(FailureSafeParser.scala:60)
	... 23 more
Caused by: java.lang.NumberFormatException: For input string: "Endeavor Air Inc."
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:580)
	at java.lang.Byte.parseByte(Byte.java:149)
	at java.lang.Byte.parseByte(Byte.java:175)
	at scala.collection.immutable.StringLike.toByte(StringLike.scala:294)
	at scala.collection.immutable.StringLike.toByte$(StringLike.scala:294)
	at scala.collection.immutable.StringOps.toByte(StringOps.scala:33)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.$anonfun$makeConverter$2(UnivocityParser.scala:183)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.$anonfun$makeConverter$2$adapted(UnivocityParser.scala:183)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.nullSafeDatum(UnivocityParser.scala:291)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.$anonfun$makeConverter$1(UnivocityParser.scala:183)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.org$apache$spark$sql$catalyst$csv$UnivocityParser$$convert(UnivocityParser.scala:346)
	... 26 more

23/09/26 17:37:03 ERROR Executor: Exception in task 0.0 in stage 4.0 (TID 9)
org.apache.spark.SparkException: Encountered error while reading file file:///datasets/nycflights13/airlines.csv. Details:
	at org.apache.spark.sql.errors.QueryExecutionErrors$.cannotReadFilesError(QueryExecutionErrors.scala:877)
	at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.nextIterator(FileScanRDD.scala:307)
	at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.hasNext(FileScanRDD.scala:125)
	at scala.collection.Iterator$$anon$10.hasNext(Iterator.scala:460)
	at org.apache.spark.sql.execution.SparkPlan.$anonfun$getByteArrayRdd$1(SparkPlan.scala:388)
	at org.apache.spark.rdd.RDD.$anonfun$mapPartitionsInternal$2(RDD.scala:888)
	at org.apache.spark.rdd.RDD.$anonfun$mapPartitionsInternal$2$adapted(RDD.scala:888)
	at org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:52)
	at org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:364)
	at org.apache.spark.rdd.RDD.iterator(RDD.scala:328)
	at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:92)
	at org.apache.spark.TaskContext.runTaskWithListeners(TaskContext.scala:161)
	at org.apache.spark.scheduler.Task.run(Task.scala:139)
	at org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$3(Executor.scala:554)
	at org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1529)
	at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:557)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.spark.SparkException: [MALFORMED_RECORD_IN_PARSING] Malformed records are detected in record parsing: [9E,null].
Parse Mode: FAILFAST. To process malformed records as null result, try setting the option 'mode' as 'PERMISSIVE'.
	at org.apache.spark.sql.errors.QueryExecutionErrors$.malformedRecordsDetectedInRecordParsingError(QueryExecutionErrors.scala:1764)
	at org.apache.spark.sql.catalyst.util.FailureSafeParser.parse(FailureSafeParser.scala:69)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser$.$anonfun$parseIterator$2(UnivocityParser.scala:456)
	at scala.collection.Iterator$$anon$11.nextCur(Iterator.scala:486)
	at scala.collection.Iterator$$anon$11.hasNext(Iterator.scala:492)
	at scala.collection.Iterator$$anon$10.hasNext(Iterator.scala:460)
	at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.hasNext(FileScanRDD.scala:125)
	at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.nextIterator(FileScanRDD.scala:297)
	... 17 more
Caused by: org.apache.spark.sql.catalyst.util.BadRecordException: java.lang.NumberFormatException: For input string: "Endeavor Air Inc."
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.org$apache$spark$sql$catalyst$csv$UnivocityParser$$convert(UnivocityParser.scala:365)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.$anonfun$parse$2(UnivocityParser.scala:307)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser$.$anonfun$parseIterator$1(UnivocityParser.scala:452)
	at org.apache.spark.sql.catalyst.util.FailureSafeParser.parse(FailureSafeParser.scala:60)
	... 23 more
Caused by: java.lang.NumberFormatException: For input string: "Endeavor Air Inc."
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:580)
	at java.lang.Byte.parseByte(Byte.java:149)
	at java.lang.Byte.parseByte(Byte.java:175)
	at scala.collection.immutable.StringLike.toByte(StringLike.scala:294)
	at scala.collection.immutable.StringLike.toByte$(StringLike.scala:294)
	at scala.collection.immutable.StringOps.toByte(StringOps.scala:33)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.$anonfun$makeConverter$2(UnivocityParser.scala:183)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.$anonfun$makeConverter$2$adapted(UnivocityParser.scala:183)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.nullSafeDatum(UnivocityParser.scala:291)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.$anonfun$makeConverter$1(UnivocityParser.scala:183)
	at org.apache.spark.sql.catalyst.csv.UnivocityParser.org$apache$spark$sql$catalyst$csv$UnivocityParser$$convert(UnivocityParser.scala:346)
	... 26 more
23/09/26 17:37:03 ERROR TaskSetManager: Task 0 in stage 4.0 failed 1 times; aborting job

The SparkSession¶

SparkSession(sparkContext, jsparkSession=None) The entry point to programming Spark with the Dataset and DataFrame API.

The SparkSession is initialized automatically in a Databricks notebook and assigned to the variable spark.

spark

No description has been provided for this image

The SparkSession¶

The SparkSession contains the SparkContext.

spark.sparkContext

No description has been provided for this image

Example: Dataset¶

The flights dataset contains:

  • year,month,day Date of departure
  • dep_time,arr_time Actual departure and arrival times, local tz.
  • sched_dep_time,sched_arr_time Scheduled departure and arrival times, local time-zone.
  • dep_delay,arr_delay Departure and arrival delays, in minutes. Negative times represent early departures/arrivals.
  • hour,minute Time of scheduled departure broken into hour and minutes.
  • carrier Two letter carrier abbreviation.
  • tailnum Plane tail number
  • flight Flight number
  • origin,dest Origin and destination. See airports for additional metadata.
  • air_time Amount of time spent in the air, in minutes
  • distance Distance between airports, in miles
  • time_hour Scheduled date and hour of the flight as a POSIXct date. Along with origin, can be used to join flights data to weather data.

I/O¶

The SparkSession is often used to read data spark.

In [8]:
flights = spark.read.csv(
    '/datasets/nycflights13/flights.csv',
    header=True,
    inferSchema=True
)
display(flights)
Out[8]:
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum origin dest air_time distance hour minute time_hour
0 2013 1 1 517 515 2 830 819 11 UA 1545 N14228 EWR IAH 227 1400 5 15 2013-01-01 05:00:00
1 2013 1 1 533 529 4 850 830 20 UA 1714 N24211 LGA IAH 227 1416 5 29 2013-01-01 05:00:00
2 2013 1 1 542 540 2 923 850 33 AA 1141 N619AA JFK MIA 160 1089 5 40 2013-01-01 05:00:00
3 2013 1 1 544 545 -1 1004 1022 -18 B6 725 N804JB JFK BQN 183 1576 5 45 2013-01-01 05:00:00
4 2013 1 1 554 600 -6 812 837 -25 DL 461 N668DN LGA ATL 116 762 6 0 2013-01-01 06:00:00

You can write the DataFrame to disk again after manipulations.

In [9]:
flights.write.csv('/tmp/flights.csv', mode='overwrite')

Types and Schema¶

The module pyspark.sql.types is the next most used submodule. It is used to create a schema, which is input to various functions, e.g. when initializing a DataFrame, and user defined functions (UDF).

Example: Operations used on a DataFrame¶

Some commenly used methods on DataFrame's include (not all).

  • select(*cols) Projects a set of expressions and returns a new DataFrame.
  • withColumn(colName, col) Returns a new DataFrame by adding a column or replacing the existing column that has the same name.
  • where(condition) / filter(condition) Filters rows using the given condition.
  • orderBy(*cols, **kwargs) Returns a new DataFrame sorted by the specified column(s).
  • agg(*exprs) Compute aggregates and returns the result as a DataFrame. Can be used on a grouped
  • groupBy(*cols) Groups the DataFrame using the specified columns, so we can run aggregation on them. DataFrame as well to do the operation by group.

By chaining these simple DataFrame methods together in a pipeline one can achieve rather complex transformations, so it makes sense to memorize these transformation methods.

Transformations and Actions¶

DataFrame's also have transfomations and actions.

Missing

Transformations: Lazy¶

Transformations are operations that Spark evaluates lazily. A huge advantage of the lazy evaluation scheme is that Spark can inspect your computational query and ascertain how it can optimize it.

Narrow and Wide Transformations¶

Transformations can be classified as having either narrow dependencies or wide dependencies. Any transformation where a single output partition can be computed from a single input partition is a narrow transformation.

If a single output partition cannot be computed from a single input partition then a shuffle is required. This is called a wide transformation.

Missing

Narrow and Wide Transformations¶

filter() and withColumn() represent narrow transformations because they can operate on a single partition and produce the resulting output partition without any exchange of data.

In [10]:
from pyspark.sql import functions as F

flights.filter(F.col('month').isin([9, 10, 11]))      # narrow transformation.
flights.withColumn('next_month', F.col('month') + 1)  # narrow transformation.
Out[10]:
DataFrame[year: int, month: int, day: int, dep_time: int, sched_dep_time: int, dep_delay: int, arr_time: int, sched_arr_time: int, arr_delay: int, carrier: string, flight: int, tailnum: string, origin: string, dest: string, air_time: int, distance: int, hour: int, minute: int, time_hour: timestamp, next_month: int]

Narrow and Wide Transformations¶

groupBy() or orderBy() instruct Spark to perform wide transformations, where data from other partitions is read in, combined, and written to disk.

In [11]:
flights.groupBy('month').agg(F.count('*'))            # wide transformation.
flights.orderBy('month')                              # wide transformation.
Out[11]:
DataFrame[year: int, month: int, day: int, dep_time: int, sched_dep_time: int, dep_delay: int, arr_time: int, sched_arr_time: int, arr_delay: int, carrier: string, flight: int, tailnum: string, origin: string, dest: string, air_time: int, distance: int, hour: int, minute: int, time_hour: timestamp]

Column Expressions¶

Columns in Spark are similar to columns in a spreadsheet, R dataframe, or pandas DataFrame. You can select, manipulate, and remove columns from DataFrames and these operations are represented as expressions.

There are a lot of different ways to construct and refer to columns but the two simplest ways are by using the col or column functions. To use either of these functions, you pass in a column name.

You can also refer directly to the columns of a DataFrame, this can be useful when joining two DataFrame's with the same column name.

In [12]:
from pyspark.sql import functions as F

F.col("someColumnName")
flights['dest']
Out[12]:
Column<'dest'>

Column Expressions¶

Columns are expressions, but what is an expression? An expression is a set of transformations on one or more values in a record in a DataFrame.

In [13]:
(((F.col("someCol") + 5) * 200) - 6) < F.col("otherCol")
Out[13]:
Column<'((((someCol + 5) * 200) - 6) < otherCol)'>

Column Expressions¶

If you want to programmatically access columns, you can use the columns property to see all columns on a DataFrame.

In [14]:
airlines.columns
Out[14]:
['carrier', 'name']

where(condition) / filter(condition)¶

Filters rows using the given condition. To filter rows, we create an expression that evaluates to true or false. You then filter out the rows with an expression that is equal to false. The most common way to do this with DataFrames is to create either an expression as a String or build an expression by using a set of column manipulations.

Use the comparison operators: >, >=, <, <=, != (not equal), == (equal) and .eqNullSafe().

Every boolean expression must be True in order for a row to be included in the output. For other types of combinations you'll need to use the Boolean operators: & (and), | (or), ~ (not), ^ (XOR).

In [15]:
display(
    flights
    .withColumn(
        'mean_speed',
        F.col('distance') / (F.col('air_time') / 60)
    )
    .where(
        F.col('mean_speed') < 400
    )
)
Out[15]:
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum origin dest air_time distance hour minute time_hour mean_speed
0 2013 1 1 517 515 2 830 819 11 UA 1545 N14228 EWR IAH 227 1400 5 15 2013-01-01 05:00:00 370.044053
1 2013 1 1 533 529 4 850 830 20 UA 1714 N24211 LGA IAH 227 1416 5 29 2013-01-01 05:00:00 374.273128
2 2013 1 1 554 600 -6 812 837 -25 DL 461 N668DN LGA ATL 116 762 6 0 2013-01-01 06:00:00 394.137931
3 2013 1 1 554 558 -4 740 728 12 UA 1696 N39463 EWR ORD 150 719 5 58 2013-01-01 05:00:00 287.600000
4 2013 1 1 557 600 -3 709 723 -14 EV 5708 N829AS LGA IAD 53 229 6 0 2013-01-01 06:00:00 259.245283

where(condition) / filter(condition)¶

Instinctually, you might want to put multiple filters into the same expression. Although this is possible, it is not always useful, because Spark automatically performs all filtering operations at the same time regardless of the filter ordering. This means that if you want to specify multiple AND filters, just chain them sequentially and let Spark handle the rest.

In [16]:
display(
    flights
    .where(
        F.col('month') > 6
    )
    .where(
        F.col('month') < 8
    )
)
Out[16]:
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum origin dest air_time distance hour minute time_hour
0 2013 7 1 1 2029 212 236 2359 157 B6 915 N653JB JFK SFO 315 2586 20 29 2013-07-01 20:00:00
1 2013 7 1 2 2359 3 344 344 0 B6 1503 N805JB JFK SJU 200 1598 23 59 2013-07-01 23:00:00
2 2013 7 1 29 2245 104 151 1 110 B6 234 N348JB JFK BTV 66 266 22 45 2013-07-01 22:00:00
3 2013 7 1 43 2130 193 322 14 188 B6 1371 N794JB LGA FLL 143 1076 21 30 2013-07-01 21:00:00
4 2013 7 1 44 2150 174 300 100 120 AA 185 N324AA JFK LAX 297 2475 21 50 2013-07-01 21:00:00

Missing Values¶

When filtering Null values are evaluated to False, this can give surprising results.

In [17]:
display(
  flights
  .where(
    F.lit(None) == F.lit(None)
  )
)
Out[17]:
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum origin dest air_time distance hour minute time_hour

Missing Values¶

The above returns an empty DataFrame because it evaluates to Null.

In [18]:
display(
    flights
    .select(
        # Boolean expressions returns Null.
        (F.lit(None) == F.lit(None)).alias('null_equal_null'),
        (F.lit(None) == F.lit(10)).alias('null_equal_number'),
        (F.lit(None) > F.lit(10)).alias('null_gt_number'),
        
        # Arithmetic expression returns NaN.
        (F.lit(None) + F.lit(10)).alias('null_add_number'),
        (F.lit(None) * F.lit(10)).alias('null_multiply_number'),
        (F.lit(None) / F.lit(10)).alias('null_division_number'),
    )
)
Out[18]:
null_equal_null null_equal_number null_gt_number null_add_number null_multiply_number null_division_number
0 None None None NaN NaN NaN
1 None None None NaN NaN NaN
2 None None None NaN NaN NaN
3 None None None NaN NaN NaN
4 None None None NaN NaN NaN

Missing Values¶

The Column functions isNotNull(), isNull() can be used to filter Null values away, replace them or whatever is the best action in the given analysis. A handy function in the pyspark.sql.functions module is coalesce(), it takes the first value in a series of column expressions which is not Null, analogous to the SQL function.

In [19]:
display(
      flights
      .select(
          F.lit(None).isNotNull().alias('isNotNull'),
          F.lit(None).isNull().alias('isNull'),
          F.coalesce(F.lit(None), F.lit(True)).alias('coalesce'),
          F.lit(None).eqNullSafe(F.lit(None)).alias('eqNullSafe'),
      )
)
Out[19]:
isNotNull isNull coalesce eqNullSafe
0 False True True True
1 False True True True
2 False True True True
3 False True True True
4 False True True True

Exercise 4.1.¶

select(*cols) and selectExpr(*cols)¶

select() and selectExpr() allow you to do the DataFrame equivalent of SQL queries on a table of data.

In [20]:
display(
    flights
    .select(
        (F.col('distance') / (F.col('air_time') / 60)).alias('mean_speed'),
        (F.col('arr_delay') - F.col('dep_delay')).alias('gain'),
    )
)
Out[20]:
mean_speed gain
0 370.044053 9
1 374.273128 16
2 408.375000 31
3 516.721311 -17
4 394.137931 -19

Dropping columns¶

You likely already noticed that we can drop columns using select() e.g. select all but the variables you want to drop. However, there is also a dedicated method called drop().

In [21]:
display(
    flights
    .select(
        'year',
        'month',
        'day'
    )
    .drop(
        'year'
    )
)
Out[21]:
month day
0 1 1
1 1 1
2 1 1
3 1 1
4 1 1

withColumn(colName, col)¶

The withColumn() adds a new column, which is a function of existing columns. It takes two arguments: the column name and the expression that will create the value for that given row in the DataFrame.

In [22]:
display(
    flights
    .withColumn(
        'mean_speed',
        F.col('distance') / (F.col('air_time') / 60)
    )
)
Out[22]:
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum origin dest air_time distance hour minute time_hour mean_speed
0 2013 1 1 517 515 2 830 819 11 UA 1545 N14228 EWR IAH 227 1400 5 15 2013-01-01 05:00:00 370.044053
1 2013 1 1 533 529 4 850 830 20 UA 1714 N24211 LGA IAH 227 1416 5 29 2013-01-01 05:00:00 374.273128
2 2013 1 1 542 540 2 923 850 33 AA 1141 N619AA JFK MIA 160 1089 5 40 2013-01-01 05:00:00 408.375000
3 2013 1 1 544 545 -1 1004 1022 -18 B6 725 N804JB JFK BQN 183 1576 5 45 2013-01-01 05:00:00 516.721311
4 2013 1 1 554 600 -6 812 837 -25 DL 461 N668DN LGA ATL 116 762 6 0 2013-01-01 06:00:00 394.137931

Renaming columns¶

You can rename a column with the withColumn() function, e.g. .withColumn(foo, foo), but it is better to use the dedicated function ẁithColumnRenamed() for this.

In [23]:
display(
    flights
    .withColumnRenamed(
        'year',
        'dep_year'
    )
)
Out[23]:
dep_year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum origin dest air_time distance hour minute time_hour
0 2013 1 1 517 515 2 830 819 11 UA 1545 N14228 EWR IAH 227 1400 5 15 2013-01-01 05:00:00
1 2013 1 1 533 529 4 850 830 20 UA 1714 N24211 LGA IAH 227 1416 5 29 2013-01-01 05:00:00
2 2013 1 1 542 540 2 923 850 33 AA 1141 N619AA JFK MIA 160 1089 5 40 2013-01-01 05:00:00
3 2013 1 1 544 545 -1 1004 1022 -18 B6 725 N804JB JFK BQN 183 1576 5 45 2013-01-01 05:00:00
4 2013 1 1 554 600 -6 812 837 -25 DL 461 N668DN LGA ATL 116 762 6 0 2013-01-01 06:00:00

sort(*cols, **kwargs) and orderBy(*cols, **kwargs)¶

Arrange rows with sort() and orderBy(), they work equivalently. They accept both column expressions and strings as well as multiple columns. The default is to sort in ascending order.

Returns a new DataFrame sorted by the specified column(s).

In [24]:
display(
    flights
    .withColumn(
        'mean_speed',
        F.col('distance') / (F.col('air_time') / 60)
    )
    .orderBy(F.col('mean_speed'))
)
Out[24]:
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum origin dest air_time distance hour minute time_hour mean_speed
0 2013 9 25 NaN 1755 NaN NaN 1932 NaN EV 5287 N722EV LGA MSN NaN 812 17 55 2013-09-25 17:00:00 NaN
1 2013 4 27 NaN 1345 NaN NaN 1700 NaN AA 117 N335AA JFK LAX NaN 2475 13 45 2013-04-27 13:00:00 NaN
2 2013 12 12 NaN 700 NaN NaN 855 NaN EV 4099 N14558 EWR STL NaN 872 7 0 2013-12-12 07:00:00 NaN
3 2013 4 27 NaN 800 NaN NaN 1135 NaN AA 59 N328AA JFK SFO NaN 2586 8 0 2013-04-27 08:00:00 NaN
4 2013 6 17 1751.0 1629 82.0 2118.0 1823 NaN EV 5818 N14573 EWR MEM NaN 946 16 29 2013-06-17 16:00:00 NaN

sort(*cols, **kwargs) and orderBy(*cols, **kwargs)¶

To sort descending you need to specify this explicitely.

In [25]:
display(
    flights
    .withColumn(
        'mean_speed',
        F.col('distance') / (F.col('air_time') / 60)
    )
    .orderBy(F.col('mean_speed').desc())
)
Out[25]:
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time arr_delay carrier flight tailnum origin dest air_time distance hour minute time_hour mean_speed
0 2013 5 25 1709 1700 9 1923 1937 -14 DL 1499 N666DN LGA ATL 65 762 17 0 2013-05-25 17:00:00 703.384615
1 2013 7 2 1558 1513 45 1745 1719 26 EV 4667 N17196 EWR MSP 93 1008 15 13 2013-07-02 15:00:00 650.322581
2 2013 5 13 2040 2025 15 2225 2226 -1 EV 4292 N14568 EWR GSP 55 594 20 25 2013-05-13 20:00:00 648.000000
3 2013 3 23 1914 1910 4 2045 2043 2 EV 3805 N12567 EWR BNA 70 748 19 10 2013-03-23 19:00:00 641.142857
4 2013 1 12 1559 1600 -1 1849 1917 -28 DL 1902 N956DL LGA PBI 105 1035 16 0 2013-01-12 16:00:00 591.428571

Exercise 4.2.¶

agg(*exprs)¶

agg() can condense multiple records to a single record. The agg() method can be used in conjuction with groupBy() which changes the scope of each function from operating on the entire DataFrame to operating on it group-by-group. This is useful to compute aggregate statistics of the records in the DataFrame, to answer questions like: What is the average month a flight took place, average year, max year, a list of all years, a list of all months.

In [26]:
display(
    flights
    .agg(
        F.mean('month'),
        F.mean('year'),
        F.max('year'),
        F.collect_set('year'),
        F.collect_set('month'),
    )
)
Out[26]:
avg(month) avg(year) max(year) collect_set(year) collect_set(month)
0 6.54851 2013.0 2013 [2013] [12, 9, 1, 5, 2, 6, 3, 10, 7, 4, 11, 8]

Aggregation Functions¶

Aggregation functions are also available in the pyspark.sql.functions module. It is good practice to give this module an alias and refer to it that way.

In [27]:
from pyspark.sql import functions as F

F.count(col)¶

The first function worth going over is count, except in this example it will perform as a transformation instead of an action. In this case, we can do one of two things: specify a specific column to count, or all the columns by using count() or count(1) to represent that we want to count every row as the literal one, when performing a count(), Spark will count null values (including rows containing all nulls). However, when counting an individual column, Spark will not count the null values.

In [28]:
flights.agg(F.count('*').alias("row_count"))
Out[28]:
DataFrame[row_count: bigint]

Now call an action:

In [29]:
display(flights.agg(F.count('*').alias("row_count")))
Out[29]:
row_count
0 336776

F.countDistinct(col, *cols)¶

Sometimes, the total number is not relevant; rather, it’s the number of unique groups that you want. To get this number, you can use the countDistinct function. This is a bit more relevant for individual column.

In [30]:
display(flights.agg(F.countDistinct("year")))
Out[30]:
count(year)
0 1

F.min(col) and F.max(col)¶

To extract the minimum and maximum values from a DataFrame, use the min and max functions

In [31]:
display(flights.agg(F.min('month'), F.max('month')))
Out[31]:
min(month) max(month)
0 1 12

F.sum(col)¶

Another simple task is to add all the values in a row using the sum function

In [32]:
display(flights.agg(F.sum("arr_delay"), F.mean("arr_delay")))
Out[32]:
sum(arr_delay) avg(arr_delay)
0 2257174 6.895377

Aggregating to Complex Types¶

In Spark, you can perform aggregations not just of numerical values using formulas, you can also perform them on complex types. For example, we can collect a list of values present in a given column or only the unique values by collecting to a set.

You can use this to carry out some more programmatic access later on in the pipeline or pass the entire collection in a user-defined function (UDF).

In [33]:
flights.agg(F.collect_set("tailnum"), F.collect_list("tailnum"))
Out[33]:
DataFrame[collect_set(tailnum): array<string>, collect_list(tailnum): array<string>]

Grouping with Expressions¶

As we saw earlier, counting is a bit of a special case because it exists as a method. For this, usually we prefer to use the count function. Rather than passing that function as an expression into a select statement, we specify it as within agg. This makes it possible for you to pass-in arbitrary expressions that just need to have some aggregation specified. You can even do things like alias a column after transforming it for later use in your data flow.

In [34]:
display(
    flights 
    .groupBy('origin')
    .agg(
        F.count('*').alias('count'),
        F.mean('distance').alias('mean_distance'),
        F.mean('arr_delay').alias('mean_delay'),
    )
    .orderBy('count')
)
Out[34]:
origin count mean_distance mean_delay
0 LGA 104662 779.835671 5.783488
1 JFK 111279 1266.249077 5.551481
2 EWR 120835 1056.742790 9.107055

Exercise 4.3.¶

Relational Data¶

Typically you have many tables of data, and you must combine them. Collectively, multiple tables of data are called relational data because it is the relations, not just the individual datasets, that are important..

Missing

Relational Data¶

The variables used to connect each pair of tables are called keys. A key is a variable (or set of variables) that uniquely identifies an observation.

There are two types of keys:

  • A primary key uniquely identifies an observation in its own table.

  • A foreign key uniquely identifies an observation in another table.

Missing

Relational Data¶

Missing Missing

Relational Data¶

As an example if one wish to investigate the type of planes for each carrier one could do a left join on tailnum.

In [35]:
planes = spark.read.csv('/datasets/nycflights13/planes.csv', header=True)
display(
    flights
    .join(
        planes,
        on='tailnum',
        how='left'
    )
    .select(
        'carrier',
        'type'
    )
)
Out[35]:
carrier type
0 UA Fixed wing multi engine
1 UA Fixed wing multi engine
2 AA Fixed wing multi engine
3 B6 Fixed wing multi engine
4 DL Fixed wing multi engine

Relational Data¶

One can use a .alias(col) to reference columns in a particular DataFrame.

In [36]:
planes = spark.read.csv('/datasets/nycflights13/planes.csv', header=True)
display(
    flights.alias('flights')
    .join(
        planes.alias('planes'),
        on='tailnum',
        how='left'
    )
    .select(
        F.col('flights.year').alias('flights_year'),
        F.col('planes.year').alias('planes_year'),
        'carrier',
        'type'
    )
)
Out[36]:
flights_year planes_year carrier type
0 2013 1999 UA Fixed wing multi engine
1 2013 1998 UA Fixed wing multi engine
2 2013 1990 AA Fixed wing multi engine
3 2013 2012 B6 Fixed wing multi engine
4 2013 1991 DL Fixed wing multi engine

Joins¶

join(other, on=None, how=None) Joins with another DataFrame, using the given join expression.

Parameters

  • other Right side of the join
  • on a string for the join column name, a list of column names, a join expression (Column), or a list of Columns. If on is a string or a list of strings indicating the name of the join column(s), the column(s) must exist on both sides, and this performs an equi-join.
  • how str, default inner. Must be one of: inner, cross, outer, full, full_outer, left, left_outer, right, right_outer, left_semi, and left_anti.

Inner and left are the two most used join types, left_anti is useful for filtering.

A cross join seems ideally for some mathematical computations, but is almost never useful in practice, it is too computationally expensive because the number of rows explodes, if there are m rows in the first dataframe and n in the other then the resulting dataframe will have m x n rows.

Repartition¶

.repartition(numPartitions, *cols) changes the number of partitions.

Hash Partitioning¶

The partitioner used by the .repartition(numPartitions, *cols) function is hash partitioning. Hash partitioning roughly assign each row to the partition:

F.hash(*cols) % numPartitions

If you repartition by a column it is roughly equivalent to indexing the data by the column in a relational database.

In [37]:
df = flights.repartition(3, F.col('month'))
print("Number of Partitions", df.rdd.getNumPartitions())
Number of Partitions 3

Repartition¶

In [38]:
display(
  df
  .repartition(200, 'month')
  .withColumn(
    'partition',
    F.spark_partition_id()
  )
  .groupBy(
    'month',
    'partition'
  )
  .count()
  .orderBy('month')
)
Out[38]:
month partition count
0 1 43 27004
1 2 174 24951
2 3 51 28834
3 4 102 28330
4 5 66 28796

Repartition¶

Assume we have a lot of observations in month 9, this is called data skew. Data skew is a condition in which a table’s data is unevenly distributed among partitions in the cluster. Data skew can severely downgrade performance of queries, especially those with joins. Joins between big tables require shuffling data and the skew can lead to an extreme imbalance of work in the cluster.

Repartition¶

In [39]:
display(
  flights
  .withColumn(
    'month',
    F.when(
      F.col('month') <= 9,
      F.lit(9)
    )
    .otherwise(F.col('month'))
  )
  .repartition(200, 'month')
  .withColumn(
    'partition',
    F.spark_partition_id()
  )
  .groupBy(
    'month',
    'partition'
  )
  .count()
  .orderBy('month')
)
Out[39]:
month partition count
0 9 89 252484
1 10 122 28889
2 11 163 27268
3 12 24 28135

Repartition - Recommendations¶

I recommend you use:

  1. parquet files (a binary file format, use spark.read.parquet and df.write.parquet)
  2. Aim of a file size when writing to disk of around 1GB
  3. File size over 128MB to avoid the small file problem
  4. Use repartition keys that are uniformly distributed.

Example: User Defined Functions (UDF)¶

With user defined functions you can distribute an arbitrary Python function to the executors and do some calculation in parrellel, it is more restricted compared to the lower level rdd.map(), in that you have to specify the output datatypes and you can't return whatever, but it works directly on DataFrames. It works like a map and not on groups, to do stuff on groups you have to use a UDAF, either a pandas UDAF or create one in Scale.

It is highly recommended to avoid UDFs in all situations, as they are dramatically less performant than native PySpark. In most situations, logic that seems to necessitate a UDF can be refactored to use only native PySpark functions (pyspark.sql.functions).

In [40]:
from pyspark.sql import functions as F
from pyspark.sql import types as T

def square(x):
    return float(x) ** 2

udf_square = F.udf(square, T.DoubleType())

display(
    flights
    .select(
        F.col('arr_delay'),
        udf_square('arr_delay').alias('arr_delay_squared')
    )
)
Out[40]:
arr_delay arr_delay_squared
0 11 121.0
1 20 400.0
2 33 1089.0
3 -18 324.0
4 -25 625.0

Exercise 4.4.¶

Example: Dataset¶

The online_retail dataset contains:

This is a transnational data set which contains all the transactions occurring between 01/12/2010 and 09/12/2011 for a UK-based and registered non-store online retail.The company mainly sells unique all-occasion gifts. Many customers of the company are wholesalers.

  • InvoiceNo Invoice number. Nominal, a 6-digit integral number uniquely assigned to each transaction. If this code starts with letter 'c', it indicates a cancellation.
  • StockCode Product (item) code. Nominal, a 5-digit integral number uniquely assigned to each distinct product.
  • Description Product (item) name. Nominal.
  • Quantity The quantities of each product (item) per transaction. Numeric.
  • InvoiceDate Invice Date and time. Numeric, the day and time when each transaction was generated.
  • UnitPrice Unit price. Numeric, Product price per unit in sterling.
  • CustomerID Customer number. Nominal, a 5-digit integral number uniquely assigned to each customer.
  • Country Country name. Nominal, the name of the country where each customer resides.

Exercise 4.5.¶

Window Functions¶

You can also use window functions to carry out some unique aggregations by either computing some aggregation on a specific “window” of data, which you define by using a reference to the current data. This window specification determines which rows will be passed in to this function. Now this is a bit abstract and probably similar to a standard group-by, so let’s differentiate them a bit more.

A group-by takes data, and every row can go only into one grouping. A window function calculates a return value for every input row of a table based on a group of rows, called a frame. Each row can fall into one or more frames. A common use case is to take a look at a rolling average of some value for which each row represents one day. If you were to do this, each row would end up in seven different frames. We cover defining frames a little later, but for your reference, Spark supports three kinds of window functions: ranking functions, analytic functions, and aggregate functions.

Missing

A Window object has 4 method calls

  • partitionBy(*cols): Describes how we will be breaking up our group.
  • orderBy(*cols): Determines the ordering within a given partition.
  • rowsBetween(start, end): States which rows will be included in the frame based on its reference to the current input row. For example, “0” means “current row”, while “-1” means the row before the current row, and “5” means the fifth row after the current row. Stated in another way: the row_number based on the ordering has to be between [current + start, current + end] for the row to be included in the calculation.
  • rangeBetween(start, end): States which rows will be included in the frame based on its reference to the value of the current input row. Because of this definition, when a RANGE frame is used, only a single ordering expression is allowed. For example, “0” means “current row”, while “-1” means one off before the current row, and “5” means the five off after the current row. Stated in another way: the value of the row in the ordering has to be between [current + start, current + end] for the row to be included in the calculation.

It is recommended to use Window.unboundedPreceding, Window.unboundedFollowing, and Window.currentRow to specify special boundary values.

Exercise 4.6.¶