如何高效从Django关联表生成目标结构的Pandas DataFrame?
高效生成订单-商品列表Pandas DataFrame的优化方案
问题背景
需要生成用于规则挖掘的Pandas DataFrame,目标结构如下:
| order | products |
|---|---|
| 1 | ['product1', 'product2'] |
| 2 | ['product1', 'product3', 'product10'] |
| ... | ... |
Django模型定义
class Order(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) product = models.ManyToManyField(Product, through='OrderedProduct', related_name='orders') class OrderedProduct(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE) product = models.ForeignKey(Product, on_delete=models.DO_NOTHING) quantity = models.IntegerField(default=1) class Product(models.Model): name = models.CharField(max_length=255) price = models.DecimalField(max_digits=10, decimal_places=2)
现有实现(大数据集下效率偏低)
orders = OrderedProduct.objects.all().values('order__id', 'product__name') df = pd.DataFrame.from_records(orders) df = df.groupby(['order__id'])['product__name'].apply(list).to_list()
优化建议
1. 利用Django ORM预查询减少数据库交互
从Order模型出发,通过prefetch_related和select_related一次性拉取所有关联数据,避免N+1查询问题:
from django.db.models import Prefetch # 预加载关联的OrderedProduct及对应的Product orders = Order.objects.prefetch_related( Prefetch( 'orderedproduct_set', queryset=OrderedProduct.objects.select_related('product'), to_attr='ordered_products' ) ).all() # 构造DataFrame数据 data = [ {'order': order.id, 'products': [op.product.name for op in order.ordered_products]} for order in orders ] df = pd.DataFrame(data)
这种方式大幅减少数据库查询次数,在数据量较大时能明显提升性能。
2. 数据库层面聚合(PostgreSQL专属)
如果使用PostgreSQL数据库,可直接用ArrayAgg聚合函数在数据库端生成商品列表,把聚合压力转移到数据库:
from django.contrib.postgres.aggregates import ArrayAgg # 直接聚合每个订单的商品名列表 order_product_data = OrderedProduct.objects.values('order__id').annotate( products=ArrayAgg('product__name') ).order_by('order__id') # 转换为目标DataFrame df = pd.DataFrame.from_records(order_product_data).rename(columns={'order__id': 'order'})
此方法无需在Python端做分组处理,效率最高。
3. 优化现有Pandas分组逻辑
若坚持原有查询方式,可优化Pandas的分组操作,避免apply(list)的额外开销:
orders = OrderedProduct.objects.all().values('order__id', 'product__name') df = pd.DataFrame.from_records(orders) # 用agg(list)替代apply(list),直接生成带索引的DataFrame后重置索引并重命名列 df = df.groupby('order__id')['product__name'].agg(list).reset_index(name='products') df.rename(columns={'order__id': 'order'}, inplace=True)
相比原代码,此方式直接生成目标结构的DataFrame,无需额外转换步骤。
内容的提问来源于stack exchange,提问作者Avishka Dambawinna
相关产品推荐
相关产品推荐

