2

Python Tutorial with Heart Beat dataset

25 min read

心音データの解析をしながら、以下のことを学んでいきます。

Pythonの簡単な文法

Google Colabの使い方

Google Colabノートブックは複数のセルで構成されます。

セルにはテキストセルコードセルの2種類があります。

テキストセル(この文章が書かれている場所)にはテキストのみを,コードセルにはPythonなどのプログラムを記述することができます・

コードセルでは、記述したプログラムにおける,最後に書いた値が表示されます。

コードセルを実行するには,実行したいセルの左端の再生ボタンを押します。

@@@@@

# ここはコードセルです。
"全国医療AIコンテスト"
'全国医療AIコンテスト'

標準入出力

標準入力とは,キーボードからの入力のことで,標準出力とは,ディスプレイへの出力のことです。

s = input("あなたの名前を入れてください→ ")
print(s," は全国医療AIコンテストに参加しています")
あなたの名前を入れてください→ name
name  は全国医療AIコンテストに参加しています

変数

変数は、値を保持する「箱」のようなものです。

変数には、数値や文字列など、どんな値でも入れることができます。

変数に値を入れることを「代入」と言います。

変数に値を代入するには、「=」を使います。

変数名には、アルファベット・数字・アンダーバーの組み合わせが使えますが、数字から始まる変数名は無効です。

a = 123
b = -23
sample_string = "Artificial Intelligence"

演算

演算には、以下のように様々な種類があります。

  • +:足し算
  • - :引き算
  • *:掛け算
  • /:割り算の商
  • %:割り算の剰余
  • **:累乗
print(12 + 3,
      "\n", # 改行を出力するときは \n を入力します。(\ はバックスラッシュと言ってパソコンの機種で打ち方が変わるので、調べましょう。)
      4 * 4,
      "\n",
      12 / 5,
      "\n",
      12 / 5.0,
      "\n",
      12 % 5,
      "\n",
      5 ** 3,
      "\n",
      a + b,
      "\n",
      "What is " + sample_string,
      "\n",
      str(a) + sample_string) # 文字列に変換するときは、str()が使えます。
15 
 16 
 2.4 
 2.4 
 2 
 125 
 100 
 What is Artificial Intelligence 
 123Artificial Intelligence

条件分岐

値に応じてプログラムの処理を変更したいときは、if 文を使って条件分岐を行います。

if文の書式

if 条件式A:
  条件式Aが真であるときに実行したい処理
elif 条件式B:
  条件式Aが偽で,条件式Bが真であるときに実行したい処理
elif 条件式C:
  ...
else:
  全ての条件が偽であるときに実行したい処理

if文の中で実行したい処理には、インデント(字下げ)することに注意しましょう。 インデントするには、tabキーを押します。

if文で使える条件式

  • ==(等号):右辺と左辺が等しいときのみ真
  • !=(ノットイコール):右辺と左辺が等しくないときのみ真
  • <(小なり):左辺が右辺より小さいとき真
  • <=(小なりイコール):左辺が右辺以下であるとき真
  • >(大なり):左辺が右辺より大きいとき真
  • >=(大なりイコール):左辺が右辺以上であるとき真
pressure = input("血圧をいれてください。") # 出力セルで数値を入力したい時は input() が使えます

#inputで入るのは文字列なのでint()で囲うこと数字に変換する↓
pressure = int(pressure)  

if pressure >= 140:
    print("あなたは高血圧です")
elif pressure >=130:
    print("あなたは高値血圧です")
else:
    print("あなたは正常血圧です")
血圧をいれてください。140
あなたは高血圧です

リスト

リストは,数値や文字列,様々な値を複数持つことができるデータ構造です。

例えば,リストは以下のように表されます。

even_list = [2, 4, 6, 8, 10]
string_list = ["abc", "def", "hello", "world"]

リストの中のn番目の要素にアクセスするには,リストを格納した変数の後ろに [n] を付けて表します。ここで、要素番号は0から始まるので、左から数えて4番目の要素のインデックスは 41=34-1=3 になります。

even_list[3]  # 8が出力される
string_list[0]  # "abc"が出力される

リストのリストを定義することもできます。「リストの入れ子」「多次元リスト」とも呼びます。

