pandasライブラリで複数のCSVデータをマージし、指定された形式でMatplotlib描画用のDataFrameに加工する方法について解説します。
今回取り上げる題材は自作の健康管理アプリで入力したデータの可視化画像の作成です。 健康管理アプリで入力したデータは可視化用途向けのRaspberryPiサーバーのデータベースに登録したもので、それをCSV出力したファイルを使用します。
| データ種別 | 睡眠管理CSV | 頻尿要因CSV |
|---|---|---|
| 通常データ (2023-01〜2023-04) | sleep_management.csv | nocturia_factors.csv |
| 欠損データ (2023-03) | sleep_management_202303.csv | nocturia_factors_202303.csv |
第1章 DataFrameの基礎
第3章 pandasのデータ構造 【2.6 データのエクスポートとインポート】
第4章 データを組み立てる 【4.4 複数のデータセットをマージする】
第9章 applayによる関数の適用
第11章 日付/時刻データの操作 【11.9 日付によるデータの絞り込み】
Chapter1 Numpyの基礎
Chapter2 Numpy配列を操作する関数を知る: 2.5 最大値、最小値を抜き出す
id,email,name
1,user1@examples.com,テスト 一郎
pid,measurement_day,wakeup_time,sleep_score,sleeping_time,deep_sleeping_time
1,2023-03-01,05:30:00,83,06:00:00,00:50:00
1,2023-03-02,05:30:00,79,06:40:00,00:50:00
1,2023-03-03,05:30:00,93,07:50:00,01:20:00
#...省略...
1,2023-03-21,05:15:00,,05:40:00,
1,2023-03-22,05:30:00,71,05:30:00,00:40:00
#...省略...
1,2023-03-30,05:10:00,90,06:20:00,01:10:00
1,2023-03-31,04:50:00,82,06:00:00,00:40:00
pid,measurement_day,midnight_toilet_visits,has_coffee,has_tea,has_alcohol,has_nutrition_drink,has_sports_drink,has_diuretic,take_medicine,take_bathing,condition_memo
1,2023-03-01,0,t,f,f,f,f,f,f,f,ちょっと風邪気味
1,2023-03-02,3,f,f,f,t,f,f,f,f,
1,2023-03-03,1,t,f,f,f,f,f,f,f,
#...省略...
1,2023-03-21,2,t,f,f,f,f,f,f,t,
1,2023-03-22,2,f,f,f,t,f,f,f,f,
#...省略...
1,2023-03-30,5,f,f,f,t,f,f,f,f,相当つかれていたのでユンケルを飲んだ
1,2023-03-31,1,t,f,f,f,f,f,f,t,
CREATE TABLE bodyhealth.person(
id smallint NOT NULL,
email varchar(50) NOT NULL,
name varchar(24) NOT NULL,
CONSTRAINT pk_person PRIMARY KEY (id)
);
CREATE TABLE bodyhealth.sleep_management(
pid smallint NOT NULL,
measurement_day date NOT NULL,
wakeup_time time without time zone NOT NULL,
sleep_score smallint,
sleeping_time time without time zone NOT NULL,
deep_sleeping_time time without time zone
);
CREATE TABLE bodyhealth.nocturia_factors(
pid smallint NOT NULL,
measurement_day date NOT NULL,
midnight_toilet_visits smallint NOT NULL,
has_coffee boolean,SleepManPlot_3_top.png
has_tea boolean,
has_alcohol boolean,
has_nutrition_drink boolean,
has_sports_drink boolean,
has_diuretic boolean,
take_medicine boolean,
take_bathing boolean,
condition_memo varchar(255)
);
SELECT
to_char(sm.measurement_day,'YYYY-MM-DD') as measurement_day
,to_char(wakeup_time,'HH24:MI:SS') as wakeup_time
,sleep_score
,to_char(sleeping_time, 'HH24:MI:SS') as sleeping_time
,to_char(deep_sleeping_time, 'HH24:MI') as deep_sleeping_time
,midnight_toilet_visits
FROM
bodyhealth.person p
INNER JOIN bodyhealth.sleep_management sm ON p.id = sm.pid
INNER JOIN bodyhealth.nocturia_factors nf ON p.id = nf.pid
WHERE
email=:emailAddress
AND
sm.measurement_day BETWEEN :startDay AND :endDay
AND
sm.measurement_day = nf.measurement_day
ORDER BY sm.measurement_day
def trimSecondsWithTime(strTime: str) -> str:
"""
時刻文字列("%H:%M:%S")から秒部分をトリムする
:param strTime: 時刻文字列("%H:%M:%S") ※欠損値(None)有り
:return: 秒をトリムした時刻文字列
"""
if strTime is None or pd.isna(strTime): # pandasでは nan のチェックが必要
return ""
timeValues: List[str] = strTime.split(":")
return f"{timeValues[0]}:{timeValues[1]}"
def toMinute(strTime: str) -> Optional[int]:
"""
時刻文字列("時:分")を分に変換する
:param strTime: 時刻文字列("時:分") ※欠損値有り(None)
:return: 分(整数), NoneならNone
"""
if strTime is None or pd.isna(strTime): # pandasでは nan のチェックが必要
return None
times: List[str] = strTime.split(":")
return int(times[0]) * 60 + int(times[1])
def minuteToFormatTime(val_minutes: int) -> str:
"""
分を時刻文字列("%H:%M")に変換する
:param val_minutes: 分
:return: 時刻文字列("%H:%M")
"""
return f"{val_minutes // 60:#02d}:{val_minutes % 60:#02d}"
def calcEndOfMonth(str_year_month: str) -> int:
"""
年月(文字列)の末日を計算する
:param str_year_month: 年月(文字列, "-"区切り)
:return: 末日
"""
yearMonths = str_year_month.split("-")
valYear, valMonth = int(yearMonths[0]), int(yearMonths[1])
if valMonth == 12:
valYear += 1
valMonth = 1
else:
valMonth += 1
# 月末日の翌月の1日
valNextYearMonth = date(valYear, valMonth, 1)
# 月末日の計算: 次の月-1日
valLastDayOfMonth = valNextYearMonth - timedelta(days=1)
return valLastDayOfMonth.day
def calcBedTime(strDate: datetime, strWakeupTime: str, strSleepingTime: str
) -> Optional[datetime]:
"""
就寝時刻(前日)を計算する
:param strDate: 測定日付文字列(ISO8601) ※必須
:param strWakeupTime: 起床時刻文字列 ("%H:%M") ※必須
:param strSleepingTime: 睡眠時間 ("%H:%M") ※任意 欠損値 None
:return: (測定日付+起床時刻) - 睡眠時間
"""
if strSleepingTime is None:
return None
wakeupDayTime: datetime = datetime.strptime(f"{strDate} {strWakeupTime}",
FMT_DATETIME)
valMinutes: int = toMinute(strSleepingTime)
return wakeupDayTime - timedelta(minutes=valMinutes)
def makeDateLabel(strIsoDay: str) -> str:
"""
X軸の日付ラベル文字列を生成する\n
[形式] "日 (曜日)"
:param strIsoDay: ISO8601 日付文字列
:return: 日付ラベル文字列
"""
val_date: datetime = datetime.strptime(strIsoDay, FMT_DATE)
weekday_name = JP_WEEK_DAY_NAMES[val_date.weekday()]
return f"{val_date.day} ({weekday_name})"
import argparse
import os
from datetime import date, datetime, timedelta
import pandas as pd
from pandas.core.frame import DataFrame, Series
# ...一部省略...
import util.date_util as du
# ISO8601フォーマット
FMT_DATE: str = '%Y-%m-%d'
# datetime変換フォーマット ※psqlでのCSVエクスポートが秒まで出力
FMT_DATETIME: str = '%Y-%m-%d %H:%M:%S'
# ...一部省略...
# 睡眠管理データ(CSVファイル)で利用するカラム ※"pid"を除く
SLEEP_MAN_COLS: List[str] = [
"measurement_day", "wakeup_time", "sleep_score", "sleeping_time", "deep_sleeping_time"
]
# 頻尿要因データ(CSVファイル)で利用するカラム ※"measurement_day"と"midnight_toilet_visits"
NOCT_FACT_COLS: List[str] = ["measurement_day", "midnight_toilet_visits"]
if __name__ == '__main__':
parser: argparse.ArgumentParser = argparse.ArgumentParser()
# プロット対象年月
parser.add_argument("--year-month", type=str, required=True,
help="年月 (例) 2023-04")
# 睡眠管理CSVデータファイルパス: 年月のみか、複数月
parser.add_argument("--sleep-man", type=str, required=True,
help="datas/csv/sleep_management.csv")
# 頻尿要因CSVデータファイルパス: 年月のみか、複数月 ※件数は一致すること
parser.add_argument("--noct-fact", type=str, required=True,
help="datas/csv/nocturia_factors.csv")
args: argparse.Namespace = parser.parse_args()
path_sleepMan: str = os.path.expanduser(args.sleep_man)
path_noctFact: str = os.path.expanduser(args.noct_fact)
# スクリプト直下の相対ファイルか、ユーザホームのCSV格納ディレクトリのファイル
if not os.path.exists(path_sleepMan) or not os.path.exists(path_noctFact):
app_logger.warning("CSV not found.")
exit(1)
year_month: str = args.year_month
# 指定年月の開始日
start_date: str = f"{year_month}-01"
# 日付文字列チェック
if not du.check_str_date(start_date):
app_logger.warning("Invalid day format!")
exit(1)
# 指定年月の月末日
endDay: int = calcEndOfMonth(year_month)
# 指定年月の終了日
end_date: str = f"{year_month}-{endDay:#02d}"
# 睡眠管理用DataFrame
df_sleepMan: DataFrame = pd.read_csv(
path_sleepMan, header=0,
parse_dates=['measurement_day'], date_format=FMT_DATE,
usecols=SLEEP_MAN_COLS
)
# 頻尿要因用DataFrame
df_noctFact: DataFrame = pd.read_csv(
path_noctFact, header=0,
parse_dates=['measurement_day'], date_format=FMT_DATE,
usecols=NOCT_FACT_COLS
)
# INNER JOIN (1:1)
df_sleepMan = df_sleepMan.set_index('measurement_day').join(
df_noctFact.set_index('measurement_day')
)
days: Series = df_sleepMan.index
bedTimes: List[Optional[datetime]] = [
calcBedTime(
day.strftime(FMT_DATE), wakeup, sleeping) for day, wakeup, sleeping in zip(
days, df_sleepMan['wakeup_time'], df_sleepMan['sleeping_time']
)
])
# 起床時刻("%H:%M:%S"): 秒部分をトリムする ※健康管理Androidアプリで入力した値が"%H:%M"
df_sleepMan['wakeup_time'] = df_sleepMan['wakeup_time'].apply(trimSecondsWithTime)
# 睡眠時間("%H:%M:%S"): 分(整数)に変換
df_sleepMan['sleeping_time'] = df_sleepMan['sleeping_time'].apply(toMinute)
# 深い睡眠("%H:%M:%S"): 分(整数)に変換
df_sleepMan['deep_sleeping_time'] = df_sleepMan['deep_sleeping_time'].apply(toMinute)
org_dataSize: int = df_sleepMan.index.shape[0]
if org_dataSize < endDay:
# 単月データで欠損値有り (測定日未登録)
# https://pandas.pydata.org/docs/reference/api/pandas.date_range.html
df_sleepMan = df_sleepMan.reindex(
pd.date_range(start=start_date, end=end_date, name='measurement_day')
)
else:
# 複数月にまたがるCSV
# 月間データを取り出す
df_sleepMan = df_sleepMan.loc[start_date:end_date]
filtered_dataSize: int = df_sleepMan.shape[0]
# 月間データに欠損データが有る場合は埋める
if filtered_dataSize < endDay:
df_sleepMan = df_sleepMan.reindex(
pd.date_range(start=start_date, end=end_date, name='measurement_day')
)
# ■(1) 深い睡眠データ
deepSleepingSer: Series = df_sleepMan['deep_sleeping_time']
# ■(2) 睡眠時間描画用の差分 ※積み上げ棒グラフの深い睡眠の上にスタック描画
sleepingDiffSer: Series = df_sleepMan['sleeping_time'] - deepSleepingSer
# データ件数(月間: 1〜末日までの日数)
dateRangeSize: int = df_sleepMan.shape[0]
# X軸のインデックス生成 ※月間の日数
xIndexes = np.arange(dateRangeSize)
# Seriesからプロット用ラベルデータを作成する
# メインプロットのX軸ラベル
daySer: Series = df_sleepMamt-2 n.index
# 起床時間の欠損値(測定日なし) NANをプランクを設定
wakeupSer: Series = df_sleepMan['wakeup_time'].fillna("")
# ■(L1) X軸ラベルリスト: "日 (曜日) " + 起床時刻
xLabels: List[str] = [
f"{makeDateLabel(day.strftime(FMT_DATE))} {wakeup}" for day, wakeup in zip(
daySer, wakeupSer
)
]
# ■(L2) Y軸 (0〜12時間) ["00:00","00:30","01:00", ..., "11:30","12:00"]
sleepingTimeYTicks: List = [minuteToFormatTime(x) for x in
range(0, SLEEP_TIME_MAX + 1, 30)]
# 就寝時間: X軸出力用に時刻部分のみ設定する
df_sleepMan['bed_time'] = [bedTm.strftime("%H:%M") for bedTm in bedTimes]
# ■(4) 夜間トイレ回数 (散布図)
toiletVisitsSer: Series = df_sleepMan['midnight_toilet_visits']
# ■(L3) X軸に表示する就寝時間の欠損値は空文字を設定
topXTicks: Series = df_sleepMan['bed_time'].fillna("")
def pixelToInch(width_px: int, height_px: int, density: float) -> Tuple[float, float]:
"""
携帯用の描画領域サイズ(ピクセル)をインチに変換する
:param width_px: 幅(ピクセル)
:param height_px: 高さ(ピクセル)
:param density: 密度
:return: 幅(インチ), 高さ(インチ)
"""
px: float = 1 / rcParams["figure.dpi"]
# ■ 複数の端末で試験した結果から導いた経験式
px = px / (2.0 if density > 2.0 else density)
inch_width = width_px * px
inch_height = height_px * px
return inch_width, inch_height
def drawScoreWithMarker(axes: matplotlib.pyplot.Axes, scoreSer: Series) -> None:
"""
睡眠スコア値出力とマーカー描画
(1)非常に良い (2)良い (3) (1),(2)以外に該当するスコア値とマーカー
:param axes: 描画領域
:param scoreSer: 睡眠スコアSeries(欠損データ[pd.na]有り)
"""
for x_idx, score in enumerate(scoreSer):
if pd.isna(score): # pandasを使う場合 nan のチェックが必要
# 欠損データはスキップ
continue
# マーカースタイル
if score >= 100 * RATE_SCORE_BEST:
scatter_style: Dict = SCATTER_SCORE_BEST_STYLE
elif score >= 100 * RATE_SCORE_GOOD:
scatter_style: Dict = SCATTER_SCORE_GOOD_STYLE
elif score < 100 * RATE_SCORE_BAD:
scatter_style: Dict = SCATTER_SCORE_BAD_STYLE
else:
scatter_style: Dict = SCATTER_SCORE_NORMAL_STYLE
# マーカープロット
axes.scatter(x_idx, score, **scatter_style)
# 睡眠スコアは整数 ※Seriesでは浮動小数点で格納されているため整数に整形
axes.text(x_idx, score + 1, f"{score:.0f}", **PLOT_TEXT_STYLE)
def drawRectBackground(axes: matplotlib.pyplot.Axes,
y_pos_top: float, y_pos_bottom: float,
x_pos_start: float, x_pos_end: float,
facecolor: str, alpha: float = 0.2,
edgecolor: str = 'none') -> None:
"""
睡眠スコアに応じた矩形領域を指定した背景色で描画
:param axes: 描画領域
:param y_pos_top: Y軸上端位置
:param y_pos_bottom: Y軸下端位置
:param x_pos_start: X軸左端位置
:param x_pos_end: X軸右端位置
:param facecolor: 背景色
:param alpha: アルファ値
:param edgecolor: 矩形の線色
"""
rect: Rectangle = Rectangle(
xy=(x_pos_start, y_pos_bottom),
width=(x_pos_end - x_pos_start), height=(y_pos_top - y_pos_bottom),
facecolor=facecolor, edgecolor=edgecolor, alpha=alpha
)
axes.add_patch(rect)
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import rcParams
from matplotlib.axes import Axes
from matplotlib.figure import Figure
from matplotlib.patches import Rectangle
# 棒グラフの幅倍率
BAR_WIDTH: float = 0.7
# 睡眠スコアの最大値
SCORE_MAX: float = 100
# 睡眠スコアステップ
SCORE_STEP: float = 10
# 睡眠時間(単位:分)の最小値
SLEEP_TIME_MIN: int = 0
# 睡眠時間(単位:分)の最大値: 12時間
SLEEP_TIME_MAX: int = 12 * 60
# Y軸(睡眠時間): 30分間隔で水平線を描画
SLEEP_TIME_STEP: int = 30
# X軸のマージン
X_LIM_MARGIN: float = -0.5
# 睡眠スコア(下限): 非常に良い (90〜100)
RATE_SCORE_BEST: float = 0.9
# 睡眠スコア(下限): 良い (80〜89)
RATE_SCORE_GOOD: float = 0.8
# 睡眠スコア(下限): やや低い (60〜79)
# 睡眠スコア(下限): 低い (60未満)
RATE_SCORE_BAD: float = 0.6
# 睡眠スコアの背景色
# 非常に良い (90〜100)
COLOR_SCORE_BEST: str = 'gold'
# 良い (80〜89)
COLOR_SCORE_GOOD: str = 'lime'
# やや低い (60〜79)
COLOR_SCORE_WORNING: str = 'red'
# 低い (60未満)
COLOR_SCORE_BAD: str = 'gray'
# 睡眠スコアが基準値以上の場合に描画するマーカー色
# 非常に良い(90)以上
MARKER_COLOR_SCORE_BEST: str = 'red'
# 良い(80)以上
MARKER_COLOR_SCORE_GOOD: str = 'green'
# 悪い
MARKER_COLOR_SCORE_BAD: str = 'gray'
# 折れ線の色: 睡眠スコア
SCORE_LINE_COLOR: str = 'black'
# 棒グラフの色: 睡眠時間
COLOR_BAR_SLEEPING: str = 'gold'
# 棒グラフの色: 深い睡眠時間
COLOR_BAR_DEEP_SLEEPING: str = 'violet'
# 凡例ラベル
LABEL_SLEEPING: str = '睡眠時間 (時:分)'
LABEL_DEEP_SLEEPING: str = '深い睡眠 (分)'
LABEL_SLEEP_SCORE: str = '睡眠スコア'
# 上端領域Y軸ラベル
TOP_AXES_LABEL: str = '夜間トイレ回数'
TOILET_VISITS_MIN: int = 0
TOILET_VISITS_MAX: int = 6
# スタイル辞書定数定義
# 睡眠スコア折れ線グラフスタイル
SCORE_LINE_STYLE: Dict = {'color': SCORE_LINE_COLOR, 'linewidth': 1.0}
# 睡眠スコア折れ線グラフスタイル
SCORE_TICKS_STYLE: Dict = {'color': SCORE_LINE_COLOR, 'fontsize': 9,
'fontweight': 'demibold'}
# 棒グラフの外郭線スタイル
BAR_LINE_STYLE: Dict = {'edgecolor': 'black', 'linewidth': 0.7}
# X軸のラベル(日+曜日)スタイル
X_TICKS_STYLE: Dict = {'fontsize': 9, 'fontweight': 'heavy', 'rotation': 90}
# 上段: X軸のラベル(起床時間)スタイル
TOP_X_TICKS_STYLE: Dict = {'fontsize': 8, 'fontweight': 'heavy', 'rotation': 90}
# 棒グラフの上部に出力する睡眠時間(時:分)のフォントスタイル
TIME_TICKS_STYLE: Dict = {'fontsize': 9}
# 深い睡眠用(分)スタイル: 赤
BAR_LABEL_STYLE: Dict = {'color': 'red', 'fontsize': 8, 'fontweight': 'heavy'}
# 睡眠時間用(時:分)スタイル: 黒
PLOT_TEXT_STYLE: Dict = {'fontsize': 8, 'fontweight': 'demibold',
'horizontalalignment': 'center', 'verticalalignment': 'bottom'}
# タイトルフォントスタイル
TITLE_FONT_DICT: Dict = {'fontsize': 10, 'fontweight': 'medium'}
# スキャッターマーカースタイル
MARKER_SIZE_WITH_MONTH: float = 9.
# https://matplotlib.org/stable/gallery/shapes_and_collections/scatter.html
# matplotlib.pyplot.scatter
# #sphx-glr-gallery-shapes-and-collections-scatter-py
SCATTER_SCORE_BEST_STYLE: Dict = {
'color': MARKER_COLOR_SCORE_BEST, 's': MARKER_SIZE_WITH_MONTH}
SCATTER_SCORE_GOOD_STYLE: Dict = {
'color': MARKER_COLOR_SCORE_GOOD, 's': MARKER_SIZE_WITH_MONTH}
SCATTER_SCORE_NORMAL_STYLE: Dict = {
'color': SCORE_LINE_COLOR, 's': MARKER_SIZE_WITH_MONTH}
SCATTER_SCORE_BAD_STYLE: Dict = {
'color': MARKER_COLOR_SCORE_BAD, 's': MARKER_SIZE_WITH_MONTH}
# 上段: 夜間トイレ回数マーカースタイル ※一回り小さく
SCATTER_TOILET_VISITS_STYLE: Dict = {'color': 'blue', 's': 8.}
# 描画領域のグリッド線スタイル: Y方向のグリッド線のみ表示
AXES_GRID_STYLE: Dict = {'axis': 'y', 'linestyle': 'dashed', 'linewidth': 0.7,
'alpha': 0.75}
# 上段プロット領域:下段プロット領域比
GRID_SPEC_HEIGHT_RATIO: List[int] = [1, 5]
# 凡例位置 (上端,右側) ※睡眠スコア値が上端にプロットされることはまれのためプロットが隠れることが無い
LEGEND_LOC: str = 'upper right'
# スマートフォンの描画領域サイズ (ピクセル): Google pixel 4a
PHONE_PX_WIDTH: int = 1064
PHONE_PX_HEIGHT: int = 1704
# 同上: 密度
PHONE_DENSITY: float = 2.75
# グラフ出力
# 携帯用の描画領域サイズ(ピクセル)をインチに変換
fig_width_inch, fig_height_inch = pixelToInch(
PHONE_PX_WIDTH, PHONE_PX_HEIGHT, PHONE_DENSITY
)
# 描画領域作成
# (1)上段描画領域: 夜間トイレ回数 (Y軸), 就寝時間 (X軸)
# (2)下段描画領域: 睡眠管理データ
fig: Figure
ax_top: Axes
ax_main: Axes
# GRID_SPEC_HEIGHT_RATIO = [1, 5]
# 上段エリア (補助プロット): 1, 下段エリア (メインプロット): 5
# 上段エリアのX軸に就寝時間を出力するため sharex=False (デフォルト) とする
fig, (ax_top, ax_main) = plt.subplots(
2, 1, gridspec_kw={'height_ratios': GRID_SPEC_HEIGHT_RATIO}, layout='constrained',
figsize=(fig_width_inch, fig_height_inch)
)
# Y方向のグリッド線のみ表示
ax_main.grid(**AXES_GRID_STYLE)
ax_top.grid(**AXES_GRID_STYLE)
# 下段メインプロット領域
# 深い睡眠: 棒グラフ
ax_main.bar(xIndexes, deepSleepingSer, BAR_WIDTH,
color=COLOR_BAR_DEEP_SLEEPING,
label=LABEL_DEEP_SLEEPING, **BAR_LINE_STYLE)
# 睡眠時間 (深い睡眠との差分): 棒グラフ
ax_main.bar(xIndexes, sleepingDiffSer, BAR_WIDTH,
color=COLOR_BAR_SLEEPING,
bottom=deepSleepingSer,
label=LABEL_SLEEPING, **BAR_LINE_STYLE)
# 凡例の位置設定
ax_main.legend(loc=LEGEND_LOC)
ax_main.set_ylabel("睡眠時間")
# y軸ラベル: 睡眠時間 "時:分"
ax_main.set_yticks(np.arange(SLEEP_TIME_MIN, (SLEEP_TIME_MAX + 1), SLEEP_TIME_STEP),
sleepingTimeYTicks,
**TIME_TICKS_STYLE)
ax_main.set_ylim(SLEEP_TIME_MIN, SLEEP_TIME_MAX)
# x軸ラベル
ax_main.set_xticks(xIndexes, xLabels, **X_TICKS_STYLE)
ax_main.set_xlim(X_LIM_MARGIN, dateRangeSize + X_LIM_MARGIN)
# 睡眠スコアを取得: 折れ線グラフ (ラベル軸は右側)
sleepScoreSer: Series = df_sleepMan['sleep_score']
# 右側に軸を作成
ax_main_score = ax_main.twinx()
ax_main_score.set_ylabel(LABEL_SLEEP_SCORE)
ax_main_score.plot(xIndexes, sleepScoreSer, **SCORE_LINE_STYLE)
# 右側y軸ラベル: 100まで表示させるため+1
ax_main_score.set_yticks(np.arange(0, (SCORE_MAX + 1), SCORE_STEP),
np.arange(0, (SCORE_MAX + 1), SCORE_STEP),
**SCORE_TICKS_STYLE)
# 右側Y軸値(0〜100)
ax_main_score.set_ylim(0, SCORE_MAX)
# 睡眠スコアが良い以上の場合はスコア値を表示
drawScoreWithMarker(ax_main_score, sleepScoreSer)
# 睡眠スコア範囲の矩形描画
# 非常に良い
drawRectBackground(ax_main, SLEEP_TIME_MAX,
SLEEP_TIME_MAX * RATE_SCORE_BEST,
X_LIM_MARGIN, dateRangeSize + X_LIM_MARGIN,
facecolor=COLOR_SCORE_BEST)
# 良い
drawRectBackground(ax_main, SLEEP_TIME_MAX * RATE_SCORE_BEST,
SLEEP_TIME_MAX * RATE_SCORE_GOOD,
X_LIM_MARGIN, dateRangeSize + X_LIM_MARGIN,
facecolor=COLOR_SCORE_GOOD)
# やや低い
drawRectBackground(ax_main, SLEEP_TIME_MAX * RATE_SCORE_GOOD,
SLEEP_TIME_MAX * RATE_SCORE_BAD,
X_LIM_MARGIN, dateRangeSize + X_LIM_MARGIN,
facecolor=COLOR_SCORE_WORNING, alpha=0.1)
# 低い
drawRectBackground(ax_main, SLEEP_TIME_MAX * RATE_SCORE_BAD,
SLEEP_TIME_MIN,
X_LIM_MARGIN, dateRangeSize + X_LIM_MARGIN,
facecolor=COLOR_SCORE_BAD, alpha=0.1)
# 上端プロット領域
# タイトル
ax_top.set_title(titleDateRange)
# 夜間トイレ回数 (散布図)
toiletVisitsSer: Series = df_sleepMan['midnight_toilet_visits']
# X軸に表示する就寝時間の欠損値は空文字を設定
topXTicks: Series = df_sleepMan['bed_time'].fillna("")
ax_top.scatter(xIndexes, toiletVisitsSer, **SCATTER_TOILET_VISITS_STYLE)
ax_top.set_ylim(TOILET_VISITS_MIN, TOILET_VISITS_MAX)
ax_top.set_ylabel(TOP_AXES_LABEL)
ax_top.set_yticks(range(TOILET_VISITS_MIN, TOILET_VISITS_MAX + 1))
# 睡眠時間をX軸に表示 ※X軸数はメインプロット領域と同一
ax_top.set_xlim(X_LIM_MARGIN, dateRangeSize + X_LIM_MARGIN)
ax_top.set_xticks(xIndexes, topXTicks, **TOP_X_TICKS_STYLE)
$ . py_venv/py_healthcare_tool/bin/activate
(py_healthcare_tool) $ python PlotSleepManBar2Plot_3_pandas_month.py
--year-month 2023-03
--sleep-man datas/csv/sleep_management_202303.csv
--noct-fact datas/csv/nocturia_factors_202303.csv
INFO Namespace(year_month='2023-03', sleep_man='datas/csv/sleep_management_202303.csv',
noct_fact='datas/csv/nocturia_factors_202303.csv')
INFO (26, 5)
INFO wakeup_time sleep_score sleeping_time deep_sleeping_time midnight_toilet_visits bed_time
measurement_day
2023-03-01 05:30 83.0 360 50.0 0 23:30
2023-03-02 05:30 79.0 400 50.0 3 22:50
2023-03-03 05:30 93.0 470 80.0 1 21:40
2023-03-04 06:15 83.0 420 60.0 2 23:15
2023-03-06 05:30 84.0 430 80.0 4 22:20
2023-03-10 05:30 70.0 400 50.0 1 22:50
2023-03-11 06:10 76.0 300 50.0 2 01:10
2023-03-13 05:30 81.0 410 50.0 2 22:40
2023-03-14 05:30 86.0 410 50.0 1 22:40
2023-03-15 05:30 68.0 320 50.0 4 00:10
2023-03-16 05:30 86.0 390 50.0 1 23:00
2023-03-17 05:30 84.0 380 50.0 1 23:10
2023-03-18 06:20 68.0 380 40.0 5 00:00
2023-03-19 05:30 64.0 320 30.0 2 00:10
2023-03-20 05:20 84.0 380 70.0 2 23:00
2023-03-21 05:15 NaN 340 NaN 2 23:35
2023-03-22 05:30 71.0 330 40.0 2 00:00
2023-03-23 05:00 80.0 360 50.0 2 23:00
2023-03-24 05:30 71.0 370 20.0 1 23:20
2023-03-25 06:15 76.0 410 50.0 3 23:25
2023-03-26 06:15 77.0 430 40.0 2 23:05
2023-03-27 05:00 78.0 480 60.0 4 21:00
2023-03-28 05:00 79.0 370 50.0 2 22:50
2023-03-29 05:00 71.0 300 50.0 2 00:00
2023-03-30 05:10 90.0 380 70.0 5 22:50
2023-03-31 04:50 82.0 360 40.0 1 22:50
INFO DatetimeIndex(['2023-03-01', '2023-03-02', '2023-03-03', '2023-03-04',
'2023-03-06', '2023-03-10', '2023-03-11', '2023-03-13',
'2023-03-14', '2023-03-15', '2023-03-16', '2023-03-17',
'2023-03-18', '2023-03-19', '2023-03-20', '2023-03-21',
'2023-03-22', '2023-03-23', '2023-03-24', '2023-03-25',
'2023-03-26', '2023-03-27', '2023-03-28', '2023-03-29',
'2023-03-30', '2023-03-31'],
dtype='datetime64[ns]', name='measurement_day', freq=None)
INFO org_dataSize:26
INFO 26 < endDay: 31
INFO (31, 6)
INFO wakeup_time sleep_score sleeping_time deep_sleeping_time midnight_toilet_visits bed_time
measurement_day
2023-03-01 05:30 83.0 360.0 50.0 0.0 23:30
2023-03-02 05:30 79.0 400.0 50.0 3.0 22:50
2023-03-03 05:30 93.0 470.0 80.0 1.0 21:40
2023-03-04 06:15 83.0 420.0 60.0 2.0 23:15
2023-03-05 NaN NaN NaN NaN NaN NaN
2023-03-06 05:30 84.0 430.0 80.0 4.0 22:20
2023-03-07 NaN NaN NaN NaN NaN NaN
2023-03-08 NaN NaN NaN NaN NaN NaN
2023-03-09 NaN NaN NaN NaN NaN NaN
2023-03-10 05:30 70.0 400.0 50.0 1.0 22:50
2023-03-11 06:10 76.0 300.0 50.0 2.0 01:10
2023-03-12 NaN NaN NaN NaN NaN NaN
2023-03-13 05:30 81.0 410.0 50.0 2.0 22:40
2023-03-14 05:30 86.0 410.0 50.0 1.0 22:40
2023-03-15 05:30 68.0 320.0 50.0 4.0 00:10
2023-03-16 05:30 86.0 390.0 50.0 1.0 23:00
2023-03-17 05:30 84.0 380.0 50.0 1.0 23:10
2023-03-18 06:20 68.0 380.0 40.0 5.0 00:00
2023-03-19 05:30 64.0 320.0 30.0 2.0 00:10
2023-03-20 05:20 84.0 380.0 70.0 2.0 23:00
2023-03-21 05:15 NaN 340.0 NaN 2.0 23:35
2023-03-22 05:30 71.0 330.0 40.0 2.0 00:00
2023-03-23 05:00 80.0 360.0 50.0 2.0 23:00
2023-03-24 05:30 71.0 370.0 20.0 1.0 23:20
2023-03-25 06:15 76.0 410.0 50.0 3.0 23:25
2023-03-26 06:15 77.0 430.0 40.0 2.0 23:05
2023-03-27 05:00 78.0 480.0 60.0 4.0 21:00
2023-03-28 05:00 79.0 370.0 50.0 2.0 22:50
2023-03-29 05:00 71.0 300.0 50.0 2.0 00:00
2023-03-30 05:10 90.0 380.0 70.0 5.0 22:50
2023-03-31 04:50 82.0 360.0 40.0 1.0 22:50
INFO sleepingDiff:
measurement_day
2023-03-01 310.0
2023-03-02 350.0
2023-03-03 390.0
2023-03-04 360.0
2023-03-05 NaN
2023-03-06 350.0
2023-03-07 NaN
2023-03-08 NaN
2023-03-09 NaN
2023-03-10 350.0
2023-03-11 250.0
2023-03-12 NaN
2023-03-13 360.0
2023-03-14 360.0
2023-03-15 270.0
2023-03-16 340.0
2023-03-17 330.0
2023-03-18 340.0
2023-03-19 290.0
2023-03-20 310.0
2023-03-21 NaN
2023-03-22 290.0
2023-03-23 310.0
2023-03-24 350.0
2023-03-25 360.0
2023-03-26 390.0
2023-03-27 420.0
2023-03-28 320.0
2023-03-29 250.0
2023-03-30 310.0
2023-03-31 320.0
Freq: D, dtype: float64
INFO figure.dpi[px]: 0.01
INFO px[2.75]: 0.005
INFO fig_width_inch: 5.32, fig_height_inch: 8.52
INFO fig: Figure(532x852), ax_top: Axes(0.125,0.763333;0.775x0.116667), ax_main: Axes(0.125,0.11;0.775x0.583333)
INFO screen_shots/PlotSleepManBar2Plot_3_pandas_month.png