从熊猫数据帧快速更新 django 模型对象



我有一个记录事务的Django模型。我只需要更新某些事务的某些字段(两个(。

为了更新,要求用户提供额外的数据,我使用熊猫来使用这些额外的数据进行计算。

我使用 pandas 脚本的输出来更新原始模型,如下所示:

for i in df.tnsx_uuid:
t = Transactions.objects.get(tnsx_uuid=i)
t.start_bal = df.loc[df.tnsx_uuid==i].start_bal.values[0]
t.end_bal = df.loc[df.tnsx_uuid==i].end_bal.values[0]
t.save()

这是非常缓慢的。最好的方法是什么?

更新: 经过更多的研究,我找到了bulk_update并将代码更改为:

transactions = Transactions.objects.select_for_update()
.filter(tnsx_uuid__in=list(df.tnsx_uuid)).only('start_bal', 'end_bal')
for t in transactions:
i = t.tnsx_uuid
t.start_bal = df.loc[df.tnsx_uuid==i].start_bal.values[0]
t.end_bal = df.loc[df.tnsx_uuid==i].end_bal.values[0]
Transactions.objects.bulk_update(transactions, ['start_bal', 'end_bal'])

这大约使所需时间减少了一半。

如何进一步提高性能?

我一直在寻找这个问题的答案,但没有找到任何权威的惯用解决方案。所以,这是我决定供自己使用的内容:

transaction = Transactions.objects.filter(tnsx_uuid__in=list(df.tnsx_uuid))
# Build a DataFrame of Django model instances
trans_df = pd.DataFrame([{'tnsx_uuid': t.tnsx_uuid, 'object': t} for t in transactions])
# Join the Django instances to the main DataFrame on the index
df = df.join(trans_df.set_index('tnsx_uuid'))
for obj, start_bal, end_bal in zip(df['object'], df['start_bal'], df['end_bal']):
obj.start_bal = start_bal
obj.end_bal = send_bal
Transactions.objects.bulk_update(df['object'], ['start_bal', 'end_bal'])

我不知道DataFrame.loc[]是如何实现的,但如果它需要搜索每次使用的整个数据帧,而不仅仅是进行哈希查找,它可能会很慢。出于这个原因,并且只是通过执行单个迭代循环来简化事情,我将所有模型实例拉入df然后使用 Stackoverflow 答案中的建议遍历数据帧来循环访问感兴趣的压缩列。

我查看了 Django 中 select_for_update 的文档,对我来说它是否提供了性能改进并不明显,但您可能正在使用它来锁定事务并以原子方式进行所有更改。根据文档,bulk_update应该比单独保存每个对象更快。

就我而言,我只更新了 3500 个项目。我对各个步骤做了一些时间安排,并提出了以下内容:

  • 3.05 秒查询和构建数据帧
  • 2.79 毫秒将实例加入df
  • 5.79 毫秒运行for循环并更新实例
  • 1.21 秒bulk_update更改

所以,我认为你需要分析你的代码,看看什么实际上需要时间,但它可能是Django问题而不是Pandas问题。

我有点面临同样的问题(几乎相同数量的记录 3500~(,我想补充一点:

  • bulk_update的性能似乎比 bulk_create,在我的情况下,允许删除对象,所以 我删除了所有对象,然后重新创建它们,而不是bulk_updating。
  • 我使用了与您相同的方法(感谢您的想法(,但进行了一些修改:

a( 我从查询本身创建数据帧:

all_objects_values = all_objects.values('id', 'date', 'amount')
self.df_values = pd.DataFrame.from_records(all_objects_values )

b( 然后我创建对象列而不迭代(我确保这些是有序的(:

self.df_values['object'] = list(all_objects)

c( 为了更新对象值(在我的数据帧中执行操作后(,我迭代行(不确定性能差异(:

for index, row in self.df_values.iterrows():
row['object'].amount= row['amount']

d( 最后,我重新创建所有对象:

MyModel.objects.bulk_create(self.df_values['object'].tolist())

结论:

  • 就我而言,最耗时的是批量更新,因此重新创建对象为我解决了这个问题(从 19 秒的 bulk_update 秒到 delete + bulk_create的 10 秒(
  • 在您的情况下,使用我的方法可能会缩短所有其他操作的时间。

最新更新