list_of_list = [[1, 2, 3], [2, 3, 4], [5, 6, 7, 8]]
list_of_list[0]  # [1, 2, 3]が出力される
list_of_list[1][2]  # 4が出力される

繰り返し(for文)

for文の書式を以下に示します。

for ループ変数 in リスト:
  繰り返したい処理

ループ変数には、リストの要素が頭から順番に代入されていきます。 そしてそのたびに繰り返したい処理が繰り返されます。

for文の中で繰り返したい処理には、インデント(字下げ)することに注意しましょう。

muscle = "sternocleidmastoid" #変数muscleにsternocleidmastoidを代入

for i in range(5):
  print(muscle)
sternocleidmastoid
sternocleidmastoid
sternocleidmastoid
sternocleidmastoid
sternocleidmastoid

関数

プログラミングでは、まとまった同じ処理を複数行うことがよくあります。

そのとき、それらの処理を全部書いているとプログラムの行数が増えて読みにくくなってしまいます。

そこで、「処理のまとまり」を表現する書式が生まれました。それが関数です。

関数を定義する書式を以下に示します。

def 関数名():
  処理

関数を実行する書式を以下に示します。

関数名()

それではカレーを作ってみましょう。

def curry():
  print('カレーを作ります')
  print('↓')
  print('まず肉を炒めます')
  print('↓')
  print('その後野菜をいためます。')
  print('↓')
  print('ルーを入れて煮込むと完成です。')
  print('↓')
  print('肉と野菜のカレーの完成です')   

curry()
カレーを作ります
↓
まず肉を炒めます
↓
その後野菜をいためます。
↓
ルーを入れて煮込むと完成です。
↓
肉と野菜のカレーの完成です

次に,引数と戻り値付きの関数を定義する書式を以下に示します。

def 関数名(引数名):
  処理
  return 戻り値

関数を実行して、戻り値を変数xxに代入する書式を以下に示します。

x = 関数名(引数)

戻り値とは,関数によって生成され,関数の外のプログラムに渡す値のことです。

それでは、カレーを作った後に、足し算を行う関数addを定義して実行してみます。

def add(a, b):
    curry()
    return a + b

add(4, 8)
カレーを作ります
↓
まず肉を炒めます
↓
その後野菜をいためます。
↓
ルーを入れて煮込むと完成です。
↓
肉と野菜のカレーの完成です





12

さてPythonの文法がわかったところで、実際の医療データ解析をしてみましょう。

公開医療データのダウンロード

今回は 公開されている The PASCAL Classifying Heart Sounds Challenge 2011(CHSC2011) という心音データをダウンロードしてみましょう。このデータは心音のS1領域・S2領域を特定するというタスクと、正常か異常かを分類するタスクの2つを解かせるために公開されています。

まず linuxコマンド を使って 公開されているデータをダウンロードしましょう。下のセルでは heartbeat というディレクトリをデフォルトのディレクトリ /content の下に作成し、そこに関連データをダウンロードしています。

コマンドとはコンピュータに計算やファイルの操作を行うように指示できるツールです。今回は詳しく触れませんが、colabのようなノートブックではpythonとコマンドを混ぜて使えるのが一つの強みです。

# ! を先頭につけると一時的に適応される。例えばワーキングディレクトリの移動をしてもその後のコマンドには適応されない。
# heartbeat ディレクトリを作成します。
!mkdir heartbeat

# % を先頭につけると永続的に適応される。ワーキングディレクトリの移動をしてもその後も適応される。
# heartbeat ディレクトリに移動します。
%cd heartbeat

# S1とS2の位置が記録されているデータのダウンロード
!wget http://www.peterjbentley.com/heartchallenge/Atraining_normal_seg.csv

# 正常心音データのダウンロード(zipの中身はaifファイル)
!wget http://www.peterjbentley.com/heartchallenge/Atraining_normal.zip
!unzip Atraining_normal.zip
!ls
%cd ..
/content/heartbeat
--2021-05-18 04:24:23--  http://www.peterjbentley.com/heartchallenge/Atraining_normal_seg.csv
Resolving www.peterjbentley.com (www.peterjbentley.com)... 209.151.22.160
Connecting to www.peterjbentley.com (www.peterjbentley.com)|209.151.22.160|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3412 (3.3K) [text/csv]
Saving to: ‘Atraining_normal_seg.csv’

