Как создать фрейм данных из всех возможных комбинаций значений каждой из категорий, перечисленных в большом словаре

#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 значений для каждого ключа по случайной выборке, а затем создайте меньший фрейм данных, а затем добавьте все меньшие фреймы данных, чтобы создать больший фрейм данных.