【python】pandasで週次集計しようとして発生した3つのエラーと改修方法

【python】pandasで週次集計しようとして発生した3つのエラーと改修方法

「週ごとの集計を行いたい」+「せっかくのPythonだからpandasで処理したい」という事で格闘していました。
pythonでは優秀なpandasのお陰でSQLで大きな処理せずに簡単に素早く処理できるとの事。
それは使わない手はないと実装を試みました。

まず先に、pandasにこだわらない場合

週次集計のやり方は沢山あります。

  1. SQLでゴリゴリ集計する
  2. SQLのテーブル設計から週次集計をしやすい様に作る
  3. 取り出した値をプログラム言語でゴリゴリと集計する

Laravelの時は [2] で実装してました。
ちなみに、1はindexの貼り方次第で速度が大きく変わり、3は一番処理に時間がかかります。
なので、最善策は「集計を意識したテーブル設計」を作成してしまう事です。
具体的には「SQL側のgroup_by を容易にするために【年/月/週】の3つの列を用意してデータ保存時に格納してしまう」などです。

苦戦した3つのエラー

pandasでの実装に戻ります。
今回、具体的には下の3つのエラーに悩まされました。

  • Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex, but got an instance of 'RangeIndex'
  • 'RangeIndex' object is not callable
  • 'method' object is not subscriptable

翻訳すると下の通り。

  • DatetimeIndex、TimedeltaIndex、または PeriodIndex でのみ有効ですが、'RangeIndex' のインスタンスを取得しています
  • RangeIndex' オブジェクトは呼び出し可能ではありません
  • メソッド' オブジェクトは添え字ができません

解決のカギとなるのは、indexの貼り方とDataFrameの呼び出し方です。

解決したスクリプト

#views.py
import pandas as pd

def WheatherIndexView(request):
    """ MYSQLから期間レコードの取り出し """
    data_01 = Weather.objects.filter(observation_date__range=['2020-02-23', '2020-11-29'])

    """ 取り出したレコードの一部だけを配列(List)に格納 """
    list_pandas = []
    for dt in data_01:
        list_pandas.append([dt.observation_date, dt.max_temperature])

    """ リストをDataFrameにしインデックスを付ける """
    df = pd.DataFrame(list_pandas, columns=["date", "temp"])
    df["date"] = pd.to_datetime(df["date"])
    mm = df.set_index(["date"])

    """ 週でまとめる """
    # 例:週ごとに最高温度を合計する
    to = mm.resample("W").sum()
    params = {
        'title': '過去の天気(週次集計)',
        'data': data_01,
        'js': list_plots_01,
        'to': to
    }
    return render(request, 'index.html', params)

重要ポイントはこの中の14~16行目。
比較用に14~16行目の『ダメScript』も記載しておきます。

    df = pd.DataFrame(list_pandas)
    df[0] = pd.to_datetime(df[0])
    df.index([0])

この『ダメScript』を書くと 'RangeIndex' object is not callable と怒られます。

日次集計を行う際の3つのポイント

  1. 日時の入ったカラムを【pandas.to_datetime】でDateTime型に変換する
  2. 日時カラムに【pandas.set_index】でインデックスを貼る
  3. 2の際は【変数名 = 】を付けて行う

これを覚えておきながら、発生したエラーの中身を見ていきます。

Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex, but got an instance of 'RangeIndex'

DatetimeIndex、TimedeltaIndex、または PeriodIndex でのみ有効ですが、'RangeIndex' のインスタンスを取得していますインデックスを何も貼っていない時、または【DateTime型/Timedeltagta型/Period型】の値に対するindexでない時に発生します。
DataFrameでは、インデックスを何も貼っていないと【RangeIndex(範囲index)】として扱われるそうです。

その為、この修正には上記3つのポイントの内、2つの処理が必要になります。

  1. 日時の入ったカラムを【pandas.to_datetime】でDateTime型に変換する
  2. 日時カラムに【pandas.set_index】でインデックスを貼る

【pandas.to_datetime】でDateTime型に変換

私がハマったポイントは思い込みでした。
「MySQLでDatetime.date 型で保存しているから改めて変換しなくても大丈夫」と思っていたんです。

実際にMySQLから抽出した値は[[datetime.date(2020,12 ,06), ...]]の様に出力されていました。
なので 、DataFrameに送った値はDatetime型になっていると思っていたんです。

でも『DataFrameに格納した時にテキストになってしまっている』ので【to_datetime】での変換が必要でした。

'RangeIndex' object is not callable

RangeIndex' オブジェクトは呼び出し可能ではありませんこのエラーはindexが存在しないのに呼び出そうとしているときに発生します。
要はインデックスの付け方が間違えていると。

『DataFrame + index』についてネット上で調べると2つの記載方法を見つけます。

  1. df.index(["date"])
  2. df.set_index(["date"])

ただ、このうちの1番はindexの取り出しであってインデックスを付けているわけではありません。
インデックスの設置は【set_index】で行います。

'method' object is not subscriptable

メソッド' オブジェクトは添え字ができませんこのエラーは【set_index()】の書き方を間違えていた時に発生していました。

df.set_index["date"]

お気づきの通り【 () 】が抜けてます。
その為 ["date"] が set_index の添え字として認識されてしまっていて「その命令の仕方おかしいです」と言われてる状態。
私はこれを「カラム名がついていないから怒られてるのではないか」と誤解してカラム名を付ける方法を探しましたが、そんなことは全くありません。
下のScriptでしっかり機能します。

    df = pd.DataFrame(list_pandas)
    df[0] = pd.to_datetime(df[0])
    mm = df.set_index[0]
    ma = mm.resample("W").sum()

【要注意】インデックスの取り付けはto_dateimeで変換した後に行う事

こうした場合【KeyError 'date'】と言われます。

    df = pd.DataFrame(list_pandas, columns=["date", "temp"])
    mm = df.set_index(["date"])
    mm["date"] = pd.to_datetime(mm["date"])

indexも付けてエラーも発生していないのに集計結果が想定と違う場合

私の場合は日次集計を行う際の3つのポイントの3番目に原因がありました。

  • 2の際は【変数名 = 】を付けて行う

まず悪い例がこちら。このスクリプトはエラーは発生しませんが週次集計を行いません。

    df = pd.DataFrame(list_pandas, columns=["date", "temp"])
    df["date"] = pd.to_datetime(df["date"])
    df.set_index(["date"])
    ma = df.resample("W").sum()

Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex, but got an instance of 'RangeIndex' は発生しないのでインデクスの取り付けは成功しています。
でもなぜか週次集計の命令を無視します。

下の様に【変数名 = 】でしっかり名付けると集計までしっかり回ります。

    df = pd.DataFrame(list_pandas, columns=["date", "temp"])
    df["date"] = pd.to_datetime(df["date"])
    #df.set_index(["date"])
    df = df.set_index(["date"])
    ma = df.resample("W").sum()#ちゃんと名付ける

まとめ

インデックスの正しい付け方は下の通り。

df = df.set_index(["date"])

そして、インデックスを付けない限り日時集計はしてくれません。

\ 最新情報をチェック /

コメントを残す

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