โหลดไฟล์ CSV ด้วย Spark


110

ฉันเพิ่งเริ่มใช้ Spark และฉันกำลังพยายามอ่านข้อมูล CSV จากไฟล์ด้วย Spark นี่คือสิ่งที่ฉันกำลังทำ:

sc.textFile('file.csv')
    .map(lambda line: (line.split(',')[0], line.split(',')[1]))
    .collect()

ฉันคาดว่าการโทรนี้จะให้รายการสองคอลัมน์แรกของไฟล์ของฉัน แต่ฉันได้รับข้อผิดพลาดนี้:

File "<ipython-input-60-73ea98550983>", line 1, in <lambda>
IndexError: list index out of range

แม้ว่าไฟล์ CSV ของฉันจะมีมากกว่าหนึ่งคอลัมน์

คำตอบ:


63

แน่ใจหรือว่าทุกบรรทัดมีอย่างน้อย 2 คอลัมน์ ลองของเช่นเพื่อตรวจสอบได้ไหม:

sc.textFile("file.csv") \
    .map(lambda line: line.split(",")) \
    .filter(lambda line: len(line)>1) \
    .map(lambda line: (line[0],line[1])) \
    .collect()

หรือคุณสามารถพิมพ์ผู้ร้าย (ถ้ามี):

sc.textFile("file.csv") \
    .map(lambda line: line.split(",")) \
    .filter(lambda line: len(line)<=1) \
    .collect()

นั่นคือบรรทัดเดียวกับคอลัมน์เดียวเท่านั้นขอบคุณ
Kernael

2
เป็นการดีกว่าที่จะแยกวิเคราะห์โดยใช้csvไลบรารีในตัวเพื่อจัดการการหลีกเลี่ยงทั้งหมดเนื่องจากการแบ่งด้วยลูกน้ำจะไม่ทำงานหากกล่าวว่ามีเครื่องหมายจุลภาคอยู่ในค่า
sudo

4
มีเครื่องมือมากมายในการแยกวิเคราะห์ csv อย่าสร้างวงล้อใหม่
Stephen

2
รหัสนี้จะแตกหากมีเครื่องหมายจุลภาคอยู่ภายในเครื่องหมายคำพูด แยก CSV ","มีความซับซ้อนมากขึ้นกว่าเพียงแค่แยก
Alceu Costa

ซึ่งแบ่งเป็นเครื่องหมายจุลภาค นี่มันแย่มาก
rjurney

184

Spark 2.0.0+

คุณสามารถใช้แหล่งข้อมูล csv ในตัวได้โดยตรง:

spark.read.csv(
    "some_input_file.csv", header=True, mode="DROPMALFORMED", schema=schema
)

หรือ

(spark.read
    .schema(schema)
    .option("header", "true")
    .option("mode", "DROPMALFORMED")
    .csv("some_input_file.csv"))

โดยไม่รวมการอ้างอิงภายนอกใด ๆ

จุดประกาย <2.0.0 :

แทนที่จะแยกวิเคราะห์ด้วยตนเองซึ่งห่างไกลจากเรื่องเล็กน้อยในกรณีทั่วไปฉันขอแนะนำspark-csv:

ตรวจสอบให้แน่ใจว่า Spark CSV จะถูกรวมอยู่ในเส้นทาง ( --packages, --jars, --driver-class-path)

และโหลดข้อมูลของคุณดังนี้:

(df = sqlContext
    .read.format("com.databricks.spark.csv")
    .option("header", "true")
    .option("inferschema", "true")
    .option("mode", "DROPMALFORMED")
    .load("some_input_file.csv"))

สามารถจัดการกับการโหลดการอนุมานสคีมาการวางบรรทัดที่ผิดรูปแบบและไม่ต้องการการส่งผ่านข้อมูลจาก Python ไปยัง JVM

หมายเหตุ :

DataFrameReaderถ้าคุณรู้ว่าสคีมามันจะดีกว่าที่จะหลีกเลี่ยงการอนุมานคีมาและผ่านไป สมมติว่าคุณมีสามคอลัมน์ - จำนวนเต็มคู่และสตริง:

from pyspark.sql.types import StructType, StructField
from pyspark.sql.types import DoubleType, IntegerType, StringType

schema = StructType([
    StructField("A", IntegerType()),
    StructField("B", DoubleType()),
    StructField("C", StringType())
])

(sqlContext
    .read
    .format("com.databricks.spark.csv")
    .schema(schema)
    .option("header", "true")
    .option("mode", "DROPMALFORMED")
    .load("some_input_file.csv"))

