Julia でふつうの統計解析

線形回帰 Linear Regression

前のページに戻る
#
# GSwR2 第5章 その3 線形回帰
#
# 植物の生育速度のデータ
#
# 2021/06/02 Daisuke TOMINGAGA

using CSV, Gadfly, DataFrames, DataFramesMeta, Statistics, LinearAlgebra,
      HypothesisTests, Cairo, Fontconfig, GLM, Distributions, Distances

set_default_plot_size(10cm, 8cm)

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

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

# 箱ヒゲ図と散布図、図をオブジェクトに入れて PDF に書き出す
p1 = hstack(plot(dat,                y = :moisture, Geom.boxplot),
            plot(dat,                y = :growth,   Geom.boxplot))
p2 =        plot(dat, x = :moisture, y = :growth,   Geom.point)
draw(PDF("fig/plant_1.pdf"), p1)
draw(PDF("fig/plant_2.pdf"), p2)

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

# 診断プロット1: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")))

# 診断プロット2: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")))

# 診断プロット3: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|1/2")))

# 診断プロット4: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")))

# で、データとモデルと信頼区間のプロット
# まずx軸の範囲を決める、データの範囲を探して、50点に区切る
# データフレームに入れて、回帰時の変数名と同じ変数名を付けないと、
# predict が受け取ってくれない
xmin = describe(dat)[1, :min]
xmax = describe(dat)[1, :max]
newx = DataFrame([range(xmin, stop = xmax, length = 50)], :auto)
rename!(newx, [:moisture])
# で予測値を計算して、結果のデータフレームにxの値を挿入
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("生育速度")))

2021, © Daisuke TOMINAGA.