如何从大字典中列出的每个类别的所有可能的值组合创建数据框

Posted

技术标签:

【中文标题】如何从大字典中列出的每个类别的所有可能的值组合创建数据框【英文标题】:How to create a data frame from the all possible combination of values of each of the categories listed in the large dictionary 【发布时间】:2021-10-14 21:22:17 【问题描述】:

我想从字典中列出的每个类别的所有可能的值组合中创建一个数据框。

我尝试了下面的代码,它适用于具有较少键和值的小型字典。但正如我在下面给出的那样,它不会为更大的字典执行。

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

内存错误

如何处理大字典创建数据框

【问题讨论】:

@棕熊,感谢您的回答。正如你所说,这需要很长时间。我用很少的字典键和值执行了代码,但它仍然执行了 2 个多小时。你有什么建议让它高效吗?我有 80 个键和大约 30 多个值。我认为这不可能创建具有此卷的数据框。 你想告诉,当你有这么大的数据框时你想评估什么?在考虑算法时,我可以想象没有必要明确地创建这个巨大的数据框。例如。 my_dict 可以转换为具有 Z 维度的 numpy 数组。使用马尔可夫链蒙特卡洛方法 (MCMC) 随机访问并在需要时构建组合值。但正如已经提到的:这取决于意图。 【参考方案1】:

在您的情况下,您不能使用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

但是需要很长时间

【讨论】:

感谢您的回答。正如你所说,这需要很长时间。我用很少的字典键和值执行了代码,但它仍然执行了 3 个多小时。我有 80 个键和大约 30+ 个值。我认为用这个卷创建数据框是不可能的。您有什么建议可以提高效率吗? 您可以尝试使用multiprocessing 实现一些代码,我会尝试但不是今天。 我认为一种可能性是通过随机样本为每个键创建 3 个值的子字典,然后创建一个较小的数据框,然后附加所有较小的数据框以创建更大的数据框。 【参考方案2】:

如果你有一个足够大 [1] Spark 集群,字典中的每个列表都可以用作 Spark 数据帧,然后所有这些数据帧都可以是cross-joined :

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 不同的组合。这比已知的可观测宇宙中估计的number of atoms (10^80) 还要多。

【讨论】:

【参考方案3】:

在这种情况下,我们有大量可能的组合。例如,如果列(A、B、C...Z)可以取值 [1...10],则总行数等于 10^26,或 100000000000000000000000000。

在我看来,解决这个问题有两个主要方向:

水平扩展:使用分布式计算框架(如Apache SparkHadoop)计算和存储结果 垂直缩放:使用以下方法优化 CPU/RAM 利用率: 矢量化(例如避免loops) 对 RAM 分配影响最小的数据类型(根据需要使用最小精度,对字符串使用 factorize()) 以压缩格式(例如parquet)将中间结果(数据帧)从 RAM 下载到光盘中 对 RAM 中的执行时间和对象大小进行基准测试。

让我介绍一下实现垂直缩放方法的一些概念的代码。

定义以下函数:

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

输出*:

Function Dataframe shape RAM size, Mb Execution time, sec
create_data_frame_baseline_test 78125x7 19 485
create_data_frame_no_loop_test 78125x7 4.4 0.20
create_data_frame_optimize_data_type_test 78125x7 0.55 0.16

使用create_data_frame_optimize_data_type_test(),我在不到 100 秒的时间内生成了*100M 行

* Ubuntu Server 20.04,Intel(R) Xeon(R) 8xCPU @ 2.60GHz,32GB RAM

【讨论】:

很好的答案,但对于大价值,您的版本不起作用。我的意思是你没有解决问题。 @BrownBear 但您在下面的回答也不能解决问题。此外,看起来你的解决方案比我的建议慢。 是的,我知道我的回答并不能解决问题,这就是我提出赏金问题的原因。我对你的答案投了赞成票,但还没准备好给你赏金。 抱歉这里的小误会。感谢您的反馈!

以上是关于如何从大字典中列出的每个类别的所有可能的值组合创建数据框的主要内容,如果未能解决你的问题,请参考以下文章

公平选择组合

如何从Python中列表的dict中的值生成所有组合

如何在 SQL SERVER 的列中列出所有可能的组合?

如何在字典中生成所有可能的组合

如何列出所有月份名称,例如一个组合?

nyoj 32 组合数