6
หากคุณทำเช่นนี้อย่าลืมรวมแพ็คเกจ databricks csv เมื่อคุณเปิด pyspark shell หรือใช้ spark-submit ตัวอย่างเช่นpyspark --packages com.databricks:spark-csv_2.11:1.4.0(อย่าลืมเปลี่ยนเวอร์ชัน databricks / spark เป็นเวอร์ชันที่คุณติดตั้งไว้)
Galen Long

มันเป็น csvContext หรือ sqlContext ใน pyspark? เพราะในสกาล่าคุณต้องมี csvContext
Geoffrey Anderson

28
from pyspark.sql import SparkSession

spark = SparkSession \
    .builder \
    .appName("Python Spark SQL basic example") \
    .config("spark.some.config.option", "some-value") \
    .getOrCreate()

df = spark.read.csv("/home/stp/test1.csv",header=True,sep="|");

print(df.collect())

ใช้ตัวคั่น 'sep not' ดังนี้: df = spark.read.csv ("/ home / stp / test1.csv", header = True, sep = "|")
Grant Shannon

18

และอีกทางเลือกหนึ่งซึ่งประกอบด้วยการอ่านไฟล์ CSV โดยใช้ Pandas จากนั้นนำเข้า Pandas DataFrame ไปยัง Spark

ตัวอย่างเช่น:

from pyspark import SparkContext
from pyspark.sql import SQLContext
import pandas as pd

sc = SparkContext('local','example')  # if using locally
sql_sc = SQLContext(sc)

pandas_df = pd.read_csv('file.csv')  # assuming the file contains a header
# pandas_df = pd.read_csv('file.csv', names = ['column 1','column 2']) # if no header
s_df = sql_sc.createDataFrame(pandas_df)

7
เหตุใด OP จึงต้องการจุดประกายหากเขาสามารถโหลดข้อมูลเป็นหมีแพนด้าได้
WoodChopper

ไม่ต้องการติดตั้งหรือระบุการอ้างอิงในทุกกลุ่มประกายไฟ ....
SummerEla

Panda อนุญาตให้แบ่งไฟล์เป็นชิ้น ๆ เมื่ออ่านดังนั้นจึงยังมีกรณีการใช้งานสำหรับการให้ Pandas จัดการการแยกวิเคราะห์ไฟล์เริ่มต้น ดูคำตอบของฉันด้านล่างสำหรับรหัส
abby sobh

ข้อควรระวัง: นุ่นยังจัดการสคีมาของคอลัมน์ด้วยวิธีที่แตกต่างจากจุดประกายโดยเฉพาะอย่างยิ่งเมื่อมีช่องว่างที่เกี่ยวข้อง ปลอดภัยกว่าเพียงโหลด csv เป็นสตริงสำหรับแต่ละคอลัมน์
AntiPawn79

@WoodChopper คุณสามารถใช้ Pandas เป็น UDF ใน Spark ได้ไหม?
flow2k

16

การแยกด้วยลูกน้ำจะเป็นการแบ่งเครื่องหมายจุลภาคที่อยู่ในช่อง (เช่นa,b,"1,2,3",c) ดังนั้นจึงไม่แนะนำ คำตอบของ zero323นั้นดีถ้าคุณต้องการใช้ DataFrames API แต่ถ้าคุณต้องการยึดติดกับพื้นฐาน Spark คุณสามารถแยกวิเคราะห์ csvs ใน Python พื้นฐานด้วยโมดูลcsv :

# works for both python 2 and 3
import csv
rdd = sc.textFile("file.csv")
rdd = rdd.mapPartitions(lambda x: csv.reader(x))

แก้ไข: ตามที่ @muon กล่าวไว้ในความคิดเห็นสิ่งนี้จะปฏิบัติต่อส่วนหัวเหมือนแถวอื่น ๆ ดังนั้นคุณจะต้องแยกมันด้วยตนเอง ตัวอย่างเช่นheader = rdd.first(); rdd = rdd.filter(lambda x: x != header)(อย่าแก้ไขheaderก่อนที่ตัวกรองจะประเมิน) แต่ ณ จุดนี้คุณน่าจะดีกว่าถ้าใช้ตัวแยกวิเคราะห์ csv ในตัว


1
คุณไม่จำเป็นต้อง Hive เพื่อใช้ DataFrames เกี่ยวกับวิธีการแก้ปัญหาของคุณ: ก) StringIOไม่จำเป็นต้องเป็น csvสามารถใช้ซ้ำได้ b) __next__ไม่ควรใช้โดยตรงและจะล้มเหลวในบรรทัดว่างเปล่า ลองดูที่ flatMap c) มันจะมีประสิทธิภาพมากกว่าที่จะใช้mapPartitionsแทนการเริ่มต้นโปรแกรมอ่านในแต่ละบรรทัด :)
zero323

