Python大規模開発の鍵!?:最新の型ヒントで実現する型安全なコード

はじめに

はじめまして、テックドクターでエンジニアリングマネージャをしている星野です。

弊社ではPythonを活用することが多く、型ヒントを積極的に導入し、型安全なコードの実現に努めています。

Pythonの型ヒントはPython 3.5(2015年9月リリース)から導入されましたが、その後も継続的に機能追加が行われ、使いやすく進化しています。

本記事では、型ヒントの基本的な説明に加え、最新バージョンでの改善点を紹介します。

型ヒントとは

Pythonは動的型付け言語のため、変数の型を指定する必要がありません。

そのためコードの記述が簡潔になりますが、一方で実行するまで型エラーが検出できず、予期しないバグの原因となることがあります。

大規模開発では、実行するまで動作が保証されないことが大きなリスクとなるため、Python 3.5 から型ヒントが導入されました。型ヒントを活用することで、実行前に型の整合性をチェックできます。

  • 型ヒントを使わない場合の書き方
def join_comma(lst):
  return ",".join(lst)

  • 型ヒントを使う場合の書き方
def join_comma(lst: list[str]) -> str:
  return ",".join(lst)

このように、型ヒントを追加することで、引数や戻り値の型を明示でき、コードの可読性と安全性が向上します。

ただし、Python自体は型ヒントを実行時に考慮しないため、静的型チェックツール(mypy など)を活用する必要があります。

Pythonでの静的型チェックツール

Pythonには標準の型チェック機能がないため、外部ツールを使用して型チェックを行います。

弊社ではGitHub Actions に静的型チェックツールを組み込み、CI/CDの一環として実行することで必ずチェックできるようにしています。

静的型チェックツールとしては以下があります。

  • mypy : 静的型チェックツールのリファレンス実装。歴史が長く、利用者が多い。
  • pyrightMicrosoft製。高速な型チェックが特徴。VS Code拡張機能 Pylance にも組み込まれている。
  • pyre:Meta製。OCaml で実装され、パフォーマンスを重視。
  • pytypeGoogle製。型ヒントがなくても型を推論可能。

弊社では mypy を主に使用していますが、高速な pyright も試してみたいと考えています。

型ヒントの進化

ここからは、Python3.9以降で加えられた改良の中からいくつかピックアップして紹介していきます。

標準コレクション型の記法変更(Python3.9〜)

PEP 585 – Type Hinting Generics In Standard Collections によりtyping.List の代わりに list を直接型ヒントとして使用できるようになりました。

  • Python3.8以前の書き方
from typing import List

def join_comma(lst: List[str]) -> str:
    return ",".join(lst)

  • Python3.9以降の書き方
def join_comma(lst: list[str]) -> str:
    return ",".join(lst)

typing.List は非推奨となったため、新しい記法を使用しましょう。
typing.Listなどの標準コレクションのエイリアスは今後利用頻度が下がるにつれて削除される可能性があります。

Unionの記法(Python3.10〜)

PEP 604 – Allow writing union types as X | Y により、Union の代わりに |(パイプ演算子)を使用できるようになりました。

この導入によって、Noneを取るかもしれない型の表現としては以下の3パターンになっています。

  • typing.Union[int,None]
  • typing.Optional[int]
  • int | None

  • typing.Unionを利用した書き方
from typing import Union

def join_comma(lst: Union[list[str],None]) -> Union[str,None]:
  return ",".join(lst) if lst is not None else None

  • typing.Optionalを利用した書き方
from typing import Optional

def join_comma(lst: Optional[list[str]]) -> Optional[str]:
    return ",".join(lst) if lst is not None else None

  • 「|」を利用した書き方
def join_comma(lst: list[str] | None) -> str | None:
    return ",".join(lst) if lst is not None else None

どの表現が推奨というのはないのですが、チーム内ではどれかに統一されているほうが良いと思います。
個人的には、「|」がシンプルかつ、分かりやすく表現されていると思います。

type文による型エイリアス(Python3.12〜)

PEP 695 – Type Parameter Syntax で型エイリアスを type 文で定義できるようになりました。

エイリアスは、最初は変数に型を代入することで実現していました。
ただし見た目が代入文と同じであり紛らわしかったことから、その後、Python3.10でtyping.TypeAlias が導入され、明示的に型として表すことができるようになりました。
Python3.12で、type文が導入されて、代入ではなく、構文として利用することができるようになりました。

typing.TypeAliasは非推奨となったので、今後はtype文を利用していきましょう。

  • Python3.10以前
from typing import Literal

# 代入で型を実現
Environment = Literal["development", "staging", "production", "test", "local"]

env:Environment = "development"

  • Python3.10 - 3.11
from typing import TypeAlias, Literal

# TypeAliasを使うことで、型であることを明示
Environment: TypeAlias = Literal["development", "staging", "production", "test", "local"]

env:Environment = "development"

  • Python3.12以降
from typing import Literal

# type文で型を定義することが可能に
type Environment = Literal["development", "staging", "production", "test", "local"]

env:Environment = "development"


TypedDictについて

TypedDictはdictに対して、より細かい型を指定することができます。

例えば、TypedDictを使わない場合、名前と年齢を表すようなデータを定義しようとすると以下のようになります。

person1: dict[str, str|int] = { "name": "Yamada", "age": 28 }


名前と年齢とで型が違うため、値に文字列か数値を取るdict型となります。
そのため、nameというキーに対して数値を設定しても型チェックとしてはエラーは出ません。

person1: dict[str, str|int] = { "name": "Yamada", "age": 28 }
person1["name"] = 1 # mypyなどの型チェックでもエラーは出ない


ですが、TypedDictを使うと、以下のように各キーに対して型を定義することができます。

from typing import TypedDict

class Person(TypedDict):
    name: str
    age: int

person1: Person = {"name": "Yamada", "age": 28}
person1["name"] = 1  # mypyでエラーになる


上記コードでmypyを実行すると以下のエラーが出てくれます。

 error: Value of "name" has incompatible type "int"; expected "str"  [typeddict-item]


NotRequiredについて(Python3.11〜)

PEP 655 – Marking individual TypedDict items as required or potentially-missingにより、TypedDictのキーを任意項目として定義できるようになりました。

例えば、以下のコードはgender キーが省略されているので、mypyでエラーが発生してしまいます。

from typing import TypedDict, Literal

class Person(TypedDict):
    name: str
    age: int
    gender: Literal["male", "female", "other"]|None

person1: Person = {"name": "Yamada", "age": 28, "gender": None } # genderキーがあるのでOK
person2: Person = {"name": "Yamada", "age": 28} # genderキーが無いのでエラー


NotRequiredを利用すると利用すると、キーが省略可能になります。

from typing import TypedDict, NotRequired, Literal

class Person(TypedDict):
    name: str
    age: int
    gender: NotRequired[Literal["male", "female", "other"]]

person1: Person = {"name": "Yamada", "age": 28} # genderキーが無くてもOK


また、Required も導入されており、必須項目が少ない場合は total=False と組み合わせると分かりやすくなります。

totalをFalseにすると全てのキーが必須ではなくなるので、Requiredで必須項目を指定する形になります。

from typing import TypedDict, Required, Literal

class Person(TypedDict, total=False):
    name:Required[str]
    age:Required[int]
    gender: Literal["male", "female", "other"]

person1: Person = {"name": "Yamada", "age": 28} # genderキーが無くてもOK
person2: Person = {"name": "Yamada", "gender": "male"} # ageキーが無いのでエラー


Unpackと併用して**kwargs引数に型を追加(Python3.12〜)

