うつ日記 ~うつを克服するための日記~

うつ病のため休職してから復帰するための体験、工夫、Androidアプリの紹介、アプリの作成ノウハウなど。アプリはこちら→ https://play.google.com/store/apps/details?id=tokin_kame.utunikki_app

Rを使った体調の予測~うつ日記の活用~

体調は良くはなっているものの、波があって、朝起きてみたら調子が悪くて起き上がれないって時もたまにあります。

予め体調の変化が予測できれば、仕事の予定をずらすとか、予め最低限は終わらせておくとか、休養を多めに取って体調の波を小さくするなど、対策を取ることができるはずです。

 前回は体調の変化を可視化して、周期性がありそうなことがわかりました。

今回は、うつ日記にたまってきたデータを用いて、体調の予測ができないか試してみることにしました。

 

予測の問題と考えた場合、データを予測する対象と、予測するための手掛かりとに分ける必要があります。
予測する対象は、目的変数と呼ばれるのですが、例えば体調の悪さにということになります。
手掛かりは、説明変数と呼ばれますが、今回は、仕事の負荷や出来事、曜日や天候なんかになります。

 

○体調の悪さを表すデータ(目的変数になるようなもの)

 ・調子の良い悪い   5段階の整数 
 ・憂鬱感       3段階の整数 
 ・不安感       3段階の整数 
 ・気が重い      3段階の整数 
 ・つらい、しんどい  3段階の整数 
 ・倦怠感       3段階の整数 
 ・気力        5段階の整数 
 ・日中の眠気     4段階の整数 
 ・早く目が覚める   有無の2値 
 ・朝起きられない   3段階の整数 
 ・頭痛        3段階の整数 
 ・腹痛        3段階の整数 
 ・活動具合      4段階の整数 

 体調の良い悪いや気力のようなものは、プラスとマイナスがあるため段階数が多くなっています。

 

○調子の悪さを予測する手掛かりになりそうなデータ(説明変数として使えそうなもの)
 ・仕事をした          6段階の整数
 ・つぶやき、日記のデータ    文字列 
 ・起床時間           時刻 
 ・就寝時間           時刻 
 ・簡単な活動内容        文字列 
 ・曜日             7つのカテゴリデータ 
 ・処方箋のデータ        薬の種類と量 
 ・天気             幾つかのカテゴリデータ 
 ・気温             実数

 ・湿度             パーセント 
 ・気圧             整数
 ・風速             実数 
 ・風向             0~359の整数 
 ・雲の割合           パーセント 
 ・体調を記録した時の緯度と経度 0~359の実数 

 

Rで簡単に使えそうだったので、ARIMAモデルを用いて、予測を行ってみます。

まずはベースラインとして、体調の良い悪いの過去のデータのみから、未来の体調の良い悪いをどの程度予測できるか試してみます。

3月1日~7月11日 のデータを、3月1日(1日目)~6月2日(94日目)とそれ以降に分けて、6月2日までのデータでモデルを作成し、その後の39日の体調を予測できるか試してみました。