Atraining_normal_se 100%[===================>]   3.33K  --.-KB/s    in 0s      

2021-05-18 04:24:23 (432 MB/s) - ‘Atraining_normal_seg.csv’ saved [3412/3412]

--2021-05-18 04:24:23--  http://www.peterjbentley.com/heartchallenge/Atraining_normal.zip
Resolving www.peterjbentley.com (www.peterjbentley.com)... 209.151.22.160
Connecting to www.peterjbentley.com (www.peterjbentley.com)|209.151.22.160|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 13876165 (13M) [application/zip]
Saving to: ‘Atraining_normal.zip’

Atraining_normal.zi 100%[===================>]  13.23M  6.31MB/s    in 2.1s    

2021-05-18 04:24:26 (6.31 MB/s) - ‘Atraining_normal.zip’ saved [13876165/13876165]

Archive:  Atraining_normal.zip
   creating: Atraining_normal/
  inflating: Atraining_normal/.DS_Store  
   creating: __MACOSX/
   creating: __MACOSX/Atraining_normal/
  inflating: __MACOSX/Atraining_normal/._.DS_Store  
  inflating: Atraining_normal/201101070538.aif  
  inflating: __MACOSX/Atraining_normal/._201101070538.aif  
  inflating: Atraining_normal/201101151127.aif  
  inflating: __MACOSX/Atraining_normal/._201101151127.aif  
  inflating: Atraining_normal/201102081152.aif  
  inflating: __MACOSX/Atraining_normal/._201102081152.aif  
  inflating: Atraining_normal/201102081321.aif  
  inflating: __MACOSX/Atraining_normal/._201102081321.aif  
  inflating: Atraining_normal/201102201230.aif  
  inflating: __MACOSX/Atraining_normal/._201102201230.aif  
  inflating: Atraining_normal/201102260502.aif  
  inflating: __MACOSX/Atraining_normal/._201102260502.aif  
  inflating: Atraining_normal/201102270940.aif  
  inflating: __MACOSX/Atraining_normal/._201102270940.aif  
  inflating: Atraining_normal/201103090635.aif  
  inflating: __MACOSX/Atraining_normal/._201103090635.aif  
  inflating: Atraining_normal/201103101140.aif  
  inflating: __MACOSX/Atraining_normal/._201103101140.aif  
  inflating: Atraining_normal/201103140132.aif  
  inflating: __MACOSX/Atraining_normal/._201103140132.aif  
  inflating: Atraining_normal/201103140135.aif  
  inflating: __MACOSX/Atraining_normal/._201103140135.aif  
  inflating: Atraining_normal/201103140822.aif  
  inflating: __MACOSX/Atraining_normal/._201103140822.aif  
  inflating: Atraining_normal/201103151912.aif  
  inflating: __MACOSX/Atraining_normal/._201103151912.aif  
  inflating: Atraining_normal/201103170121.aif  
  inflating: __MACOSX/Atraining_normal/._201103170121.aif  
  inflating: Atraining_normal/201103221214.aif  
  inflating: __MACOSX/Atraining_normal/._201103221214.aif  
  inflating: Atraining_normal/201104122156.aif  
  inflating: __MACOSX/Atraining_normal/._201104122156.aif  
  inflating: Atraining_normal/201104141251.aif  
  inflating: __MACOSX/Atraining_normal/._201104141251.aif  
  inflating: Atraining_normal/201105011626.aif  
  inflating: __MACOSX/Atraining_normal/._201105011626.aif  
  inflating: Atraining_normal/201105021654.aif  
  inflating: __MACOSX/Atraining_normal/._201105021654.aif  
  inflating: Atraining_normal/201105021804.aif  
  inflating: __MACOSX/Atraining_normal/._201105021804.aif  
  inflating: Atraining_normal/201105151450.aif  
  inflating: __MACOSX/Atraining_normal/._201105151450.aif  
  inflating: Atraining_normal/201106111136.aif  
  inflating: __MACOSX/Atraining_normal/._201106111136.aif  
  inflating: Atraining_normal/201106141148.aif  
  inflating: __MACOSX/Atraining_normal/._201106141148.aif  
  inflating: Atraining_normal/201106151236.aif  
  inflating: __MACOSX/Atraining_normal/._201106151236.aif  
  inflating: Atraining_normal/201106210943.aif  
  inflating: __MACOSX/Atraining_normal/._201106210943.aif  
  inflating: Atraining_normal/201106221418.aif  
  inflating: __MACOSX/Atraining_normal/._201106221418.aif  
  inflating: Atraining_normal/201106221450.aif  
  inflating: __MACOSX/Atraining_normal/._201106221450.aif  
  inflating: Atraining_normal/201108011112.aif  
  inflating: __MACOSX/Atraining_normal/._201108011112.aif  
  inflating: Atraining_normal/201108011114.aif  
  inflating: __MACOSX/Atraining_normal/._201108011114.aif  
  inflating: Atraining_normal/201108011115.aif  
  inflating: __MACOSX/Atraining_normal/._201108011115.aif  
  inflating: Atraining_normal/201108011118.aif  
  inflating: __MACOSX/Atraining_normal/._201108011118.aif  