PEP 692 – Using TypedDict for more precise **kwargs typingにて、TypedDictとUnpackを利用して**kwargs引数に対してより厳密な型指定が可能になりました。
今までは単一の型しか指定できず、さらに仕様にない引数を指定してもエラーになりませんでした。

  • Unpackを使わない場合
from typing import TypedDict, NotRequired, Literal, Unpack

def print_person(**kwargs: str|int) -> None:
    print(kwargs)
    return

print_person(name="Yamada", age=28)  # OK
print_person(name=1, age=28)         # nameにint入ってしまうがエラーは出ない
print_person(name="Yamada", aeg=28)  # ageを誤字してしまっているがエラーは出ない

  • Unpackを使う場合
from typing import TypedDict, NotRequired, Literal, Unpack

class Person(TypedDict):
    name: str
    age: int

def print_person(**kwargs: Unpack[Person]) -> None:
    print(kwargs)
    return


print_person(name="Yamada", age=28)  # OK
print_person(name=1, age=28)         # nameにintが指定されているのでエラー
print_person(name="Yamada", aeg=28)  # ageを誤字してしまっているのでエラー


読み取り専用のアイテム定義:ReadOnly(Python3.13〜)

PEP 705 – TypedDict: Read-only itemsにより、一部のキーを読み取り専用に設定できるようになりました。

一部のキーだけ値の変更をしたくない場合に利用します。

from typing import TypedDict, ReadOnly, NotRequired, Literal

class Person(TypedDict):
    name:ReadOnly[str]
    age: int
    gender: NotRequired[Literal["male", "female", "other"]]

person1: Person = {"name": "Yamada", "age": 28, "gender": "male"}
person1["age"] = 30  # OK
person1["name"] = "Tanaka"  # nameはReadOnlyなのでエラー

まとめ

バージョンアップする毎に型ヒントが色々と改良されており、自身でもきちんと把握したいと思い今回の記事を執筆しました。

TypedDictについては、今回執筆するにあたって初めて知った機能です。
既存のコードで既にdictを使っている部分に型を導入する際、既存のコードへの変更なしに型安全にすることができるので、試してみたいです。

今回は、Generic関係については触れられていないのでどこかでまとめたいと思います。

朝ダラダラ型?朝・夜元気型?データから見る心拍の5タイプ

こんにちは、データサイエンス部の深見です。
日々ウェアラブルデータ(※)と向き合う仕事をしていると、データに対する勘のようなものがついてきて、ふと「この値にはこんな傾向があるのでは?」とひらめいたりします。

今日はそんなひらめきの中から、心拍のタイプ分けに関するお話です。

また、後半ではタイプ分けに使用したDTW(Dynamic Time Warping:動的時間伸縮法)と呼ばれる手法についても解説します。

ウェアラブルデータ……スマートウォッチを代表とするウェアラブル端末で測定された生体情報等のデータ


イメージ画像

心拍データに見る5つのタイプ

心拍のデータをじっくりと眺めていると、就寝や運動、または食事など、様々な動作・環境に影響を受けている様子を垣間見ることができます。また、ただじっとしている時でも、その日の体調や天候などによって挙動が変わって興味深いものです。

私はテックドクターに入社して以来、数年に渡って自分の心拍データを見てきました。すると、「もしかしたら、人によって心拍の挙動にある程度のパターンがあるのでは?」と思い至るようになったのです。

そこでテックドクター社員の心拍データをクラスタリングしてみたところ、以下の5つのタイプに分類できることがわかりました。(※クラスタ名は私の感性によるものです。他意は全くございません。)

1.就寝時高め型

就寝中の心拍数が日中と同程度に高く、睡眠の質が心配になる。

2.低心拍型

日中・睡眠中を問わず1日を通して心拍の低い状態が続いている。

3.朝ダラダラ型

起床時の心拍上昇が見られず昼頃にかけてのんびり心拍が上がっていく。

4.朝・夜元気型

起床直後と就寝直前に心拍数のピークが見られる元気型。

5.朝活発型

起床時に心拍数のピークがあるものの、その後はずっと波がなく穏やか。

グラフ
図1.各クラスタの代表的な心拍推移

さらに、各クラスタウェアラブルバイスから取得できる心拍以外の情報(活動・睡眠)と照合してみたところ、こんな特徴があることがわかりました。

一覧表
表1.各クラスタの心拍以外の特徴一覧

全体的な傾向として、(因果関係は不明ですが)心拍のタイプと普段の運動・睡眠習慣が関連しているように見受けられます。

個別に見ていくと、「2.低心拍型」は運動・睡眠のバランスが良いことがわかります。おそらく日々きちんと運動していることから、心拍数が全体的に低くなり、身体の調子が整えられていると思われます。

反対に「1.就寝時高め型」の人たちは運動・睡眠両面で改善が必要な方々であることがわかりました。私の推測にはなりますが、睡眠中に心拍が高くなる典型例はお酒ですので、生活習慣を改善する必要があると思われます。睡眠時間や質もイマイチなようです。

起床時に心拍が上がらない「3.朝ダラダラ型」の人たちはあまり運動ができていない傾向がありました。逆に「4.朝・夜元気型」「5.朝活発型」の朝からちゃんと心拍が上がる人たちは、睡眠リズムが整っているようです。

こうして睡眠中の心拍のさまざまなタイプと日常生活の関連が見えてくると、いまいちど自分の生活習慣を見直してみようかなという気持ちになります。ウェアラブルデータを見ていると行動変容を促されるのが面白いところだと思います。

続いて、データサイエンスの知識のある方向けに、この結果に至るまでの分析方法についてもご説明したいと思います。今回はDTW(Dynamic Time Warping:動的時間伸縮法)と呼ばれる手法を使ったところがポイントです。

DTWを使った心拍クラスタリング

調査方法概要

  • 対象とするデータ
    • ウェアラブル端末から取得できる24時間の分単位心拍データ
    • 10名、合計151日分
  • 前処理
    • 秒単位の心拍数を15分ごとの平均値に集約
  • クラスタリング
    • Dynamic Time Warping(動的時間伸縮法:DTW) + k-means法

DTWとは、動的時間伸縮法DTWは時系列データを分析する際に用いられる手法の一つで、ユークリッド距離やマンハッタン距離のようにデータ間の距離を求めることができます。

通常、2つの時系列データが異なる長さや異なる速度で進む場合、それらの類似度(距離)を直接比較することは困難であったり、時に実際よりも過小・過大に評価してしまうことがあります。

DTWではこのような問題を解決するため、データの時間軸を動的に伸縮させて最適な整合性を見つけて類似度を算出します(下図)。例えば、速いペースで歩いている人とゆっくり歩いている人の歩行中の心拍数を比較するとき、心拍の増減のタイミング・長さが両者で変わってきますが、DTWを使うとタイミングのズレが吸収され類似した増減パターンとして認識できます。

増減パターンの例
*出典:「Pattern Matching Trading System Based on the Dynamic Time Warping Algorithm」

心拍数は1日の活動(起床・活動・就寝など)に大きな影響を受けますが、それらのイベントが起こる時刻は人によってもバラバラですし、同じ人でも平日や休日で必ずしも一致するものではありません。DTWを使えばこういったイベントが起きた時刻に影響を受けることなく、イベントが起きた際の心拍の挙動の類似性に基づいた分析が可能となります。

クラスタリングの流れ

  • 一人の1日(00:00~23:59)のデータを1レコードとして計151日分の心拍データをクラスタリング対象とする。
  • 心拍のデータは15分単位で平均化し、1日のレコードを下記のように整形。([時刻, 心拍])
