LoginSignup
0
0

More than 1 year has passed since last update.

Django Rest Framework(DRF) + Django Pandas で ピボットテーブル的なデータ変換

Posted at

はじめに

Django Rest Framework(DRF) で、出力レイアウトの変換をDjango Pandasで実現した記事となります。(EXCELで言うところのピボットテーブル)

Pandasのシリアライズ化するのにDjango REST Pandasを使っています。
https://django-rest-pandas.wq.io/
クライアント側はDanfo.jsを使っています。
https://danfo.jsdata.org/

説明

例えば下記のような、預金項目、登録年月、金額のレイアウトの資産テーブル(Tt_Assets)があるとします。

資産ID(PK) 預金項目ID 登録年月 金額
1 1(積立) 2023/01 1,230
2 1(積立) 2023/02 500
3 1(積立) 2023/03 400
4 2(給与) 2023/01 100,000
5 2(給与) 2023/02 200,000
6 2(給与) 2023/03 300,000
7 3(支払) 2023/01 110,000
8 3(支払) 2023/02 210,000
9 3(支払) 2023/03 310,000

下記のレイアウトのように、行を年月単位にして、列を預金項目ID、値を金額にしたい。
(預金項目IDは預金マスタと紐づいているため列数は固定にならない)

登録年月 1(積立) 2(給与) 3(支払)
2023/01 1,230 100,000 110,000
2023/02 500 200,000 210,000
2023/03 100 300,000 310,000

モデル定義

model.py
#預金項目
class Tm_DepositItem(ModelBase):
    class Meta:
        db_table = 'Tm_DepositItem'
        verbose_name = '預金項目(Tm_DepositItem)'
        verbose_name_plural = '預金項目(Tm_DepositItem)'
        unique_together = (
            ('depositItem_name',),
        )
        ordering = ('order_dsp',)

    depositItem_key = models.AutoField(primary_key=True, verbose_name='預金項目ID')
    depositItem_name = models.CharField(max_length=40, verbose_name='預金項目名')
    order_dsp = models.IntegerField(verbose_name='表示順序')
    def __str__(self):
        return self.depositItem_name


#資産トラン
class Tt_Assets(ModelBase):
    class Meta:
        db_table = 'Tt_Assets'
        verbose_name = '資産トラン(Tt_Assets)'
        verbose_name_plural = '資産トラン(Tt_Assets)'

    assets_key = models.AutoField(primary_key=True, verbose_name='資産ID')
    depositItem_key = models.ForeignKey(Tm_DepositItem, on_delete=models.PROTECT, related_name='assets_deposititem_key', verbose_name='預金項目ID')
    deposit_value = models.IntegerField(verbose_name='金額')
    insert_yyyymm = models.CharField(max_length=7, verbose_name='登録年月')

View実装

view.py
from rest_pandas import PandasViewSet, PandasUnstackedSerializer

class AssetsPandasViewSet(PandasViewSet):
    '''
    資産トランのPivotデータ取得
    insert_yyyymm_from, insert_yyyymm_toが設定されている場合は絞込も行う    
    '''
    queryset = Tt_Assets.objects.all()
    serializer_class = Tt_AssetsPandasSerializer
    pandas_serializer_class = PandasUnstackedSerializer

    @log_decorate('AssetsPandasViewSet::get_queryset()')
    def get_queryset(self):
        insert_yyyymm_from = None
        insert_yyyymm_to = None
        if 'insert_yyyymm_from' in self.request.GET:
            insert_yyyymm_from = self.request.GET.get('insert_yyyymm_from')
        if 'insert_yyyymm_to' in self.request.GET:
            insert_yyyymm_to = self.request.GET.get('insert_yyyymm_to')
        records = Tt_Assets.objects.all()
        if insert_yyyymm_from :
            records = records.filter(insert_yyyymm__gte=insert_yyyymm_from)
        if insert_yyyymm_to :
            records = records.filter(insert_yyyymm__lte=insert_yyyymm_to)
        return records

シリアライズ実装

serializers.py
from rest_framework import serializers
from .models import Tt_Assets