Atraining_normal  Atraining_normal_seg.csv  Atraining_normal.zip  __MACOSX
/content

ダウンロードされたファイルは左のバーのフォルダーマークを押すとみることができます。

今回はいろんなPythonモジュールを使って、一つの心音データを図示することを目標にしましょう。

モジュールとは

あらかじめ定義された関数群をモジュールといいます。

科学技術計算や機械学習、アプリケーション開発では、同じ複雑な処理を複数回行うことがあるので、先人達がすでに有用な関数を実装してくれています。

モジュールを使うには、import文を使います。

import文を使うことで、そのモジュールが、今自分の書いているプログラムの中で定義されているように使用することができます。

import モジュール名 as モジュールを代入する変数名

まずはデータがどんな構造をしているのかみていくために、pandasというモジュールを使っていきましょう。

Pandasの使い方

Pandasとは、テーブルデータの処理に特化したライブラリです。データフレームと呼ばれる形式のデータを加工することができ、データの結合や切り取り、表計算、時系列データの取り扱いなどが可能です。また、簡単なグラフ化も同時にできます。

Pandasは、データサイエンスの分野でよく使われるライブラリで、機械学習を行う前にデータの整形を行う前処理のツールとして使用することが多いです。

csvデータの読み込み

まずはCHSCデータのS1とS2の位置情報がまとまっているcsvファイルの中身をみてみましょう。pandas は慣例的にpdとしてimportします。 pd.read_csv(ファイル名)でデータを読み込めます。

import pandas as pd

csv_path = "/content/heartbeat/Atraining_normal_seg.csv"
df = pd.read_csv(csv_path) # DataFrame の頭文字をとってよく変数名に df が使われます。

データフレームの情報

df.head()で先頭の5行をみることができます。

df.head()
Unnamed: 0 S1 S2 S1.1 S2.1 S1.2 S2.2 S1.3 S2.3 S1.4 S2.4 S1.5 S2.5 S1.6 S2.6 S1.7 S2.7 S1.8 S2.8 S1.9 S2.9 S1.10 S2.10 S1.11 S2.11 S1.12 S2.12 S1.13 S2.13 S1.14 S2.14 S1.15 S2.15 S1.16 S2.16 S1.17 S2.17 S1.18 S2.18 Unnamed: 39
0 201102081321.aif 10021.0 20759 35075 47244 62992 73729 88761 101646 115246 127415 143163.0 153900.0 168216.0 180385.0 195417.0 206155.0 220471.0 231924.0 272010.0 283463.0 297779.0 309232.0 324264.0 335001.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 201102260502.aif NaN 11526 27941 42197 58163 71278 88955 102641 122028 134573 152821.0 184753.0 197869.0 216686.0 230942.0 251211.0 265168.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 201103090635.aif 5366.0 17632 31432 44464 59030 71296 86629 99661 116527 128793 144125.0 156391.0 174024.0 185523.0 200856.0 213122.0 226154.0 239187.0 252220.0 265252.0 277518.0 290551.0 303583.0 315849.0 329649.0 342681.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 201103140132.aif 16358.0 29272 89539 105036 128282 142057 170469 183383 207490 221265 242789.0 256564.0 279810.0 292725.0 320275.0 334911.0 363323.0 377098.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 201103140822.aif 3444.0 18080 44770 58545 84374 98149 123977 134309 157555 175635 195437.0 214378.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

