某ヘルスケアデータサイエンティストのブログ

ヘルスケア方面で働くデータサイエンティストが、いろいろやってみたことをメモするブログです。

新型コロナ新規陽性者数と都営地下鉄の利用者数の関連分析

1.目的

SIRモデル等では接触率を変化させることで、将来の状況をシミュレーションするわけですが、これは「接触率と新規感染者数には因果関係がある」という前提の話です。
定性的に考えれば、この前提は正しいとは思いますが、定量的に示している記事などを見たことがありません。
ということで、本稿の目的は当該前提をデータを使って定量的に考察してみる、です。

2.方法

時系列解析で外生変数の影響を分析する、という目的に合致しているモデルを探していたら、どうやら「状態空間モデル」がよさげなので、今回はこのモデルを用いて考察してみます。
状態空間モデルについては、以下のブログを参照しました。
logics-of-blue.com

また、各データの取得元は以下の通りです。
・新規感染者数
toyokeizai.net
・外生変数(都営地下鉄の利用者数の推移)
 メインの出勤時間帯を7時30分~9時30分と考え、当該時間帯のデータを利用。
stopcovid19.metro.tokyo.lg.jp

3.結果と考察

言語はPythonで行ないますが、その前にエクセルで「都営地下鉄の利用者数の推移」を加工します。
当該データは週単位であるため、日単位にするために「線形補完→Grevilleの3次13項式」によりなめらかな曲線にします。
また、感染から発症まで約2週間のタイムラグが存在しているそうなので、2週間前数値を現在値として使用することにします。
計算式などは割愛しますが、結果は以下の通り。徐々に利用者数が増加してますね(当該数値は、ある時点を1とした時のrelativity)。
f:id:tmaj:20200712125221p:plain

準備ができたので、Pythonで分析していきます。

# 基本のライブラリを読み込む
import numpy as np
import pandas as pd
from scipy import stats

# グラフ描画
from matplotlib import pylab as plt
import seaborn as sns
%matplotlib inline

import warnings
warnings.simplefilter('ignore')

# グラフを横長にする
from matplotlib.pylab import rcParams
rcParams['figure.figsize'] = 15, 6

# 統計モデル
import statsmodels.api as sm