[00:00,  73],
[00:15,  70],
[00:30,  69],
…
[23:30,  58],
[23:45,  56]
  • 15分単位の151日分のデータに対してDTWを適用し、レコードごとの距離を算出。
  • 距離行列に対してk-meansを適用して5クラスタに分類。

クラスタ数の決定

クラスタ数を自動決定するアルゴリズムは、古くはエルボー法など様々なものが提案されています。今回はデータ数もさほど多くなく、かつ全体傾向を把握することが目的であるため、クラスタ数を増やしていってクラスタの代表によく似た傾向のグループが出てきた段階で止めました。

図2はクラスタ数を7とした場合の例です。下から2つ目のcluster_5については一般的な心拍の挙動とはかけ離れたノイズ、cluster_6についてはcluster_2と類似したクラスタでした。こういった結果から今回はクラスタ数を5としています。

グラフ
図2.クラスタ数7の場合のクラスタリング結果

クラスタの構成数

やや「2.低心拍型」が少ないものの、ほぼ均等に分かれました(データ欠損などがみられた7レコードは除外しています)。今回対象としたデータにおいては、各タイプが拮抗して出現している様子がわかります。

クラスタ 構成数 比率
1.就寝時高め型 25 22%
2.低心拍型 20 18%
3.朝ダラダラ型 30 26%
4.朝・夜元気型 34 30%
5.朝活発型 35 31%

表2.クラスタ構成数

個人のデータ分類

できあがったクラスタにおいて、私個人の27日分のデータを確認してみました。以下が分類結果の円グラフです。私の場合は血圧がやや高めなことと、数年前から朝方にシフトして5時起床を心がけていることも相まってか、朝から元気な日や夜に心拍の高い日が多いようでした。逆に朝ダラダラとしている日は少なく、それなりに生活リズムは整っていると感じました。

円グラフ
図3.個人データのクラスタ所属比率

いかがでしたでしょうか。実生活のイベントタイミングを配慮して時系列データ解析をすることで、24時間の心拍の推移にも様々なタイプがあることがわかりました。今回は特別な考慮はせずざっくりと心拍数の推移をクラスタリングしましたが、平日・休日の違いや男女、年代、職業などを考慮することでまだまだ興味深いクラスタが見つかりそうです。

似顔絵
書いた人:深見

プロダクトマネージャーが考える、『データで調子を良くする』ってどういうこと?

こんにちは。テックドクターでプロダクトマネージャーをしている、鹿見と申します。

テックドクターでは、「データで調子を良くする」という言葉を掲げ、医療やヘルスケアの新しい形をつくることを目指しています。

データで”調子”をよくする時代へ
テックドクターWebサイトより

私は現在新規プロダクト開発を担当しており、「データで調子を良くする」ことに役立つプロダクトをつくるために日々試行錯誤しています。

今回の記事では、プロダクト開発にあたって、この「データで調子を良くする」についてどのようなことを考えているか、お伝えしたいと思います。

「調子」とは何か?

そもそも「調子」とは何でしょうか。それを理解するために、「調子」という言葉が私たちの生活のなかで具体的にどのように使用されているか確認してみましょう。

よく身体や心の状態に関して「調子が良い」とか「調子が悪い」とか言いますよね。「調子が良い」というのはどんなときでしょうか。
例えば……

  • いつもより疲れにくく長時間活動できる
  • 日頃悩まされている肩こりを今日はほとんど感じない
  • 気分が晴れやかだ
  • 物事に意欲を持てる
  • 活力があり、仕事や学業に集中できる
  • 持病の症状が落ち着いている

などが挙げられます。
「調子が悪い」の方はどうでしょうか。

  • 体がだるい
  • どこかに痛みがある
  • 持病の症状が悪化している
  • わけもなくイライラする
  • 不安や気分の落ち込みが強い
  • 仕事や学業に集中できない

などが挙げられるでしょう。もちろん、このほかにもたくさんあると思います。

調子が良い人と悪い人のイラスト

こうして並べてみると、私たちが「調子が良い」とか「調子が悪い」とか言うとき、そこで指されている対象はつねに同じものとは限らないように思えます。例えば、「調子が悪い」という際に、ある時は「体がだるい」ことを言い、またある時は「わけもなくイライラする」ことを言う。あるいは、同時に発生している「体のだるさ」「イライラ」「不安」「気分の落ち込み」がごちゃ混ぜになって、それを一言で「調子が悪い」と言うようなことも多いでしょう。

このように考えると、「調子」は様々な要素によって構成されていて、またその言葉によって意味されている内容がその都度変わるような、複合的かつ曖昧な概念であるように思えます。

にもかかわらず、私たちは「調子が良い」「調子が悪い」と感じたり言ったりし、しかもそれが意味として伝わります。けれども、その中身は複合的かつ曖昧なので、私たちは「調子」をどのように扱えばよいかが、実はよくわからないのではないでしょうか。

「調子を良くする」のは一筋縄ではいかない

それをふまえて、「調子を良くする」についても考えてみましょう。

これを読んでくださっているあなたは、ふだん「調子を良くする」ために意識していることはありますか?あるとしたら、どんなことでしょうか?
運動、睡眠、食事、マッサージや整体、入浴、趣味、仕事ややりたいことへの没頭、ストレスへの対処、人とのコミュニケーション、病気の予防、などといったことが挙げられるかもしれません。その内容はさまざまでしょう。
それによって調子が良くなっているか、というと、どうでしょうか。常にそうではなく、日による、という方も多いのではないでしょうか。

「調子」という概念自体が複合的かつ曖昧なものですから、「調子を良くする」方法も唯一の正解といったものはないように思います。

デジタルバイオマーカーによって「調子を良くする」

このように単純には捉えにくい「調子」というものに対して、データでアプローチすることが有効なのではないか、と私たちは考えています。

データと一口に言ってもいろいろですが、とりわけデジタルバイオマーカーと呼ばれるものが「調子を良くする」ことに大きく貢献できるのではないかと考えています。
デジタルバイオマーカーとは、スマートウォッチなどのデジタルデバイスで取得したデータを用いて、身体の状態を表す指標のことを指します。

イメージイラスト
デジタルバイオマーカーのイメージ

身体の状態のデータというと、体温計や体重計などの測定機器で測ったり、病院などで専用の検査を受けたりといったことがイメージされるかもしれません。そうした方法で得られるデータは、その時点での身体の状態を表すいわば「点のデータ」です。一方、近年ではウェアラブルバイスなどを利用することで、継続的かつ長期的な「線のデータ」をより簡単に取得できるようになりました。

私たちの身体は、様々な観点で、何かしらの「状態」を持っています。例えば、体温が何度であるとか、汗をどれくらいかいているとか、エネルギーをどれくらい消費しているとか。そしてそれらは、何かしらのサインであると見ることもできます(体温、心拍、声の抑揚、顔色などは、その人の体調や精神状態をある程度反映していると言えます)。

これら全てを継続的に測定しつづけることは難しいです。しかし、現在ウェアラブルバイス等で記録可能なもの、例えば心拍のデータを取得するだけでも、その変動を通して身体の状態がより深くわかります。このようなデータからは、疲労やストレスといったサインが読み取れることもあり、私たちはその可能性を広げるべく様々な研究や取り組みをしています。

「調子」をデータによって明らかにする

どのようにしてデジタルバイオマーカーが「調子を良くする」ことに役立つのでしょうか。

私たちの「調子」を構成する要素は様々です。デジタルバイオマーカーは、ひとつには、「調子」を構成するこれらの要素のいくつかを客観的な数値に置き換えることによって、「調子」をより解像度高く捉えるための手助けとなるでしょう。
また、これらの諸要素のあいだの関係を明らかにすることも、データに可能なことのひとつです。「調子」を構成するのは身体的なものも精神的なものもありますが、これらは必ずしも明確に分けられるものではなく、また相互に関係し合ってもいます。