各行は一つ一つの心音データに関する情報を表してます。それぞれデータが記録されているファイル名とS1、S2それぞれの位置情報を表していると考えられます。

df.shapeでデータのサイズをみることができます。

df.shape
(21, 40)

21件のデータがあることがわかります。

データの抽出

特定の列を取り出したい時は、df[列の名前]あるいはdf.列の名前で取り出せます。列が全部取ってこられるので、特定の行を取り出したい時はそのインデックスを指定する必要があります。

Unnamed: 0という列のインデックスが1の要素の中身をみてみましょう。

index = 1
FILE = df["Unnamed: 0"][index]
FILE
'201102260502.aif'

波形のデータが記録されているファイル名は、Unnamed: 0に書かれています。

少し発展してS1とS2に関する情報をそれぞれ抽出してみましょう。

cols = df.columns.values # .valuesでデータフレームの中身の値をとってこれる

s1cols = [col for col in cols if col[:2] == "S1" ] # 最初の2文字がS1の列名を格納
s2cols = [col for col in cols if col[:2] == "S2" ] # 最初の2文字がS2の列名を格納

s1data = df.loc[index,s1cols].values # データフレーム.values で中身の値の配列が返される
s2data = df.loc[index,s2cols].values

print(s1data, "\n", s2data)
[nan 27941 58163 88955 122028 152821.0 197869.0 230942.0 265168.0 nan nan
 nan nan nan nan nan nan nan nan] 
 [11526 42197 71278 102641 134573 184753.0 216686.0 251211.0 nan nan nan
 nan nan nan nan nan nan nan nan]

次は取得したFILEにどのようなデータが入っているのかみてみましょう。

オーディオデータの構造

今回は心音データが aif というファイル形式を扱うので、それに特化した aifc というモジュールを使用します。今回のように知らないモジュールを使う場面に沢山出くわしますが、全ての使い方を覚えなければいけないというわけではありません。知らない人でも使えるように、モジュールを作った人たちが使い方の解説を書いてくれています。色んなサイトで使い方の記事がありますが、基本的には公式のドキュメントが一番正確です。今回の aifc というモジュールはPythonにもともと入っている標準ライブラリで、こちらのPython公式ドキュメントに使い方が書いてあるので、参考にしながら使ってみます。

まずは先程 pandas で抽出したデータの保存先ファイル名から読み込んでどんなデータなのかみてみましょう。

import aifc

PATH = "/content/heartbeat/Atraining_normal/" + FILE # ファイル名だけでなく、ファイルの場所を指定する必要がある(パス)
f = aifc.open(PATH,"rb")
params = f.getparams()
params
_aifc_params(nchannels=1, sampwidth=2, framerate=44100, nframes=277130, comptype=b'NONE', compname=b'not compressed')

公式ドキュメントによると、

オーディオファイルには、オーディオデータについて記述したパラメータがたくさん含まれています。サンプリングレートあるいはフレームレートは、1秒あたりのオーディオサンプル数です。チャンネル数は、モノラル、ステレオ、4チャンネルかどうかを示します。フレームはそれぞれ、チャンネルごとに一つのサンプルからなります。サンプルサイズは、一つのサンプルの大きさをバイト数で示したものです。したがって、一つのフレームは nchannels * samplesize バイト からなり、1秒間では nchannels * samplesize * framerate バイトで構成されます。

ということなので、上のparamsで返されたメタ情報から今回の音声ファイルは

チャンネル数 = 1 (モノラル)
サンプルサイズ = 2 [byte]
サンプリングレート = 44100 [Hz]

で一つのフレームは 1 * 2 = 2 [byte] 、1秒間では 1 * 2 * 44100 = 88200 [byte] であることがわかります。

nframes = f.getnframes()
framerate = f.getframerate()
T = nframes / framerate
T
6.284126984126984

長さは 6.28 [s] であることもわかります。このようにオーディオデータはフレームという単位に情報が格納されています。

次に全てのフレームのデータを抽出しましょう。

frames = f.readframes(nframes) # 抽出するフレーム数を指定する
print(type(frames))
<class 'bytes'>

Pythonでは扱うデータをオブジェクトと呼びますが、オブジェクトの型は type(object) でみることができます。フレームデータはバイト型であることがわかりました。

