从冗长的字典生成时,Pandas DataFrame.from_dict() 性能不佳

Posted

技术标签:

【中文标题】从冗长的字典生成时,Pandas DataFrame.from_dict() 性能不佳【英文标题】:Pandas DataFrame.from_dict() poor performance when generating from a lengthy dict of dicts 【发布时间】:2018-09-04 14:25:14 【问题描述】:

在我的 Python 应用程序中,我发现使用字典字典作为构建稀疏 pandas DataFrame 的源数据很方便,然后我用它在 sklearn 中训练模型。

字典的结构是这样的:

data = "X": 'a': 1, 'b': 2, 'c': 3, "Y": 'd': 4, 'e': 5, 'f': 6, "Z": 'g': 7, 'h': 8, 'i': 9

理想情况下,我想把它变成这样的数据框:

df = pandas.DataFrame.from_dict(data, orient="index").fillna(0).astype(int)

生成这个:

e d f a c b i h g X 0 0 0 1 3 2 0 0 0 Y 5 4 6 0 0 0 0 0 0 Z 0 0 0 0 0 0 9 8 7

现在,这是我的问题。我的数据有数十万行(即外部字典中的键数)。这些中的每一个都只有少数与之关联的列(即,每个内部字典中的键数),但列的总数为数千。我发现使用 from_dict 生成 DataFrame 非常慢,对于 200,000 行和 6,000 列大约需要 2.5-3 分钟。

此外,如果行索引是 MultiIndex(即,外部方向的键是元组而不是 X、Y 和 Z),from_dict 甚至更慢,对于 200,000 行大约需要 7 分钟以上.我发现如果使用字典列表而不是字典字典,然后使用 set_index 将 MultiIndex 添加回生成的 DataFrame,则可以避免这种开销。

总之,您建议我如何处理这个问题?库开发人员显然可以提高 MultiIndex 的性能,但是我在这里使用了错误的工具吗?如果写入磁盘,DataFrame 的大小约为 2.5GB。在大约 2 分钟左右从磁盘读取一个 2.5GB 的文件似乎是正确的,但我在内存中的稀疏数据理论上应该可以让这更快。

【问题讨论】:

【参考方案1】:

OP 的答案仍然无法用于非常大的字典(或有更多内存限制)。最好使用 sklearn 的稀疏特性,让生活更轻松:

data = "X": 'a': 1, 'b': 2, 'c': 3, "Y": 'd': 4, 'e': 5, 'f': 6, "Z": 'g': 7, 'h': 8, 'i': 9
vectorizer = sklearn.feature_extraction.DictVectorizer(dtype=numpy.uint8, 
    sparse=True) # <------ Here

row_labels = list(data) 
matrix = vectorizer.fit_transform([data[i] for i in row_labels]) 
column_labels = vectorizer.get_feature_names()

df = pandas.DataFrame.sparse.from_spmatrix(matrix,    # <----- and Here
 index=row_labels, columns=column_labels) 

【讨论】:

【参考方案2】:

事实证明,sklearn 有一个类可以满足我的需要。

sklearn.feature_extraction.DictVectorizer

我将数据生成为字典列表,将行标签放在一边。然后:

vectorizer = sklearn.feature_extraction.DictVectorizer(dtype=numpy.uint8, 
sparse=False)

matrix = vectorizer.fit_transform(data)
column_labels = vectorizer.get_feature_names()

df = pandas.DataFrame(matrix, index=row_labels, columns=column_labels)

一分钟左右就完成了,这对我来说已经足够快了。也许有人可以进一步改进它。

【讨论】:

如果他们有字符串值并且想要维护它们会怎么做【参考方案3】:

我的建议是使用稀疏矩阵并将字母替换为数字(行/列)标识符。

以下是对您的最小示例进行基准测试的示例。

import pandas as pd, numpy as np
from scipy.sparse import coo_matrix

def original(data):
    df = pd.DataFrame.from_dict(data, orient="index").fillna(0).astype(int)
    return df

def jp(data):
    res = (ord(k), ord(i)): j for k, v in data.items() for i, j in v.items()

    n = len(res)

    rows = np.array(pd.factorize(list(zip(*res.keys()))[0])[0])
    cols = np.array(pd.factorize(list(zip(*res.keys()))[1])[0])
    values = np.array(list(res.values()))

    return pd.DataFrame(coo_matrix((values, (rows, cols)),
                        shape=(len(np.unique(rows)), n)).toarray())

%timeit original(data)  # 1.45 ms
%timeit jp(data)        # 488 µs

如果您愿意,可以将您的索引/列重命名为单独的步骤。我还没有对此进行测试,但我的直觉是该方法在这一步中仍然会相当快。

结果

   0  1  2  3  4  5  6  7  8
0  1  2  3  0  0  0  0  0  0
1  0  0  0  4  5  6  0  0  0
2  0  0  0  0  0  0  7  8  9

【讨论】:

这是正确的方向,但我找到了一种更好的方法,可以让我保留列标签。

以上是关于从冗长的字典生成时,Pandas DataFrame.from_dict() 性能不佳的主要内容,如果未能解决你的问题,请参考以下文章

pandas相关操作

pandas一些基本操作(DataFram和Series)_1

Pandas-DataFrame基础知识点总结

数据分析--pandas DataFrame

Pandas:从 dict 在 DataFrame 中创建命名列

从 python pandas 数据框/字典生成表达式