例えば、運動すると爽やかな気分になったり、自然豊かな場所で過ごすと穏やかな気分になったりしますよね。睡眠不足や運動不足、栄養不足だと気分が落ち込んできたりイライラが強くなったりするのも自然なことです。これらは、あえて分けるとするなら、身体が心に影響を与えていると言えるでしょう。
一方、緊張で心拍数が上がることや、ストレスで胃が痛くなること、反対に、喜びや興奮で痛みや疲れが吹き飛ぶといったこともあるでしょう。これらは、心が身体に影響を与えていると言えます。
様々な要素とそれらの複雑な相互作用が、私たち一人ひとりの「調子」を形づくっています。データがこれを明らかにすることが、「調子を良くする」ことにつながるはずです。

患者の調子と心拍数の関係を表現したグラフ
デジタルバイオマーカーを使った適応障害のサポート事例

あなたの「調子を良くする」を支える伴走者として

さらに、これらのデータは、活かし方によっては、調子の維持を助ける伴走者としても人をサポートできると考えています。

「調子を良くする」というと、自分一人でやること、というふうにイメージされるかもしれません。自分で自分の調子がどんな状態か気にしたり、調子を整えるために行動したりするなどです。自分で自分の調子を整えることは「セルフケア」と呼ばれます。

ですが、必ずしもすべて自分だけでやる必要はありません。誰かの助けを借りてもいいはずです。実際、家族と互いを気づかい合うこともあるでしょうし、医師や看護師、心理師(士)、理学療法士などといった専門家に伴走してもらう形でサポートを受けることもあるかと思います。
データは、このような家族や専門家といった伴走者とあなたをつなげることもできますし、またはデータ自体があなたを助ける伴走者として機能することもありうると考えています。

おわりに

今回は、「データで調子をよくする」について、私なりの考えをご紹介しました。
どんな考えのもとプロダクト作りに取り組んでいるのか、それによってどんなふうに価値を生み出しうるのか、その可能性の一端が伝われば幸いです。

未来の「調子」の維持は、今とは異なるやり方が当たり前になっているかもしれません。私たちは、その未来の当たり前をつくることを目指しています。
仲間を募集しています。ぜひ一緒に、人を助ける、おもしろいプロダクトづくりにチャレンジしましょう。

テックドクター 採用情報

書いた人
似顔絵
鹿見

楽しみながら健康チャレンジ!データと健康のつながりを体感する社内イベント「ウェルリンピック」とは

今回は、テックドクターで開催した社内イベント「ウェルリンピック」(Well-lympics)をご紹介します。

ウェルリンピックは、参加者が種目ごとに分かれて競い合ったり協力したりしながら、生活改善をするイベントです。

参加メンバーは「睡眠」「リラックス」「消費カロリー」の3つの種目からひとつを選び、それぞれスマートウォッチのFitbitを着用してウェアラブルデータ(※)を記録。競技ごとの基準に沿ってデータの分析を行いながら、開催期間の2週間の間に生活改善ナンバーワンを目指します。

ウェアラブルデータ……スマートウォッチを代表とするウェアラブル端末で測定された生体情報等のデータ

開催の目的

スライド資料
使用したスライド資料より

ウェルリンピックの目的はいくつかありますが、ここでは2つを紹介します。ひとつは社員が楽しみながら健康を推進すること。もうひとつは、私たちデータサイエンスチームの、健康に関するデータを扱う仕事に関するものです。

私たちは、日々ウェアラブルバイスから得られるデータを分析しています。そこで心がけているのが、「目の前の数値がすべてだと考えないこと」です。数字の向こうには身体の変化・身体の状態があるからです。

日中の歩数が減少したり、横になっている時間が増えたりする背景には、疾患や体調不良が存在します。数字の変化を通して患者様の状態を想像することこそが、分析の出発点になると私たちは考えています。

そう考えたとき、最も身近で理解しやすいデータは何でしょうか。それは、自分自身の身体から得られる情報です。

そこで弊社では、自分自身や身近な人の状態とデータを結びつけて考える習慣をつける目的で、社内イベント「ウェルリンピック」を開催しました。

取り組み紹介

今回のウェルリンピックでは、「睡眠」「リラックス」「消費カロリー」の3つの種目を実施しました。

スライド資料
使用したスライド資料より

ここからは種目ごとに、どんな取り組みをしたかを紹介していきます。

「睡眠」:改善のトップを狙おう!

睡眠種目では、「睡眠の質」改善を競い合いました。

レギュレーション

厚生労働省が作成した「成人のためのGood Sleepガイド」によれば、良い睡眠には「量(時間)」と「質(休養感)」が重要とされています。今回は生活リズムを大きく変える「量」ではなく、「質」に重点を置きました。

評価基準として、睡眠効率(ベッドにいる時間の何%の間、睡眠できているか)を採用しました。自分の2週間前の平均睡眠効率を基準として、期間中毎日の改善ポイント(%)の累積が得点になります。

上位3名には睡眠にまつわるプレゼントを用意し、楽しみながら取り組める仕組みを整えました。

期間中の過ごし方

メンバーそれぞれ工夫を凝らし、就寝前のスマートフォン使用を控えたり、ヨガや瞑想を取り入れたりと、生活に取り入れやすい改善活動を行いました。

毎日、各自の睡眠効率を共有し、睡眠の質に影響を与える要因を考察。それを互いに共有することで、自分の行動を振り返る機会としました。

さらに、改善累積ポイントのランキングも公開。楽しみながら競争心も刺激し、参加者全員が意欲的に取り組める環境を整えました。

結果

最もうまくいったメンバーでは、11日間で累積27.2ポイントの改善が見られました!

特に効果が実感された取り組みとしては、

  • 就寝前のTVドラマ視聴を控えること
  • 熱帯夜対策(室温調整など)

が挙げられます。

就寝前の行動が目を冴えさせ、入眠を遅らせ、睡眠効率を低下させることが考えられ、こうした行動を控えることが効果的である示唆が得られました。また、夏の開催という背景もあり、室温対策や寝室環境の整備が夜中の中途覚醒を減らす効果に寄与した可能性があります。

一方で、ライフイベントや騒音といった自身ではコントロールできない外的要因により、改善が難しかったメンバーもいました。
しかし、全員が睡眠の質向上に前向きに取り組み、改善意識を高められたことは、本イベントの成果と言えます。

グラフ
栄えある金メダリスト、累積改善ポイントが最も高かったメンバーのデータ。「就寝直前のネットフリックスを止めるのが効果的だった」とのことです
ホワイトノイズマシン
一位のメンバーには商品として安眠グッズ(ホワイトノイズマシン)が贈呈されました。(写真はAmazon商品ページより)

「リラックス」:リラックス度の自己ベストに挑む

リラックス種目は、リラックス度の自己ベストを更新することが目標です。
各社員が自分がリラックスできる活動(リラックス施策)を考え、生活に取り入れました。

レギュレーション

リラックス施策は毎日おなじ時間帯・所要時間で続けられる15分程度の内容で、途中で変更することも可能とし、リラックスの対象時間帯は6:00から23:45の間で行いました。

評価方法は、毎日の時間ごとのストレス度の変化を計算し、ストレス度が減少するほどリラックスできたと評価しました。
運動中のデータはストレス度の測定に影響するため除外し、対象時間帯のうち安静時心拍数の範囲内のデータを選択しました。
また、各参加者の過去のデータと照らし合わせて正規化処理をすることで個人ごとのばらつきを抑え、ストレス度の変化をわかりやすくしました。

期間中の過ごし方

