Django:Views.pyでmodels.modelを使い日付毎の集計を行う方法

Django:Views.pyでmodels.modelを使い日付毎の集計を行う方法

本当はpandasによる集計で完結したかったのですが、グラフを書こうとしたら「そうもいかない事情」が発生。

グラフ描写プラグインに【 [UNIX時, 値] 】の配列で渡さないといけない

せかっくPandasで集計したのでコレを生かしたかったのですが、DataFrameにしたものをまたListに戻すと言うどうも納得できない処理が発生したので「だったらSQL側で抽出しよう」との考えに至りました。

構成の概要

■models.py

from django.db import models
from django.core.validators import RegexValidator

 class Dailies(models.Model):
    """日計基礎DATA"""
    observation_date = models.DateField(verbose_name='観測日')
    client = models.ForeignKey(Clients, verbose_name='クライアント', on_delete=models.PROTECT)
    shop = models.ForeignKey(Shops, verbose_name='店舗', on_delete=models.PROTECT)
    TYPE_CHOICES = (
        (1, '総合'),
        (2, 'PC'),
        (3, 'SP'),
        (4, 'アプリ'),
    )
    types = models.IntegerField(verbose_name='販売メディア', choices=TYPE_CHOICES)
    sales = models.FloatField(verbose_name='売上高(税別)')
    contracts = models.FloatField(verbose_name='売上件数', null=True)
    access_count = models.FloatField(verbose_name='アクセス数', null=True)
    conversion_rate = models.FloatField(verbose_name='転換率', null=True)
    customer_unit_price = models.FloatField(verbose_name='客単価', null=True)
    average_purchases = models.FloatField(verbose_name='平均購入点数', null=True)
    items_ordered = models.FloatField(verbose_name='注文点数', null=True)
    page_views = models.FloatField(verbose_name='ページビュー', null=True)
    staying_time = models.FloatField(verbose_name='平均滞在時間', null=True)
    page_migrations = models.FloatField(verbose_name='平均回遊ページ数', null=True)
    created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)

    class Meta:
        verbose_name_plural = 'Dailies'

        def __str__(self):
            return self.observation_date

■urls.py

from django.urls import path
from . import views

app_name = 'repo'
urlpatterns = [
    path('<int:pk>/', views.DetailView, name="client_show"),
]

こんな感じでmodelsに登録しました。

repo/<int:pk>でクライアント毎の集計データをグラフで表現し最終的に下のようなグラフに描写します。

日間売上集計

Views.pyでmodels.modelクラスを継承してGroup_by & SUMを実行

import logging, datetime
from datetime import date, timedelta
from dateutil.relativedelta import relativedelta
from django.shortcuts import render
from django.views import generic
from .models import Dailies
from django.views.decorators.csrf import csrf_exempt
from django.db.models import Avg, Count, Min, Sum

logger = logging.getLogger(__name__)

@login_required
@csrf_exempt

def DetailView(request, pk):
  (中略)
  
  """データ抽出日時調整"""
  d = datetime.date.today()# 今日
  yd = (d - datetime.timedelta(days=1))# 昨日
  fd = yd.replace(day=1)# 同月1日
  ed = yd.replace(day=calendar.monthrange(d.year, d.month)[1])# 同月末日

  """Models.Modelクラスからクライアントの情報を抽出"""
  d_box = Dailies.objects.filter(observation_date__range=[fd, ed])\
          .filter(types=1)\
          .filter(client_id=pk)

  """salesカラムを日付ごとにsum集計""""
  list_sales = d_box.values('observation_date').order_by('observation_date')\
          .annotate(total_sale=Sum('sales'))

 (後略)

30行目~31行目:日毎sum集計の解説

前段の25行目~27行目でクライアントデータと端末(総合)の抽出が終わっています。

30行目では d_box で抽出した値から日付データを選択し昇順並替えを実行しています。

注目点はこの【 オブジェクト名.values('集計区分')】の場所です。

例えば、ここを [ d_box.values('observation_date','sales') ] とした場合、observation_dateとsalesの2軸でGroup_byが実行されます。

なので、例えば端末ごとの集計を出したい時などは下のようにすればOKです。

list_sales = d_box.values('observation_date').order_by('observation_date','types').annotate(total_sale=Sum('sales'))

31行目はWEB上に誤った情報を多く見かけました。

... annotate(total_sale=Sum('sales')) のSumは【sum】が正しい

sumでも間違いと言う事はできません。ただし、select でSQLを直接呼び出すなど今回とは違うルートで合算する場合です。models.modelからのルートの場合は【 Sum 】が正しいです。

unsupported operand type(s) for +: 'int' and 'str'が発生する

models.modelクラスでsumを使うと文字列は集計できないとエラー発生

これは「modelから送られてくるときに数値をテキスト化しているから」なのだそうです。
※この理由については真実性を確かめる事が出来ませんでしたが、sumとした場合のエラー再現性は100%でした。
上記エラーを回避するために利用するのが【from django.db.models import Avg, Count, Min, Sum】です。
ぱっと見でわかる通り、modelsクラスで平均、カウント、集計などを使える様にします。

models.modelクラスを継承しての集計はこのSumを使うので「sumではないよ、Sumなのよ」となります。

まとめ

分析ページ内に複数のグラフを配置したいのですが、グラフ用の配列を作るのが結構大変です。

「きっと上手くやる方法あるんだろうなぁ」と思いながら取り合えず力業でもがいています。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください