Перетворити стовпець spark DataFrame у список python


104

Я працюю над фреймом даних із двома стовпцями, mvv та count.

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

я хотів би отримати два списки, що містять значення mvv та значення підрахунку. Щось на зразок

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

Отже, я спробував наступний код: Перший рядок повинен повертати список рядків python. Я хотів побачити перше значення:

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

Але я отримую повідомлення про помилку з другим рядком:

AttributeError: getInt


Станом на Спарк 2.3, цей код є найшвидшим і найменш ймовірно, викличе OutOfMemory виключення: list(df.select('mvv').toPandas()['mvv']). Стрілка була інтегрована в PySpark, що toPandasзначно пришвидшилось . Не використовуйте інші підходи, якщо ви використовуєте Spark 2.3+. Дивіться мою відповідь, щоб отримати докладнішу інформацію.
Повноваження

Відповіді:


141

Подивіться, чому цей спосіб, який ви робите, не працює. По-перше, ви намагаєтесь отримати ціле число з типу рядка , результат вашого збору такий:

>>> 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

Чи можете ви додати, що ви робите з графом? Додайте сюди в коментарі.
Тьяго Балдім

дякую за вашу відповідь Отже, цей рядок працює mvv_list = [int (i.mvv) for i у mvv_count.select ('mvv'). collect ()], але не цей count_list = [int (i.count) для i у mvv_count .select ('count'). collect ()] повернути недійсний синтаксис
a.moussa

Не потрібно додавати це select('count')використання таким чином: count_list = [int(i.count) for i in mvv_list.collect()]я додам приклад до відповіді.
Тьяго Балдім

1
@ a.moussa [i.['count'] for i in mvv_list.collect()]працює над тим, щоб явно використовувати стовпець з назвою "count", а не countфункцію
user989762

103

Слідом за одним лайнером подається список, який ви хочете.

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

3
З точки зору продуктивності це рішення набагато швидше , ніж mvv_list рішення = [Int (i.mvv) для г в mvv_count.select ( 'MVV') Collect ().]
Чанакья Фернандо

Це, безумовно, найкраще рішення, яке я бачив. Дякую.
хуй чень,


16

Наступний код допоможе вам

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

3
Це має бути прийнятою відповіддю. причина полягає в тому, що ви залишаєтеся в контексті іскри протягом усього процесу, а потім збираєте в кінці, на відміну від того, щоб вийти з контексту іскри раніше, що може спричинити більший збір залежно від того, що ви робите.
AntiPawn79

15

За своїми даними я отримав такі орієнтири:

>>> 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 сек

Результат той самий


1
Якщо ви використовуєте toLocalIteratorзамість цього, collectце навіть має бути більш ефективним для пам'яті[row[col] for row in data.toLocalIterator()]
oglop

6

Якщо ви отримуєте помилку нижче:

AttributeError: об'єкт 'list' не має атрибута 'collect'

Цей код вирішить ваші проблеми:

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

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

Я теж отримав цю помилку, і це рішення вирішило проблему. Але чому я отримав помилку? (Багато хто, схоже, цього не
розуміє

3

Я провів порівняльний аналіз і list(mvv_count_df.select('mvv').toPandas()['mvv'])є найшвидшим методом. Я дуже здивований.

Я використовував різні підходи на 100 тис. / 100 млн. Наборів даних рядків, використовуючи кластер i3.xlarge з 5 вузлів (кожен вузол має 30,5 Гб оперативної пам'яті та 4 ядра) з Spark 2.4.5. Дані розподілялись рівномірно по 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] 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.