#python-3.x #pandas #pyspark #iterator #multiprocessing
Вопрос:
Я хотел бы создать фрейм данных из всех возможных комбинаций значений каждой из категорий, перечисленных в словаре.
Я попробовал приведенный ниже код, он отлично работает для небольшого словаря с меньшим ключом и значениями. Но он не выполняется для большего словаря, как я привел ниже.
import itertools as it
import pandas as pd
my_dict= {
"A":[0,1,.....25],
"B":[4,5,.....35],
"C":[0,1,......30],
"D":[0,1,........35],
.........
"Y":[0,1,........35],
"Z":[0,1,........35],
}
df=pd.DataFrame(list(it.product(*my_dict.values())), columns=my_dict.keys())
Это ошибка, которую я получаю, как решить эту проблему с большим словарем.
Traceback (most recent call last):
File "<ipython-input-11-723405257e95>", line 1, in <module>
df=pd.DataFrame(list(it.product(*my_dict.values())), columns=my_dict.keys())
Ошибка памяти
Как работать с большим словарем для создания фрейма данных
Комментарии:
1. @Бурый медведь, спасибо за ваш ответ. Как вы сказали, это займет много времени. Я выполнил код с несколькими словарными ключами и значениями, но он все еще выполняется более 2 часов. У вас есть какие-либо предложения, чтобы сделать его эффективным. У меня есть 80 ключей и более 30 значений. Я думаю, что создать фрейм данных с таким объемом будет невозможно.
2. Вы хотите рассказать, что вы хотите оценить, когда у вас есть этот огромный фрейм данных ? Когда я думаю об алгоритмах, я могу представить, что нет необходимости явно создавать этот огромный фрейм данных. Например
my_dict
, его можно преобразовать в числовой массив сZ
размерами. С помощью метода Монте-Карло с цепочкой Маркова (MCMC) можно получить случайный доступ и построить комбинаторные значения., когда они необходимы. Но, как уже упоминалось: это зависит от намерения.
Ответ №1:
Если у вас достаточно большой кластер[1] Spark, каждый список в словаре можно использовать в качестве фрейма данных Spark, а затем все эти фреймы данных можно объединить:
def to_spark_dfs(dict):
for key in dict:
l=[[e] for e in dict[key]]
yield spark.createDataFrame(l, schema=[key])
dfs=to_spark_dfs(my_dict)
from functools import reduce
res=reduce(lambda df1,df2: df1.crossJoin(df2),dfs)
Если оригинал my_dict
не слишком велик
my_dict= {
"A":[0,1,2],
"B":[4,5,6],
"C":[0,1,2],
"D":[0,1],
"Y":[0,1,2],
"Z":[0,1],
}
код выдает ожидаемый результат:
res.show()
# --- --- --- --- --- ---
#| A| B| C| D| Y| Z|
# --- --- --- --- --- ---
#| 0| 4| 0| 0| 0| 0|
#| 0| 4| 0| 0| 0| 1|
#| 0| 4| 0| 0| 1| 0|
#| 0| 4| 0| 0| 1| 1|
#...
res.count()
#324
[1]
Используя цифры, приведенные в комментарии (80 ключей и около 30 значений на ключ), вам понадобится действительно большой кластер Spark: 30 ^ 80
дает 1.5*10^118
другую комбинацию. Это больше, чем предполагаемое количество атомов ( 10^80
) в известной, наблюдаемой вселенной.
Ответ №2:
В этом случае у нас есть огромное количество возможных комбинаций. Например, если столбцы (A, B, C… Z) могут принимать значения [1…10], то общее количество строк равно 10^26 или 1000000000000000000000000000000.
На мой взгляд, есть 2 основных направления для решения этой проблемы:
- Горизонтальное масштабирование: вычисление и хранение результатов с использованием платформ для распределенных вычислений (таких как
Apache Spark
илиHadoop
) - Вертикальное масштабирование: оптимизируйте использование процессора/оперативной памяти с помощью:
- Векторизация (например, избегать
loops
) - Типы данных с минимальным влиянием на распределение оперативной памяти (используйте с минимальной точностью, как вам нужно, используйте
factorize()
для строк) - мини-пакетирование и загрузка промежуточных результатов (кадров данных) из оперативной памяти на диск в сжатом формате (например
parquet
) - проверьте время выполнения и размер объекта в оперативной памяти.
- Векторизация (например, избегать
Позвольте мне представить код, реализующий некоторые концепции подхода вертикального масштабирования.
Определите следующие функции:
create_data_frame_baseline()
: генератор фреймов данных с циклом, не оптимальные типы данных (базовый уровень)create_data_frame_no_loop()
: нет цикла, не оптимальные типы данныхcreate_data_frame_optimize_data_type()
: нет цикла, оптимальные типы данных.
import itertools as it
import pandas as pd
import numpy as np
from string import ascii_uppercase
def create_letter_dict(cols_n: int = 10, levels_n: int = 6) -> dict:
letter_dict = {letter: list(range(levels_n)) for letter in ascii_uppercase[0:cols_n]}
return letter_dict
def create_data_frame_baseline(dict: dict) -> pd.DataFrame:
df = pd.DataFrame(columns=dict.keys())
for row in it.product(*dict.values()):
df.loc[len(df.index)] = row
return df
def create_data_frame_no_loop(dict: dict) -> pd.DataFrame:
return pd.DataFrame(
list(it.product(*dict.values())),
columns=dict.keys()
)
def create_data_frame_optimize_data_type(dict: dict) -> pd.DataFrame:
return pd.DataFrame(
np.int8(list(it.product(*dict.values()))),
columns=dict.keys()
)
Контрольные показатели:
import sys
import timeit
cols_n = 7
levels_n = 5
iteration_n = 2
# Baseline
def create_data_frame_baseline_test():
my_dict = create_letter_dict(cols_n, levels_n)
df = create_data_frame_baseline(my_dict)
assert(df.shape == (levels_n**cols_n, cols_n))
print(sys.getsizeof(df))
return df
print(timeit.Timer(create_data_frame_baseline_test).timeit(number=iteration_n))
# No loop, not optimal data types
def create_data_frame_no_loop_test():
my_dict = create_letter_dict(cols_n, levels_n)
df = create_data_frame_no_loop(my_dict)
assert(df.shape == (levels_n**cols_n, cols_n))
print(sys.getsizeof(df))
return df
print(timeit.Timer(create_data_frame_no_loop_test).timeit(number=iteration_n))
# No loop, optimal data types.
def create_data_frame_optimize_data_type_test():
my_dict = create_letter_dict(cols_n, levels_n)
df = create_data_frame_optimize_data_type(my_dict)
assert(df.shape == (levels_n**cols_n, cols_n))
print(sys.getsizeof(df))
return df
print(timeit.Timer(create_data_frame_optimize_data_type_test).timeit(number=iteration_n))
Выходы*:
Функция | Форма фрейма данных | Объем оперативной памяти, Мб | Время выполнения, сек |
---|---|---|---|
create_data_frame_baseline_тест | 78125×7 | 19 | 485 |
create_data_frame_no_loop_test | 78125×7 | 4.4 | 0.20 |
create_data_frame_optimize_data_type_test | 78125×7 | 0.55 | 0.16 |
Используя create_data_frame_optimize_data_type_test()
I, я сгенерировал* 100 млн строк менее чем за 100 секунд.
* Сервер Ubuntu 20.04, Intel(R) Xeon(R) 8xCPU @ 2,60 ГГц, 32 ГБ оперативной памяти
Комментарии:
1. хороший ответ, но для большой ценности ваша версия не работает. Я имею в виду, что ты не решаешь вопрос.
2. @BrownBear, но ваш ответ ниже также не решает этот вопрос. Более того, похоже, что ваше решение медленнее, чем мое предложение.
3. Да, я знаю, что мой ответ не решает проблему, поэтому я задал этот вопрос баунти. И я проголосовал за ваш ответ, но не готов отдать награду вам.
4. Извините за небольшое недоразумение. Спасибо за отзыв!
Ответ №3:
В вашем случае вы не можете сгенерировать все возможные комбинации сразу, используя list()
, например, но делайте это в цикле:
import itertools as it
import pandas as pd
from string import ascii_uppercase
N = 36
my_dict = {x: list(range(N)) for x in ascii_uppercase}
df = pd.DataFrame(columns=my_dict.keys())
for row in it.product(*my_dict.values()):
df.loc[len(df.index)] = row
но, конечно, это займет много времени
Комментарии:
1. Спасибо за ваш ответ. Как вы сказали, это займет много времени. Я выполнил код с несколькими ключами и значениями словаря, но он все еще выполняется более 3 часов. У меня есть 80 ключей и около 30 значений каждый. Я думаю, что с таким объемом невозможно создать фрейм данных. У вас есть какие-либо предложения, чтобы сделать его эффективным?
2. вы можете попробовать реализовать какой-то код с помощью многопроцессорной обработки, я попробую это сделать, но не сегодня.
3. Я думаю , что есть одна возможность, создав дополнительный словарь из 3 значений для каждого ключа по случайной выборке, а затем создайте меньший фрейм данных, а затем добавьте все меньшие фреймы данных, чтобы создать больший фрейм данных.