Juliaで線形回帰

[Rをはじめよう 生命科学のためのRStudio入門](https://www.yodosha.co.jp/yodobook/book/9784758120951/index.html)のデータセット（[ここ](http://r4all.org/books/datasets/)にある）を使って､線形回帰をやるJuliaコード。データを読み込み､図で結論を予想し、モデルの健全性を診断し､検定し､図を仕上げる。コードや出力の説明などは[技術書典６](https://techbookfest.org/event/tbf06)(2019/04/14)で頒布予定。

```plant.jl
using CSV, Gadfly, DataFrames, DataFramesMeta, Statistics, HypothesisTests,
      Cairo, Fontconfig, GLM, Distributions, Distances, LinearAlgebra

# データ読み込み
dat = CSV.read("datasets/plant.growth.rate.csv", allowmissing=:none)
# カラム名に . が入ってるとあとでうまく扱えないので､名前を変える
names!(dat, [:moisture, :growth]);

# データは要するに二群、各群でのオゾン濃度の平均値と分散
describe(dat)
var(dat[:moisture])
var(dat[:growth])

draw(PDF("fig/plant_1.pdf", 10cm, 8cm),
     hstack(plot(dat, y = :moisture, Geom.boxplot),
            plot(dat, y = :growth, Geom.boxplot)))
draw(PDF("fig/plant_2.pdf", 10cm, 8cm),
     plot(dat, x = :moisture, y = :growth, Geom.point))

# 線形回帰して、モデルの値（期待値）を計算し、診断プロットを描く
model = lm(@formula(growth ~ moisture), dat)
pred = predict(model)

# 診断プロット１：Residuals vs Fitted
res = dat[:growth] - pred
draw(PDF("fig/plant_3.pdf", 10cm, 8cm),
     plot(x = pred, y = res, Geom.point,
          Guide.title("Residuals vs Fitted"),
          Guide.xlabel("Fitted values"), Guide.ylabel("Residuals")))

# 診断プロット２：Normal Q-Q plot
N = length(res)
stdRes = (res .- mean(res) .* ones(N)) ./ (std(res)) # 標準化残差
qx = quantile.(Normal(), range(0.5, stop=(N-0.5), length=(N)) / (N + 1))
draw(PDF("fig/plant_4.pdf", 10cm, 8cm),
     plot(layer(x = qx, y = sort(stdRes), Geom.point),
          layer(x = [-3,3], y = [-3,3], Geom.line, style(line_style = [:dot])),
          Guide.title("Normal Q-Q plot"),
          Guide.xlabel("Theoretical Quantiles"),
          Guide.ylabel("Standardized residuals")))

# 診断プロット３：Scale-Location
sqrtStdRes = sqrt.(abs.(stdRes))
draw(PDF("fig/plant_5.pdf", 10cm, 8cm),
     plot(x = pred, y = sqrtStdRes, Geom.point,
          Guide.title("Scale-Location"),
          Guide.xlabel("Fitted values"),
          Guide.ylabel("|Standardized residuals|<sup>1/2</sup>")))

# 診断プロット４：Residuals vs Leverage
x = dat[:moisture]
Sxx = sum(x .* x) - sum(x)^2/N
m = mean(x)
leverage = ones(length(res))
for i in 1:N leverage[i] = (x[i] - m)^2 / Sxx + 1/N end
draw(PDF("fig/plant_6.pdf", 10cm, 8cm),
     plot(x = leverage, y = stdRes, Geom.point,
          Guide.xlabel("leverage"), Guide.ylabel("stdRes")))

# で、データとモデルと信頼区間のプロット
# まずｘ軸の範囲を決める、データの範囲を探して､50点に区切る
# データフレームに入れて、回帰時の変数名と同じ変数名を付けないと､
# predict が受け取ってくれない
xmin = describe(dat)[1, :min]
xmax = describe(dat)[1, :max]
newx = DataFrame([range(xmin, stop = xmax, length = 50)])
names!(newx, [:moisture])
# で予測値を計算して、結果のデータフレームにｘの値を挿入
pred = predict(model, newx, interval=:confidence)
insertcols!(pred, 1, moisture=newx[:moisture])
# でデータと予測直線をプロット
# 先に置いた Geom.point が Geom.line の上に描画される
# ymin と ymax は Geom.ribbon が使う
draw(PDF("fig/plant_7.pdf", 10cm, 8cm),
     plot(layer(dat, x = :moisture, y = :growth, Geom.point),
          layer(pred,x = :moisture, y = :prediction,
                     ymin = :lower, ymax = :upper,
                     Geom.line, Geom.ribbon),
          Coord.cartesian(xmin=0.2, xmax=2.0),
          Guide.title("土壌水分量と生育速度"),
          Guide.xlabel("土壌水分量"),
          Guide.ylabel("生育速度")))
```