แปลงคอลัมน์ Spark DataFrame เป็นรายการ python


109

ฉันทำงานกับ dataframe ที่มีสองคอลัมน์ mvv และ count

+---+-----+
|mvv|count|
+---+-----+
| 1 |  5  |
| 2 |  9  |
| 3 |  3  |
| 4 |  1  |

ฉันต้องการได้รับสองรายการที่มีค่า mvv และค่าการนับ สิ่งที่ต้องการ

mvv = [1,2,3,4]
count = [5,9,3,1]

ดังนั้นฉันลองใช้รหัสต่อไปนี้: บรรทัดแรกควรส่งคืนรายการไพ ธ อนของแถว ฉันต้องการเห็นค่าแรก:

mvv_list = mvv_count_df.select('mvv').collect()
firstvalue = mvv_list[0].getInt(0)

แต่ฉันได้รับข้อความแสดงข้อผิดพลาดในบรรทัดที่สอง:

AttributeError: getInt


ในฐานะของ Spark 2.3 รหัสนี้เป็นวิธีที่เร็วและอย่างน้อยน่าจะทำให้เกิดข้อยกเว้น list(df.select('mvv').toPandas()['mvv'])OutOfMemory: Arrow ถูกรวมเข้ากับ PySparkซึ่งเพิ่มความเร็วขึ้นtoPandasอย่างมาก อย่าใช้แนวทางอื่นหากคุณใช้ Spark 2.3+ ดูคำตอบของฉันสำหรับรายละเอียดการเปรียบเทียบเพิ่มเติม
อำนาจ

คำตอบ:


148

ดูสิทำไมวิธีที่คุณทำอยู่นี้ไม่ได้ผล ขั้นแรกคุณพยายามหาจำนวนเต็มจากRow Type ผลลัพธ์ของการรวบรวมของคุณจะเป็นดังนี้:

>>> mvv_list = mvv_count_df.select('mvv').collect()
>>> mvv_list[0]
Out: Row(mvv=1)

หากคุณใช้สิ่งนี้:

>>> firstvalue = mvv_list[0].mvv
Out: 1

คุณจะได้รับความmvvคุ้มค่า หากคุณต้องการข้อมูลทั้งหมดของอาร์เรย์คุณสามารถใช้สิ่งนี้:

>>> mvv_array = [int(row.mvv) for row in mvv_list.collect()]
>>> mvv_array
Out: [1,2,3,4]

แต่ถ้าคุณลองแบบเดียวกันกับคอลัมน์อื่นคุณจะได้รับ:

>>> mvv_count = [int(row.count) for row in mvv_list.collect()]
Out: TypeError: int() argument must be a string or a number, not 'builtin_function_or_method'

สิ่งนี้เกิดขึ้นเนื่องจากcountเป็นวิธีการในตัว และคอลัมน์มีชื่อเดียวกับcount. วิธีแก้ปัญหาในการดำเนินการนี้คือเปลี่ยนชื่อคอลัมน์countเป็น_count:

>>> mvv_list = mvv_list.selectExpr("mvv as mvv", "count as _count")
>>> mvv_count = [int(row._count) for row in mvv_list.collect()]

แต่ไม่จำเป็นต้องใช้วิธีแก้ปัญหานี้เนื่องจากคุณสามารถเข้าถึงคอลัมน์โดยใช้ไวยากรณ์ของพจนานุกรม:

>>> mvv_array = [int(row['mvv']) for row in mvv_list.collect()]
>>> mvv_count = [int(row['count']) for row in mvv_list.collect()]

และในที่สุดมันก็จะทำงาน!


มันใช้งานได้ดีสำหรับคอลัมน์แรก แต่ไม่ได้ผลสำหรับการนับคอลัมน์ที่ฉันคิดว่าเป็นเพราะ (จำนวนฟังก์ชันของประกายไฟ)
a.moussa

คุณสามารถเพิ่มสิ่งที่คุณกำลังทำกับการนับได้หรือไม่? เพิ่มที่นี่ในความคิดเห็น
Thiago Baldim