#data import
df = pd.read_csv(prefectures.csv')

#加工(東京都に限定する等)
tokyo = df[df['prefectureNameJ']=='東京都']

tokyo['ymd'] = ''
for i in range(len(tokyo)):
    tokyo.iloc[i,9] = str(tokyo.iloc[i,0]) + '/' + str(tokyo.iloc[i,1]) + '/' + str(tokyo.iloc[i,2])

tokyo.ymd = pd.to_datetime(tokyo.ymd)
tokyo = tokyo.set_index("ymd")
tokyo['testedPositive'] = tokyo['testedPositive'].astype('float64')

tokyo['NewPositive'] = 0.0
for i in range(len(tokyo)-1):
    tokyo.iloc[i+1,9] = tokyo.iloc[i+1,5] - tokyo.iloc[i,5]
    
tokyo['ymd_num'] = 0.0
for i in range(len(tokyo)):
    tokyo.iloc[i,10] = tokyo.iloc[i,0]*10000 + tokyo.iloc[i,1]*100 + tokyo.iloc[i,2]

tokyo = tokyo[tokyo['ymd_num']>=20200317]

# 日付形式にする
ts = tokyo['NewPositive'] 

#Subway Passengerをインポート
df2 = pd.read_csv(SubwayPassengers_input.csv')
df2.ymd = pd.to_datetime(df2.ymd)
df2 = df2.set_index("ymd")

ライブラリやデータの準備ができたので、早速モデリングをしてみます。
モデルとしては「季節変動あり+外生因子ありのローカル線形トレンドモデル」を採用しています。

# 季節変動あり+外生因子ありのローカル線形トレンドモデル
mod_season_trend_x = sm.tsa.UnobservedComponents(
    ts,
    'local linear trend',
    seasonal=7,
    exog=x.values
)

# まずはNelder-Meadでパラメタを推定し、その結果を初期値としてまた最適化する。2回目はBFGSを使用。
res_season_trend_x = mod_season_trend_x.fit(
    method='bfgs', 
    maxiter=500, 
    start_params=mod_season_trend_x.fit(method='nm', maxiter=500).params,
)

# 推定されたパラメタ一覧
print(res_season_trend_x.summary())

# 推定された状態・トレンド・季節の影響の描画
rcParams['figure.figsize'] = 15, 20
fig = res_season_trend_x.plot_components()

結果は以下の通り。
外生変数の係数は232.8916ですが、95%CIは0を含んでいます。
ということで、統計的には「何とも言えない」という結論なわけですが、係数はプラスなので、定性的な感覚とは合致する結果となりました。
もちろん「接触率」を「都営地下鉄の利用者数」だけで完全に表現することはできないわけで、今回のモデルでは接触率を表す要素は他の変数で(無理矢理)表現しようとしています(どの変数が頑張っているかはわかりませんが)。

 Unobserved Components Results                            
====================================================================================
Dep. Variable:                  NewPositive   No. Observations:                  115
Model:                   local linear trend   Log Likelihood                -512.541
                   + stochastic seasonal(7)   AIC                           1035.082
Date:                      Sat, 11 Jul 2020   BIC                           1048.447
Time:                              11:42:22   HQIC                          1040.500
Sample:                          03-17-2020                                         
                               - 07-09-2020                                         
Covariance Type:                        opg                                         
====================================================================================
                       coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------------
sigma2.irregular   299.7816     70.591      4.247      0.000     161.426     438.137
sigma2.level       143.6784     77.113      1.863      0.062      -7.461     294.817
sigma2.trend         0.8645      1.179      0.733      0.464      -1.447       3.176
sigma2.seasonal     12.0896      9.912      1.220      0.223      -7.337      31.516
beta.x1            232.8916    185.786      1.254      0.210    -131.243     597.026
===================================================================================
Ljung-Box (Q):                       46.12   Jarque-Bera (JB):                11.90
Prob(Q):                              0.23   Prob(JB):                         0.00
Heteroskedasticity (H):               0.67   Skew:                             0.21
Prob(H) (two-sided):                  0.23   Kurtosis:                         4.58
===================================================================================

f:id:tmaj:20200712130637p:plain

PCR検査の実施基準が変わっているっぽいので、将来予測はあまり意味がないのですが、状態空間モデルでは外生変数を用いて将来予測ができるようなので、念のために計算してみました。

#利用比率の値(7/31まで)
df3 = pd.read_csv(SubwayPassengers_pred.csv')
pred_x = df3.iloc[:,1].as_matrix().reshape(22,1)
# 予測
pred = res_season_trend_x.predict('2020-03-17', '2020-07-31', exog=pred_x)
pred.index = pred.index -1 
# 実データと予測結果の図示
rcParams['figure.figsize'] = 15, 6
plt.plot(ts)
plt.plot(pred, "r")

f:id:tmaj:20200712132015p:plain
案の定、嘘くさい予測になりました_| ̄|○

新型コロナ観察記2020/7/11

1.最新データに基づくアップデート

最新のデータ(7/10更新)に基づいて、時系列分析データをアップデートしました。

2.方法

引き続きstatsmodelsを使用します。

3.結果と考察

3-1. 自己相関係数

これまでと異なり、7日目との自己相関がなくなりました。
もはや過去との関連が薄くなってきているということか。
f:id:tmaj:20200711110912p:plain

3-2. 成分分解

引き続き上昇トレンドですね。
f:id:tmaj:20200711110932p:plain

東京都の新型コロナウィルス時系列データを時系列分析してみる

1.背景

時系列分析の勉強をしている最中に新型コロナウィルスが発生したので、ホットな話題ということもあり、時系列分析をぶつけてみた次第です。

2.方法

・使用データは東洋経済オンラインのサイトから取得しました。
toyokeizai.net

・言語はPython。時系列分析はstatsmodelsを使用します。tsa.seasonal_decomposeによるいわゆる乗法モデルベースの基本成分分解ですね。

3.結果と考察

まずはパッケージとデータのインポートから。

import statsmodels.api as sm
import pandas as pd
import numpy as np
import requests
import io
from matplotlib import pylab as plt
%matplotlib inline
# グラフを横長にする
from matplotlib.pylab import rcParams
rcParams['figure.figsize'] = 15, 6

import warnings
warnings.simplefilter('ignore')

df = pd.read_csv('prefectures.csv')

データの中身。とりあえず、目的である東京だけ見てみる。

tokyo = df[df['prefectureNameJ']=='東京都']
tokyo.tail()

	year	month	date	prefectureNameJ	prefectureNameE	testedPositive	peopleTested	discharged	deaths
4524	2020	6	15	東京都	Tokyo	5592	16351.0	4887.0	314.0
4571	2020	6	16	東京都	Tokyo	5619	16351.0	4936.0	316.0
4618	2020	6	17	東京都	Tokyo	5633	61112.0	4978.0	317.0
4665	2020	6	18	東京都	Tokyo	5674	63083.0	5017.0	317.0
4712	2020	6	19	東京都	Tokyo	5709	64844.0	5071.0	320.0

新規感染者数を分析したいのですが、累積の感染者数しかありません。
なので、手作業で新規感染者数を作りにいきます。

tokyo['NewPositive'] = 0.0
for i in range(len(tokyo)-1):
    tokyo.iloc[i+1,9] = tokyo.iloc[i+1,5] - tokyo.iloc[i,5]
plt.figure(figsize=(15, 8))
plt.plot(tokyo.NewPositive)

f:id:tmaj:20200702203509p:plain
よくみるグラフが出てきました。ぱっと見、上昇トレンドかなと。
次に自己相関係数と偏自己相関係数を見てみましょう。

fig = plt.figure(figsize=(12,8))
ax1 = fig.add_subplot(211)
fig = sm.graphics.tsa.plot_acf(tokyo.NewPositive, lags=30, ax=ax1)
ax2 = fig.add_subplot(212)
fig = sm.graphics.tsa.plot_pacf(tokyo.NewPositive, lags=30, ax=ax2)

f:id:tmaj:20200702203736p:plain
15日前との偏自己相関が大きい。しかもマイナスの偏自己相関になりました。
ということは約2週間前の結果が小さくなると、当日の結果が大きくなる関係にあります。
人々はある日の新規感染者数を見て警戒度を変えるので上記のような関係になるということでしょうか。
すなわち、2週間前の新規感染者数が小さい→油断する→当日の新規感染者数が増える、ということなのかもしれないです。
ただし、トレンドがある場合に偏自己相関係数をそのまま解釈してよいのか、勉強不足故、よくわかりません。
トレンドがある時系列なので、周期性を観察するために1階差分をとって自己相関係数をみてみます。

diff = tokyo.NewPositive.diff()
diff = diff.dropna()
fig = plt.figure(figsize=(12,8))
ax1 = fig.add_subplot(211)
fig = sm.graphics.tsa.plot_acf(diff, lags=30, ax=ax1)

f:id:tmaj:20200702210311p:plain
まあ、7日ですかね。
次に、成分分解をしてみます。

fig, (ax1,ax2,ax3) = plt.subplots(3,1, figsize=(15,8))
seasonal_decompose_res = sm.tsa.seasonal_decompose(tokyo.NewPositive, freq=7)
seasonal_decompose_res.trend.plot(ax=ax1)
seasonal_decompose_res.seasonal.plot(ax=ax2)
seasonal_decompose_res.resid.plot(ax=ax3)

f:id:tmaj:20200702210429p:plain
上昇トレンドにみえますね。
特段何もしなければ、今後も上昇トレンドは続いていくのか、注視が必要でしょう。