開いたファイルは閉じてあげます。

f.close()

それでは心音データをnumpy配列として変換してみましょう。データをnumpy配列として扱うことは機械学習において後々楽になることが多いです。

Numpyの使い方

numpy は行列の処理に非常に長けたモジュールです。

慣例的にnpでimportします。

import numpy as np

Numpy配列

次に、Numpyで定義されたリストの拡張である「Numpy配列」を使います。

Numpy配列は,以下のように使います。

np.array(リスト)

次に使用例を示します。

a = np.array([1, 2, 4])
a
array([1, 2, 4])

Numpy配列には、多次元リスト(リストのリスト)を渡すこともできます。

多次元リストをNumpy配列に渡すと,多次元配列と呼ばれます。

このようにして、行列やベクトルを表現できます。

matrix = np.array([[1, 2, 3], [2, 3, 4], [3, 4, 5]])
matrix
array([[1, 2, 3],
       [2, 3, 4],
       [3, 4, 5]])
vector = np.array([[1], [2], [3]])
vector
array([[1],
       [2],
       [3]])

Numpy配列の形を調べたいときは、np.array.shapeを参照します。

matrix.shape
(3, 3)

行列の演算

Pythonのリストよりも、Numpy配列の方が線形代数演算に向いています。

ベクトルや行列を含む計算には、積極的にNumpy配列を使います。

例えばこのように、+記号でベクトル同士の和を表現できたり、*記号で定数倍を表現できます。

vector_2 = np.array([[2], [5], [6]])
vector + vector_2
array([[3],
       [7],
       [9]])
3 * vector
array([[3],
       [6],
       [9]])

@演算を使って行列同士の積や行列とベクトルの積を表現できます。

matrix @ vector
array([[14],
       [20],
       [26]])

Note

実は行列演算に特化している点とは別にNumpyを使う大きなメリットがあります。それは実行時間

です。Numpyの内部ではC言語によって記述されていて、また行列演算にはBLAS APIという高性能の行列演算ライブラリを用いているのでPythonとは比べ物にならないくらい高速となります。

フレームデータの変換

それではバイト型のフレームデータを numpy配列 に変換してみましょう。np.frombuffer(bytes file, dtype = "型を指定") で変換することができます。

また今回は1フレームで2 [byte] = 16 [bit]なのでファイル規格から dtype = "int16" を指定することも大事です。データの構造も知っている必要はなくて、わからなかったことは調べたら出てきます。

data = np.frombuffer(frames, dtype="int16") 
data
array([ 12542,  13822,   8446, ..., -29441, -29697, -28161], dtype=int16)

S1、S2データの整形

s1data
array([nan, 27941, 58163, 88955, 122028, 152821.0, 197869.0, 230942.0,
       265168.0, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan],
      dtype=object)

配列の中身をみるとわかりますが、nan が混ざっていたり、小数点がついていたりついてなかったりするので、整形しましょう。

s1data = s1data.astype(np.int32)
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-29-77bdc267fed2> in <module>()
----> 1 s1data = s1data.astype(np.int32)


ValueError: cannot convert float NaN to integer
s1data = s1data.astype(np.float32) 
s2data = s2data.astype(np.float32)

print(s1data, "\n", s2data)
[    nan  27941.  58163.  88955. 122028. 152821. 197869. 230942. 265168.
     nan     nan     nan     nan     nan     nan     nan     nan     nan
     nan] 
 [ 11526.  42197.  71278. 102641. 134573. 184753. 216686. 251211.     nan
     nan     nan     nan     nan     nan     nan     nan     nan     nan
     nan]

次に nan 以外の値だけ抽出しましょう。

s1data = s1data[~np.isnan(s1data)]
s2data = s2data[~np.isnan(s2data)]
print(s1data,"\n",s2data)
[ 27941.  58163.  88955. 122028. 152821. 197869. 230942. 265168.] 
 [ 11526.  42197.  71278. 102641. 134573. 184753. 216686. 251211.]

出力からわかるように、それぞれ整数で各S1、S2のフレーム位置を表していると推測できます。ということはフレームの番号をインデックスにしたら対応する心音データの値をとってこれることになります。インデックスとして使いたいので、ここでは整数に変換しましょう。

