概要
センサーデータを収集したとき、その分析では全体をプロットして終わりではなく、時間ごと・日ごとなどの時間軸で集計して傾向を把握したいことがあります。集めたデータの1件ごとにタイムスタンプを付与しておけば、もともとのデータが等間隔でなくとも、うまく処理する方法があります。
ここでは、時系列のレコードをいったん解きごたえがあるように加工し、その集計を試みます。
対象とする元データ
Kaggleには様々なデータセットが公開されています。今回は、まさに練習用と銘打たれたデータを見つけたので、これをもとに操作していきます。
(事前の加工)
適当な秒数のインターバル、1年分でかつ、9:00-21:00の範囲にdatetimeを付与しました。
## 加工のコード
def get_interval():
## データ点数を1日12時間x365に分けたとき、点の間の時間幅はおよそ69秒なので、平均69の乱数をとる
return int(np.random.random() * 138)
df = pd.read_csv("train.csv")
start = pd.to_datetime("2025-01-01T09:00")
prev = start
datetimes = [start]
for i in range(1,230090):
next_time = prev + pd.to_timedelta("1sec") * get_interval()
if next_time.hour >= 21:
#21:00すぎたら次の日のデータとする
new_day = pd.to_datetime(next_time.strftime("%Y-%m-%d"))+pd.to_timedelta("1day")
diff = next_time - (pd.to_datetime(next_time.strftime("%Y-%m-%d"))+pd.to_timedelta("21hour"))
next_time = new_day + pd.to_timedelta("9hour") + diff
datetimes.append(next_time)
prev = next_time
df["datetime"] = datetimes
# やけに数値が低いデータが混じっているので、その範囲だけ一律+600する
sub = df.loc[np.logical_and("2025-06-06"<=df["datetime"],df["datetime"]<="2025-07-28"),["datetime","number_sold"]]
low_index = sub.loc[sub.number_sold < 500].index
df.loc[low_index,["number_sold"]] += 600
df.to_csv("modified_data.csv")
Pandasで読み込む
事前加工ですでに使っていますが、今回の集計にはpandasを利用します。pd.to_datetime()
と pd.to_timedelta()
が時系列データの加工に便利です。
df = pd.read_csv("modified_data.csv")
# 扱う対象をシンプルなものに限定、datetimeと数量[number_sold]のみに
df = df.loc[:,["datetime","number_sold"]]
さて、特に指定せずに読み込むと、datetimeの列のdtypeはobjectになっていて、時刻として処理しにくいです。そこで、pd.to_datetime()で変換しておきます。
df["datetime"] = pd.to_datetime(df["datetime"])
dfは次のようになります。
datetime number_sold
0 2025-01-01 09:00:00 801
1 2025-01-01 09:01:01 810
2 2025-01-01 09:01:08 818
3 2025-01-01 09:02:02 796
4 2025-01-01 09:03:05 808
一定間隔の集計
インターバルをランダム化したため、datetimeの間隔はまちまちです。これを分単位に集計してみましょう。
df.resample("1min",on="datetime").sum()
number_sold
datetime
2025-01-01 09:00:00 801
2025-01-01 09:01:00 1628
2025-01-01 09:02:00 796
2025-01-01 09:03:00 1620
2025-01-01 09:04:00 830
... ...
2025-12-31 12:38:00 890
2025-12-31 12:39:00 0
2025-12-31 12:40:00 892
2025-12-31 12:41:00 895
2025-12-31 12:42:00 1811
1分おきに、データを集計できました。.resample()
のパラメータを変えていけば、当然好きな間隔で集計することができます。
例) 1時間なら "1h" 1日なら "1d"など。
一定間隔の集計ができたので、各単位ごとの平均値を集計することは簡単にできます。
minute_average = df.resample("1min",on="datetime").sum().mean()
print("1分あたりの数量1平均:",minute_average)
時間帯ごとの集計
ここで、11時台の平均値を求めることを考えます。1時間あたりの平均は出せても、前項の集計方法では個別の時間帯ごとの値にはなりません。
ここで、datetimeがdatetime形式になっていることが役立ちます。値そのものに.hourで時間を返すパラメータがあります。これを使って、hourを設定し、それをgroupbyで取り出せば、平均値を出せます。
df_hourly = df.resample("1h",on="datetime").sum()
# resampleでdatetimeがindexに移動していることに注意
df_hourly["hour"] = df_hourly.index.hour
df_hourly.groupby("hour").mean().loc[range(9,21)]
この結果は以下の通りです。
number_sold
hour
9 45377.931507
10 45631.841096
11 45625.893151
12 45796.443836
13 45838.162088
14 45467.587912
15 45690.829670
16 45702.994505
17 45344.662088
18 45821.774725
19 45393.612637
20 45623.804945
同様に、1日ごとのデータに直して、各月の1日平均を出すと、
df_daily = df.resample("1d",on="datetime").sum()
df_daily["month"] = df_daily.index.month
df_daily.groupby("month").mean()
number_sold
month
1 544746.290323
2 501459.000000
3 454676.354839
4 480952.233333
5 522399.225806
6 584870.366667
7 598112.129032
8 612879.258065
9 592174.900000
10 567799.451613
11 548664.466667
12 543405.903226
1時間ごとは、今回の加工だと変動が見えませんでしたが、月ごとには変動の様子が見えています。
時間帯別と月別では、単純な集計単位の変更とは違っていたことに注意してください
(時間帯ごとの1時間平均では1時間ごとの合計値を出してから、時間帯別に平均をとりました。一方で、月別平均では1か月おきの合計値にしてしまうと、XX月の平均、とは異なる値になってしまうので、1日ごとの合計値にして、各日付のXX月をとりだし、その月別に平均をとる必要がありました。)
まとめ
1分当たり、時間帯ごと、月ごと、などの集計をするときに、Pandasが便利です。
resampleでデータの粒度と、平均や合計する範囲については、集計軸に応じて考慮が必要です。
今回は以上です。