毎日、前日分のストレス度の推移のグラフを作成しました。
下の図のように最もストレスが高かった時間帯3つ(Top3)と、ストレスが低かった時間帯3つ(Low3)を示すことで、各社員にどのような行動がストレス値の高低に繋がっていたかメモしてもらいます。

効果のあるリラックス施策は続け、効果がなさそうな場合は変えるなど、ベストなリラックス施策を模索してもらいました。

グラフ
ストレス度の推移グラフの例
結果

リラックス施策を行うことでストレス度が下がり効果を実感できた社員と、そうでない社員がいました。

ストレス度が下がった施策の例としては、

  • 好きな飲み物を飲みながらドラマを見る
  • 静かに音楽を聞く
  • 瞑想

などがあり、これらの施策はリラックスに効果的なのではないかと考えられました。

また、リラックス施策を行った時間以外でストレス値が下がっていた例として、

  • 自宅やオフィスに着く
  • 会議が終わる
  • 仕事が終わる
  • 家族のお手製のパンを食べる

などがありました。

逆に今回あまり効果的でなかったリラックス施策としては、

  • 音楽を聴く
  • 読書

などがありました。

データを可視化することで、普段自分が意識していない「リラックスしている瞬間」を実感することができました。

バスボム
リラックスアイテムの1つとしてみんなで購入したバスボム

「消費カロリー」: メンバー全体で目標達成へ

消費カロリー種目は、参加者全員が一つのチームになる協力種目です。「消費カロリー合計で目標をクリアする」ことが目標です。

レギュレーション

参加者は自分が継続可能な運動を選び、2週間にわたり実践。国が推奨する1日の運動量である300kcal(1日1万歩相当)の消費を、ひとりあたりの1日の目標に設定しました。

全体としては、参加者10人が11日間(検証期間)取り組み、合計33000kcalを消費するのが目標です。

※Fitbitから得られるデータは1日の合計消費カロリーのため、取り組みを始める前に、参加者それぞれに身長・体重・年齢から基礎代謝を算出してもらいました。1日の合計消費カロリーからその基礎代謝を差し引いたものを運動による消費カロリーとして定義しました。

期間中の過ごし方

ウォーキングやジョギングをはじめ、ジムでの筋トレなど、楽しみながら継続できる運動が多く取り入れられました。
また参加者同士で進捗を共有し励まし合うことで、自然とチームの団結力が高まりました。

2日に1回ほど、参加者ごとの消費カロリーを共有し、「〇〇さんの消費カロリーが高いけどどんな運動をしているのか?」などと他メンバーの運動内容を参考にしたり、「この日は天気が良かったからたくさん歩いた」「調子が悪かったから無理せず休んだ」といった進捗の振り返りも行いました。

スマホのヘルストラッカーの画面キャプチャ
自分の運動状況を送り合うなど楽しく進めました
結果

合計33000kcalの消費を目指しましたが、最終的には目標を大きく上回り、90000kcal近い消費カロリーを記録しました!

期間の後半には全体的に消費カロリーが減少傾向になりましたが、最終日には参加者全員が盛り返し、最終的にどのメンバーも1日平均300kcalを上回る消費を達成。

定期的なフィードバックとチームメンバー同士の励まし合いが功を奏し、期間中には「これまでより運動量が増えた」「日常的に身体を動かす習慣がついた」との声が挙がりました。

チーム全体で協力しての取り組みが、モチベーション向上や健康習慣の定着に大きく貢献したのではないかと思います。

消費カロリーが多かったメンバーにはジムに行っている人が多く、活動内容としては筋トレや有酸素運動で高い心拍数の状態をキープするのが良さそうという意見が出ました。

グラフ
日ごとの累計運動消費カロリーの推移(赤が目標、青が測定値)
グラフ
メンバーごとの日ごとの運動消費カロリー

※消費カロリーはFitbitの合計消費カロリーから基礎代謝を差し引くことで算出しましたが、Fitbitの詳細な仕様が不明なため、実際の消費カロリーより多めの値が出ているかもしれません。もしかしたら、平均心拍数が高めの参加者では、消費カロリーが高めに算出される可能性も考えられます。

得られた気づきと次へのステップ

今回の「ウェルリンピック」を通じて、社員の健康意識とデータへの理解が一層深まる結果となりました。

参加者からはこんな声がありました。

  • ウェアラブルデータの記録の重要性があらためてわかった。
  • データをちゃんと見て、改善する動きが取れて良かった。
  • 自分のデータをよく見たことで、データと体調の関係をもっと知りたくなった
  • 健康について他の社員と話し、コミュニケーションを取る機会が増えた。

総じて、データを通じて自身の体調や生活習慣を振り返る良いきっかけとなったようです。また、「健康とデータの橋渡し」というイベントの目的も少なからず達成できたと感じます。

ウェルリンピックは継続的に開催していきたいと思います。

今後は、現在の年1回から数ヶ月に1回など開催頻度を増やして継続的な健康促進を目指したり、期間も今回の2週間よりも長くする、また他の種目に参加中のメンバーとの交流を増やしたりすることで、より多くの学びや発見が期待できそうです。
さらに深いインサイトを得られるよう、一層工夫を凝らしていきたいと思います。

まとめ

データ分析を通してデータと健康を結びつける試みとして、社員一人ひとりの意識醸成とスキル向上につながった今回のウェルリンピック。あらためて「データは体調のサインを示す」ということを、私たちに実感させてくれました。

データが示す「体調のサイン」とその背景を一つ一つ解き明かすことは、体調不良や疾患への理解を深め、新たなdBM(デジタルバイオマーカー)の開発につながります。

※デジタルバイオマーカー……デジタルデバイスで測定した『日常データ』をもとにした、病気の早期発見や治療につながる客観的指標

そして何より、悩みを抱える多くの方々の助けになることを願い、私たちは日々研究・開発に取り組んでいます。
ウェルリンピックは、そんな私たちにとって健康データ分析の視野を広げる第一歩となったのではないかと思います。

似顔絵
書いた人:瀬川

PyCon JP 2024参加レポート&良かったセッション紹介

はじめまして、バックエンドエンジニアの伊藤です。

9月に開催されたPython のカンファレンスイベント「PyCon」に、テックドクターのバックエンドエンジニア3人で参加してきました。

今回はその体験記として、イベントの様子や気になった発表などをレポートします。

PyConとは

PyConについて、公式サイトではこのように説明されています。

PyCon JP は、Python ユーザが集まり、PythonPython を使ったソフトウェアについて情報交換、交流をするためのカンファレンスです。 PyCon JP の開催を通じて、Python の使い手が一堂に集まり、Python にまつわる様々な分野の知識や情報を交換し、新たな友達やコミュニティとのつながり、仕事やビジネスチャンスを増やせる場所とすることが目標です。

PyCon JP 2024 より引用

ひとことで言えば、Pythonにまつわる情報を共有することがメインのイベントです。具体的には大きく2つの要素で構成されます。セッションとスポンサーブースです。

セッション

Pythonユーザーによるスライド発表です。Pythonにまつわるノウハウや活用事例、課題解決や関連ツールの紹介など、多岐にわたる発表があります。

1つのセッションは時間にして30分ほど。登壇者はセッションは4つの会場で並行して行われるため、効率的に情報収集するためには事前にタイムテーブルを見ておき、会場を移動しながら興味のある発表を聞くのがおすすめです。発表資料の一部がこちらに掲載されていますので、今回参加できなかった方もどんな内容か知ることができます。

スポンサーブース