とりあえずは、auto.arimaという関数を使って、予測モデルを作ってみました。 
以下のコマンドで予測モデルを作成しました。
  library(forecast) 
  feel <- read.table(“feel5.txt”) 
  model <- auto.arima(feel, ic=“aic", trace=T, stepwise=F, approximation=F,  start.p=0, start.q=0, start.P=0, start.Q=0) 

以下のコマンドで予測結果を取得しました。 
  forecast(model, level = c(50,95), h = 39) 

モデルのパラメータは(1,0,0)になったが、予測が良くないみたいでした。

 

自分の体調の波は、18日周期があるみたいでしたので、今度はauto.arimaに頼らず、適当にパラメータを決めて、モデルを作成してみました。  
以下のコマンドで予測モデルを作成しました。
  model <- arima(feel, order=c(18,0,1)) 
こうして作ったモデルで39日間の予測をして、実際の体調と比較してみたところ、上がり下がりの傾向が結構一致しているようでした。 
○実測の特徴 
 ・101日目に凄く悪い 
 ・118日~120日の間、凄く悪い 
○予測結果の特徴 
 ・100日目で最も悪く、101日目もかなり悪い 
 ・その後回復して、111日目で一旦落ちて、回復 
 ・119日目に向けて落ちて、120日目は一旦上がり121日目に少し落ちてから上がる 

f:id:tokin_kame:20150715205418j:plain

 

まぁ、元々規則性が強かったってのはあるけど、傾向は結構予測できている感じ。

この方法による予測がある程度参考になるとして、私の明日からの体調はいったいどうなるのか、予測してみました。

 

f:id:tokin_kame:20150718084023j:plain

これによると、今週は金曜まで比較的低調で、木曜日に特に調子が悪くなるとのことです。
なんか、今週の占いみたいな感じがしてきましたが。

これを見ながら、一週間を過ごしてみました。

結果はと言うと、微妙です。
木曜日の体調は、良くはけど、凄く悪いってほどでもなく、どちらかと言えば悪いというぐらいでした。
まぁ、水曜日よりは悪くなっていましたので、調子の上がり下がりの傾向で言えば当たってると言えなくもないのかもしれません。
金曜日には少し回復しましたし。 

まぁ、元々、体調の良い悪いのデータだけで予測するのは無理があるとは思ってたんですけどね。
途中で処方も変わってますし。
ここをベースラインとして、他のデータと組み合わせることで、どこまで精度を上げられるかですね。
他のデータを組み合わせるためには、Rの使い方ももっと勉強しないといけません。

それはそれとして、予測結果をもっと簡単に出せるようにしたいです。
うつ日記に、データをサーバへ送る機能を追加したい。
家のパソコンをWebサーバにして、WebAPIを作って予測結果を返すようにすれば、気軽に予測を出して、1日ごとに予測精度を評価できますね。

ということで、今後の課題は、以下のような感じになります。
 ・体調の良い悪い以外のデータも活用して予測精度を上げること。
 ・ARIMAモデルのパラメータをもっとちゃんと決めて最適化すること。
 ・アプリに予測用のサーバとデータをやり取りする機能を実装すること。


 

うつ日記 ~うつを克服するための日記~
https://play.google.com/store/apps/details?id=tokin_kame.utunikki_app 

体調の変化を可視化する~うつ日記の活用~

体調は良くはなっているものの、波があって、朝起きてみたら調子が悪くて起き上がれないって時もたまにあります。

突然動けなくなるのって、困りものです。

うつ日記にデータもたまってきたことですし、体調の変化を分析してみたいと思います。

 

○活用できそうなデータ

うつ日記を使って体調を記録することで残るデータと、普段のつぶやきや日記などSNSに残っているデータ、そのほかには仕事のスケジュールなどのデータが活用できるかと思います。

 ・毎日の体調の記録(うつ日記)
 ・その日ごとの活動具合や活動内容など(うつ日記)
 ・天候の情報(うつ日記)

 ・処方されている薬の種類と量(うつ日記)

 ・つぶやきや日記などの内容(SNS)
 ・必要があれば業務内容なども(仕事のスケジュールなど)

 

 ちなみに、うつ日記では、以下のような感じで、体調を記録します。

f:id:tokin_kame:20150715205416p:plain

 

天候は、体調を記録した時に自動的に記録されます。

f:id:tokin_kame:20150531104217p:plain

たまったデータをCSV形式で表示する機能がありますので、それをコピーペーストしてパソコンで処理することにしました。

 

○体調の可視化

調子の良い悪いがどういう傾向で変化しているか見てみることにしました。

処方の量も安定し、復職した後の3月1日から、7月11日までの期間でグラフにしてみた。f:id:tokin_kame:20150715205417j:plain

上に行くほど体調が良く、下に行くほど体調が悪いことを表します。
左端が3月1日、右端が7月11日です。
こうしてみると、周期的に体調が変化しているようにも見えます。

4つの深い谷の間を数えてみると、ほとんど正確に18日周期になってることがわかりました。

理由はわかりませんが、周期性があるのなら、予測もできるかもしれません。

ということで、次回は予測を試してみることにします。

 

 

天気の情報を取得する~Androidアプリの作り方~

スマートフォンのセンサと、Webに公開されている情報を利用するひとつの例として、位置情報を取得してその位置に対応する天気の情報を取得する方法についてまとめてみました。

 

位置情報の取得は、以下のページを参考にすれば、それほど問題なく出来ました。

Android 位置情報を取得するには / Getting Started | Tech Booster

 

天気の情報は、OpenWeatherMapというところで提供しているAPIを使わせて頂くことにしました。

OpenWeatherMap current weather and forecast

 

APIのライセンスについては、以下のページに書いてありますが、クリエイティブ・コモンズ・ライセンスの中の、「cc-by-sa」というものだそうです。

price_detailes

このライセンスについて、以下に解説がありますが、使用する場合に「OpenWeatherMap」から提供されたデータを使っているというのを表示して、リンクを付ければ良いようです。

Creative Commons — 表示 - 継承 2.1 日本 — CC BY-SA 2.1 JP

 

具体的になんて書くのが良いのか迷いましたが、結局ありがちな感じで、「Powered by OpenWeatherMap」と入れることにしました。

 

アプリからのリンクのはり方ですが、別のAtcivityへ移る時と同じようにonClickLintnerを使って、以下のようにすれば、出来ました。

  OnClickListener link_Listener = new OnClickListener() {
    public void onClick(View v) {
      Uri uri = Uri.parse("http://openweathermap.org/");
      Intent i = new Intent(Intent.ACTION_VIEW,uri);
      startActivity(i);
    }
  };

 

これを応用して、緯度と経度をgoogleMapに渡して表示させるというのも、こんな感じで出来ました。

  OnClickListener map_Listener = new OnClickListener() {
    public void onClick(View v) {
      TextView data1 = (TextView)findViewById(2000);
      TextView data2 = (TextView)findViewById(4000);
      Uri uri = Uri.parse("http://maps.google.com/maps?q=" + data1.getText().toString() + "," + data2.getText().toString());
      Intent i = new Intent(Intent.ACTION_VIEW,uri);
      startActivity(i);
    }
  }; 

※緯度と経度が、2000と4000のIDのTextViewに書かれている場合の例です。

 

OpenWeatherMapから取得できるデータは天気のデータだけでも色々あります。

形式もJASON形式とXML形式があります。

例えば、以下のようなにすれば、データを取得できます。

  // 緯度の取得
  double latitude = location.getLatitude();
  // 経度の取得
  double longitude = location.getLongitude();
  String requestURL = "http://api.openweathermap.org/data/2.5/weather?lat=" + String.valueOf(latitude) + "&lon=" + String.valueOf(longitude) + "&mode=xml";
  URL url;
  try {
    url = new URL(requestURL);
    InputStream is = url.openConnection().getInputStream();
    BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
    StringBuilder sb = new StringBuilder();
    String line;
    while (null != (line = reader.readLine())) {
      sb.append(line.replace(BR, ""));
    }

  } catch (MalformedURLException e) {

    // TODO Auto-generated catch block
    e.printStackTrace();
  } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
  }

として、sb.toString();を参照することで、データを取得できます。

 

JavaのクラスでJASON形式のハンドリングを試みましたが、うまくいかなかったので、今回はXML形式を使うことにしました。

XML形式といっても複雑な構造ではないので、タグをパターンでマッチングさせてやれば結構簡単にハンドリングできました。

本当はもっとちゃんとしたやり方があるはずですけど。

あとは表形式に変換してやると以下のような感じになりました。

 

f:id:tokin_kame:20150531104217p:plain

私が使っているスマートフォンでは位置情報の精度が悪いみたいで結構離れた場所の天気をもらってくることもあるのですが、まぁ、天気なので実用上は問題ないかと思います。

 

最近はオープンデータが話題になっていますが、こんな感じで、WebAPIからデータを取得できたので、色々な応用ができるかもしれません。

 

天気によって、体調が左右されるというお話もよく聞きますので、天気を記録する機能をアプリに追加しました。

 

うつ日記 ~うつを克服するための日記~
https://play.google.com/store/apps/details?id=tokin_kame.utunikki_app

 

Android上で使われている改行コードは何か~Androidアプリの作り方~

editText なんかにテキストを表示する時に、改行を入れるにはどうしたら良いかわからなくて困りました。

調べてみると、最初に以下のようにして、改行コードを取得しておいて、あとはこの「BR」を入れてやれば改行が入ることになります。

  static final String BR = System.getProperty("line.separator");

 この方法では、実行する環境での改行コードを取得することができるので、環境に依存せずに動作することになります。

例えば、以下のように使用します。

  text = "Version:" + versionName + BR;

  edit = (EditText) findViewById(R.id.edit);
  edit.setText(text);

 

ただ、他のOSとファイルでデータの受け渡しをする時などは、改行コードの実体がわからないままでは不便だと思いますので、以下のようにして改行の文字コードを調べてみました。

  int code_br1 = (int)BR.toCharArray()[0];//BRを文字配列に変換してその最初の文字を取る

  test = String.valueOf(Integer.toHexString(code_br1)) + BR;//16進数にして文字列に変換

  edit.setText(test);//EditTextに表示

 

これを実行してみた結果、表示された値は「a」でした。

16進数で表示させているわけなので、文字コードは「0x0a」ということでしょう。

つまり、Androidで使われている文字コードLinuxと同じくLFというわけですね。

ちなみにBRの2文字目を取ろうとするとエラーになってしまうので、文字コードは1バイトということだと思います。

 Windowsで作成したファイルを読み込む時などには注意が必要かもしれません。

 

 

うつ日記 ~うつを克服するための日記~
https://play.google.com/store/apps/details?id=tokin_kame.utunikki_app

 

バックアップ機能の実装 ~Androidアプリの作り方~

実用的なアプリを作るには、バックアップ機能は必須だと思うので何とか作ってみた。

わかってみるとそんなに難しくないんだけど、ちょっとつっかかるところもあったので、まとめてご紹介します。

 

○バックアップを保存する機能の実装

・保存するディレクトリ名を決める

毎回入力するのは面倒なので、年月日と時間で勝手にディレクトリ名を生成するようにした。

日時を取得する方法を使って以下のようにした。

 Calendar now = Calendar.getInstance();
 date = String.format("%04d%02d%02d_%02d%02d%02d", now.get(now.YEAR), now.get(now.MONTH) + 1, now.get(now.DATE), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND));

毎回桁数が変わらないようにString.formatを使って、「1」→「01」のように0を追加している。

 

・ディレクトリを作成する

上記で作成した文字列に、わかりやすいように「backup_」という文字列を足して、ディレクトリを作成する。

  String dir = getFilesDir() + "/backup_" + date;
  File file = new File(dir);
  if(file.mkdir() == true){

    //ここにファイルをコピーする処理を書く

  }

file.mkdir()を実行して、ディレクトリの作成に成功したらファイルのコピーを行うようにする。

 

・ファイルをコピーする

ファイルを単純にコピーできればよかったんだけど、調べても方法がわからなかったので、一旦読み込んで、書き込むということをする。

以下の例では、readStringにデータを読み込んであるとした時の、書き込み処理。

  try {
    OutputStream fileOutputStream = new FileOutputStream(dir + "/" + file_name);
    fileOutputStream.write(readString.getBytes());
    fileOutputStream.close();
    err_flag = "OK";
  } catch (FileNotFoundException e) {
    err_flag = "NG";
  } catch (IOException e) {
    err_flag = "NG";
  }

ファイルの書き込みは、上記のようにFileOutputStreamを使うと、パス(ディレクトリ名)を指定できる。

openFileOutputでは、パスは指定できないらしい。

 

○バックアップを削除する機能の実装

削除するバックアップのディレクトリを取得する操作は別途作るとして、削除するバックアップのディレクトリがdirとして得られたとすると以下のように削除することができる。

  if(dir.matches(".*/backup_[0-9]+_[0-9]+") == true){

    int i;

    for(i = 0; i < 6; i++){
      File file = new File(dir + "/" + file_name[i]);
      file.delete();
    }
    File del_dir = new File(dir);
    del_dir.delete();

  }

念のため、ディレクトリ名をチェックした後、ファイル名のリストfile_name[i]について、file.delete()でファイルを削除した後に、del_dir.delete()でディレクトリを削除している。

 

○バックアップを復元する機能の実装

