欠損値のあるデータの折れ線グラフ
東京都_新型コロナウイルス陽性患者発表詳細
URL: https://stopcovid19.metro.tokyo.lg.jp/data/130001_tokyo_covid19_patients.csv
このデータファイルは,陽性者一人ずつのデータが 1 行ごとに入力されている。
したがって,ある日の陽性者数はカウントするしかない。
また,わが国で COVID19 の陽性者が確認された初期では,陽性者の確認がなかった日もあるので,その日付でのデータはない(欠損値ですらない)。
そのような状況であることを確認していないと不適切なグラフを描くことになってしまうよ!ということを示そう。
入力データセットで,必要なのは陽性者であると公表された年月日つまり "公表_年月日" 列のデータである。この列のみを入力しデータフレームとする。
using CSV, DataFrames
df = CSV.read("/Users/foo/Downloads/130001_tokyo_covid19_patients.csv", DataFrame, select=[:公表_年月日]);
println(size(df))
first(df, 2)
(374931, 1)
2 rows × 1 columns
公表_年月日
Date
1 Date("2020-01-24")
2 Date("2020-01-25")
全部で 374,931 件のデータである(ずいぶん多くなったものだ)。
さて本題に戻ろう。これを,横軸を年月日,縦軸を陽性者数として折線を描くならば,VegaLite だと,以下のように y="count()" と指定すればすればよい。
しかし,どういうわけか実行時間がべらぼうにかかる(コンピュータがフリーズしたかと思う。なので,以下のプログラムでは,描画行はコメントアウトしている)。
using VegaLite
# df |> @vlplot(:line, x={:公表_年月日}, y="count()")
y="count()" は,単に度数分布をとっているだけだと思うのだが。
ということで,"公表_年月日" で度数分布をとってみる。
using FreqTables
tbl = freqtable(df.公表_年月日);
first(tbl, 5)
5-element Named Vector{Int64}
Dim1 │
───────────────────┼──
Date("2020-01-24") │ 1
Date("2020-01-25") │ 1
Date("2020-01-30") │ 1
Date("2020-02-13") │ 1
Date("2020-02-14") │ 2
確かに陽性者の報告されていない日付のデータはない(当たり前だが)。
freqtable( ) の結果に names( ) し,その第 1 要素をとれば日付データ Vector{Dates.Date} になる。
date=names(tbl)[1];
first(date, 5)
5-element Vector{Date}:
2020-01-24
2020-01-25
2020-01-30
2020-02-13
2020-02-14
日付データを横軸,度数 vec(tbl) を縦軸にして折れ線グラフを描く。
using Plots, Dates
plot(date, vec(tbl),
xlabel="date", xtickfontsize=6, tick_direction=:out, xminorticks=3,
ylabel="counts", size=(600, 400), grid=false, label="")
しかし,陽性者の確認されていない日付のデータはどうなっているのだろうか?
df2 = DataFrame(date=date, counts = vec(tbl));
first(df2, 20)
20 rows × 2 columns
|
date |
counts |
|
Date |
Int64 |
1 |
Date("2020-01-24") |
1 |
2 |
Date("2020-01-25") |
1 |
3 |
Date("2020-01-30") |
1 |
4 |
Date("2020-02-13") |
1 |
5 |
Date("2020-02-14") |
2 |
6 |
Date("2020-02-15") |
8 |
7 |
Date("2020-02-16") |
5 |
8 |
Date("2020-02-18") |
3 |
9 |
Date("2020-02-19") |
3 |
10 |
Date("2020-02-21") |
3 |
11 |
Date("2020-02-22") |
1 |
12 |
Date("2020-02-24") |
3 |
13 |
Date("2020-02-26") |
3 |
14 |
Date("2020-02-27") |
1 |
15 |
Date("2020-02-29") |
1 |
16 |
Date("2020-03-01") |
2 |
17 |
Date("2020-03-03") |
1 |
18 |
Date("2020-03-04") |
4 |
19 |
Date("2020-03-05") |
8 |
20 |
Date("2020-03-06") |
6 |
plot(df2.date[1:20], df2.counts[1:20],
xlabel="date", xtickfontsize=6, tick_direction=:out, xminorticks=3,
ylabel="counts", size=(600, 400), grid=false, label="",
marker=true)
欠損値(すなわち,陽性者数が0)は,データとしてマーカーではプロットされていない。
しかし,折線では繋がれてしまっているので,(あたかもデータがあるかのごとく描画されており)マーカーを描いていなければ正しく描かれているかどうか,判然としない。
そこで,欠損値を処理するために,対称とするデータフレームにデータがある 2020-01-24 から 2021-09-28 までの連続する日付データだけをもつデータフレーム df3 を作成する。
using Dates
dt = collect(Date(2020,01,24):Day(1):Date(2021,09,28));
df3 = DataFrame(date=dt);
df3 と 日付が欠損している df2 をマージ(Julia では join(..., kind=:outer))を行い,df4 を作成する。
df4 = join(df3, df2, on=:date, kind=:outer);
df4.counts[ismissing.(df4.counts)] .= 0;
first(df4, 30)
30 rows × 2 columns
|
date |
counts |
|
Date⍰ |
Int64⍰ |
1 |
Date("2020-01-24") |
1 |
2 |
Date("2020-01-25") |
1 |
3 |
Date("2020-01-26") |
0 |
4 |
Date("2020-01-27") |
0 |
5 |
Date("2020-01-28") |
0 |
6 |
Date("2020-01-29") |
0 |
7 |
Date("2020-01-30") |
1 |
8 |
Date("2020-01-31") |
0 |
9 |
Date("2020-02-01") |
0 |
10 |
Date("2020-02-02") |
0 |
11 |
Date("2020-02-03") |
0 |
12 |
Date("2020-02-04") |
0 |
13 |
Date("2020-02-05") |
0 |
14 |
Date("2020-02-06") |
0 |
15 |
Date("2020-02-07") |
0 |
16 |
Date("2020-02-08") |
0 |
17 |
Date("2020-02-09") |
0 |
18 |
Date("2020-02-10") |
0 |
19 |
Date("2020-02-11") |
0 |
20 |
Date("2020-02-12") |
0 |
21 |
Date("2020-02-13") |
1 |
22 |
Date("2020-02-14") |
2 |
23 |
Date("2020-02-15") |
8 |
24 |
Date("2020-02-16") |
5 |
25 |
Date("2020-02-17") |
0 |
26 |
Date("2020-02-18") |
3 |
27 |
Date("2020-02-19") |
3 |
28 |
Date("2020-02-20") |
0 |
29 |
Date("2020-02-21") |
3 |
30 |
Date("2020-02-22") |
1 |
date は,もとの df2 で欠損していた日付のデータと合わせてカウント = 0 が補われた。
このデータフレームに基づく描画が正当な描画である。
plot(df4.date[1:43], df4.counts[1:43],
xlabel="date", xtickfontsize=6, tick_direction=:out, xminorticks=3,
ylabel="counts", size=(600, 400), grid=false, label="",
marker=true)
確かに,元の df2 に存在しない日付の counts 値は 0 になっている。
この段階までくれば(df4 を使えば),VegaLite でも適切な(我慢できる範囲内の)処理時間で,同じグラフを描くことができる。
using VegaLite
df4[1:43, :] |> @vlplot(
mark={:line, point=true},
x=:date,
y=:counts,
width=600,
height=400)
さて,最終段階である。欠損値を補間したデータフレーム df4 を使って,全期間のグラフを描画しよう。
plot(df4.date, df4.counts,
xlabel="date", xtickfontsize=6, tick_direction=:out, xminorticks=3,
ylabel="counts", size=(600, 400), grid=false, label="")
横軸目盛りのフォーマットを変更し,'年/月' にする。
plot!(xformatter = x -> Dates.format(Date(Dates.UTD(x)), "yyyy/mm"))
VegaLite でも,適切な処理時間で,同じようなグラフが描ける。
using VegaLite
df4 |> @vlplot(
mark=:line,
x={:date, axis={grid=false, format="%Y-%b"}},
y={:counts, axis={grid=false}},
width=600,
height=400)