箱ひげ図

箱ひげ図はデータの分布を箱とひげで表したグラフです。データの分布の様子が分かりやすく表示できます。

次のグラフは30秒ごとに測定した1日分の「あるデータ」です。

1日の時系列でのデータの動きが分かりますし、値の範囲も4.07ぐらいから4.54ぐらいの間で、中央値が4.37ぐらいというのも見て取れます。ただし、30秒ごと1日分のデータなので、データ数は2880あります。1週間、あるいは1ヶ月の傾向を見ようとすると、非常に煩雑なグラフになってしまいます。

このデータのヒストグラムを見ると、次のようになります。

値の小さい方から25%のデータが4.07から4.34の間にあり、次の25%が4.34から4.37の間にあります。このようにデータの分布を25%ずつに区切り、箱とひげで表したのが箱ひげ図(Box plot チャート)です。箱ひげ図(左側)とヒストグラム(右側)を対比して表示しました。

ひげの一番下が最小値、箱の中央が中央値、ひげの一番上が最大値になります。箱の下の辺、小さい方から25%のデータが含まれる境界の値を第1四分位数、箱の上の辺の値を第3四分位数といいます。

箱の部分に中央値を挟んで50%のデータがあるので、箱の縦幅が短いほど、データが中央値に集中していることがわかります。また、上のひげが長ければ、上位のデータがまばらに存在していることになります。

1週間分のデータを箱ひげ図で表わしたものが次のグラフです。

1週間あるいは1ヶ月といった長期トレンドを見るには、分かりやすいグラフです。

Ambientでの箱ひげ図

Ambientでは、チャート種類として「箱ひげ図」を選択すると箱ひげ図が表示されます。

データとしてはd1に区間の最小値、d2に第1四分位数、d3に中央値、d4に第3四分位数、d5に最大値を与えます。上の例の1週間分のデータをリスト表示すると次のようなデータになっているのが確認できます。

ちなみに、箱ひげ図のデータにもコメントをつけられます。チャート種類をリスト形式にして、リストのコメント欄にコメントを付加することもできますし、データと一緒に送信することもできます。

データにコメントをつけると箱ひげ図には次のように表示されます。

Ambientでは、登録されたデータから四分位数を計算する機能は提供していませんので、ご自分で計算してください。サンプルとしてAmbientに登録されたデータをチャネルから読み、四分位数を計算して別のチャネルに書き込むプログラムを示します。このプログラムはデータを読み込むチャネル(IDとリードキー)、計算結果を書き込むチャネル(IDとライトキー)、d1からd8いずれかのフィールド名、計算するデータの日付を指定して起動します。1日分のデータを読み込み、四分位数を計算して、指定されたチャネルに書き込みます。

$ python3 quantile.py chIn readKeyIn chOut writeKeyOut field date
# -*- coding: utf-8 -*-
#
# 箱ひげ図を描画するために、最小値、第1四分位数、中央値、第3四分位数、最大値を計算し、
# Ambientに書き戻す
# python3 quantile.py chIn readKeyIn chOut writeKeyOut field date
# YYYY-mm-dd 形式で指定した日のfield(d1〜d8のいずれか)に対して値を計算し、d1〜d5に書く
# {'d1': 最小値, 'd2': 第1四分位数, 'd3': 中央値, 'd4: 第3四分位数, 'd5': 最大値}

import ambient
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import sys

if len(sys.argv) != 7:
    print('\'python3 quantile.py chIn readKeyIn chOut writeKeyOut field date\'')
    sys.exit(1)

chIn = sys.argv[1]
readKeyIn = sys.argv[2]
chOut = sys.argv[3]
writeKeyOut = sys.argv[4]

amIn = ambient.Ambient(chIn, '', readKeyIn)
amOut = ambient.Ambient(chOut, writeKeyOut)

field = sys.argv[5]
if field not in ['d1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8']:
    print('フィールドはd1からd8のいずれか')
    sys.exit(1)

try:
    date = datetime.strptime(sys.argv[6], '%Y-%m-%d')
except ValueError as e:
    print('引数形式エラー: python3 %s chIn readKeyIn chOut writeKeyOut field YYYY-mm-dd' % sys.argv[0]);
    sys.exit(1)

d = amIn.read(date = date.strftime('%Y-%m-%d'))

df = pd.DataFrame(d)
df['created'] = pd.to_datetime(list(df['created'])).tz_convert('Asia/Tokyo')
df = df.set_index('created')

q = df[field].quantile([0, 0.25, 0.5, 0.75, 1.0])

data = {'created': date.strftime('%Y-%m-%d 23:59:59')}
for i, j in enumerate([0, 0.25, 0.5, 0.75, 1.0]):
    data['d' + str(i + 1)] = q[j]

print(data)
r = amOut.send(data)
print(r.status_code)