【问题标题】:Fast moving average computation with Django ORM使用 Django ORM 进行快速移动平均计算
【发布时间】:2018-07-25 05:04:10
【问题描述】:

我们运行 Postgres 9.6.5 和 Django 2.0。我们有一个Model 字段created_atvalue。我们需要计算某个date_range 的 90 天移动平均线。我们就是这样做的:

output = []

for i in range(len(date_range)):
    output.append(
        Model.objects.filter(
            created_at__date__range=(date_range[i]-timezone.timedelta(days=90), date_range[i]),
        ).aggregate(Avg('value'))['value__avg'].days
    )

这使用Avg 聚合函数,因此速度相当快,但是我们需要对date_range 中的每个日期进行一次查询。对于更长的范围,这意味着很多查询。

Postgres can do this in a single query。我的问题是 - 我们可以使用 Django ORM 在单个查询中以某种方式做到这一点吗?

(我知道我可以使用 Django ORM 执行原始 SQL,但我想尽可能避免这种情况,这就是我问的原因。)

【问题讨论】:

    标签: django postgresql django-models django-orm django-postgresql


    【解决方案1】:

    假设您每个日期有一个条目,您可以使用 Django 2.0 的新窗口表达式在单个查询中计算 90 周期移动平均值:

    from django.db.models import Avg, F, RowRange, Window
    
    items = Model.objects.annotate(
        avg=Window(
            expression=Avg('value'), 
            order_by=F('created_at').asc(), 
            frame=RowRange(start=-90,end=0)
        )
    )
    

    如果您想改为使用特定字段值框住,另请参阅 ValueRange,例如,如果您每天都有多行,这可能会派上用场。

    【讨论】:

      【解决方案2】:

      再试一次。这更高效,因为它只使用一个查询,但从数据库中获取所有必需的模型实例以在 python 中而不是数据库级别中执行逻辑。仍然不是最佳的,但希望这次它做对了;)您必须比较它是否真的在您的情况下提高了性能。

      import numpy as np
      instances =  Model.objects.filter(
              created_at__gte=min(date_range)-timezone.timedelta(days=90),
              created_at__lte=max(date_range)
          ).values('created_at', 'value')
      
      instances = list(instances)  # evaluate QuerySet and hit DB only once
      
      output = []
      for i in range(len(date_range)):    
          output.append(
              np.mean(np.array([inst.value for inst in instances if \
                  inst.created_at >= date_range[i]-timezone.timedelta(days=90) and \
                  inst.created_at <  date_range[i]
              ]))
          )
      

      【讨论】:

        【解决方案3】:

        您可以使用annotation,而不是聚合。在我开始测试时考虑这一点,我不完全确定下面的代码。另请参阅有关 F() 对象的文档

            Model.objects.annotate(
                value_avg=Avg(
                    'value',
                    filter=Q(
                        created_at__date__range=(
                            F('created_at__date')-timezone.timedelta(days=90),
                            F('created_at__date')
                        )
                    )
                )
            )
        

        your_date_field 取决于你

        【讨论】:

        • 谢谢。我看不到您的解决方案中的逻辑。我需要日期范围内每一天的移动平均线。如果我将它用于模型实例,为什么会有帮助?
        • 我认为每个日期都与您的模型相关联。但如果它是独立的,我的方法确实行不通。
        • 是的,但它是一对多的关系。一个日期可以有多个模型实例。很抱歉没有在问题中澄清这一点。
        猜你喜欢
        • 1970-01-01
        • 2018-10-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-18
        相关资源
        最近更新 更多