スポンサーブースは、PyConに出資したスポンサー企業が自分たちの魅力を紹介するためのブースです。こちらはセッションとは違い常設ブースとなっており、PyCon参加者は誰でも立ち入ることができます。ノベルティを配っている企業も多く、お祭りの屋台のような形で楽しめました。

ブースの写真
Forkwellさんの「恐怖の鳥釣り」ブース。Pythonにちなんで、ヘビの釣り竿でアヒルちゃんを釣るというゲームをやっていました。
ブースの写真
KRAKENさんのブース。アンケート回答でかわいいクラーケンの指人形がもらえました。
ブースの写真
GlobalWayさんのブース。トートバッグ等を配布されていました。
ブースの写真
Findyさんのブース。モバイルバッテリーやお守りが当たるガチャくじを実施されていました。
ノベルティの写真
たくさんのノベルティをいただきました!

PyConに参加した目的

今回は個人的な参加ではなく、テックドクターの社員として、2つの目的でPyConに参加しました。

ひとつはほかの参加者の皆さんと同じく、Pythonについての理解を深めることです。

もうひとつは、スポンサー協賛の検討です。テックドクターでは日頃からお世話になっているPythonのコミュニティ発展に貢献するため、PyConへのスポンサー協賛を検討しています。PyCon自体の雰囲気を知ることと、スポンサーになるとどのようなことが行えるのかなど検討材料の収集を行いました。

良かったセッション

テックドクターからは私(伊藤)以外に、星野、魚木が参加しました。二人とも私と同じバックエンドエンジニアですが、それぞれPythonの経験年数は違います。またPyConは3人とも初参加でした。ここでは今回は私と星野が選んだ、良かったセッションをご紹介します。

伊藤(Python経験 7年)の印象に残ったセッション

「MLOps in Mercari Group’s Trust and Safety ML Team」

発表者等: Calvin Janitra Halim, Ayato Toyokuni(株式会社メルカリ)

メルカリさんのMLOpsのアーキテクチャについての発表です。
最近テックドクターでも機械学習モデルの本番利用に向けたシステムの構築を行っており、試行錯誤しながら進めている段階のため、非常に参考になるセッションでした。

アーキテクチャ図がふんだんに用いられていることで、複雑な構成も視覚的にも論理構造的にもわかりやすく、参考にしやすいと感じました。また英語での発表だったものの、図のおかげで理解しやすかったです。

弊社のアーキテクチャはよりシンプルな作りでも問題ないはずですが、先人の事例と比較することで方向性が大きく間違っていないことを確認できたのは、大きな収穫でした。(伊藤)

星野(Python経験 3年、Pythonでの大規模開発経験なし)の印象に残ったセッション

「あなたのアプリケーションをレガシーコードにしないための実践Pytest入門」

発表者:fujine (みずほリサーチ&テクノロジーズ株式会社 先端技術研究部)

Pythonに限らずテスト全般の内容が幅広く含まれた発表でした。テストをあまり書いたことがない人でもわかりやすかったのではないかと思います。

導入部の切り口も面白く、「レガシーコードとは」から始まる冒頭にまず興味を惹かれました。その後、データベースや時刻処理などのありがちなテストパターンにどう対処するかについて詳しく解説され、実践的なケースに対する理解が深まりました。

また、Pytestの小技も紹介され、今後テストを作成する際に参考になりそうな内容でした。Pytest初心者の私にとっては、今後使うであろうトピックスがまとまっており学びが多かったです。(星野)

全体の感想

個人としてもテックドクターとしても初のPyCon JPへの参加でしたが、まずは参加すること自体が楽しいイベントだと思いました。星野からも「久しぶりのテックカンファレンスで純粋に楽しかった」というコメントがありました。

セッションはここでは2つだけご紹介しましたが、それ以外にも良いものがたくさんありました。全体の傾向としては、Python初級者から中級者向けの発表が多かったように思います。(あくまで推測ですが)Pythonの利用用途が広範囲になっているので、間口を広くとるために初学者向けのセッションを多くしたのかもしれません。

カンファレンスイベントに敷居の高さを感じる人もいるかもしれませんが、少なくともPyConは、Pythonを使いはじめたばかりの人が気軽に参加しても得られるものが多くありそうです。

上級者が参加する場合は、初心者向けのセッションに当たって物足りない思いをしないように、あらかじめタイムテーブルを見て自身のレベルにあったセッションを選んでおくとより効率的な情報収集を行えそうです。

スポンサー出展について

将来的な出展を意識してスポンサーブースを回ってみると、単にノベルティをばらまくのではなく、ブースの方々が来場者を飽きさせないように配布方法やタイミングなどいろいろと工夫されていることがわかり、面白かったです。

また、何を目的にスポンサーをするのかで、どこに重きを置くのかが変わってきそうだと感じました。
例えば会社の知名度を上げたいという目的であれば、豪華なスポンサーブースを作るよりも、セッションに何人か登壇する方が結果として印象に残りやすいように思います。

まとめ

来年のPyCon JPは広島での開催だそうです。今回の体験を踏まえて、実際にスポンサーをするのか検討していきたいと思います。

運営事務局の皆さん、発表者・出展者の皆さん、楽しく役に立つカンファレンスをありがとうございました。

似顔絵
書いた人:伊藤

「意味のある外れ値」のデータ解析 〜極端に高い心拍数を定量的に評価する〜

こんにちは。データサイエンスチームの坂本と申します。

みなさん、データ処理における「外れ値」と聞くと、真っ先に除外すべきものというイメージをお持ちではないでしょうか。実は、必ずしもそうとはいえない場合もあるのです。

TechBlog第八回では、ウェアラブルデータ(※)解析の面白さを知っていただくために、この「外れ値」をめぐるひとつの事例をご紹介したいと思います。

ウェアラブルデータ……スマートウォッチを代表とするウェアラブル端末で測定された生体情報等のデータ

ウェアラブルデータの分析手法

ウェアラブルデータの分析では、目的に応じてさまざまな手法を使います。

代表的なものには、2つの群の差を取り扱う手法(t検定、分散分析等)や、データとデータの関連性を捉える手法(相関分析、一般化線形モデル等)、反復測定を考慮した手法(マルチレベル分析等)、分類や予測に適した手法(機械学習、深層学習等)、グループ分けに適した手法(クラスター分析等)などがあります。

これらの手法を駆使してデータの切り口を工夫したり、的確な分析手法を選択したり、ときには分析手法自体をオーダーメイドすることで、データからいろいろな情報を抽出することができます。

ウェアラブルデータは非常に豊かな情報源です。同じデータに対しても、さまざまなアイデアと手法を駆使することで、多様な研究に使える・発展する可能性を秘めています。言いかえれば、解析者が自身のアイデアでデータの持つ可能性を広げられる点こそが解析の面白さでもあります。

未知なる可能性を秘めたウェアラブルデータの解析。なかでも今回注目したいのが、先ほども触れた「外れ値」です。

眠っている男性のイメージ

極端に高い心拍数の捉え方

研究やデータ解析において、「外れ値」は除外対象として捉えられるのが一般的です。外れ値が、たとえばセンサーの誤作動といったような「適切に測定されなかったことに由来するノイズ」である場合、それを含めて分析してしまうと、結論に誤りが生じてしまう可能性があるためです。

しかし、一見すると除外対象のように思える「外れ値」も、ノイズとは異なる重要な意味を持っている場合があります。それを本稿では「意味のある外れ値」と表現しています。

たとえば、解析対象のデータが「前方に道路を横切る歩行者が現れてから、運転者がブレーキを踏むまでの時間」であったとします。このようなケースでは、ブレーキを踏むまでの平均時間よりも、極端な踏み遅れ(=意味のある外れ値)が重要な意味を持つことになります。事故につながるリスクが格段に高い現象だからです。意味のある外れ値がどのような条件の時に生じやすいのか。運転者が強い眠気を感じていたのか、歩行者を視認しにくい特別な交通状況があったのか……がわかれば、対策を立てることができます。

