Showing posts with label resilient distributed dataset. Show all posts
Showing posts with label resilient distributed dataset. Show all posts

Tuesday, September 20, 2016

Zeppelin and Spark: Merge Multiple CSVs into Parquet

Introduction

The purpose of this article is to demonstrate how to load multiple CSV files on an HDFS filesystem into a single Dataframe and write to Parquet.

Two approaches are demonstrated.  The first approach is not recommended, but is shown for completeness.


First Approach

One approach might be to define each path:
%pyspark

import locale
locale.setlocale(locale.LC_ALL, 'en_US')

p1 = "/data/output/followers/mitshu/ec2-52-39-251-219.us-west-2.compute.amazonaws.com/0-ec2-52-39-251-219.us-west-2.compute.amazonaws.com/twitterFollowers.csv"
p2 = "/data/output/followers/mitshu/ec2-52-42-100-207.us-west-2.compute.amazonaws.com/0-ec2-52-42-100-207.us-west-2.compute.amazonaws.com/twitterFollowers.csv"
p3 = "/data/output/followers/mitshu/ec2-52-42-198-4.us-west-2.compute.amazonaws.com/0-ec2-52-42-198-4.us-west-2.compute.amazonaws.com/twitterFollowers.csv"
p4 = "/data/output/followers/mitshu/ec2-54-70-37-224.us-west-2.compute.amazonaws.com/0-ec2-54-70-37-224.us-west-2.compute.amazonaws.com/twitterFollowers.csv"

and then open each CSV at that path as an RDD and transform to a dataframe:
%pyspark

rdd_m1 = sc.textFile(p1)
print rdd_m1.take(5)

df_m1 = rdd_m1.\
    map(lambda x: x.split("\t")).\
    filter(lambda x: len(x) == 6). \
    map(lambda x: {
        'id':x[0],
        'profile_id':x[1],
        'profile_name':x[2],
        'follower_id':x[3],
        'follwer_name':x[4],
        'unknown':x[5]})\
    .toDF()
df_m1.limit(5).show()
df_m1.registerTempTable("df_m1")
This would need to be repeated for each dataframe.

The dataframes could then be merged using the unionAll operator.
%pyspark
import pandas as pd

df = df_m1.unionAll(df_m2).unionAll(df_m3).unionAll(df_m4)

print "DF 1: {0}".format(df_m1.count())
print "DF 2: {0}".format(df_m2.count())
print "DF 3: {0}".format(df_m3.count())
print "DF 4: {0}".format(df_m4.count())
print "Merged Dataframe: {0}".format(df.count())


and finally written to parquet.
%pyspark

df.write.parquet("/data/output/followers/mitshu/joined.prq")


Easier Approach

Notice the convenient way of reading multiple CSV in nested directories into a single RDD:
%pyspark

path="/data/output/followers/mitshu/*/*/*.csv"
rdd = sc.textFile(path)
print "count = {}".format(rdd.count())
This is clearly better than defining each path individually.


There are multiple ways to transform RDDs into Dataframes (DFs):
%pyspark

def to_json(r):
    j = {}
    t = r.split("\t")
    j['num_followers'] = t[0]
    j['followed_userid'] = t[1]
    j['followed_handle'] = t[2]
    j['follower_userid'] = t[3]
    j['follower_handle'] = t[4]
    return j
    
df = rdd.map(to_json).toDF()
print "count = {}".format(df.count())
df.show()
This is not necessarily superior to the first approach; but it is an alternative to consider.

 

Load from Parquet

For subsequent analysis, load from Parquet using this code:
%pyspark

df = sqlContext.read.parquet("/data/output/followers/mitshu/joined.prq")
df.limit(5).show()

 

References

  1. [Blogger] Writing to Parquet

Tuesday, July 12, 2016

Zeppelin and Spark: Transforming a CSV to Parquet

Transform a CSV file to Parquet Format

Apache Parquet is a columnar storage format available to any project in the Hadoop ecosystem.  Parquet is built to support very efficient compression and encoding schemes.  Twitter is starting to convert some of its major data source to Parquet in order to take advantage of the compression and deserialization savings.

CSV to RDD

Load the CSV file into an RDD
%pyspark

rdd = sc.textFile("/path/to/input/myfile.csv")
print rdd.take(5)

and the output looks like this:
[u'19999174,Logicknot,Cameron MacKinnon,0,Male, , , ', u'433249647,LogieTatjana,Tatjana Logie \u2653,0,Female, , , ', u'1346426538,Logistic_soares,Luciana Soares,0,Female, , , ', u'18355981,Loh69,Laurent Grad,0,Male, , , ', u'2976559335,LohMarta,Marta Loh,0,Female, , , ']


