Pandas DataFrame.from_dict() 从冗长的字典生成时性能不佳
Pandas DataFrame.from_dict() poor performance when generating from a lengthy dict of dicts
在我的 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 的文件似乎是正确的,但我的数据在内存中的稀疏性理论上应该允许它更快。
我的建议是使用稀疏矩阵并将字母替换为数字(行/列)标识符。
下面是一个在您的最小示例上进行基准测试的示例。
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
事实证明,sklearn 有一个 class 完全可以满足我的需要。
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)
大约在一分钟内完成,这对我来说已经足够快了。也许有人可以进一步改进它。
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)
在我的 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 的文件似乎是正确的,但我的数据在内存中的稀疏性理论上应该允许它更快。
我的建议是使用稀疏矩阵并将字母替换为数字(行/列)标识符。
下面是一个在您的最小示例上进行基准测试的示例。
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
事实证明,sklearn 有一个 class 完全可以满足我的需要。
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)
大约在一分钟内完成,这对我来说已经足够快了。也许有人可以进一步改进它。
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)