ขอบคุณมากสำหรับการแก้ไข! ก่อนจะแก้ไขคำตอบฉันต้องการให้แน่ใจว่าฉันเข้าใจอย่างถ่องแท้ 1) เหตุใดจึงใช้rdd.mapPartitions(lambda x: csv.reader(x))งานได้ขณะrdd.map(lambda x: csv.reader(x))เกิดข้อผิดพลาด TypeError: can't pickle _csv.reader objectsผมคาดว่าทั้งสองจะโยนเดียวกัน ดูเหมือนว่าmapPartitionsจะเรียกสิ่งที่เทียบเท่ากับ "readlines" บนcsv.readerวัตถุโดยอัตโนมัติโดยที่mapฉันต้องโทร__next__อย่างชัดเจนเพื่อให้รายการออกจากไฟล์csv.reader. 2) ที่flatMapมา? แค่โทรmapPartitionsคนเดียวก็ใช้ได้แล้ว
Galen Long

1
rdd.mapPartitions(lambda x: csv.reader(x))ทำงานได้เพราะmapPartitionsคาดหวังIterableวัตถุ ถ้าคุณต้องการชัดเจนคุณสามารถเข้าใจหรือสร้างนิพจน์ mapเพียงอย่างเดียวไม่ได้ผลเพราะมันไม่ได้ทำซ้ำบนวัตถุ ดังนั้นคำแนะนำของฉันที่จะใช้flatMap(lambda x: csv.reader([x]))ซึ่งจะย้ำกับผู้อ่าน แต่mapPartitionsจะดีกว่าที่นี่มาก
zero323

1
โปรดทราบว่าสิ่งนี้จะอ่านส่วนหัวเป็นแถวของข้อมูลไม่ใช่ส่วนหัว
muon


6

หากคุณต้องการโหลด csv เป็น dataframe คุณสามารถทำสิ่งต่อไปนี้:

from pyspark.sql import SQLContext
sqlContext = SQLContext(sc)

df = sqlContext.read.format('com.databricks.spark.csv') \
    .options(header='true', inferschema='true') \
    .load('sampleFile.csv') # this is your csv file

มันทำงานได้ดีสำหรับฉัน


@GalenLong ถ้าคุณไม่รังเกียจคุณช่วยแบ่งปันคำตอบที่มีอยู่แล้วได้
ไหม

แปลกฉันสาบานว่ามีคำตอบอื่นสำหรับวิธีแก้ปัญหานี้ บางทีฉันอาจจะสับสนกับคำถามอื่น ความผิดฉันเอง.
Galen Long

5

สิ่งนี้สอดคล้องกับสิ่งที่JP Mercier แนะนำในตอนแรกเกี่ยวกับการใช้ Pandas แต่มีการปรับเปลี่ยนครั้งใหญ่: หากคุณอ่านข้อมูลใน Pandas เป็นชิ้น ๆ ข้อมูลควรจะอ่อนกว่านี้ หมายความว่าคุณสามารถแยกวิเคราะห์ไฟล์ขนาดใหญ่กว่าที่ Pandas สามารถจัดการเป็นชิ้นเดียวและส่งต่อไปยัง Spark ในขนาดที่เล็กกว่าได้ (สิ่งนี้ยังตอบความคิดเห็นเกี่ยวกับสาเหตุที่ใคร ๆ ก็อยากใช้ Spark ถ้าพวกเขาสามารถโหลดทุกอย่างลงใน Pandas ได้)

from pyspark import SparkContext
from pyspark.sql import SQLContext
import pandas as pd

sc = SparkContext('local','example')  # if using locally
sql_sc = SQLContext(sc)

Spark_Full = sc.emptyRDD()
chunk_100k = pd.read_csv("Your_Data_File.csv", chunksize=100000)
# if you have headers in your csv file:
headers = list(pd.read_csv("Your_Data_File.csv", nrows=0).columns)

for chunky in chunk_100k:
    Spark_Full +=  sc.parallelize(chunky.values.tolist())

YourSparkDataFrame = Spark_Full.toDF(headers)
# if you do not have headers, leave empty instead:
# YourSparkDataFrame = Spark_Full.toDF()
YourSparkDataFrame.show()

5

ตอนนี้ยังมีตัวเลือกอื่นสำหรับไฟล์ csv ทั่วไป: https://github.com/seahboonsiew/pyspark-csvดังต่อไปนี้:

สมมติว่าเรามีบริบทดังต่อไปนี้

sc = SparkContext
sqlCtx = SQLContext or HiveContext

ขั้นแรกแจกจ่าย pyspark-csv.py ให้กับผู้ดำเนินการโดยใช้ SparkContext

import pyspark_csv as pycsv
sc.addPyFile('pyspark_csv.py')

อ่านข้อมูล csv ผ่าน SparkContext และแปลงเป็น DataFrame

plaintext_rdd = sc.textFile('hdfs://x.x.x.x/blah.csv')
dataframe = pycsv.csvToDataFrame(sqlCtx, plaintext_rdd)

3

หากข้อมูล csv ของคุณไม่มีขึ้นบรรทัดใหม่ในฟิลด์ใด ๆ คุณสามารถโหลดข้อมูลของคุณtextFile()และแยกวิเคราะห์ได้

import csv
import StringIO

def loadRecord(line):
    input = StringIO.StringIO(line)
    reader = csv.DictReader(input, fieldnames=["name1", "name2"])
    return reader.next()

input = sc.textFile(inputFile).map(loadRecord)

2

หากคุณมีแถวหนึ่งแถวหรือมากกว่าที่มีจำนวนคอลัมน์น้อยกว่าหรือมากกว่า 2 ในชุดข้อมูลข้อผิดพลาดนี้อาจเกิดขึ้น

ฉันยังใหม่กับ Pyspark และพยายามอ่านไฟล์ CSV รหัสต่อไปนี้ใช้ได้ผลสำหรับฉัน:

ในรหัสนี้ฉันใช้ชุดข้อมูลจาก kaggle ลิงก์คือ: https://www.kaggle.com/carrie1/ecommerce-data

1. โดยไม่ต้องกล่าวถึงสคีมา:

from pyspark.sql import SparkSession  
scSpark = SparkSession \
    .builder \
    .appName("Python Spark SQL basic example: Reading CSV file without mentioning schema") \
    .config("spark.some.config.option", "some-value") \
    .getOrCreate()

sdfData = scSpark.read.csv("data.csv", header=True, sep=",")
sdfData.show()

ตรวจสอบคอลัมน์: sdfData.columns

ผลลัพธ์จะเป็น:

['InvoiceNo', 'StockCode','Description','Quantity', 'InvoiceDate', 'CustomerID', 'Country']

ตรวจสอบประเภทข้อมูลสำหรับแต่ละคอลัมน์:

sdfData.schema
StructType(List(StructField(InvoiceNo,StringType,true),StructField(StockCode,StringType,true),StructField(Description,StringType,true),StructField(Quantity,StringType,true),StructField(InvoiceDate,StringType,true),StructField(UnitPrice,StringType,true),StructField(CustomerID,StringType,true),StructField(Country,StringType,true)))

สิ่งนี้จะให้กรอบข้อมูลพร้อมคอลัมน์ทั้งหมดที่มีประเภทข้อมูลเป็น StringType

2. ด้วยสคีมา: หากคุณรู้จักสคีมาหรือต้องการเปลี่ยนประเภทข้อมูลของคอลัมน์ใด ๆ ในตารางด้านบนให้ใช้สิ่งนี้ (สมมติว่าฉันมีคอลัมน์ต่อไปนี้และต้องการให้อยู่ในประเภทข้อมูลเฉพาะสำหรับแต่ละคอลัมน์)

from pyspark.sql import SparkSession  
from pyspark.sql.types import StructType, StructField
from pyspark.sql.types import DoubleType, IntegerType, StringType
    schema = StructType([\
        StructField("InvoiceNo", IntegerType()),\
        StructField("StockCode", StringType()), \
        StructField("Description", StringType()),\
        StructField("Quantity", IntegerType()),\
        StructField("InvoiceDate", StringType()),\
        StructField("CustomerID", DoubleType()),\
        StructField("Country", StringType())\
    ])

scSpark = SparkSession \
    .builder \
    .appName("Python Spark SQL example: Reading CSV file with schema") \
    .config("spark.some.config.option", "some-value") \
    .getOrCreate()

sdfData = scSpark.read.csv("data.csv", header=True, sep=",", schema=schema)

ตรวจสอบสคีมาสำหรับประเภทข้อมูลของแต่ละคอลัมน์:

sdfData.schema

StructType(List(StructField(InvoiceNo,IntegerType,true),StructField(StockCode,StringType,true),StructField(Description,StringType,true),StructField(Quantity,IntegerType,true),StructField(InvoiceDate,StringType,true),StructField(CustomerID,DoubleType,true),StructField(Country,StringType,true)))