RDD to DF

Now we need to convert the RDD into a Dataframe (DF):
%pyspark

df = rdd.\
    map(lambda x: x.split(",")).\
    filter(lambda x: len(x) == 8). \
    map(lambda x: {
        'userid':x[0],
        'twitterhandle':x[1],
        'full_name':x[2],
        'country':x[3],
        'gender':x[4],
        'age':x[5],
        'age_min':x[6],
        'age_max':x[7]})\
    .toDF()
df.limit(5).show()

The bad news is that this code might look complicated. The good news is that the code is a lot simpler than it appears.

The first map condition splits each record on the delimiter (a comma).  Now we're dealing with a list of 8 tokens per record.  The second condition (the filter) will reject any line that does not have 8 tokens.  The third, and final, map condition will take each token in the list and create a heading for it.

The output looks like this:
+---+-------+-------+-------+-----------------+------+---------------+----------+
|age|age_max|age_min|country|        full_name|gender|  twitterhandle|    userid|
+---+-------+-------+-------+-----------------+------+---------------+----------+
|   |       |       |      0|Cameron MacKinnon|  Male|      Logicknot|  19999174|
|   |       |       |      0|  Tatjana Logie ♓|Female|   LogieTatjana| 433249647|
|   |       |       |      0|   Luciana Soares|Female|Logistic_soares|1346426538|
|   |       |       |      0|     Laurent Grad|  Male|          Loh69|  18355981|
|   |       |       |      0|        Marta Loh|Female|       LohMarta|2976559335|
+---+-------+-------+-------+-----------------+------+---------------+----------+


Note that we could have greatly simplified the code by just doing this:
%pyspark

df = rdd.\
    map(lambda x: x.split(",")).toDF()
df.limit(5).show()

And then we would have got this:
+----------+---------------+-----------------+---+------+---+---+---+
|        _1|             _2|               _3| _4|    _5| _6| _7| _8|
+----------+---------------+-----------------+---+------+---+---+---+
|  19999174|      Logicknot|Cameron MacKinnon|  0|  Male|   |   |   |
| 433249647|   LogieTatjana|  Tatjana Logie ♓|  0|Female|   |   |   |
|1346426538|Logistic_soares|   Luciana Soares|  0|Female|   |   |   |
|  18355981|          Loh69|     Laurent Grad|  0|  Male|   |   |   |
|2976559335|       LohMarta|        Marta Loh|  0|Female|   |   |   |
+----------+---------------+-----------------+---+------+---+---+---+

But presumably it's important to have column headers; hence the additional code in the first snippet above.

DF to Parquet

The final step is to transform the Dataframe into a Parquet file.

This can be accomplished in a single line:
%pyspark

df.write.parquet("/path/to/output/myfile")


Parquet to DF

Read the parquet file using this code:
%pyspark

df = sqlContext.read.parquet("/path/to/output/myfile")
df.limit(5).show()

And the output, as expected, looks like this:
+---+-------+-------+-------+----------------+------+---------------+----------+
|age|age_max|age_min|country|       full_name|gender|  twitterhandle|    userid|
+---+-------+-------+-------+----------------+------+---------------+----------+
|   |       |       |      0|nelson rodriguez|  Male| ojooooopublico|2771575827|
|   |       |       |      0|    Mary Garciaа|Female|  ojor_ozefofuw|3056364751|
|   |       |       |      0|   Andres nucleo|  Male|ojosrastafara12|1035247920|
|   |       |       |      0|          Omaira|Female|      ojovalles| 183602059|
|   |       |       |      0|   Olivia Scotti|Female|       ojscotti|3072660401|
+---+-------+-------+-------+----------------+------+---------------+----------+

This saved us a significant amount of time. By saving as a parquet file, we not only achieve space efficiency on our cluster, but have the ability to rapidly load the parquet data into a Dataframe with the column headings we specified earlier.

Monday, May 16, 2016

PySpark: Operations Overview

Reslient Distributed Dataset:  A dataset is distributed across the cluster nodes.  No single node has all the data.  The data is recoverable when a single node fails. 

an RDD is distributed across the worker nodes


Collect
<RDD>.collect() pulls back all the data from an RDD into the driver program.  You don't necessarily want to re-assemble a large dataset, distributed onto multiple nodes onto a single driver node over the network.  The function call will probably hang for a while and then die.

If you are doing exploratory data analysis, collect() can be useful when run in conjunction with the sample() function to decrease the size of the data.
rdd.sample(withReplacement=False, fraction=0.01, seed=1).collect()
Because I provided a seed, the random sample will be consistent.  Each time I call this function, I will see the same thing.

