Djangoでデータ分析をレベルアップ! `django.db.models.functions.TruncYear` 関数で年単位集計をマスター


django.db.models.functions.TruncYear 関数は、Django クエリ内で日付時刻フィールドを年単位で切り捨て、その年の最初の日の日付を返す関数です。これは、データ分析や可視化において、年ごとの集計や比較を行う際に役立ちます。

構文

from django.db.models.functions import TruncYear

truncated_date = TruncYear(date_field)

ここで、

  • date_field は、切り捨て対象の日付時刻フィールドを指定します。
  • truncated_date は、切り捨て後の日付を格納する変数です。

from django.db.models import F, TruncYear

# orders テーブルの注文日をもとに、各年の注文件数を集計する
from orders.models import Order

order_counts_by_year = Order.objects.annotate(
    year=TruncYear(F('order_date')),
).values('year').annotate(count=Count('id')).order_by('year')

この例では、orders テーブルの order_date フィールドを年単位で切り捨て、各年の注文件数を集計しています。結果は、year フィールドと count フィールドを持つクエリセットとして返されます。

オプション

TruncYear 関数は、オプション引数を使用して、出力フィールドの種類を指定することができます。

  • output_field: 切り捨て後の日付の型を指定します。デフォルトは DateField です。
    • DateField: 年のみを含む日付型
    • DateTimeField: 年と月を含む日付時刻型
from django.db.models.functions import TruncYear

# 切り捨て後の日付を DateTimeField として取得する
truncated_date_with_month = TruncYear(date_field, output_field=models.DateTimeField)
  • 切り捨て対象の日付時刻フィールドが NULL の場合、TruncYear 関数は NULL を返します。
  • TruncYear 関数は、データベースの EXTRACT 関数を使用して実装されています。そのため、使用しているデータベースによっては、サポートされていない場合があります。


from django.db.models import F, TruncYear

from orders.models import Order

# 2023年の注文データのみを取得する
year = 2023
orders_in_2023 = Order.objects.filter(
    year=TruncYear(F('order_date')),
    year__exact=year
)

この例では、TruncYear 関数と year__exact フィルタを組み合わせて、2023年の注文データのみを取得しています。

各年の平均注文金額を計算

from django.db.models import F, TruncYear, Avg

from orders.models import Order

# 各年の平均注文金額を計算する
order_avg_prices_by_year = Order.objects.annotate(
    year=TruncYear(F('order_date')),
).values('year').annotate(avg_price=Avg('price')).order_by('year')

この例では、TruncYear 関数と Avg 集計関数を使用して、各年の平均注文金額を計算しています。

各年の注文件数と売上合計を計算

from django.db.models import F, TruncYear, Count, Sum

from orders.models import Order

# 各年の注文件数と売上合計を計算する
order_stats_by_year = Order.objects.annotate(
    year=TruncYear(F('order_date')),
).values('year').annotate(
    order_count=Count('id'),
    total_sales=Sum('price')
).order_by('year')

この例では、TruncYear 関数と Count および Sum 集計関数を使用して、各年の注文件数と売上合計を計算しています。

特定の月の注文データのみを取得し、各日の注文件数を集計

from django.db.models import F, TruncYear, TruncMonth, Count

from orders.models import Order

# 2023年1月の注文データのみを取得し、各日の注文件数を集計
year = 2023
month = 1
orders_in_2023_jan = Order.objects.filter(
    year=TruncYear(F('order_date')),
    year__exact=year,
    month=TruncMonth(F('order_date')),
    month__exact=month
).annotate(
    day=TruncDay(F('order_date'))
).values('day').annotate(order_count=Count('id')).order_by('day')

この例では、TruncYear 関数と TruncMonth 関数を組み合わせて、2023年1月の注文データのみを取得しています。さらに、TruncDay 関数を使用して各日の注文件数を集計しています。

from django.db.models import F, TruncYear, TruncMonth, Count

from orders.models import Order

# 過去N年間の注文データを月ごとに集計
n_years = 5
current_year = 2024

# 開始年を計算
start_year = current_year - n_years

orders_stats = Order.objects.filter(
    year__gte=start_year,
    year__lte=current_year
).annotate(
    year=TruncYear(F('order_date')),
    month=TruncMonth(F('order_date'))
).values('year', 'month').annotate(order_count=Count('id')).order_by('year', 'month')


代替方法の選択肢

  1. 生のSQLクエリを使用する
  • データベースによっては、EXTRACT 関数など、独自の年切り捨て関数を提供している場合があります。
  • 複雑なクエリや、TruncYear 関数でサポートされていないオプションが必要な場合に有効です。
SELECT EXTRACT(YEAR FROM order_date) AS year, COUNT(*) AS order_count
FROM orders
GROUP BY EXTRACT(YEAR FROM order_date)
ORDER BY year;
  1. カスタム抽出関数を作成する
  • django.db.models.expressions モジュールを使用して、抽出関数を定義することができます。
  • 独自のカスタマイズやロジックが必要な場合に有効です。
from django.db.models import Expression, F, Subquery
from django.db.models.functions import Extract

def custom_trunc_year(date_field):
    year = Extract('year', date_field)
    # ここで、独自のロジックを追加できます。
    # 例:最初の四半期のみを対象とする場合
    if Extract('month', date_field) <= 3:
        return year
    else:
        return None

order_counts_by_custom_year = Order.objects.annotate(
    year=custom_trunc_year(F('order_date')),
).values('year').annotate(count=Count('id')).order_by('year')
  1. ** сторонних ライブラリを使用する**
  • これらのライブラリは、独自の年切り捨て関数を含む場合があり、より柔軟な処理が可能になる可能性があります。
  • pandasSQLAlchemy などのライブラリは、データ分析や操作に役立つ様々な機能を提供しています。
import pandas as pd

# pandas を使用して、注文データを DataFrame に読み込む
orders_df = pd.read_sql_query("SELECT * FROM orders", conn=db_connection)

# 年ごとに注文データをグループ化し、集計する
orders_by_year = orders_df.groupby(pd.Grouper(orders_df['order_date'], freq='Y'))['id'].count()
方法利点欠点具体的な使用例
生のSQLクエリ複雑なクエリや独自オプションに対応可能Django ORM から切り離される高度なデータ分析や、データベース固有の機能が必要な場合
カスタム抽出関数柔軟性とカスタマイズ性が高い複雑なロジックを実装する必要がある独自の処理や、標準機能では実現できない要件がある場合
сторонних ライブラリデータ分析や操作に役立つ追加機能Django ORM との統合が必要になる場合があるデータ分析や可視化を高度にカスタマイズしたい場合