長岡市内の新型コロナウイルス感染症患者の発生状況を可視化してみた

## やったこと

長岡市HPに掲載されている「[長岡市内での新型コロナウイルス感染症患者の発生状況](https://www.city.nagaoka.niigata.jp/kurashi/cate96/hasseijokyo.html)」のデータを取得し、グラフにしてみました。毎日自動更新されるようになっているので、後日開くとグラフ等が更新されているはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/489030/7767ab6e-23db-d35a-dd70-1f14a6ca3761.png)

https://datastudio.google.com/reporting/245ab32a-3034-4688-b40d-99807250b3d8

## なんでやろうと思ったの？

気になったから＆調べた感じ誰もやってなさそうだったから。

## どうやってやったの？

1. 長岡市HPをスクレイピングして、発生状況データを取得する
1. Google Data Studioで発生状況データを可視化
1. Google Apps Scriptでデータの自動更新を実現

## 1. 発生状況データの取得

長岡市HPをスクレイピングして、発生状況データを取得します。今回、スクレイピング環境にはGoogle Colaboratoryを使いました。理由はプロキシやネットワークなどの問題を考えずにサクッとスクレイピングできるからです。

スクレイピングするページは次の3つです。

- ① [長岡市内での新型コロナウイルス感染症患者の発生状況](https://www.city.nagaoka.niigata.jp/kurashi/cate96/hasseijokyo.html)
- ② [令和4年1月18日～3月31日に県が公表した市内感染症患者（PDF 276KB）](https://www.city.nagaoka.niigata.jp/kurashi/cate96/file/hasseijokyo04.pdf)
- ③ [令和4年1月17日以前に県が公表した市内感染症患者（1～941例目※取り下げ1例あり）（PDF 1,089KB）](https://www.city.nagaoka.niigata.jp/kurashi/cate96/file/20220117hasseijokyo.pdf)

①はPandasに用意されている [pandas.read_html](https://pandas.pydata.org/docs/reference/api/pandas.read_html.html)という便利なメソッドで簡単に取得できます。

②、③はPDFを読み取る必要があります。今回、PDFの読み取りツールには[pdfminier.six](https://pypi.org/project/pdfminer.six/)を利用しました。理由は、日本語に対応している＆表のセルを解釈して読み取ってくれるからです。

実際に、②を読み取った結果が以下の通りです。

```Python
>>> from pdfminer.high_level import extract_text
>>> text = extract_text('hasseijokyo03.pdf')
>>> repr(text)
'長岡市内における感染者の発生状況\n（令和4年1月18日以降に新潟県が公表した市内感染症患者）\n\n公表日\n\n新規感染者数\n\n累計感染者数\n\n備考\n\n3月31日（木）\n\n3月30日（水）\n\n3月29日（火）\n\n3月28日（月）\n\n3月27日（日）\n\n3月26日（土）\n\n3月25日（金）\n\n3月24日（木）\n\n3月23日（水）\n\n3月22日（火）\n\n3月21日（月）\n\n3月20日（日）\n\n3月19日（土）\n\n3月18日（金）\n\n3月17日（木）\n\n3月16日（水）\n\n3月15日（火）\n\n3月14日（月）\n\n3月13日（日）\n\n3月12日（土）\n\n3月11日（金）\n\n3月10日（木）\n\n3月9日（水）\n\n3月8日（火）\n\n3月7日（月）\n\n3月6日（日）\n\n3月5日（土）\n\n3月4日（金）\n\n3月3日（木）\n\n3月2日（水）\n\n3月1日（火）\n\n2月28日（月）\n\n2月27日（日）\n\n2月26日（土）\n\n2月25日（金）\n\n81人\n\n72人\n\n53人\n\n85人\n\n116人\n\n131人\n\n80人\n\n50人\n\n60人\n\n89人\n\n39人\n\n75人\n\n44人\n\n67人\n\n83人\n\n75人\n\n31人\n\n30人\n\n56人\n\n28人\n\n49人\n\n34人\n\n45人\n\n29人\n\n32人\n\n24人\n\n33人\n\n27人\n\n28人\n\n29人\n\n19人\n\n15人\n\n21人\n\n54人\n\n19人\n\n4,702人\n\n3月31日付新潟県発表資料\n\n4,621人\n\n3月30日付新潟県発表資料\n\n4,549人\n\n3月29日付新潟県発表資料\n\n4,496人\n\n3月28日付新潟県発表資料\n\n4,411人\n\n3月27日付新潟県発表資料\n\n4,295人\n\n3月26日付新潟県発表資料\n\n4,164人\n\n3月25日付新潟県発表資料\n\n4,084人\n\n3月24日付新潟県発表資料\n\n4,034人\n\n3月23日付新潟県発表資料\n\n3,974人\n\n3月22日付新潟県発表資料\n\n3,885人\n\n3月21日付新潟県発表資料\n\n3,846人\n\n3月20日付新潟県発表資料\n\n3,771人\n\n3月19日付新潟県発表資料\n\n3,727人\n\n3月18日付新潟県発表資料\n\n3,660人\n\n3月17日付新潟県発表資料\n\n3,577人\n\n3月16日付新潟県発表資料\n\n3,502人\n\n3月15日付新潟県発表資料\n\n3,471人\n\n3月14日付新潟県発表資料\n\n3,441人\n\n3月13日付新潟県発表資料\n\n3,385人\n\n3月12日付新潟県発表資料\n\n3,357人\n\n3月11日付新潟県発表資料\n\n3,308人\n\n3月10日付新潟県発表資料\n\n3,274人\n\n3月9日付新潟県発表資料\n\n3,229人\n\n3月8日付新潟県発表資料\n\n3,200人\n\n3月7日付新潟県発表資料\n\n3,168人\n\n3月6日付新潟県発表資料\n\n3,144人\n\n3月5日付新潟県発表資料\n\n3,111人\n\n3月4日付新潟県発表資料\n\n3,084人\n\n3月3日付新潟県発表資料\n\n3,056人\n\n3月2日付新潟県発表資料\n\n3,027人\n\n3月1日付新潟県発表資料\n\n3,008人\n\n2月28日付新潟県発表資料\n\n2,993人\n\n2月27日付新潟県発表資料\n\n2,972人\n\n2月26日付新潟県発表資料\n\n2,918人\n\n2月25日付新潟県発表資料\n\n1 / 3 ページ\n\n\x0c長岡市内における感染者の発生状況\n（令和4年1月18日以降に新潟県が公表した市内感染症患者）\n\n公表日\n\n新規感染者数\n\n累計感染者数\n\n備考\n\n2月24日（木）\n\n2月23日（水）\n\n2月22日（火）\n\n2月21日（月）\n\n2月20日（日）\n\n2月19日（土）\n\n2月18日（金）\n\n2月17日（木）\n\n2月16日（水）\n\n2月15日（火）\n\n2月14日（月）\n\n2月13日（日）\n\n2月12日（土）\n\n2月11日（金）\n\n2月10日（木）\n\n2月9日（水）\n\n2月8日（火）\n\n2月7日（月）\n\n2月6日（日）\n\n2月5日（土）\n\n2月4日（金）\n\n2月3日（木）\n\n2月2日（水）\n\n2月1日（火）\n\n1月31日（月）\n\n1月30日（日）\n\n1月29日（土）\n\n1月28日（金）\n\n1月27日（木）\n\n1月26日（水）\n\n1月25日（火）\n\n1月24日（月）\n\n1月23日（日）\n\n1月22日（土）\n\n1月21日（金）\n\n24人\n\n40人\n\n26人\n\n29人\n\n51人\n\n55人\n\n59人\n\n44人\n\n79人\n\n35人\n\n48人\n\n42人\n\n48人\n\n62人\n\n86人\n\n51人\n\n74人\n\n76人\n\n81人\n\n74人\n\n87人\n\n96人\n\n82人\n\n40人\n\n35人\n\n49人\n\n40人\n\n65人\n\n60人\n\n48人\n\n45人\n\n24人\n\n47人\n\n28人\n\n37人\n\n2,899人\n\n2月24日付新潟県発表資料\n\n2,875人\n\n2月23日付新潟県発表資料\n\n2,835人\n\n2月22日付新潟県発表資料\n\n2,809人\n\n2月21日付新潟県発表資料\n\n2,780人\n\n2月20日付新潟県発表資料\n\n2,729人\n\n2月19日付新潟県発表資料\n\n2,674人\n\n2月18日付新潟県発表資料\n\n2,615人\n\n2月17日付新潟県発表資料\n\n2,571人\n\n2月16日付新潟県発表資料\n\n2,492人\n\n2月15日付新潟県発表資料\n\n2,457人\n\n2月14日付新潟県発表資料\n\n2,409人\n\n2月13日付新潟県発表資料\n\n2,367人\n\n2月12日付新潟県発表資料\n\n2,319人\n\n2月11日付新潟県発表資料\n\n2,257人\n\n2月10日付新潟県発表資料\n\n2,171人\n\n2月9日付新潟県発表資料\n\n2,120人\n\n2月8日付新潟県発表資料\n\n2,046人\n\n2月7日付新潟県発表資料\n\n1,970人\n\n2月6日付新潟県発表資料\n\n1,889人\n\n2月5日付新潟県発表資料\n\n1,815人\n\n2月4日付新潟県発表資料\n\n1,728人\n\n2月3日付新潟県発表資料\n\n1,632人\n\n2月2日付新潟県発表資料\n\n1,550人\n\n2月1日付新潟県発表資料\n\n1,510人\n\n1月31日付新潟県発表資料\n\n1,475人\n\n1月30日付新潟県発表資料\n\n1,426人\n\n1月29日付新潟県発表資料\n\n1,386人\n\n1月28日付新潟県発表資料\n\n1,321人\n\n1月27日付新潟県発表資料\n\n1,261人\n\n1月26日付新潟県発表資料\n\n1,213人\n\n1月25日付新潟県発表資料\n\n1,168人\n\n1月24日付新潟県発表資料\n\n1,144人\n\n1月23日付新潟県発表資料\n\n1,097人\n\n1月22日付新潟県発表資料\n\n1,069人\n\n1月21日付新潟県発表資料\n\n2 / 3 ページ\n\n\x0c長岡市内における感染者の発生状況\n（令和4年1月18日以降に新潟県が公表した市内感染症患者）\n\n公表日\n\n新規感染者数\n\n累計感染者数\n\n備考\n\n1月20日（木）\n\n1月19日（水）\n\n1月18日（火）\n\n37人\n\n26人\n\n28人\n\n1,032人\n\n1月20日付新潟県発表資料\n\n995人\n\n1月19日付新潟県発表資料\n\n969人\n\n1月18日付新潟県発表資料\n\n3 / 3 ページ\n\n\x0c'
```

あとは、読み取り結果を整形してテーブルデータにします。整形はPandasのDataFrameや正規表現などを活用しながら気合でやっていきます。今回、気合で頑張ったコードを以下に示します。

- ①、②：https://colab.research.google.com/drive/1xA7O8HVsGSgbY-iGYP2nZiTHXf9ODJNw?usp=sharing
- ③：https://colab.research.google.com/drive/1Kitnq2bQJ9x5lDgsH6yNS8j7EmQKhyWP?usp=sharing

最後に、①、②、③を結合してCSVファイルとして出力します（今回の出力結果は[コチラ](https://drive.google.com/file/d/1OEcebyYms2QJFra0F-_4VMXGcqogBp4U/view?usp=sharing)）。

注意点として、②と③の境目となる1/17のデータは欠損します。

## 2. データの可視化

先ほど出力したCSVファイルのデータを可視化します。可視化方法は、可視化したグラフ等を簡単に共有できる＆Google Apps Scriptを使えば自動更新がサクッとできるということで[Google Data Studio](https://developers.google.com/datastudio?hl=ja)を採用しました。Google Data Studioについて雑に紹介すると、BIツール等によくあるダッシュボード的なやつを簡単に作れるサービスです。

Google Data Studioでの可視化は以下の手順で行いました。Google Data Studioの細かい操作は参考記事に貼った記事を参考にしたのでそちらをご参照ください。

1. 先ほど出力したCSVファイルを適当にGoogleドライブへアップロードして、スプレッドシートに変換
1. Google Data Studioでレポートを新規作成し、データソースに変換したスプレッドシートを接続

今回、変換したスプレッドシート及びレポートは以下の通りです。

- [スプレッドシート](https://docs.google.com/spreadsheets/d/1-df8012qcQHiwOWgvc65enWMqnhPGfgi8PwyFBSPr0Y/edit?usp=sharing)
- [Google Data Studio レポート](https://datastudio.google.com/reporting/245ab32a-3034-4688-b40d-99807250b3d8)

## 3. 自動更新の実現

長岡市HPの「[長岡市内での新型コロナウイルス感染症患者の発生状況](https://www.city.nagaoka.niigata.jp/kurashi/cate96/hasseijokyo.html)」は毎日更新されるので、それを自動でGoogle Data Studioにも反映するようにします。スプレッドシートの自動更新はGoogle Apps Scriptを使えば実現できます。今回は以下のような自動更新スクリプトを作成しました。

1. 発生状況のページをスクレイピングして、データを取得
1. 取得データをスプレッドシートに追記

なお、単純に追記してしまうと同じ日のデータが複数できてしまうため、次の記事を参考にしてそうならないようにしています。

- [【Google Apps Script】その5　該当する日付の行が存在したら上書き、存在しなかったら挿入する - Qiita](https://qiita.com/rf_p/items/f423ed2b23b789b2ebe9)

スクリプトは毎日23:00～24:00の間に自動実行するようスケジュールしました。この時間に設定した理由は、一日の最後の1時間でキリが良いからです。スケジュール方法は次の記事を参考にしました。

- [【Google Apps Script】その8　スクリプトを定期実行し、ビットコインの1分ごとの価格を自動取得する - Qiita](https://qiita.com/rf_p/items/267a8d9daa8c9f1ef027)

## おまけ：7日間移動平均を計算→可視化する

新規感染者数は日々の増減が激しく、そのままだと増減の傾向が読み取りづらかったりするので、移動平均を取って読み取りやすくします。窓幅は1週間＝7日間くらいが良いかなと思いました。移動平均の計算は、先の自動更新スクリプトでスプレッドシートへの追記後に以下の処理を実行するようにして行っています。

1. スプレッドシート上の全データを取得
1. 移動平均を計算してスプレッドシートに追記（最初の6日間は0として扱いました）

なお、移動平均の計算方法ですが、当初はスプレッドシートの関数で計算していました。計算式の例を以下に示します。 `IF(A8="",TRUE,FALSE)` で該当する日付にデータが存在しないか調べ、存在する場合のみ移動平均を計算し、存在しない場合は空文字を表示します。この計算式をD列の2行目から最終行まで貼り付けます。

<img width="475.5" alt="image.png (18.0 kB)" src="https://img.esa.io/uploads/production/attachments/14611/2022/04/26/74743/8fd373d0-5f1d-43a2-b772-3637a25be3b3.png">

しかしこの方法だと、次回スクリプトを実行した際に、スプレッドシートへの追記位置がスプレッドシートの最終行となってしまい、意図した挙動になりませんでした。

<img width="347.25" alt="image.png (3.9 kB)" src="https://img.esa.io/uploads/production/attachments/14611/2022/04/26/74743/1d48dc13-b7ca-4eba-ac13-fa3e3af2a01f.png">

原因はスプレッドシート追記の際に利用している[appendRow](https://developers.google.com/apps-script/reference/spreadsheet/sheet#appendrowrowcontents)メソッドです。appendRowメソッドはデータが入力されている行の1つ下の行にデータを追加するメソッドです。先の計算式をD列の最終行まで張り付けてしまった結果、データが入力されている行がスプレッドシートの最終行となってしまっていたのです。

## まとめ

今回は、長岡市HPに掲載されている「長岡市内での新型コロナウイルス感染症患者の発生状況」のデータを可視化し、自動更新するようにしてみました。やってみた感想としては、予想以上に簡単にデータの可視化＆自動更新の仕組みが作れて面白かった、という感じです。

ここまでお読みいただきありがとうございました:bow:

## 参考記事

- PDFからのテキスト抽出：
    - [PythonでPDFからテキストを読み取る方法について - ガンマソフト株式会社](https://gammasoft.jp/blog/python-parse-pdf-contents/)
    - [pdfminer.six · PyPI](https://pypi.org/project/pdfminer.six/)
    - [Extract text from a PDF using Python — pdfminer.six __VERSION__ documentation](https://pdfminersix.readthedocs.io/en/latest/tutorial/highlevel.html)

- Pandas関連：
    - [Python, pandasでwebページの表（htmlのtable）をスクレイピング | note.nkmk.me](https://note.nkmk.me/python-pandas-web-html-table-scraping/)
    - [pandasで窓関数を適用するrollingを使って移動平均などを算出 | note.nkmk.me](https://note.nkmk.me/python-pandas-rolling/)

- Google Data Studio：
    - [【連載】第1回 Google データスタジオ講座 Google データスタジオを使ってみよう - Unyoo.jp](https://unyoo.jp/2018/02/google-data-studio/)

- GAS関連：
    - Hello World：
        - [【Google Apps Script】その1　Hello, world! - Qiita](https://qiita.com/rf_p/items/0535ec495df8f9d2b894)
        - [【Google Apps Script】その2　スプレッドシートのデータと連携する - Qiita](https://qiita.com/rf_p/items/38faa78243ee238265ff)
    - スクレイピング：
        - [Google Apps Script(GAS)でwebスクレイピング](https://tech.anti-pattern.co.jp/google-apps-script-gas-dewebsukureipingu/)
        - [Parserライブラリを使用してGASでスクレイピングをする | specially198のブログ](https://specially198.com/scraping-with-gas-using-parser-library/)
    - 日付：
        - [GoogleAppsScriptで現在日時の取得](http://www.googleappsscript.info/2017-07-27/get_now.html)
    - 型付け：
        - [GASの新エディタで自動補完を効かせて快適にプログラムを組もう – 経理業務向けGoogle Apps Script講座](https://gas.excelspeedup.com/gas_neweditor/)
        - [Use JSDoc: @see](https://jsdoc.app/tags-see.html)
    - フォーマット：
        - [GAS（Google Apps Script） で ゼロ埋め – エンジニアブログ](https://blog.ark3.net/2018/07/04/157/)
        - [Google Apps Scriptの文字列フォーマット系関数 - Bye Bye Moore](https://shuzo-kino.hateblo.jp/entry/2018/07/26/232414)
        - [Google Apps Script(GAS)で文字列を切り出し・抽出(slice,substring,substr)の解説 | AutoWorker〜Google Apps Script(GAS)とSikuliで始める業務改善入門](https://auto-worker.com/blog/?p=594)
        - [Google Apps Scriptの文字列を検索する方法(indexOf,lastIndexOf,searchメソッド) | AutoWorker〜Google Apps Script(GAS)とSikuliで始める業務改善入門](https://auto-worker.com/blog/?p=937)
    - 更新：
        - [【Google Apps Script】その5　該当する日付の行が存在したら上書き、存在しなかったら挿入する - Qiita](https://qiita.com/rf_p/items/f423ed2b23b789b2ebe9)
        - [【Google Apps Script】その8　スクリプトを定期実行し、ビットコインの1分ごとの価格を自動取得する - Qiita](https://qiita.com/rf_p/items/267a8d9daa8c9f1ef027)