s1frame = s1data.astype(np.int32)
s2frame = s2data.astype(np.int32)
print(s1frame,"\n",s2frame)
[ 27941  58163  88955 122028 152821 197869 230942 265168] 
 [ 11526  42197  71278 102641 134573 184753 216686 251211]

それでは最後に抽出した II誘導 の波形データを図示してみましょう。

Matplotlibの使い方

import周りの注意点

データの理解において可視化は非常に重要です。

Matplotlibでは、ほとんどの描画機能がpyplot.機能名で提供されています。慣例的にimport matplotlib.pyplot as pltでimportし、plt.機能名でさまざまな描画機能を使えるようにします。

%matplotlib inlineは、Notebook上にグラフを表示するためのマジックコマンドです。忘れやすいので、注意しましょう。

import matplotlib.pyplot as plt
%matplotlib inline

心音データの図示

散布図や円グラフ、棒グラフ、線グラフなどいろんなグラフの種類をこのmatplotlibを使って描画することができます。

今回は心音を図示したいので、点と点を連続的につなげる線グラフを描画します。この点と点を繋げてプロットするには、plt.plot(x, y)を使います。100個の点を図示したかったらそれぞれのx座標とy座標の配列を入れてあげます。配列の形式はリストでもnumpy配列でもどちらでも大丈夫です。

またグラフのスタイルを調節する機能もたくさんあるので、実際に使う時は色々試してみましょう。

scale = 1000 / framerate # 44100 Hz で記録しているので、間隔は 1000 / 44100 ms

# データ点のx座標の配列を指定
x = np.arange(0, T*1000, scale) 

# グラフの大きさを指定
plt.figure(figsize=(20, 10)) 

# plt.plot(x, y)
plt.plot(x, data)

# x軸の名前を指定
plt.xlabel("ms")

# グラフのタイトルを指定
plt.title("Heartbeat (Index: {})".format(index))

# 図を表示
plt.show()

png

おまけの宿題

解答はこちらのgithubからみることができます。

問題1

index = 1 のデータのプロットに重ねて、S1とS2それぞれのフレーム位置を垂直線でマーキングしてみましょう。

# データ点のx座標の配列を指定

# S1, S2 それぞれの座標を指定

# グラフの大きさを指定

# plt.plot(x, y)

# S1とS2を散布図でマーキング

# 各線や点の説明を表示する

# x軸の名前を指定

# グラフのタイトルを指定

# 図を表示

問題2

ある index の心音データの波形を図示する関数を作ってみましょう。

def displayHb(index):
    # index に対応するファイル名の取得

    # ファイルのパスを指定して開く

    # フレーム数・フレームレート・記録時間を取得

    # 全フレームのデータを取得

    # フレームデータを int16 型 のnumpy配列に変換

    # メモリのスケールを指定 [ms]

    # データ点のx座標の配列を指定

    # グラフの大きさを指定

    # plt.plot(x, y)

    # x軸の名前を指定

    # グラフのタイトルを指定

    # 図を表示

# test
# indexが[3, 5, 8, 10, 16]のいずれかはファイルが開けないのでそれ以外

index = 2
displayHb(index)

問題3

ある index の心音データのプロットに重ねてS1とS2のフレーム位置を垂直線でマーキングする関数を作ってみましょう。

def displayHbWithS1S2(index):
    # s1, s2のフレーム位置を取得

    # s1, s2のフレーム位置配列をそれぞれ nan を除いた int32 型 のnumpy配列に変換

    # index に対応するファイル名の取得

    # ファイルのパスを指定して開く

    # フレーム数・フレームレート・記録時間を取得

    # 全フレームのデータを取得

    # フレームデータを int16 型 のnumpy配列に変換

    # メモリのスケールを指定 [ms]

    # データ点のx座標の配列を指定

    # S1, S2 それぞれの座標を指定

    # グラフの大きさを指定

    # plt.plot(x, y)

    # S1とS2を散布図でマーキング

    # 各線や点の説明を表示する

    # x軸の名前を指定

    # グラフのタイトルを指定

    # 図を表示

# test
# indexが[3, 5, 8, 10, 16]のいずれかはファイルが開けないのでそれ以外

index = 2
displayHbWithS1S2(index)

Discussion

コメントにはログインが必要です。