処理的には、バックアップを保存する処理とたいして変わらないが、パスを指定して、読み込む場合には、BufferedReaderというのを使うとパスを指定して読み込むことができる。

  File file = new File(dir + "/" + file_name[i]);

  FileReader filereader = new FileReader(file);
  BufferedReader br = new BufferedReader(filereader);
  while ( (readText = br.readLine()) != null ){
    readString += readText;

  }
  br.close();

・バックアップリストを表示する機能 の実装

特定のディレクトリの下にあるファイル名のリストを取得して、ディレクトリ名のパターンで絞り込んでいる。

  File cd = getFilesDir();
  for(int j = 0; j < cd.listFiles().length; j++){
    if(cd.listFiles()[j].getName().matches("backup_[0-9]+_[0-9]+") == true){
      text += cd.listFiles()[j].getName() + BR;

      id++;
    }
  }

 

これらを組み合わせると、アプリがインストールされているディレクトリの下にバックアップを保存することができるようになる。

 

画面に合わせてサイズを決める ~Androidアプリの作り方~

Androidのアプリを作る時に戸惑ったのが、端末の画面に合わせて、ボタンやスクロールなどのviewの大きさを調節すること。
端末によって解像度(1インチ幅のドット数)も違えば、画面の大きさも違うので、どの端末でも画面からはみ出したり、余計な隙間を作ったりしないようにするのは、結構難しかった。

ネットで調べると、解像度や画面の大きさを取得する方法や、画像を一旦設定して画面上でのサイズを取得する方法が見つかったので、色々試してはみたものの、上手くいかなかった。
デバイスのDPIを取得 と dip ⇔ px の変換 - 戌印-INUJIRUSHI- (Androidあれこれ) -
【Android】画面サイズに合わせて、動的にView要素のサイズの変更 - Qiita
まぁ、やり方が悪かっただけかもしれないが。
結局のところ、ぴったり合わせるのは難しかったが、簡単な方法でおおよそ上手くいったので、書き残しておく。

今回のは、大雑把に言うと、画面の大きさを取得して、viewのサイズは画面の大きさに対する比率で計算するという方法。

そもそも、画面上の部品にサイズを指定する方法には、2種類ある。
1つ目は、xmlファイルに書く方法で、文字のサイズを指定する場合には、以下のような感じ。

もう1つは、javaで後から変更する方法で、上記のxmlタグに対して、IDを使って以下のように設定する。
TextView text = (TextView) findViewById(R.id.text_id);
text.setTextSize(24);

サイズが固定のボタンやタイトルのようなものは、xmlファイルに書く方が無難。
画面に合わせて、ボタンを大きくしたり、できるだけviewを広くしたい場合には、javaで指定する方法を使うことになる。
javaで指定する場合には、多分ピクセル数を引数として与えるんだけど、いくつにすれば良いのかよくわからない。
解像度が高くなったり、画面が大きくなったりすればピクセル数が多くなるはずなので、その分引数に大きな値を入れてやれば良いのかなと思うんだけど、結局良くわからなかった。
しょうがないので、絶対値で与えるのはあきらめて、画面サイズに対する比率で与えることにした。

まず、画面サイズは以下のように取得する。
WindowManager wm = (WindowManager)getSystemService(WINDOW_SERVICE);
Display disp = wm.getDefaultDisplay();
これで、disp.getHeight()としてやれば、画面の高さが得られるし、disp.getWidth()としてやれば、幅が得られる。
例えば、以下のようにすれば、画面の半分の幅のボタンにできる。
Button b = (Button)findViewById(R.id.button_id);
b.setWidth( (int)(Double.valueOf(disp.getWidth()) / 2.0));

スクロールビューの場合はちょっと複雑で、以下のように、一旦パラメータを取得してからサイズを指定すると上手くいった。

ScrollView scrollview1 = (ScrollView)findViewById(R.id.scroll_id);
LayoutParams params1;
params1 = scrollview1.getLayoutParams();
params1.height = (int)(Double.valueOf(disp.getHeight()) * 4.0 / 7.0);
scrollview1.setLayoutParams(params1);

ここでは、画面の大きさの4/7をサイズとして指定している。
ボタンやテキストと並べたりする時に、画面の大きさとの比率をどれぐらいにするかは、試行錯誤するしかない。
そういう意味では、かなり雑な方法ではあるけど、実用的ではある。