こう考えると、意味のある外れ値を除外せず、研究することの重要性がよくわかると思います。

同じくして、「極端に高い心拍数」にも重要な意味があると考えられます(もちろん、測定ミスの場合を除いて)。その背景には、急激な運動、動機や息切れの発生、心疾患などの病気や、手術後で弱った身体への負担、強度の緊張の発生など、さまざまな理由が想像できます。極端に高い心拍数の発生を定量的に評価することができれば、さまざまな研究への応用が期待できそうです。

では、実際に「定量的に評価する」方法にはどのようなものがあるでしょうか?

指数−ガウス分布

外れ値のような極端に大きな値を除外せず、解析的に考慮するための手法の一つに、「指数−ガウス分布(執筆者訳。一般的にはex-Gaussian Distributionや Exponentially Modified Gaussian Distribution等と呼ばれる)」を用いた分析があります。先ほど例に挙げた「ブレーキを踏むまでの時間」など、反応にかかる所要時間の分析に用いられることがあります。

指数−ガウス分布は、基本的なデータのばらつき部分を表現する正規分布と、外れ値に相当する成分を表現する指数分布とを合成(畳み込み積分により導出)した確率分布です。「山なりで右に裾が長い(重い)分布」という特徴的な形状を持ちます(図1)。

グラフ
図1 正規分布・指数分布・指数−ガウス分布の例 (μ=25, σ=3, τ=10の場合)

合成元となる正規分布の平均(μ)と分散(または標準偏差σ)、指数分布の期待値(τ、場合により、その逆数のλ)がパラメータであり、特に「外れ値の大きさや出現頻度に関する情報」をパラメータτで捉えることができます。データが指数−ガウス分布にしたがう(近似できる)と仮定し、パラメータ推定を実施することで、これらを定量化することができます。

睡眠中の心拍数データの分析

図2は、特定の期間に測定された、睡眠中の心拍数の度数分布です。2名分の実際の測定データ(※)を可視化しています。

※ 社内メンバーの提供データであり、お預かりした研究データではありません。

グラフ
図2 睡眠中の心拍数の度数分布

※最小値と最大値を破線、中央値を実線で示しています。

横軸は1分あたりの心拍数です。その心拍数が睡眠中の何分間、観測できたかを表すのが縦軸です。グラフからは、Bさんのみ1分あたり100以上の心拍数が観測されていることがわかります(赤矢印の箇所)。

Aさんは、睡眠中の最小心拍数が約50、最大心拍数が約80です。測定データはこの区間において「山なりで、右に裾が長い(重い)形状」で分布していることがわかります。

他方、Bさんも「山なりで、右に裾が長い(重い)形状」であることに変わりはありませんが、その裾がAさんよりも長い(重い)です。最大値も180を超えており、ときおり極端に高い心拍数が観測されていることがわかります。

「睡眠中の1分あたりの心拍数が80を超えたものは極端な値(=外れ値)」として、その割合を求めるなどの方法も考えられます。ただ、個人によって心拍数の平均値などが違うことを考えると、一律に「心拍数が80を超えたもの」のようなしきい値を決めるのではなく、個人差を考慮できる何らかの統計分析手法を適用した方がよいでしょう。

そこで今回は、AさんとBさんの2名について、それぞれのデータを用いて指数−ガウス分布のパラメータを推定します。統計解析環境R(version4.3.3)と、ハミルトニアンモンテカルロ法(NUTSアルゴリズム)を搭載したソフトウェアSTAN(version 2.32.2)を使用します(iteration = 2000, warmup = 1000, thinning = 4, chains = 4, total samples = 1000)。分析モデルの詳細は、本稿末尾をご覧ください。

分析結果

パラメータ推定の結果、全てのパラメータについて適切に推定できていることが確認できました(Rhat統計量<1.01、実効サンプルサイズ>10%、モンテカルロ標準誤差/標準偏差 < 10%)。

実際の度数分布と事後予測分布を重ね、視覚的にチェックしてみましょう(図3)。

※事後予測分布…既に観測されたデータに基づいて、将来のデータや新しい観測がどのような値をとるかを予測するために使う分布。ここでは先ほどパラメータ推定した指数−ガウス分布のこと。

グラフ
図3 視覚的事後予測チェック(Visual Posterior Predictive Checks)

※破線は測定データの最小値と最大値を示す。

図3では、実際の測定データの度数分布(ヒストグラム)と、曲線で示した事後予測分布(5つのiterationを無作為に抜粋)を重ねて表示しています。指数−ガウス分布によって、睡眠中の心拍数のばらつきかた(分布)を的確に捉えられていると判断してよさそうです。

次に、パラメータの推定結果も見ていきましょう。推定の結果、Aさんより、Bさんの方が、外れ値の頻度や大きさを表すパラメータτの値が高いことが確認されました(δ(τA - τB) = -0.508[-0.887, -0.1234])。結果を図4に示します。

グラフ
図4 指数−ガウス分布を用いた睡眠中心拍数のパラメータ推定結果

※点は事後分布の期待値(Expected A Posteriori)を示し、エラーバーは95%信用区間(Credible Interval)を示します。

技術の応用と研究への発展

このように、指数−ガウス分布を用いた解析は、「意味のある外れ値」を定量的に評価する際に有用だと考えられます。

さらにここから、さまざまな分析モデルへと発展させることもできそうです。「年齢が高くなるほど、パラメータτの値が高くなる」ことを仮定した回帰モデルや、「対照群より疾患群の方がパラメータτの値が高い」という個人と集団の階層性を仮定した分析モデル、そして、「手術直後からの3ヶ月間でパラメータτの推定値が徐々に低下した」などを取り扱う時系列モデルなど、やり方次第で多くの研究に貢献できる可能性があります。

もちろん、適用範囲は「極端に高い心拍数」だけではありません。極端に長い睡眠時間の発生や、極端に多い運動量など、さまざまな測定データに対して活用できる可能性があります。

「極端に高い」や「極端に長い」がキーワードになる現象を取り扱う際には、指数−ガウス分布の活用を検討してみてください。ウェアラブルデータを用いた詳細な研究等のご相談は、ぜひ弊社まで。

分析モデルとスクリプト(.stanファイル)

分析モデルを記載したStanスクリプト

// ExGaussian.stan

// 入力データ
data {
  int<lower=0> N;     // 対象者数(今回はN=2)
  int<lower=0> r;      // 観測したすべての心拍数の数(レコード数)
  int<lower=1> ID[r];   // 観測レコードに対応した対象者のID番号(A=1, B=2)
  vector[r] Y;          // 観測した1分ごとの心拍数の値
  int Const;            // 心拍数のスケールを調整する定数(今回はConst = 60)
}

// データの前処理
transformed data {
  // 0付近の正の実数で安定した推定をするために、スケールを秒単位に。
  vector[r] rescaled_Y;
  rescaled_Y = Y / Const;
}

// 推定パラメータ
parameters {
  // 個人ごとのパラメータ
  vector[N] mu;
  vector<lower=0>[N] sigma;
  vector<lower=0>[N] tau;
}


// モデル
model {
  // 事前分布
  mu ~ normal(0, 10);
  sigma ~ student_t(3, 0, 1);
  tau ~ gamma(0.01, 0.01);
  // 尤度関数
  for(i in 1:r) {
    // stanでは指数-ガウス分布の引数にλを使用するため、1/τを所与する
    rescaled_Y[i] ~ exp_mod_normal(mu[ID[i]], sigma[ID[i]], inv(tau[ID[i]]));
  }
}