ขอบคุณสำหรับการตอบกลับดังนั้นบรรทัดนี้จึงใช้งานได้ mvv_list = [int (i.mvv) สำหรับ i ใน mvv_count.select ('mvv'). .select ('count'). collect ()] ส่งคืนไวยากรณ์ที่ไม่ถูกต้อง
a.moussa

ไม่จำเป็นต้องเพิ่มการselect('count')ใช้งานแบบนี้: count_list = [int(i.count) for i in mvv_list.collect()]ฉันจะเพิ่มตัวอย่างในการตอบสนอง
Thiago Baldim

1
@ a.moussa [i.['count'] for i in mvv_list.collect()]ทำงานเพื่อให้ชัดเจนที่จะใช้คอลัมน์ชื่อ 'count' ไม่ใช่countฟังก์ชัน
user989762

110

การติดตามซับหนึ่งทำให้รายการที่คุณต้องการ

mvv = mvv_count_df.select("mvv").rdd.flatMap(lambda x: x).collect()

3
ประสิทธิภาพที่ชาญฉลาดโซลูชันนี้เร็วกว่าโซลูชันของคุณมาก mvv_list = [int (i.mvv) สำหรับ i ใน mvv_count.select ('mvv') รวบรวม ()]
Chanaka Fernando

นี่เป็นทางออกที่ดีที่สุดที่ฉันเคยเห็น ขอบคุณ.
hui chen

สิ่งนี้จะใช้ได้กับคำถามของ OP หรือไม่: mvv = mvv_count_df.select ("mvv"). rdd.flatMap (list)
.collect

23

สิ่งนี้จะให้องค์ประกอบทั้งหมดเป็นรายการ

mvv_list = list(
    mvv_count_df.select('mvv').toPandas()['mvv']
)

1
นี่คือโซลูชันที่เร็วและมีประสิทธิภาพที่สุดสำหรับ Spark 2.3+ ดูผลการเปรียบเทียบในคำตอบของฉัน
อำนาจ

19

รหัสต่อไปนี้จะช่วยคุณได้

mvv_count_df.select('mvv').rdd.map(lambda row : row[0]).collect()

3
นี่ควรเป็นคำตอบที่ได้รับการยอมรับ เหตุผลก็คือคุณอยู่ในบริบทจุดประกายตลอดกระบวนการจากนั้นคุณจะรวบรวมในตอนท้ายซึ่งต่างจากการออกจากบริบทจุดประกายก่อนหน้านี้ซึ่งอาจทำให้เกิดการรวบรวมที่มากขึ้นขึ้นอยู่กับสิ่งที่คุณกำลังทำ
AntiPawn79

16

ในข้อมูลของฉันฉันได้รับมาตรฐานเหล่านี้:

>>> data.select(col).rdd.flatMap(lambda x: x).collect()

0.52 วินาที

>>> [row[col] for row in data.collect()]

0.271 วินาที

>>> list(data.select(col).toPandas()[col])

0.427 วินาที

ผลลัพธ์ก็เหมือนกัน


2
ถ้าคุณใช้toLocalIteratorแทนcollectมันควรจะช่วยให้หน่วยความจำมีประสิทธิภาพมากขึ้น[row[col] for row in data.toLocalIterator()]
oglop

ขอบคุณเคล็ดลับ! @o
Andre Carneiro

6

หากคุณได้รับข้อผิดพลาดด้านล่าง:

AttributeError: วัตถุ 'list' ไม่มีแอตทริบิวต์ 'collect'

รหัสนี้จะช่วยแก้ปัญหาของคุณ:

mvv_list = mvv_count_df.select('mvv').collect()

mvv_array = [int(i.mvv) for i in mvv_list]

ฉันได้รับข้อผิดพลาดนั้นเช่นกันและวิธีนี้ช่วยแก้ปัญหาได้ แต่ทำไมฉันถึงได้รับข้อผิดพลาด (อีกหลายคนดูเหมือนจะไม่เข้าใจ!)
bikashg