class Tt_AssetsPandasSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tt_Assets
        # 利用列
        fields = ['depositItem_key', 'deposit_value', 'insert_yyyymm']
        # 行
        pandas_index = ['insert_yyyymm']
        # 列
        pandas_unstacked_header = ['depositItem_key']

(fields、index, unstacked は、Pandasを理解するとなんとなくわかるのだと思います・・。)

確認

テストプログラムで中身を確認してみます。
パラメータにはJSONで受取るために?format=jsonを指定しています。

test_AssetsPandas.py
class AssetsandasTestCase(TestCase):
  def test_AssetsPandasViewSet(self):
    #
    # テスト対象モジュールを呼出す
    #
    search_dates = ['2022/12', '2023/01', '2023/02', '2023/03', '2023/04', '2023/05', '2023/06']
    TEST_URL = '/api/assets_pandas/?format=json&insert_yyyymm_from='   \
      + search_dates[0] + '&insert_yyyymm_to=' + search_dates[-1]
    #TEST_URL = '/api/assets_pandas/?format=json'
    logger.debug(TEST_URL)
    res = self.client.get(TEST_URL)
    # 確認
    print('res.data.index')
    print(res.data.index)
    print('res.data.columns')
    print(res.data.columns)
    print('res.data')
    print(res.data)

実行すると下記のように出力されます。データ型もPandasとなります。

console.py
res.data.index
RangeIndex(start=0, stop=7, step=1)
res.data.columns
MultiIndex([('insert_yyyymm', ''),
            ('deposit_value',  1),
            ('deposit_value',  2),
            ('deposit_value',  3),
            ('deposit_value',  4),
            ('deposit_value',  5),
            ('deposit_value',  6),
            ('deposit_value',  7),
            ('deposit_value',  8),
            ('deposit_value',  9),
            ('deposit_value', 10)],
           names=['', 'depositItem_key'])
res.data
<class 'pandas.core.frame.DataFrame'>
                insert_yyyymm deposit_value                                     
depositItem_key                           1   2   3   4   5   6   7   8   9   10
0                     2022/12             0  10  20  30  40  50  60  70  80   90
1                     2023/01             2  12  22  32  42  52  62  72  82   92
2                     2023/02             4  14  24  34  44  54  64  74  84   94
3                     2023/03             6  16  26  36  46  56  66  76  86   96
4                     2023/04             8  18  28  38  48  58  68  78  88   98
5                     2023/05            10  20  30  40  50  60  70  80  90  100
6                     2023/06            12  22  32  42  52  62  72  82  92  102


クライアント側

クライアント(React)で受信してみるとこんな感じのデータを受信出来ました。
どうもPandasフォーマットらしいです。まぁこれでもいいっちゃー良いのだけど。

console.log出力結果
スクリーンショット 2023-03-05 151312.jpg

そこでPandasをJavaScriptで利用出来るDanfo.jsというライブラリを使ってみました。
https://danfo.jsdata.org/
JSONフォーマットからDataFrameへの変換が出来ます。
https://danfo.jsdata.org/api-reference/dataframe/dataframe.to_json

記載のある通りにJSに書いてみました

AssetsTable.js
import * as dfd from "danfojs"

console.debug('-*----------apiResult2Rowdata');
//console.debug(resultDatas);
// Response結果をPandasへ
const df = new dfd.DataFrame(resultDatas)
console.debug(df.print())

console.log出力結果をみるとちゃんとPandasとして扱えるのが確認出来ました。
スクリーンショット 2023-03-05 152407.jpg

列情報が、('insert_yyyymm')や('deposit_value')などになっているため多少の加工が必要となりますが
クラアントとしてデータ表示することが出来ました。
スクリーンショット 2023-03-05 153018.jpg

注意事項

Django REST Pandas の列指定は、リレーション先の列情報を指定するのはダメそうでした。

参考サイト

https://django-rest-pandas.wq.io/serializers/PandasUnstackedSerializer
https://danfo.jsdata.org/api-reference/dataframe/dataframe.to_json

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0