// 事後予測分布の生成
generated quantities {
  vector[r] predY;
  for(i in 1:r) {
    // 生成した予測分布を元のスケールに戻す
    predY[i] = Const * exp_mod_normal_rng(mu[ID[i]], sigma[ID[i]], inv(tau[ID[i]]));
  }
}


似顔絵
書いた人:坂本

HealthKitを使ってAppleデバイス向け健康管理アプリ開発を始める方法

こんにちは。プロダクト開発チームでネイティブアプリの開発を担当している大嶋です。

この記事では、Appleが提供する健康データ管理のためのフレームワークであるHealthKitを活用し、健康管理アプリを開発する方法について解説します。

HealthKitを利用することで、iOSおよびwatchOSアプリがユーザーの健康やフィットネスデータを収集・管理できるようになります。具体的には、iPhoneやAppleWatchが収集したデータをアプリから読み込んだり、逆にアプリで収集したデータをHealthKit経由で蓄積することも可能です。

これから健康やフィットネスに関するアプリを開発したいと考えている方は、ぜひ本記事を参考にしてください。

Apple公式サイト「ヘルスケアとフィットネス」より引用

HealthKitの概要

HealthKitは、ユーザーの同意のもとでアクティビティ・心拍数・体重・栄養・睡眠などのデータを統合し、Appleの「ヘルスケア」アプリや「フィットネス」アプリで一元管理するためのフレームワークです。

例えば、月次の運動量を確認するとか、睡眠の質を確認するといった機能のアプリを比較的簡単に作ることができます。

データはデバイス内部のローカルストレージに保存されるため、ユーザーの健康データを安全に管理できます。

HealthKitを使用する利点としては、下記の3点が挙げられます。

  • データが統合管理できる
    「ヘルスケア」アプリで様々な情報を統合的に閲覧・管理できるため、ユーザーが自身の健康状態を把握しやすくなります。
  • プライバシー保護がしやすい
    データへのアクセスにはユーザーの明示的な許可が必要なため、データの利用や共有は厳密に管理されます。フレームワーク側でそれらの機能が実装されているので、アプリ側の設計で考慮する必要がありません。
  • サードパーティアプリと連携可能
    データを直接蓄積したアプリ以外のアプリからも、HealthKitを介してデータにアクセスすることができます。より広範囲な健康管理が可能です。

HealthKitを使ったアプリ開発の準備(Xcodeでの設定)

ここからは、実際の使用方法を紹介していきます。
HealthKitの利用の前に、Xcodeでの設定が必要です。以下の手順で設定を行います。

  1. CapabilityにHealthKitを追加
    プロジェクトのターゲット設定から「HealthKit」を有効化します。
  2. Info.plistにHealthKit関連の項目を追加
    HealthKitを使用するためには、Info.plistにHealthKitの利用目的を記載します。利用目的は、特にリリース時にはユーザーが理解できるように丁寧に記述する必要があります。

HealthKit実装サンプルの解説

次に、実際のHealthKitの実装についてもサンプルコードとともにご紹介します。実装例はGitHubに公開しているので、以下のリンクから確認してみてください。

github.com

アプリの概要

このサンプルアプリは、以下の3つの健康データを表示します。

  • 歩数:当日の合計歩数を表示します。
  • 平均心拍数:当日の平均心拍数をBPM(beats per minute)で表示します。
  • 睡眠時間:当日の睡眠時間(in bedの時間)を時間単位で表示します。
サンプルアプリの画面

また、画面にある「データを更新」ボタンを押すと、最新のデータが取得され、表示が更新されます。

アプリ開発において、考慮すべき項目がいくつかあります。

HealthKitの権限管理

概要のパートでプライバシー保護について書きましたが、HealthKitを利用する際には、ユーザーから必要なデータのアクセス権を得る必要があります。これを行うのがrequestAuthorizationメソッドです。

func requestAuthorization(completion: @escaping (Bool, Error?) -> Void) {
    let stepType = HKObjectType.quantityType(forIdentifier: .stepCount)!
    let heartRateType = HKObjectType.quantityType(forIdentifier: .heartRate)!
    let sleepType = HKObjectType.categoryType(forIdentifier: .sleepAnalysis)!
    let readTypes: Set = [stepType, heartRateType, sleepType]
    
    healthStore.requestAuthorization(toShare: nil, read: readTypes) { (success, error) in
        completion(success, error)
    }
}

requestAuthorizationメソッドでは、取得したいデータタイプをreadTypesセットで指定し、HealthKitの権限を求めます。ここでは、ユーザーの「歩数」「心拍数」「睡眠データ」にアクセスするための権限をリクエストしています。

このメソッドが実行されると、ユーザーの端末にHealthKit連携を許可するかどうかのポップアップが表示されます。

表示されるポップアップ画面の例

このメソッドが成功すると(=ユーザーが許可すると)、アプリはこれらのデータタイプに対してアクセス可能となります。

※ユーザーが許可しなかった場合は、アプリ側から再度アクセスを取ることはできません。「ヘルスケア」アプリから指標別にアクセス許可を取ることになります。

データ取得のクエリ

HealthKitでは、健康データを取得するためにクエリを使用します。ここでは、期間内の歩数データの合計を例にして、データ取得の方法を解説します。

歩数データの合計値を取得するためには、HKStatisticsQueryを使用します。これは平均値や合計などの統計値を計算するクエリです。今回は、合計を計算するためのオプション(.cumulativeSum)を指定します。

func fetchStepCount() {
    let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
    let startDate = Calendar.current.startOfDay(for: Date())
    let predicate = HKQuery.predicateForSamples(withStart: startDate, end: Date(), options: .strictStartDate)

    let query = HKStatisticsQuery(quantityType: stepType, quantitySamplePredicate: predicate, options: .cumulativeSum) { _, result, error in
        if let sum = result?.sumQuantity() {
            DispatchQueue.main.async {
                self.stepCount = sum.doubleValue(for: HKUnit.count())
            }
        }
    }
    healthStore.execute(query)
}

サンプルアプリでは、このクエリが返す合計値をstepCountプロパティに反映しています。

他の健康データ(心拍数や睡眠データ等)も、HealthKitが提供するクエリのオプションを変えることで簡単に取得できます。まずは基本的なクエリの使い方を習得して、徐々に応用してみてください。

データの同期や更新の遅延について

Apple WatchiPhone間でデータを同期する場合、バックグラウンドでデータのやり取りが行われます。基本的には、バッテリー消費やシステム負荷が考慮され適切なタイミングで更新が行われますが、以下の点は注意が必要だと思います。

  • 同期のタイミングについて
    両デバイスが適切と判断したタイミングで同期が行われるため、同期が遅延していると感じることがあるかもしれません。一方、 ユーザーの明示的なアクションに対しては即時にデータが更新されます。例えば同期ボタンを配置し、押されたタイミングでHealthKitデータの同期をトリガーすることが可能です。
  • バッテリー消費に注意
    データの頻繁な更新はバッテリー消費につながるため、必要に応じて更新頻度を見直すことも検討しましょう。例えばユーザーによる同期ボタンの連打を抑止するなどの対策は有効です。

まとめ

HealthKitを活用すれば、ユーザーの健康データを効率的に管理し、アプリの付加価値を高めることができます。さらに進化した健康管理アプリを目指していきましょう!

今回はHealthKitを用いた健康管理アプリ開発の概要と実装方法の一部をご紹介しました。ぜひこの内容を参考に、ご自身のアプリにHealthKitを取り入れてみてください。

参考

HealthKit | Apple Developer Documentation



書いた人:大嶋