4

ฉันใช้การวิเคราะห์เปรียบเทียบและlist(mvv_count_df.select('mvv').toPandas()['mvv'])เป็นวิธีที่เร็วที่สุด ฉันประหลาดใจมาก

ฉันใช้วิธีการต่างๆบนชุดข้อมูล 100 พัน / 100 ล้านแถวโดยใช้คลัสเตอร์ i3.xlarge 5 โหนด (แต่ละโหนดมี RAM 30.5 GB และ 4 คอร์) ด้วย Spark 2.4.5 ข้อมูลถูกกระจายอย่างเท่าเทียมกันในไฟล์ Parquet ที่บีบอัดเร็ว 20 ไฟล์ด้วยคอลัมน์เดียว

นี่คือผลการเปรียบเทียบ (เวลาทำงานเป็นวินาที):

+-------------------------------------------------------------+---------+-------------+
|                          Code                               | 100,000 | 100,000,000 |
+-------------------------------------------------------------+---------+-------------+
| df.select("col_name").rdd.flatMap(lambda x: x).collect()    |     0.4 | 55.3        |
| list(df.select('col_name').toPandas()['col_name'])          |     0.4 | 17.5        |
| df.select('col_name').rdd.map(lambda row : row[0]).collect()|     0.9 | 69          |
| [row[0] for row in df.select('col_name').collect()]         |     1.0 | OOM         |
| [r[0] for r in mid_df.select('col_name').toLocalIterator()] |     1.2 | *           |
+-------------------------------------------------------------+---------+-------------+

* cancelled after 800 seconds

กฎทองที่ต้องปฏิบัติตามเมื่อรวบรวมข้อมูลบนโหนดไดรเวอร์:

  • พยายามแก้ปัญหาด้วยแนวทางอื่น การรวบรวมข้อมูลไปยังโหนดไดรเวอร์มีราคาแพงไม่ได้ใช้ประโยชน์จากคลัสเตอร์ Spark และควรหลีกเลี่ยงเมื่อทำได้
  • รวบรวมแถวให้น้อยที่สุด รวมลบข้อมูลซ้ำกรองและตัดคอลัมน์ก่อนรวบรวมข้อมูล ส่งข้อมูลไปยังโหนดไดรเวอร์ให้น้อยที่สุดเท่าที่จะทำได้

toPandas ได้รับการปรับปรุงอย่างมีนัยสำคัญใน Spark 2.3 อาจไม่ใช่แนวทางที่ดีที่สุดหากคุณใช้ Spark เวอร์ชันก่อนหน้า 2.3

ดูที่นี่สำหรับรายละเอียดเพิ่มเติม / ผลการเปรียบเทียบ


2

วิธีการแก้ปัญหาที่เป็นไปได้คือการใช้ฟังก์ชั่นจากcollect_list() pyspark.sql.functionsสิ่งนี้จะรวมค่าคอลัมน์ทั้งหมดลงในอาร์เรย์ pyspark ที่ถูกแปลงเป็นรายการ python เมื่อรวบรวม:

mvv_list   = df.select(collect_list("mvv")).collect()[0][0]
count_list = df.select(collect_list("count")).collect()[0][0] 

1

มาสร้าง dataframe ที่เป็นปัญหากัน

df_test = spark.createDataFrame(
    [
        (1, 5),
        (2, 9),
        (3, 3),
        (4, 1),
    ],
    ['mvv', 'count']
)
df_test.show()

ซึ่งจะช่วยให้

+---+-----+
|mvv|count|
+---+-----+
|  1|    5|
|  2|    9|
|  3|    3|
|  4|    1|
+---+-----+

จากนั้นใช้ rdd.flatMap (f) .collect () เพื่อรับรายการ

test_list = df_test.select("mvv").rdd.flatMap(list).collect()
print(type(test_list))
print(test_list)

ซึ่งจะช่วยให้

<type 'list'>
[1, 2, 3, 4]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.