แก้ไข: เราสามารถใช้โค้ดบรรทัดต่อไปนี้ได้เช่นกันโดยไม่ต้องกล่าวถึงสคีมาอย่างชัดเจน:

sdfData = scSpark.read.csv("data.csv", header=True, inferSchema = True)
sdfData.schema

ผลลัพธ์คือ:

StructType(List(StructField(InvoiceNo,StringType,true),StructField(StockCode,StringType,true),StructField(Description,StringType,true),StructField(Quantity,IntegerType,true),StructField(InvoiceDate,StringType,true),StructField(UnitPrice,DoubleType,true),StructField(CustomerID,IntegerType,true),StructField(Country,StringType,true)))

ผลลัพธ์จะมีลักษณะดังนี้:

sdfData.show()

+---------+---------+--------------------+--------+--------------+----------+-------+
|InvoiceNo|StockCode|         Description|Quantity|   InvoiceDate|CustomerID|Country|
+---------+---------+--------------------+--------+--------------+----------+-------+
|   536365|   85123A|WHITE HANGING HEA...|       6|12/1/2010 8:26|      2.55|  17850|
|   536365|    71053| WHITE METAL LANTERN|       6|12/1/2010 8:26|      3.39|  17850|
|   536365|   84406B|CREAM CUPID HEART...|       8|12/1/2010 8:26|      2.75|  17850|
|   536365|   84029G|KNITTED UNION FLA...|       6|12/1/2010 8:26|      3.39|  17850|
|   536365|   84029E|RED WOOLLY HOTTIE...|       6|12/1/2010 8:26|      3.39|  17850|
|   536365|    22752|SET 7 BABUSHKA NE...|       2|12/1/2010 8:26|      7.65|  17850|
|   536365|    21730|GLASS STAR FROSTE...|       6|12/1/2010 8:26|      4.25|  17850|
|   536366|    22633|HAND WARMER UNION...|       6|12/1/2010 8:28|      1.85|  17850|
|   536366|    22632|HAND WARMER RED P...|       6|12/1/2010 8:28|      1.85|  17850|
|   536367|    84879|ASSORTED COLOUR B...|      32|12/1/2010 8:34|      1.69|  13047|
|   536367|    22745|POPPY'S PLAYHOUSE...|       6|12/1/2010 8:34|       2.1|  13047|
|   536367|    22748|POPPY'S PLAYHOUSE...|       6|12/1/2010 8:34|       2.1|  13047|
|   536367|    22749|FELTCRAFT PRINCES...|       8|12/1/2010 8:34|      3.75|  13047|
|   536367|    22310|IVORY KNITTED MUG...|       6|12/1/2010 8:34|      1.65|  13047|
|   536367|    84969|BOX OF 6 ASSORTED...|       6|12/1/2010 8:34|      4.25|  13047|
|   536367|    22623|BOX OF VINTAGE JI...|       3|12/1/2010 8:34|      4.95|  13047|
|   536367|    22622|BOX OF VINTAGE AL...|       2|12/1/2010 8:34|      9.95|  13047|
|   536367|    21754|HOME BUILDING BLO...|       3|12/1/2010 8:34|      5.95|  13047|
|   536367|    21755|LOVE BUILDING BLO...|       3|12/1/2010 8:34|      5.95|  13047|
|   536367|    21777|RECIPE BOX WITH M...|       4|12/1/2010 8:34|      7.95|  13047|
+---------+---------+--------------------+--------+--------------+----------+-------+
only showing top 20 rows

1

เมื่อใช้spark.read.csvฉันพบว่าการใช้ตัวเลือกescape='"'และmultiLine=Trueให้โซลูชันที่สอดคล้องกับมาตรฐาน CSVมากที่สุดและจากประสบการณ์ของฉันจะทำงานได้ดีที่สุดกับไฟล์ CSV ที่ส่งออกจาก Google ชีต

นั่นคือ,

#set inferSchema=False to read everything as string
df = spark.read.csv("myData.csv", escape='"', multiLine=True,
     inferSchema=False, header=True)

ประกายไฟมาจากไหน? มันคือ import pyspark as sparkอะไร?
ลูก

@LukAron ในเปลือก pyspark sparkเริ่มต้นแล้ว ในสคริปต์ที่ส่งมาspark-submitคุณสามารถสร้างอินสแตนซ์เป็นfrom pyspark.sql import SparkSession; spark = SparkSession.builder.getOrCreate()ไฟล์.
flow2k
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.