Count
The most basic form of count() tells you how many items are present in your RDD.
rdd.count()
it is very useful, and very simple.  There are no optional parameters.

There are a couple of interesting variations:
rdd.countApprox(timeout=200, confidence=0.5)
This function returns an approximate count.  If your dataset is extremely large, and you don't care exactly how many items you have, this is a great function to use. 

4 Paragraphs from a Zeppelin notebook showing
count() and countApprox() in action


Counting distinct elements in a large dataset can be a daunting task.
rdd.countApproxDistinct(relativeSD=0.05)
This function makes it easy by returning an approximate count of the number of distinct elements in the RDD.  Under the covers, the function uses the HyperLogLog algorithm.  This algorithm hashes data into buckets, then looks at smallest data in bucket, then estimates how many values are likely in bucket, then sees has an element of that size, then uses a harmonic mean of all its estimates.

By making the SD parameter smaller, the count will be more accurate and the function runtime will be slower.

First
Sometimes you just need one item in the RDD to play with.  You can use first() to pull the first item from the rdd.
rdd.first()
This function is simple.  There are no optional parameters, and it does respect any sorting that you've done.  You can't use first() on an empty RDD.

Limit a DataFrame (df) to 1 result
or use first() on the underlying RDD

Take
The take() function is a general purpose "return data to the driver" command.
rdd.take(1)
is almost the same as
rdd.first()
although take() returns a list instead of an element

take(n) vs first()



If I take as many rows as are in my collection, that's the same as using collection
rdd.take(rdd.count())
is functionally equivalent to
rdd.collect()
As a rule:
  • if you want one item, use first()
  • if you want all the items, use collect()
  • if you want 0 < x < MAX, use take(n)
Both first() and collect() are optimized for their own particular functions and are better than take(), under these specialized circumstances.  The take() function looks at one partition, and then estimates how many partitions will be needed to provide the number of elements you requested.  The function then returns all the elements you have requested to the driver.

Take Sample

This function allows you to pull a random sample of elements from your RDD into your driver program
rdd.takeSample(withReplacement=False, num=1, seed=1)
When you sample, you need to provide the target sample and if you want replacement while sampling.

Sampling without replacement means you can only select a given element once.  If the RDD has 100 elements, and you want a sample with 10,000 elements, and you set withReplacement=False the sample will only have 100 elements.  Each element can only be selected once.

Sampling with replacement means each element in the input has an equal chance of being picked for each element in the output.  Using the example above, an RDD with 100 elements could be used to generate a random sample with 10,000 elements.  Each input element would be repeated multiple times in the output.

Another way to look at this is that if you had an input RDD like this:
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
and you selected
rdd.takeSample(withReplacement=True, num=rdd.count(), seed=1)
in theory, you could end up with an output (the random sample) that looked like this:
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
this is very unlikely, but it could happen.

Use of takeSample() with the optional seed parameter

You can also provide a random seed when sampling.  Using the parameter enables repeatable research as it enables others to replicate your findings.  Use of this parameter guarantees the function will always make the same random sample selections.

Take Ordered

This function is similar to sorting the entire RDD, then performing a take() function.

Without this function, given a large RDD and a desire for an ordered sample, you would have to reduce over each partition keeping the first n elements by sort.  Then the elements would need to be combined in a reduce, then a final combine performed in the driver.  At each point, would only keep n items.  This is all handled behind the scenes.

This function is only fast if n is small.  Don't use this function when n ~= rdd.count().  As the value of n approaches the size of your rdd, it's best to just sort the rdd then use take() or takeSample()

rdd.takeOrdered(10)
 
the "userid" column is sorted in ascending order



References

  1. [StackOverflow] Take Ordered
    1. Good overview on a variety of takeOrdered(n, key) variations.

Monday, March 23, 2015

Spark Architecture and Design

Cluster Mode Overview

Spark applications run as independent sets of processes on a cluster, coordinated by the SparkCOntext object in your main program (aka Driver Program).
Fig 1: Cluster Mode Overview



What is RDD?


Write programs in terms of transformations on distributed datasets.
    Resilient Distributed Datasets
  1. Collections of objects spread across a cluster, stored in RAM or on Disk
  2. Built through parallel transformations
  3. Automatically rebuilt on failure Operations
    Operations:
  1. Transformations (e.g. map, filter, groupBy)
  2. Actions (e.g. count, collect, save)



References

  1. Cluster Design
    1. [Spark] Cluster Mode Overview
  2. RDD
    1. The RDD API by Example
      1. Zhen He's page at La Trobe University.  
      2. Current with Spark 1.1.0
      3. A helpful introduction to the RDD API.
    2. [DataBricks, PDF] Spark Tutorial Summit 2013
      1. Introductory level talk