From 18e17d31eaea9c0a36409fec08b0a313055e421e Mon Sep 17 00:00:00 2001 From: Matthieu Gomez Date: Tue, 14 Mar 2023 19:56:05 -0400 Subject: [PATCH] Update to StatsModels 0.7 (#220) * Update FixedEffectModels.jl * Update FixedEffectModels.jl * Update FixedEffectModels.jl * Update Project.toml * update * Update Project.toml * Update FixedEffectModels.jl * update benchmarks * Update partial_out.jl * Update README.md * update stata too * Update README.md * Update README.md * Update Project.toml * Update README.md * Update README.md * Update README.md * Update README.md * better printing * update version to 1.9.0 * update tests * Update FixedEffectModel.jl * use snoopcompile * precompile * Update FixedEffectModel.jl * Update FixedEffectModel.jl * Update runtests.jl * Update formula.jl * Update fit.jl * update to Julia 1.6 --- .github/workflows/ci.yml | 2 +- Project.toml | 12 +- README.md | 15 +- benchmark/.sublime2Terminal.jl | 10 - benchmark/benchmark.csv | 2 +- benchmark/benchmark.jl | 27 +- benchmark/benchmark.md | 76 +- benchmark/fixedeffectmodels_benchmark.png | Bin 0 -> 128533 bytes benchmark/result.jl | 2 +- benchmark/result.png | Bin 31634 -> 0 bytes src/FixedEffectModel.jl | 227 ++-- src/FixedEffectModels.jl | 20 +- src/fit.jl | 34 +- src/partial_out.jl | 2 +- src/utils/formula.jl | 17 +- test/Project.toml | 4 +- test/fit.jl | 1283 +++++++++++---------- test/formula.jl | 98 +- test/partial_out.jl | 32 +- test/predict.jl | 599 +++++----- test/runtests.jl | 32 +- 21 files changed, 1237 insertions(+), 1257 deletions(-) delete mode 100644 benchmark/.sublime2Terminal.jl create mode 100644 benchmark/fixedeffectmodels_benchmark.png delete mode 100644 benchmark/result.png diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9469bcf2..99aa7378 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: version: - - '1.3' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'. + - '1.6' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'. - '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia. os: - ubuntu-latest diff --git a/Project.toml b/Project.toml index dc8a78f3..e4bdfe36 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "FixedEffectModels" uuid = "9d5cd8c9-2029-5cab-9928-427838db53e3" -version = "1.8.1" +version = "1.9.0" [deps] DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" @@ -15,15 +15,17 @@ StatsFuns = "4c63d2b9-4356-54db-8cca-17b64c39e42c" StatsModels = "3eaba693-59b7-5ba5-a881-562e759f1c8d" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" Vcov = "ec2bfdc2-55df-4fc9-b9ae-4958c2cf2486" +SnoopPrecompile = "66db9d55-30c0-4569-8b51-7e840670fc0c" [compat] -DataFrames = "0.21, 0.22, 1.0" +DataFrames = "0.21, 0.22, 1" FixedEffects = "2" -Reexport = "0.1, 0.2, 1.0" +Reexport = "0.1, 0.2, 1" +SnoopPrecompile = "1" StatsAPI = "1" StatsBase = "0.33" StatsFuns = "0.9, 1" -StatsModels = "0.6" +StatsModels = "0.7" Tables = "1" Vcov = "0.7" -julia = "1.3" +julia = "1.6" diff --git a/README.md b/README.md index cb245ec4..51751add 100755 --- a/README.md +++ b/README.md @@ -6,12 +6,10 @@ This package estimates linear models with high dimensional categorical variables The package is registered in the [`General`](https://github.com/JuliaRegistries/General) registry and so can be installed at the REPL with `] add FixedEffectModels`. ## Benchmarks -The objective of the package is similar to the Stata command [`reghdfe`](https://github.com/sergiocorreia/reghdfe) and the R function [`felm`](https://cran.r-project.org/web/packages/lfe/lfe.pdf). The package tends to be much faster than these two options. - -![benchmark](http://www.matthieugomez.com/files/fixedeffectmodels_benchmark.png) +The objective of the package is similar to the Stata command [`reghdfe`](https://github.com/sergiocorreia/reghdfe) and the R packages [`lfe`](https://cran.r-project.org/web/packages/lfe/lfe.pdf) and [`fixest`](https://lrberge.github.io/fixest/). The package is much faster than `reghdfe` or `lfe`. It also tends to be a bit faster than the more recent `fixest` (depending on the exact command). For complicated models, `FixedEffectModels` can also run on Nvidia GPUs for even faster performances (see below) -Performances are roughly similar to the newer R function [`feols`](https://cran.r-project.org/web/packages/fixest/fixest.pdf). The main difference is that `FixedEffectModels` can also run the demeaning operation on a GPU (with `method = :gpu`). +![benchmark](http://www.matthieugomez.com/files/fixedeffectmodels_benchmark.png) ## Syntax @@ -99,14 +97,13 @@ You may use [RegressionTables.jl](https://github.com/jmboehm/RegressionTables.jl ## Performances - ### MultiThreads -`FixedEffectModels` is multi-threaded. Use the option `nthreads` to select the number of threads to use in the estimation (defaults to `Threads.nthreads()`). That being said, multithreading does not usually make a big difference. +`FixedEffectModels` is multi-threaded. Use the option `nthreads` to select the number of threads to use in the estimation (defaults to `Threads.nthreads()`). -### GPU -The package has support for GPUs (Nvidia) (thanks to Paul Schrimpf). This can make the package an order of magnitude faster for complicated problems. +### Nvidia GPU +The package has support for Nvidia GPUs (thanks to Paul Schrimpf). This can make the package an order of magnitude faster for complicated problems. -To use GPU, run `using CUDA` before `using FixedEffectModels`. Then, estimate a model with `method = :gpu`. For maximum speed, set the floating point precision to `Float32` with `double_precision = false`. +If you have a Nvidia GPU, run `using CUDA` before `using FixedEffectModels`. Then, estimate a model with `method = :gpu`. For maximum speed, set the floating point precision to `Float32` with `double_precision = false`. ```julia using CUDA, FixedEffectModels diff --git a/benchmark/.sublime2Terminal.jl b/benchmark/.sublime2Terminal.jl deleted file mode 100644 index b96deafd..00000000 --- a/benchmark/.sublime2Terminal.jl +++ /dev/null @@ -1,10 +0,0 @@ -N = 800000 # number of observations -M = 40000 # number of workers -O = 5000 # number of firms -id1 = rand(1:M, N) -id2 = [rand(max(1, div(x, 8)-10):min(O, div(x, 8)+10)) for x in id1] -x1 = 5 * cos.(id1) + 5 * sin.(id2) + randn(N) -x2 = cos.(id1) + sin.(id2) + randn(N) -y= 3 .* x1 .+ 5 .* x2 .+ cos.(id1) .+ cos.(id2).^2 .+ randn(N) -df = DataFrame(id1 = id1, id2 = id2, x1 = x1, x2 = x2, y = y) -@time reg(df, @formula(y ~ x1 + x2 + fe(id1) + fe(id2))) \ No newline at end of file diff --git a/benchmark/benchmark.csv b/benchmark/benchmark.csv index a5da65f2..b72424db 100644 --- a/benchmark/benchmark.csv +++ b/benchmark/benchmark.csv @@ -1 +1 @@ -Order,Command,Julia,R,Stata 1,simple,0.601445,1.843,1.2 2,1 hd fe,1.624446 ,14.831,15.51 3,2 hd fe,3.639817,10.626,49.38 4, 1 cluster,1.462648,9.255,11.15 5, 2 cluster,7.187382,96.958,118.67 \ No newline at end of file +Order,Command,FixedEffectModels.jl (Julia),fixest (R),lfe (R),reghdfe (Stata) 1,simple,0.35,0.317,1.843, 0.61 2,1 hd fe,0.463 ,0.704 ,14.831, 4.64 3,2 hd fe,1.00,1.297 ,10.626, 22.99 4, 1 cluster se,0.38058,0.700 ,9.255, 8.28 5, 2 clusters se,0.765,1.803,96.958, 70.44 \ No newline at end of file diff --git a/benchmark/benchmark.jl b/benchmark/benchmark.jl index 5877edb8..c5de3d15 100755 --- a/benchmark/benchmark.jl +++ b/benchmark/benchmark.jl @@ -1,5 +1,6 @@ -using DataFrames, FixedEffectModels, Random, CategoricalArrays - +using DataFrames, Random, CategoricalArrays +@time using FixedEffectModels +# 13s precompiling # Very simple setup N = 10000000 K = 100 @@ -11,17 +12,19 @@ y= 3 .* x1 .+ 5 .* x2 .+ cos.(id1) .+ cos.(id2).^2 .+ randn(N) df = DataFrame(id1 = id1, id2 = id2, x1 = x1, x2 = x2, y = y) # first time @time reg(df, @formula(y ~ x1 + x2)) -# 14s +# 3.5s @time reg(df, @formula(y ~ x1 + x2)) -# 0.582029 seconds (852 allocations: 535.311 MiB, 18.28% gc time) +# 0.497374 seconds (450 allocations: 691.441 MiB, 33.18% gc time) +@time reg(df, @formula(y ~ x1 + x2), Vcov.cluster(:id2)) +# 1.898018 seconds (7.10 M allocations: 1.220 GiB, 8.20% gc time, 4.46% compilation time) @time reg(df, @formula(y ~ x1 + x2), Vcov.cluster(:id2)) -# 0.621690 seconds (693 allocations: 768.945 MiB, 7.69% gc time) +# 0.605172 seconds (591 allocations: 768.939 MiB, 42.38% gc time) @time reg(df, @formula(y ~ x1 + x2 + fe(id1))) -# 1.143941 seconds (245.39 k allocations: 942.937 MiB, 12.93% gc time, 14.99% compilation time) +# 0.893835 seconds (1.03 k allocations: 929.130 MiB, 54.19% gc time) @time reg(df, @formula(y ~ x1 + x2 + fe(id1)), Vcov.cluster(:id1)) -# 1.242207 seconds (245.73 k allocations: 1022.348 MiB, 9.48% gc time, 14.10% compilation time) +# 1.015078 seconds (1.18 k allocations: 1008.532 MiB, 56.50% gc time) @time reg(df, @formula(y ~ x1 + x2 + fe(id1) + fe(id2))) -# 2.255812 seconds (351.74 k allocations: 1.076 GiB, 3.98% gc time, 12.93% compilation time) +# 1.835464 seconds (4.02 k allocations: 1.057 GiB, 35.59% gc time) # More complicated setup N = 800000 # number of observations @@ -34,7 +37,7 @@ x2 = cos.(id1) + sin.(id2) + randn(N) y= 3 .* x1 .+ 5 .* x2 .+ cos.(id1) .+ cos.(id2).^2 .+ randn(N) df = DataFrame(id1 = id1, id2 = id2, x1 = x1, x2 = x2, y = y) @time reg(df, @formula(y ~ x1 + x2 + fe(id1) + fe(id2))) -# 3.048292 seconds (422.51 k allocations: 114.317 MiB, 6.86% compilation time) +# 2.504294 seconds (75.83 k allocations: 95.525 MiB, 0.23% gc time) +# fixest @@ -48,8 +51,8 @@ X1 = rand(n) ln_y = 3 .* X1 .+ rand(n) df = DataFrame(X1 = X1, ln_y = ln_y, id1 = id1, id2 = id2, id3 = id3) @time reg(df, @formula(ln_y ~ X1 + fe(id1)), Vcov.cluster(:id1)) -# 0.869512 seconds (234.23 k allocations: 828.818 MiB, 18.95% compilation time) +# 0.543996 seconds (873 allocations: 815.677 MiB, 34.15% gc time) @time reg(df, @formula(ln_y ~ X1 + fe(id1) + fe(id2)), Vcov.cluster(:id1)) -# 2.192262 seconds (300.08 k allocations: 985.534 MiB, 4.61% gc time, 9.42% compilation time) +# 1.301908 seconds (3.03 k allocations: 968.729 MiB, 25.84% gc time) @time reg(df, @formula(ln_y ~ X1 + fe(id1) + fe(id2) + fe(id3)), Vcov.cluster(:id1)) -# 2.700051 seconds (406.80 k allocations: 1.117 GiB, 3.56% gc time, 10.41% compilation time) +# 1.658832 seconds (4.17 k allocations: 1.095 GiB, 29.78% gc time) diff --git a/benchmark/benchmark.md b/benchmark/benchmark.md index 4188f87c..3217393f 100755 --- a/benchmark/benchmark.md +++ b/benchmark/benchmark.md @@ -3,9 +3,9 @@ Code to reproduce this graph: - Julia + FixedEffectModels.jl v1.9.0 (Julia 1.9) ```julia - using DataFrames, FixedEffectModels + using DataFrames, CategoricalArrays, FixedEffectModels N = 10000000 K = 100 id1 = rand(1:(N/K), N) @@ -13,21 +13,51 @@ Code to reproduce this graph: x1 = randn(N) x2 = randn(N) y= 3 .* x1 .+ 2 .* x2 .+ sin.(id1) .+ cos.(id2).^2 .+ randn(N) - df = DataFrame(id1 = categorical(id1), id2 = categorical(id2), x1 = x1, x2 = x2, w = w, y = y) + df = DataFrame(id1 = categorical(id1), id2 = categorical(id2), x1 = x1, x2 = x2, y = y) @time reg(df, @formula(y ~ x1 + x2)) - #0.601445 seconds (1.05 k allocations: 535.311 MiB, 31.95% gc time) + # 0.338749 seconds (450 allocations: 691.441 MiB, 2.30% gc time) @time reg(df, @formula(y ~ x1 + x2 + fe(id1))) - # 1.624446 seconds (1.21 k allocations: 734.353 MiB, 17.27% gc time) + # 0.463058 seconds (1.00 k allocations: 929.129 MiB, 13.31% gc time) @time reg(df, @formula(y ~ x1 + x2 + fe(id1) + fe(id2))) - # 3.639817 seconds (1.84 k allocations: 999.675 MiB, 11.25% gc time) + # 1.006031 seconds (3.22 k allocations: 1.057 GiB, 1.68% gc time) @time reg(df, @formula(y ~ x1 + x2), Vcov.cluster(:id1)) - # 1.462648 seconds (499.30 k allocations: 690.102 MiB, 15.92% gc time) - @time reg(df, @formula(y ~ x1 + x2, Vcov.cluster(:id1, :id2))) - # 7.187382 seconds (7.02 M allocations: 2.753 GiB, 24.19% gc time) + # 0.380562 seconds (580 allocations: 771.606 MiB, 3.07% gc time) + @time reg(df, @formula(y ~ x1 + x2), Vcov.cluster(:id1, :id2)) + #0.765847 seconds (719 allocations: 1.128 GiB, 2.01% gc time) ```` - R (lfe package) + fixest v0.8.4 (R 4.2.2) + ```R + library(fixest) + N = 10000000 + K = 100 + df = data.frame( + id1 = as.factor(sample(N/K, N, replace = TRUE)), + id2 = as.factor(sample(K, N, replace = TRUE)), + x1 = runif(N), + x2 = runif(N) + ) + df[, "y"] = 3 * df[, "x1"] + 2 * df[, "x2"] + sin(as.numeric(df[, "id1"])) + cos(as.numeric(df[, "id2"])) + runif(N) + system.time(feols(y ~ x1 + x2, df)) + #> user system elapsed + #> 0.280 0.036 0.317 + system.time(feols(y ~ x1 + x2|id1, df)) + #> user system elapsed + #> 0.616 0.089 0.704 + system.time(feols(y ~ x1 + x2|id1 + id2, df)) + #> user system elapsed + #> 1.181 0.120 1.297 + system.time(feols(y ~ x1 + x2, cluster = "id1", df)) + #> user system elapsed + #> 0.630 0.071 0.700 + system.time(feols(y ~ x1 + x2, cluster = c("id1", "id2"), df)) + #> user system elapsed + #> 1.570 0.197 1.803 + ``` + + + lfe v2.8-8 (R 4.2.2) ```R library(lfe) N = 10000000 @@ -42,22 +72,22 @@ Code to reproduce this graph: system.time(felm(y ~ x1 + x2, df)) #> user system elapsed - #> 1.843 0.476 2.323 + #> 1.137 0.232 1.596 system.time(felm(y ~ x1 + x2|id1, df)) #> user system elapsed - #> 14.831 1.342 15.993 + #> 7.08 0.41 7.46 system.time(felm(y ~ x1 + x2|id1 + id2, df)) #> user system elapsed - #> 10.626 1.358 10.336 + #> 4.832 0.370 4.615 system.time(felm(y ~ x1 + x2|0|0|id1, df)) #> user system elapsed - #> 9.255 0.843 10.110 + #> 3.712 0.287 3.996 system.time(felm(y ~ x1 + x2|0|0|id1 + id2, df)) #> user system elapsed - #> 96.958 1.474 99.113 - ``` + #> 59.119 0.889 59.946 + - Stata (reghdfe version 5.2.9 06aug2018) + reghdfe version 5.6.8 03mar2019 (Stata 16.1) ``` clear all local N = 10000000 @@ -72,13 +102,13 @@ Code to reproduce this graph: set rmsg on reg y x1 x2 - #> r; t=1.20 - areg y x1 x2, a(id1) - #>r; t=15.51 + #> r; t=0.61 + reghdfe y x1 x2, a(id1) + #>r; t=4.64 reghdfe y x1 x2, a(id1 id2) - #> r; t=49.38 + #> r; t==22.99 reg y x1 x2, cl(id1) - #> r; t=11.15 + #> r; t=8.28 ivreg2 y x1 x2, cluster(id1 id2) - #> r; t=118.67 + #> r; t=70.44 ```` diff --git a/benchmark/fixedeffectmodels_benchmark.png b/benchmark/fixedeffectmodels_benchmark.png new file mode 100644 index 0000000000000000000000000000000000000000..b79ebed1d7c34edd47cd81c7e53a2f85a013abdf GIT binary patch literal 128533 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 WgUt8ejV!nPj*v@M7xCvk?))EoWd7Lz literal 0 HcmV?d00001 diff --git a/benchmark/result.jl b/benchmark/result.jl index 4d2c004a..07ad8502 100644 --- a/benchmark/result.jl +++ b/benchmark/result.jl @@ -1 +1 @@ -using DataFrames, CSV, Gadfly df = CSV.read("/Users/Matthieu/Dropbox/Github/FixedEffectModels.jl/benchmark/benchmark.csv") df.R = df.R ./ df.Julia df.Stata = df.Stata ./ df.Julia df.Julia = df.Julia ./ df.Julia mdf = melt(df[!, [:Command, :Julia, :R, :Stata]], :Command) mdf = rename(mdf, :variable => :Language) p = plot(mdf, x = "Language", y = "value", color = "Command", Guide.ylabel("Time (Ratio to Julia)"), Guide.xlabel("Model"), Guide.yticks(ticks= [1, 5, 10, 15])) draw(PNG("/Users/Matthieu/Dropbox/Github/FixedEffectModels.jl/benchmark/fixedeffectmodels_benchmark.png", 8inch, 5inch, dpi=300), p) \ No newline at end of file +using DataFrames, CSV, Gadfly df = CSV.read("/Users/matthieugomez/Dropbox/Github/FixedEffectModels.jl/benchmark/benchmark.csv", DataFrame) df."fixest (R)" = df."fixest (R)" ./ df."FixedEffectModels.jl (Julia)" df."lfe (R)" = df."lfe (R)" ./ df."FixedEffectModels.jl (Julia)" df."reghdfe (Stata)" = df."reghdfe (Stata)" ./ df."FixedEffectModels.jl (Julia)" df."FixedEffectModels.jl (Julia)" = df."FixedEffectModels.jl (Julia)" ./ df."FixedEffectModels.jl (Julia)" mdf = stack(df, Not([:Command, :Order])) mdf = rename(mdf, :variable => :Language) p = plot(mdf, x = "Command", y = "value", color = "Language", Guide.ylabel("Time (Ratio to Julia)"), Guide.xlabel("Command"), Scale.y_log10) draw(PNG("/Users/matthieugomez/Dropbox/Github/FixedEffectModels.jl/benchmark/fixedeffectmodels_benchmark.png", 8inch, 5inch, dpi=300), p) \ No newline at end of file diff --git a/benchmark/result.png b/benchmark/result.png deleted file mode 100644 index 8829001716b34a5cb79db667527fe20783bdd68e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31634 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 A`2YX_ diff --git a/src/FixedEffectModel.jl b/src/FixedEffectModel.jl index da3fddf5..a65428a4 100644 --- a/src/FixedEffectModel.jl +++ b/src/FixedEffectModel.jl @@ -18,7 +18,7 @@ struct FixedEffectModel <: RegressionModel coefnames::Vector # Name of coefficients - yname::Union{String, Symbol} # Name of dependent variable + responsename::Union{String, Symbol} # Name of dependent variable formula::FormulaTerm # Original formula formula_schema::FormulaTerm # Schema for predict contrasts::Dict @@ -52,7 +52,7 @@ has_fe(m::FixedEffectModel) = has_fe(m.formula) StatsAPI.coef(m::FixedEffectModel) = m.coef StatsAPI.coefnames(m::FixedEffectModel) = m.coefnames -StatsAPI.responsename(m::FixedEffectModel) = m.yname +StatsAPI.responsename(m::FixedEffectModel) = m.responsename StatsAPI.vcov(m::FixedEffectModel) = m.vcov StatsAPI.nobs(m::FixedEffectModel) = m.nobs StatsAPI.dof(m::FixedEffectModel) = m.dof @@ -63,7 +63,7 @@ StatsAPI.islinear(m::FixedEffectModel) = true StatsAPI.deviance(m::FixedEffectModel) = m.tss StatsAPI.rss(m::FixedEffectModel) = m.rss StatsAPI.mss(m::FixedEffectModel) = deviance(m) - rss(m) - +StatsModels.formula(m::FixedEffectModel) = m.formula_schema function StatsAPI.confint(m::FixedEffectModel; level::Real = 0.95) scale = tdistinvcdf(StatsAPI.dof_residual(m), 1 - (1 - level) / 2) @@ -72,17 +72,22 @@ function StatsAPI.confint(m::FixedEffectModel; level::Real = 0.95) end # predict, residuals, modelresponse -function StatsAPI.predict(m::FixedEffectModel, t) - # Require DataFrame input as we are using leftjoin and select from DataFrames here - # Make sure fes are saved - if has_fe(m) - throw("To predict in a fixed effect regression, run `reg` with the option save = true, and then access predicted values using `fe().") - end - ct = StatsModels.columntable(t) - cols, nonmissings = StatsModels.missing_omit(ct, MatrixTerm(m.formula_schema.rhs)) + + +function StatsAPI.predict(m::FixedEffectModel, data) + Tables.istable(data) || + throw(ArgumentError("expected second argument to be a Table, got $(typeof(data))")) + has_fe(m) && + throw("To predict for a model with high-dimensional fixed effects, run `reg` with the option save = true, and then access predicted values using `fe().") + cdata = StatsModels.columntable(data) + cols, nonmissings = StatsModels.missing_omit(cdata, m.formula_schema.rhs) Xnew = modelmatrix(m.formula_schema, cols) - out = Vector{Union{Float64, Missing}}(missing, length(Tables.rows(ct))) - out[nonmissings] = Xnew * m.coef + if all(nonmissings) + out = Xnew * m.coef + else + out = Vector{Union{Float64, Missing}}(missing, length(Tables.rows(cdata))) + out[nonmissings] = Xnew * m.coef + end # Join FE estimates onto data and sum row-wise # This code does not work propertly with missing or with interacted fixed effect, so deleted @@ -96,29 +101,29 @@ function StatsAPI.predict(m::FixedEffectModel, t) return out end -function StatsAPI.residuals(m::FixedEffectModel, t) - if has_fe(m) - throw("To access residuals in a fixed effect regression, run `reg` with the option save = :residuals, and then access residuals with `residuals()`") +function StatsAPI.residuals(m::FixedEffectModel, data) + Tables.istable(data) || + throw(ArgumentError("expected second argument to be a Table, got $(typeof(data))")) + has_fe(m) && + throw("To access residuals for a model with high-dimensional fixed effects, run `reg` with the option save = :residuals, and then access residuals with `residuals()`.") + cdata = StatsModels.columntable(data) + cols, nonmissings = StatsModels.missing_omit(cdata, m.formula_schema.rhs) + Xnew = modelmatrix(m.formula_schema, cols) + y = response(m.formula_schema, cdata) + if all(nonmissings) + out = y - Xnew * m.coef else - ct = StatsModels.columntable(t) - cols, nonmissings = StatsModels.missing_omit(ct, MatrixTerm(m.formula_schema.rhs)) - Xnew = modelmatrix(m.formula_schema, cols) - y = response(m.formula_schema, ct) - if all(nonmissings) - out = y - Xnew * m.coef - else - out = Vector{Union{Float64, Missing}}(missing, length(Tables.rows(ct))) - out[nonmissings] = y - Xnew * m.coef - end - return out + out = Vector{Union{Float64, Missing}}(missing, length(Tables.rows(cdata))) + out[nonmissings] = y - Xnew * m.coef end + return out end function StatsAPI.residuals(m::FixedEffectModel) if m.residuals === nothing has_fe(m) && throw("To access residuals in a fixed effect regression, run `reg` with the option save = :residuals, and then access residuals with `residuals()`") - !has_fe(m) && throw("To access residuals, use residuals(x, t) where t is a Table") + !has_fe(m) && throw("To access residuals, use residuals(m, data) where `m` is an estimated FixedEffectModel and `data` is a Table") end m.residuals end @@ -159,7 +164,7 @@ function StatsAPI.coeftable(m::FixedEffectModel; level = 0.95) tt = cc ./ se CoefTable( hcat(cc, se, tt, fdistccdf.(Ref(1), Ref(StatsAPI.dof_residual(m)), abs2.(tt)), conf_int[:, 1:2]), - ["Estimate","Std.Error","t value", "Pr(>|t|)", "Lower 95%", "Upper 95%" ], + ["Estimate","Std. Error","t-stat", "Pr(>|t|)", "Lower 95%", "Upper 95%" ], ["$(coefnms[i])" for i = 1:length(cc)], 4) end @@ -170,133 +175,95 @@ end ## ############################################################################## -function title(m::FixedEffectModel) - iv = has_iv(m) - fe = has_fe(m) - if !iv & !fe - return "Linear Model" - elseif iv & !fe - return "IV Model" - elseif !iv & fe - return "Fixed Effect Model" - elseif iv & fe - return "IV Fixed Effect Model" - end -end - -format_scientific(x) = @sprintf("%.3f", x) - function top(m::FixedEffectModel) out = [ "Number of obs" sprint(show, nobs(m), context = :compact => true); "Degrees of freedom" sprint(show, dof(m), context = :compact => true); - "R2" format_scientific(r2(m)); - "R2 Adjusted" format_scientific(adjr2(m)); - "F-Stat" sprint(show, m.F, context = :compact => true); - "p-value" format_scientific(m.p); + "R²" @sprintf("%.3f",r2(m)); + "R² adjusted" @sprintf("%.3f",adjr2(m)); + "F-statistic" sprint(show, m.F, context = :compact => true); + "P-value" @sprintf("%.3f",m.p); ] if has_iv(m) out = vcat(out, - ["F-Stat (First Stage)" sprint(show, m.F_kp, context = :compact => true); - "p-value (First Stage)" format_scientific(m.p_kp); + [ + "F-statistic (first stage)" sprint(show, m.F_kp, context = :compact => true); + "P-value (first stage)" @sprintf("%.3f",m.p_kp); ]) end if has_fe(m) out = vcat(out, - ["R2 within" format_scientific(m.r2_within); - "Iterations" sprint(show, m.iterations, context = :compact => true); + [ + "R² within" @sprintf("%.3f",m.r2_within); + "Iterations" sprint(show, m.iterations, context = :compact => true); ]) end return out end + +import StatsBase: NoQuote, PValue function Base.show(io::IO, m::FixedEffectModel) - ctitle = title(m) - ctop = top(m) - cc = coef(m) - se = stderror(m) - yname = responsename(m) - coefnms = coefnames(m) - conf_int = confint(m) - # put (intercept) last - if !isempty(coefnms) && ((coefnms[1] == Symbol("(Intercept)")) || (coefnms[1] == "(Intercept)")) - newindex = vcat(2:length(cc), 1) - cc = cc[newindex] - se = se[newindex] - conf_int = conf_int[newindex, :] - coefnms = coefnms[newindex] - end - tt = cc ./ se - mat = hcat(cc, se, tt, fdistccdf.(Ref(1), Ref(StatsAPI.dof_residual(m)), abs2.(tt)), conf_int[:, 1:2]) - nr, nc = size(mat) - colnms = ["Estimate","Std.Error","t value", "Pr(>|t|)", "Lower 95%", "Upper 95%"] - rownms = ["$(coefnms[i])" for i = 1:length(cc)] - pvc = 4 - # print + ct = coeftable(m) + #copied from show(iio,cf::Coeftable) + cols = ct.cols; rownms = ct.rownms; colnms = ct.colnms; + nc = length(cols) + nr = length(cols[1]) if length(rownms) == 0 - rownms = AbstractString[lpad("[$i]",floor(Integer, log10(nr))+3) for i in 1:nr] - end - if length(rownms) > 0 - rnwidth = max(4, maximum(length(nm) for nm in rownms) + 2, length(yname) + 2) - else - # if only intercept, rownms is empty collection, so previous would return error - rnwidth = 4 + rownms = [lpad("[$i]",floor(Integer, log10(nr))+3) for i in 1:nr] end - rownms = [rpad(nm,rnwidth-1) * "|" for nm in rownms] - widths = [length(cn)::Int for cn in colnms] - str = [sprint(show, mat[i,j]; context=:compact => true) for i in 1:nr, j in 1:nc] - if pvc != 0 # format the p-values column - for i in 1:nr - str[i, pvc] = format_scientific(mat[i, pvc]) - end + mat = [j == 1 ? NoQuote(rownms[i]) : + j-1 == ct.pvalcol ? NoQuote(sprint(show, PValue(cols[j-1][i]))) : + j-1 in ct.teststatcol ? TestStat(cols[j-1][i]) : + cols[j-1][i] isa AbstractString ? NoQuote(cols[j-1][i]) : cols[j-1][i] + for i in 1:nr, j in 1:nc+1] + io = IOContext(io, :compact=>true, :limit=>false) + A = Base.alignment(io, mat, 1:size(mat, 1), 1:size(mat, 2), + typemax(Int), typemax(Int), 3) + nmswidths = pushfirst!(length.(colnms), 0) + A = [nmswidths[i] > sum(A[i]) ? (A[i][1]+nmswidths[i]-sum(A[i]), A[i][2]) : A[i] + for i in 1:length(A)] + totwidth = sum(sum.(A)) + 2 * (length(A) - 1) + + + #intert my stuff which requires totwidth + ctitle = string(typeof(m)) + halfwidth = div(totwidth - length(ctitle), 2) + print(io, " " ^ halfwidth * ctitle * " " ^ halfwidth) + ctop = top(m) + for i in 1:size(ctop, 1) + ctop[i, 1] = ctop[i, 1] * ":" end - for j in 1:nc - for i in 1:nr - lij = length(str[i, j]) - if lij > widths[j] - widths[j] = lij - end + println(io, '\n', repeat('=', totwidth)) + halfwidth = div(totwidth, 2) - 1 + interwidth = 2 + mod(totwidth, 2) + for i in 1:(div(size(ctop, 1) - 1, 2)+1) + print(io, ctop[2*i-1, 1]) + print(io, lpad(ctop[2*i-1, 2], halfwidth - length(ctop[2*i-1, 1]))) + print(io, " " ^interwidth) + if size(ctop, 1) >= 2*i + print(io, ctop[2*i, 1]) + print(io, lpad(ctop[2*i, 2], halfwidth - length(ctop[2*i, 1]))) end + println(io) end - widths .+= 1 - totalwidth = sum(widths) + rnwidth - if length(ctitle) > 0 - halfwidth = div(totalwidth - length(ctitle), 2) - println(io, " " ^ halfwidth * string(ctitle) * " " ^ halfwidth) + + # rest of coeftable code + println(io, repeat('=', totwidth)) + print(io, repeat(' ', sum(A[1]))) + for j in 1:length(colnms) + print(io, " ", lpad(colnms[j], sum(A[j+1]))) end - if length(ctop) > 0 - for i in 1:size(ctop, 1) - ctop[i, 1] = ctop[i, 1] * ":" - end - println(io, "=" ^totalwidth) - halfwidth = div(totalwidth, 2) - 1 - interwidth = 2 + mod(totalwidth, 2) - for i in 1:(div(size(ctop, 1) - 1, 2)+1) - print(io, ctop[2*i-1, 1]) - print(io, lpad(ctop[2*i-1, 2], halfwidth - length(ctop[2*i-1, 1]))) - print(io, " " ^interwidth) - if size(ctop, 1) >= 2*i - print(io, ctop[2*i, 1]) - print(io, lpad(ctop[2*i, 2], halfwidth - length(ctop[2*i, 1]))) - end - println(io) - end + println(io, '\n', repeat('─', totwidth)) + for i in 1:size(mat, 1) + Base.print_matrix_row(io, mat, A, i, 1:size(mat, 2), " ") + i != size(mat, 1) && println(io) end - println(io,"=" ^totalwidth) - println(io, rpad(string(yname), rnwidth-1) * "|" * - join([lpad(string(colnms[i]), widths[i]) for i = 1:nc], "")) - println(io,"-" ^totalwidth) - for i in 1:nr - print(io, rownms[i]) - for j in 1:nc - print(io, lpad(str[i,j],widths[j])) - end - println(io) - end - println(io,"=" ^totalwidth) + println(io, '\n', repeat('=', totwidth)) + nothing end - + ############################################################################## ## diff --git a/src/FixedEffectModels.jl b/src/FixedEffectModels.jl index 8152164e..88bf5f7f 100644 --- a/src/FixedEffectModels.jl +++ b/src/FixedEffectModels.jl @@ -1,16 +1,13 @@ module FixedEffectModels -# slows down tss -#if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@optlevel")) -# @eval Base.Experimental.@optlevel 1 -#end using DataFrames using FixedEffects using LinearAlgebra using Printf using Reexport +using SnoopPrecompile using Statistics using StatsAPI using StatsBase @@ -38,14 +35,15 @@ has_iv, has_fe, Vcov -if ccall(:jl_generating_output, Cint, ()) == 1 # if we're precompiling the package - let - df = DataFrame(x1 = [1.0, 2.0, 3.0, 4.0], x2 = [1.0, 2.0, 4.0, 4.0], y = [3.0, 4.0, 4.0, 5.0], id = [1, 1, 2, 2]) - reg(df, @formula(y ~ x1 + x2)) - reg(df, @formula(y ~ x1 + fe(id))) - reg(df, @formula(y ~ x1), Vcov.cluster(:id)) - end + +@precompile_all_calls begin + df = DataFrame(x1 = [1.0, 2.0, 3.0, 4.0], x2 = [1.0, 2.0, 4.0, 4.0], y = [3.0, 4.0, 4.0, 5.0], id = [1, 1, 2, 2]) + reg(df, @formula(y ~ x1 + x2)) + reg(df, @formula(y ~ x1 + fe(id))) + reg(df, @formula(y ~ 1), Vcov.cluster(:id)) end + + end diff --git a/src/fit.jl b/src/fit.jl index 4dbd5c34..97bde38e 100644 --- a/src/fit.jl +++ b/src/fit.jl @@ -46,22 +46,22 @@ using RDatasets, FixedEffectModels df = dataset("plm", "Cigar") fit(FixedEffectModel, @formula(Sales ~ NDI + fe(State) + fe(State)&Year), df) """ -function reg(@nospecialize(df), - @nospecialize(formula::FormulaTerm), - @nospecialize(vcov::CovarianceEstimator = Vcov.simple()); - @nospecialize(contrasts::Dict = Dict{Symbol, Any}()), - @nospecialize(weights::Union{Symbol, Nothing} = nothing), - @nospecialize(save::Union{Bool, Symbol} = :none), - @nospecialize(method::Symbol = :cpu), - @nospecialize(nthreads::Integer = method == :cpu ? Threads.nthreads() : 256), - @nospecialize(double_precision::Bool = true), - @nospecialize(tol::Real = 1e-6), - @nospecialize(maxiter::Integer = 10000), - @nospecialize(drop_singletons::Bool = true), - @nospecialize(progress_bar::Bool = true), - @nospecialize(dof_add::Integer = 0), - @nospecialize(subset::Union{Nothing, AbstractVector} = nothing), - @nospecialize(first_stage::Bool = true)) +function reg(df, + formula::FormulaTerm, + vcov::CovarianceEstimator = Vcov.simple(); + contrasts::Dict = Dict{Symbol, Any}(), + weights::Union{Symbol, Nothing} = nothing, + save::Union{Bool, Symbol} = :none, + method::Symbol = :cpu, + nthreads::Integer = method == :cpu ? Threads.nthreads() : 256, + double_precision::Bool = true, + tol::Real = 1e-6, + maxiter::Integer = 10000, + drop_singletons::Bool = true, + progress_bar::Bool = true, + dof_add::Integer = 0, + subset::Union{Nothing, AbstractVector} = nothing, + first_stage::Bool = true) StatsAPI.fit(FixedEffectModel, formula, df, vcov; contrasts = contrasts, weights = weights, save = save, method = method, nthreads = nthreads, double_precision = double_precision, tol = tol, maxiter = maxiter, drop_singletons = drop_singletons, progress_bar = progress_bar, dof_add = dof_add, subset = subset, first_stage = first_stage) end @@ -230,7 +230,7 @@ function StatsAPI.fit(::Type{FixedEffectModel}, all(isfinite, Z) || throw("Some observations for the instrumental variables are infinite") # modify formula to use in predict - formula_schema = FormulaTerm(formula_schema.lhs, (tuple(eachterm(formula_schema.rhs)..., (term for term in eachterm(formula_endo_schema.rhs) if term != ConstantTerm(0))...))) + formula_schema = FormulaTerm(formula_schema.lhs, MatrixTerm(tuple(eachterm(formula_schema.rhs)..., (term for term in eachterm(formula_endo_schema.rhs) if term != ConstantTerm(0))...))) end # compute tss now before potentially demeaning y diff --git a/src/partial_out.jl b/src/partial_out.jl index 4f258345..933be079 100644 --- a/src/partial_out.jl +++ b/src/partial_out.jl @@ -9,7 +9,7 @@ Partial out variables in a Dataframe * `maxiter::Integer`: Maximum number of iterations * `double_precision::Bool`: Should the demeaning operation use Float64 rather than Float32? Default to true. * `tol::Real`: Tolerance -* `align::Bool`: Should the returned DataFrame align with the original DataFrame in case of missing values? Default to true +* `align::Bool`: Should the returned DataFrame align with the original DataFrame in case of missing values? Default to true. ### Returns * `::DataFrame`: a dataframe with as many columns as there are dependent variables and as many rows as the original dataframe. diff --git a/src/utils/formula.jl b/src/utils/formula.jl index 9d57a02f..f3240734 100644 --- a/src/utils/formula.jl +++ b/src/utils/formula.jl @@ -6,15 +6,7 @@ eachterm(@nospecialize(x::AbstractTerm)) = (x,) eachterm(@nospecialize(x::NTuple{N, AbstractTerm})) where {N} = x -TermOrTerms = Union{AbstractTerm, NTuple{N, AbstractTerm} where N} -hasintercept(@nospecialize(t::TermOrTerms)) = - InterceptTerm{true}() ∈ terms(t) || - ConstantTerm(1) ∈ terms(t) -omitsintercept(@nospecialize(f::FormulaTerm)) = omitsintercept(f.rhs) -omitsintercept(@nospecialize(t::TermOrTerms)) = - InterceptTerm{false}() ∈ terms(t) || - ConstantTerm(0) ∈ terms(t) || - ConstantTerm(-1) ∈ terms(t) + ############################################################################## ## ## Parse IV @@ -48,18 +40,19 @@ struct FixedEffectTerm <: AbstractTerm x::Symbol end StatsModels.termvars(t::FixedEffectTerm) = [t.x] -fe(x::Term) = FixedEffectTerm(Symbol(x)) +fe(x::Term) = fe(Symbol(x)) fe(s::Symbol) = FixedEffectTerm(s) has_fe(::FixedEffectTerm) = true has_fe(::FunctionTerm{typeof(fe)}) = true -has_fe(t::InteractionTerm) = any(has_fe(x) for x in t.terms) +has_fe(@nospecialize(t::InteractionTerm)) = any(has_fe(x) for x in t.terms) has_fe(::AbstractTerm) = false + has_fe(@nospecialize(t::FormulaTerm)) = any(has_fe(x) for x in eachterm(t.rhs)) fesymbol(t::FixedEffectTerm) = t.x -fesymbol(t::FunctionTerm{typeof(fe)}) = Symbol(t.args_parsed[1]) +fesymbol(t::FunctionTerm{typeof(fe)}) = Symbol(t.args[1]) """ parse_fixedeffect(data, formula::FormulaTerm) diff --git a/test/Project.toml b/test/Project.toml index 6168988c..8b7bc29a 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -11,7 +11,7 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] CategoricalArrays = "0.10" -CSV = "0.8" -CUDA = "1, 2, 3" +CSV = "0.8, 0.9, 0.10" +CUDA = "1, 2, 3, 4" DataFrames = "0.21, 0.22, 1" FixedEffects = "2" diff --git a/test/fit.jl b/test/fit.jl index 3a58c90a..fb3885e8 100644 --- a/test/fit.jl +++ b/test/fit.jl @@ -1,690 +1,705 @@ using CUDA, FixedEffectModels, CategoricalArrays, CSV, DataFrames, Test, LinearAlgebra -df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv"))) -df.StateC = categorical(df.State) -df.YearC = categorical(df.Year) -############################################################################## -## -## coefficients -## -############################################################################## - -# simple -m = @formula Sales ~ Price -x = reg(df, m) -@test coef(x) ≈ [139.73446,-0.22974] atol = 1e-4 -m = @formula Sales ~ Price -x = reg(df, m, weights = :Pop) -@test coef(x) ≈ [137.72495428982756,-0.23738] atol = 1e-4 - -df.SalesInt = round.(Int64, df.Sales) -m = @formula SalesInt ~ Price -x = reg(df, m) -@test coef(x) ≈ [139.72674,-0.2296683205] atol = 1e-4 - - -# absorb -m = @formula Sales ~ Price + fe(State) -x = reg(df, m) -@test coef(x) ≈ [-0.20984] atol = 1e-4 -@test x.iterations == 1 - -m = @formula Sales ~ Price + fe(State) + fe(Year) -x = reg(df, m) -@test coef(x) ≈ [-1.08471] atol = 1e-4 -m = @formula Sales ~ Price + fe(State) + fe(State)*Year -x = reg(df, m) -@test coef(x) ≈ [-0.53470, 0.0] atol = 1e-4 -m = @formula Sales ~ Price + fe(State)*Year -x = reg(df, m) -@test coef(x) ≈ [-0.53470, 0.0] atol = 1e-4 - -#@test isempty(coef(reg(df, @formula(Sales ~ 0), @fe(State*Price)))) -df.mState = div.(df.State, 10) -m = @formula Sales ~ Price + fe(mState)&fe(Year) -x = reg(df, m) -@test coef(x) ≈ [-1.44255] atol = 1e-4 - -m = @formula Sales ~ Price + fe(State)&Year -x = reg(df, m) -@test coef(x) ≈ [13.993028174622104,-0.5804357763515606] atol = 1e-4 -m = @formula Sales ~ Price + Year&fe(State) -x = reg(df, m) -@test coef(x) ≈ [13.993028174622104,-0.5804357763515606] atol = 1e-4 -m = @formula Sales ~ 1 + Year&fe(State) -x = reg(df, m) -@test coef(x) ≈ [174.4084407796102] atol = 1e-4 - -m = @formula Sales ~ Price + fe(State)&Year + fe(Year)&State -x = reg(df, m) -@test coef(x) ≈ [51.2359,- 0.5797] atol = 1e-4 -m = @formula Sales ~ Price + NDI + fe(State)&Year + fe(Year)&State -x = reg(df, m) -@test coef(x) ≈ [-46.4464,-0.2546, -0.005563] atol = 1e-4 -m = @formula Sales ~ 0 + Price + NDI + fe(State)&Year + fe(Year)&State -x = reg(df, m) -@test coef(x) ≈ [-0.21226562244177932,-0.004775616634862829] atol = 1e-4 - - -# recheck these two below -m = @formula Sales ~ Pimin + Price&NDI&fe(State) -x = reg(df, m) -@test coef(x) ≈ [122.98713, 0.30933] atol = 1e-4 -# SSR does not work well here -m = @formula Sales ~ Pimin + (Price&NDI)*fe(State) -x = reg(df, m) -@test coef(x) ≈ [0.421406, 0.0] atol = 1e-4 - -# only one intercept -m = @formula Sales ~ 1 + fe(State) + fe(Year) -x = reg(df, m) - - - - - -# TO DO: REPORT INTERCEPT IN CASE OF FIXED EFFFECTS, LIKE STATA -df.id3 = categorical(mod.(1:size(df, 1), Ref(3))) -df.id4 = categorical(div.(1:size(df, 1), Ref(10))) - -m = @formula Sales ~ id3 -x = reg(df, m) -@test length(coef(x)) == 3 - -m = @formula Sales ~ 0 + id3 -x = reg(df, m) -@test length(coef(x)) == 3 - - -# with fixed effects it's like implicit intercept -m = @formula Sales ~ id3 + fe(id4) -x = reg(df, m) -@test length(coef(x)) == 2 - - -m = @formula Sales ~ Year + fe(State) -x = reg(df, m) - - -m = @formula Sales ~ id3&Price -x = reg(df, m) -@test length(coef(x)) == 4 -m = @formula Sales ~ id3&Price + Price -x = reg(df, m) -@test length(coef(x)) == 4 - - - - - -m = @formula Sales ~ Year + fe(State) -x = reg(df, m) - - - -m = @formula Sales ~ Year&Price + fe(State) -x = reg(df, m) - - - - -# absorb + weights -m = @formula Sales ~ Price + fe(State) -x = reg(df, m, weights = :Pop) -@test coef(x) ≈ [- 0.21741] atol = 1e-4 -m = @formula Sales ~ Price + fe(State) + fe(Year) -x = reg(df, m, weights = :Pop) -@test coef(x) ≈ [- 0.88794] atol = 1e-3 -m = @formula Sales ~ Price + fe(State) + fe(State)&Year -x = reg(df, m, weights = :Pop) -@test coef(x) ≈ [- 0.461085492] atol = 1e-4 - -# iv -m = @formula Sales ~ (Price ~ Pimin) -x = reg(df, m) -@test coef(x) ≈ [138.19479,- 0.20733] atol = 1e-4 -m = @formula Sales ~ NDI + (Price ~ Pimin) -x = reg(df, m) -@test coef(x) ≈ [137.45096,0.00516,- 0.76276] atol = 1e-4 -m = @formula Sales ~ NDI + (Price ~ Pimin + Pop) -x = reg(df, m) -@test coef(x) ≈ [137.57335,0.00534,- 0.78365] atol = 1e-4 -## multiple endogeneous variables -m = @formula Sales ~ (Price + NDI ~ Pimin + Pop) -x = reg(df, m) -@test coef(x) ≈ [139.544, .8001, -.00937] atol = 1e-4 -m = @formula Sales ~ 1 + (Price + NDI ~ Pimin + Pop) -x = reg(df, m) -@test coef(x) ≈ [139.544, .8001, -.00937] atol = 1e-4 -result = [196.576, 0.00490989, -2.94019, -3.00686, -2.94903, -2.80183, -2.74789, -2.66682, -2.63855, -2.52394, -2.34751, -2.19241, -2.18707, -2.09244, -1.9691, -1.80463, -1.81865, -1.70428, -1.72925, -1.68501, -1.66007, -1.56102, -1.43582, -1.36812, -1.33677, -1.30426, -1.28094, -1.25175, -1.21438, -1.16668, -1.13033, -1.03782] - -m = @formula Sales ~ NDI + (Price&YearC ~ Pimin&YearC) -x = reg(df, m) -@test coef(x) ≈ result atol = 1e-4 - -# iv + weight -m = @formula Sales ~ (Price ~ Pimin) -x = reg(df, m, weights = :Pop) -@test coef(x) ≈ [137.03637,- 0.22802] atol = 1e-4 - -# iv + weight + absorb -m = @formula Sales ~ (Price ~ Pimin) + fe(State) -x = reg(df, m) -@test coef(x) ≈ [-0.20284] atol = 1e-4 -m = @formula Sales ~ (Price ~ Pimin) + fe(State) -x = reg(df, m, weights = :Pop) -@test coef(x) ≈ [-0.20995] atol = 1e-4 -m = @formula Sales ~ NDI + (Price ~ Pimin) -x = reg(df, m) -@test coef(x) ≈ [137.45096580480387,0.005169677634275297,-0.7627670265757879] atol = 1e-4 -m = @formula Sales ~ NDI + (Price ~ Pimin) + fe(State) -x = reg(df, m) -@test coef(x) ≈ [0.0011021722526916768,-0.3216374943695231] atol = 1e-4 - -# non high dimensional factors -m = @formula Sales ~ Price + YearC -x = reg(df, m) -m = @formula Sales ~ YearC + fe(State) -x = reg(df, m) -m = @formula Sales ~ Price + YearC + fe(State) -x = reg(df, m) -@test coef(x)[1] ≈ -1.08471 atol = 1e-4 -m = @formula Sales ~ Price + YearC + fe(State) -x = reg(df, m, weights = :Pop) -@test coef(x)[1] ≈ -0.88794 atol = 1e-4 -m = @formula Sales ~ NDI + (Price ~ Pimin) + YearC + fe(State) -x = reg(df, m) -@test coef(x)[1] ≈ -0.00525 atol = 1e-4 - ############################################################################## ## -## Programming -## -############################################################################## -reg(df, term(:Sales) ~ term(:NDI) + fe(:State) + fe(:Year), Vcov.cluster(:State)) -@test fe(:State) + fe(:Year) === reduce(+, fe.([:State, :Year])) === fe(term(:State)) + fe(term(:Year)) - - -############################################################################## -## -## Functions -## -############################################################################## - -# function -m = @formula Sales ~ log(Price) -x = reg(df, m) -@test coef(x)[1] ≈184.98520688 atol = 1e-4 - -# function defined in user space -mylog(x) = log(x) -m = @formula Sales ~ mylog(Price) -x = reg(df, m) - -# Function returning Inf -df.Price_zero = copy(df.Price) -df.Price_zero[1] = 0.0 -m = @formula Sales ~ log(Price_zero) -@test_throws "Some observations for the regressor are infinite" reg(df, m) -############################################################################## -## -## collinearity -## add more tests +## coefficients ## ############################################################################## -# ols -df.Price2 = df.Price -m = @formula Sales ~ Price + Price2 -x = reg(df, m) -@test coef(x) ≈ [139.7344639806166,-0.22974688593485126,0.0] atol = 1e-4 - +@testset "coefficients" begin + + df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv"))) + df.StateC = categorical(df.State) + df.YearC = categorical(df.Year) + + # simple + m = @formula Sales ~ Price + x = reg(df, m) + @test coef(x) ≈ [139.73446,-0.22974] atol = 1e-4 + m = @formula Sales ~ Price + x = reg(df, m, weights = :Pop) + @test coef(x) ≈ [137.72495428982756,-0.23738] atol = 1e-4 -## iv -df.NDI2 = df.NDI -m = @formula Sales ~ NDI2 + NDI + (Price ~ Pimin) -x = reg(df, m) -@test iszero(coef(x)[2]) || iszero(coef(x)[3]) + df.SalesInt = round.(Int64, df.Sales) + m = @formula SalesInt ~ Price + x = reg(df, m) + @test coef(x) ≈ [139.72674,-0.2296683205] atol = 1e-4 -## endogeneous variables collinear with instruments are reclassified -df.zPimin = df.Pimin -m = @formula Sales ~ zPimin + (Price ~ NDI + Pimin) -x = reg(df, m) + # absorb + m = @formula Sales ~ Price + fe(State) + x = reg(df, m) + @test coef(x) ≈ [-0.20984] atol = 1e-4 + @test x.iterations == 1 -m2 = @formula Sales ~ (zPimin + Price ~ NDI + Pimin) -NDI = reg(df, m2) -@test coefnames(x) == coefnames(NDI) -@test coef(x) ≈ coef(NDI) -@test vcov(x) ≈ vcov(NDI) + m = @formula Sales ~ Price + fe(State) + fe(Year) + x = reg(df, m) + @test coef(x) ≈ [-1.08471] atol = 1e-4 + m = @formula Sales ~ Price + fe(State) + fe(State)*Year + x = reg(df, m) + @test coef(x) ≈ [-0.53470, 0.0] atol = 1e-4 + m = @formula Sales ~ Price + fe(State)*Year + x = reg(df, m) + @test coef(x) ≈ [-0.53470, 0.0] atol = 1e-4 + #@test isempty(coef(reg(df, @formula(Sales ~ 0), @fe(State*Price)))) + df.mState = div.(df.State, 10) + m = @formula Sales ~ Price + fe(mState)&fe(Year) + x = reg(df, m) + @test coef(x) ≈ [-1.44255] atol = 1e-4 -# catch when IV underidentified -@test_throws "Model not identified. There must be at least as many ivs as endogeneneous variables" reg(df, @formula(Sales ~ Price + (NDI + Pop ~ NDI))) -@test_throws "Model not identified. There must be at least as many ivs as endogeneneous variables" reg(df, @formula(Sales ~ Price + (Pop ~ Price))) + m = @formula Sales ~ Price + fe(State)&Year + x = reg(df, m) + @test coef(x) ≈ [13.993028174622104,-0.5804357763515606] atol = 1e-4 + m = @formula Sales ~ Price + Year&fe(State) + x = reg(df, m) + @test coef(x) ≈ [13.993028174622104,-0.5804357763515606] atol = 1e-4 + m = @formula Sales ~ 1 + Year&fe(State) + x = reg(df, m) + @test coef(x) ≈ [174.4084407796102] atol = 1e-4 + m = @formula Sales ~ Price + fe(State)&Year + fe(Year)&State + x = reg(df, m) + @test coef(x) ≈ [51.2359,- 0.5797] atol = 1e-4 + m = @formula Sales ~ Price + NDI + fe(State)&Year + fe(Year)&State + x = reg(df, m) + @test coef(x) ≈ [-46.4464,-0.2546, -0.005563] atol = 1e-4 + m = @formula Sales ~ 0 + Price + NDI + fe(State)&Year + fe(Year)&State + x = reg(df, m) + @test coef(x) ≈ [-0.21226562244177932,-0.004775616634862829] atol = 1e-4 + # recheck these two below + m = @formula Sales ~ Pimin + Price&NDI&fe(State) + x = reg(df, m) + @test coef(x) ≈ [122.98713, 0.30933] atol = 1e-4 + # SSR does not work well here + m = @formula Sales ~ Pimin + (Price&NDI)*fe(State) + x = reg(df, m) + @test coef(x) ≈ [0.421406, 0.0] atol = 1e-4 + + # only one intercept + m = @formula Sales ~ 1 + fe(State) + fe(Year) + x = reg(df, m) + + + + + + # TO DO: REPORT INTERCEPT IN CASE OF FIXED EFFFECTS, LIKE STATA + df.id3 = categorical(mod.(1:size(df, 1), Ref(3))) + df.id4 = categorical(div.(1:size(df, 1), Ref(10))) + + m = @formula Sales ~ id3 + x = reg(df, m) + @test length(coef(x)) == 3 + + m = @formula Sales ~ 0 + id3 + x = reg(df, m) + @test length(coef(x)) == 3 + + + # with fixed effects it's like implicit intercept + m = @formula Sales ~ id3 + fe(id4) + x = reg(df, m) + @test length(coef(x)) == 2 + + + m = @formula Sales ~ Year + fe(State) + x = reg(df, m) + + + m = @formula Sales ~ id3&Price + x = reg(df, m) + @test length(coef(x)) == 4 + m = @formula Sales ~ id3&Price + Price + x = reg(df, m) + @test length(coef(x)) == 4 + + + + + + m = @formula Sales ~ Year + fe(State) + x = reg(df, m) + + + + m = @formula Sales ~ Year&Price + fe(State) + x = reg(df, m) + + + + + # absorb + weights + m = @formula Sales ~ Price + fe(State) + x = reg(df, m, weights = :Pop) + @test coef(x) ≈ [- 0.21741] atol = 1e-4 + m = @formula Sales ~ Price + fe(State) + fe(Year) + x = reg(df, m, weights = :Pop) + @test coef(x) ≈ [- 0.88794] atol = 1e-3 + m = @formula Sales ~ Price + fe(State) + fe(State)&Year + x = reg(df, m, weights = :Pop) + @test coef(x) ≈ [- 0.461085492] atol = 1e-4 + + # iv + m = @formula Sales ~ (Price ~ Pimin) + x = reg(df, m) + @test coef(x) ≈ [138.19479,- 0.20733] atol = 1e-4 + m = @formula Sales ~ NDI + (Price ~ Pimin) + x = reg(df, m) + @test coef(x) ≈ [137.45096,0.00516,- 0.76276] atol = 1e-4 + m = @formula Sales ~ NDI + (Price ~ Pimin + Pop) + x = reg(df, m) + @test coef(x) ≈ [137.57335,0.00534,- 0.78365] atol = 1e-4 + ## multiple endogeneous variables + m = @formula Sales ~ (Price + NDI ~ Pimin + Pop) + x = reg(df, m) + @test coef(x) ≈ [139.544, .8001, -.00937] atol = 1e-4 + m = @formula Sales ~ 1 + (Price + NDI ~ Pimin + Pop) + x = reg(df, m) + @test coef(x) ≈ [139.544, .8001, -.00937] atol = 1e-4 + result = [196.576, 0.00490989, -2.94019, -3.00686, -2.94903, -2.80183, -2.74789, -2.66682, -2.63855, -2.52394, -2.34751, -2.19241, -2.18707, -2.09244, -1.9691, -1.80463, -1.81865, -1.70428, -1.72925, -1.68501, -1.66007, -1.56102, -1.43582, -1.36812, -1.33677, -1.30426, -1.28094, -1.25175, -1.21438, -1.16668, -1.13033, -1.03782] + + m = @formula Sales ~ NDI + (Price&YearC ~ Pimin&YearC) + x = reg(df, m) + @test coef(x) ≈ result atol = 1e-4 + + # iv + weight + m = @formula Sales ~ (Price ~ Pimin) + x = reg(df, m, weights = :Pop) + @test coef(x) ≈ [137.03637,- 0.22802] atol = 1e-4 + # iv + weight + absorb + m = @formula Sales ~ (Price ~ Pimin) + fe(State) + x = reg(df, m) + @test coef(x) ≈ [-0.20284] atol = 1e-4 + m = @formula Sales ~ (Price ~ Pimin) + fe(State) + x = reg(df, m, weights = :Pop) + @test coef(x) ≈ [-0.20995] atol = 1e-4 + m = @formula Sales ~ NDI + (Price ~ Pimin) + x = reg(df, m) + @test coef(x) ≈ [137.45096580480387,0.005169677634275297,-0.7627670265757879] atol = 1e-4 + m = @formula Sales ~ NDI + (Price ~ Pimin) + fe(State) + x = reg(df, m) + @test coef(x) ≈ [0.0011021722526916768,-0.3216374943695231] atol = 1e-4 -# catch when IV underidentified -@test_throws "Model not identified. There must be at least as many ivs as endogeneneous variables" reg(df, @formula(Sales ~ Price + (NDI + Pop ~ NDI))) - - -# Make sure all coefficients are estimated -p = [100.0, -40.0, 30.0, 20.0] -df_r = DataFrame(y = p, x = p.^4) -result = reg(df_r, @formula(y ~ x)) -@test sum(abs.(coef(result)) .> 0) == 2 - -############################################################################## -## -## std errors -## -############################################################################## - -# Simple - matches stata -m = @formula Sales ~ Price -x = reg(df, m) -@test stderror(x) ≈ [1.521269, 0.0188963] atol = 1e-6 -# Stata ivreg - ivreg2 discrepancy - matches with df_add=-2 -m = @formula Sales ~ (Price ~ Pimin) -x = reg(df, m) -@test stderror(x) ≈ [1.53661, 0.01915] atol = 1e-4 -# Stata areg -m = @formula Sales ~ Price + fe(State) -x = reg(df, m) -@test stderror(x) ≈ [0.0098003] atol = 1e-7 - -# White -# Stata reg - matches stata -m = @formula Sales ~ Price -x = reg(df, m, Vcov.robust()) -@test stderror(x) ≈ [1.686791, 0.0167042] atol = 1e-6 -# Stata ivreg - ivreg2 discrepancy - matches with df_add=-2 -m = @formula Sales ~ (Price ~ Pimin) -x = reg(df, m, Vcov.robust()) -@test stderror(x) ≈ [1.63305, 0.01674] atol = 1e-4 -# Stata areg - matches areg -m = @formula Sales ~ Price + fe(State) -x = reg(df, m, Vcov.robust()) -@test stderror(x) ≈ [0.0110005] atol = 1e-7 - -# Clustering models -# cluster - matches stata -m = @formula Sales ~ Price -x = reg(df, m, Vcov.cluster(:State)) -@test stderror(x)[2] ≈ 0.0379228 atol = 1e-7 -# cluster with fe - matches areg & reghdfe -m = @formula Sales ~ Price + fe(State) -x = reg(df, m, Vcov.cluster(:Year)) -@test stderror(x) ≈ [0.0220563] atol = 1e-7 -# stata reghxe - matches reghdfe (not areg) -m = @formula Sales ~ Price + fe(State) -x = reg(df, m, Vcov.cluster(:State)) -@test stderror(x) ≈ [0.0357498] atol = 1e-7 -# iv + fe + cluster - matches ivreghdfe -m = @formula Sales ~ NDI + (Price ~Pimin) + fe(State) -x = reg(df, m, Vcov.cluster(:State)) -@test stderror(x) ≈ [0.0019704, 0.1893396] atol = 1e-7 -# iv + fe + cluster + weights - matches ivreghdfe -m = @formula Sales ~ NDI + (Price ~ Pimin) + fe(State) -x = reg(df, m, Vcov.cluster(:State), weights = :Pop) -@test stderror(x) ≈ [0.000759, 0.070836] atol = 1e-6 -# iv + fe + cluster + weights - matches ivreghdfe -m = @formula Sales ~ (Price ~ Pimin) + fe(State) -x = reg(df, m, Vcov.cluster(:State), weights = :Pop) -@test stderror(x) ≈ [0.0337439] atol = 1e-7 -# multiway clustering - matches reghdfe -m = @formula Sales ~ Price -x = reg(df, m, Vcov.cluster(:State, :Year)) -@test stderror(x) ≈ [6.196362, 0.0403469] atol = 1e-6 -# multiway clustering - matches reghdfe -m = @formula Sales ~ Price -x = reg(df, m, Vcov.cluster(:State, :Year)) -@test stderror(x) ≈ [6.196362, 0.0403469] atol = 1e-6 -# fe + multiway clustering - matches reghdfe -m = @formula Sales ~ Price + fe(State) -x = reg(df, m, Vcov.cluster(:State, :Year)) -@test stderror(x) ≈ [0.0405335] atol = 1e-7 -m = @formula Sales ~ Price + fe(State) -x = reg(df, m, Vcov.cluster(:State, :Year)) -@test stderror(x) ≈ [0.0405335] atol = 1e-7 -# fe + clustering on interactions - matches reghdfe -#m = @formula Sales ~ Price + fe(State) vcov = cluster(State&id2) -#x = reg(df, m) -#@test stderror(x) ≈ [0.0110005] atol = 1e-7 -# fe partially nested in interaction clusters - matches reghdfe -#m=@formula Sales ~ Price + fe(State) vcov=cluster(State&id2) -#x = reg(df, m) -#@test stderror(x) ≈ [0.0110005] atol = 1e-7 -# regressor partially nested in interaction clusters - matches reghdfe -#m=@formula Sales ~ Price + StateC vcov=cluster(State&id2) -#x = reg(df, m) -#@test stderror(x)[1:2] ≈ [3.032187, 0.0110005] atol=1e-5 - -#check palue printed is correct -#m = @formula Sales ~ Price + fe(State) -#x = reg(df, m, Vcov.cluster(:State)) -#@test coeftable(x).mat[1, 4] ≈ 4.872723900371927e-7 atol = 1e-7 - - -############################################################################## -## -## subset -## -############################################################################## -m = @formula Sales ~ Price + StateC -x0 = reg(df[df.State .<= 30, :], m) + # non high dimensional factors + m = @formula Sales ~ Price + YearC + x = reg(df, m) + m = @formula Sales ~ YearC + fe(State) + x = reg(df, m) + m = @formula Sales ~ Price + YearC + fe(State) + x = reg(df, m) + @test coef(x)[1] ≈ -1.08471 atol = 1e-4 + m = @formula Sales ~ Price + YearC + fe(State) + x = reg(df, m, weights = :Pop) + @test coef(x)[1] ≈ -0.88794 atol = 1e-4 + m = @formula Sales ~ NDI + (Price ~ Pimin) + YearC + fe(State) + x = reg(df, m) + @test coef(x)[1] ≈ -0.00525 atol = 1e-4 + + + ############################################################################## + ## + ## Programming + ## + ############################################################################## + reg(df, term(:Sales) ~ term(:NDI) + fe(:State) + fe(:Year), Vcov.cluster(:State)) + @test fe(:State) + fe(:Year) === reduce(+, fe.([:State, :Year])) === fe(term(:State)) + fe(term(:Year)) + + + ############################################################################## + ## + ## Functions + ## + ############################################################################## + + # function + m = @formula Sales ~ log(Price) + x = reg(df, m) + @test coef(x)[1] ≈184.98520688 atol = 1e-4 + + # function defined in user space + mylog(x) = log(x) + m = @formula Sales ~ mylog(Price) + x = reg(df, m) + + # Function returning Inf + df.Price_zero = copy(df.Price) + df.Price_zero[1] = 0.0 + m = @formula Sales ~ log(Price_zero) + @test_throws "Some observations for the regressor are infinite" reg(df, m) + ############################################################################## + ## + ## collinearity + ## add more tests + ## + ############################################################################## + # ols + df.Price2 = df.Price + m = @formula Sales ~ Price + Price2 + x = reg(df, m) + @test coef(x) ≈ [139.7344639806166,-0.22974688593485126,0.0] atol = 1e-4 + + + ## iv + df.NDI2 = df.NDI + m = @formula Sales ~ NDI2 + NDI + (Price ~ Pimin) + x = reg(df, m) + @test iszero(coef(x)[2]) || iszero(coef(x)[3]) + + + ## endogeneous variables collinear with instruments are reclassified + df.zPimin = df.Pimin + m = @formula Sales ~ zPimin + (Price ~ NDI + Pimin) + x = reg(df, m) + + m2 = @formula Sales ~ (zPimin + Price ~ NDI + Pimin) + NDI = reg(df, m2) + @test coefnames(x) == coefnames(NDI) + @test coef(x) ≈ coef(NDI) + @test vcov(x) ≈ vcov(NDI) + + + # catch when IV underidentified + @test_throws "Model not identified. There must be at least as many ivs as endogeneneous variables" reg(df, @formula(Sales ~ Price + (NDI + Pop ~ NDI))) + @test_throws "Model not identified. There must be at least as many ivs as endogeneneous variables" reg(df, @formula(Sales ~ Price + (Pop ~ Price))) + + + + + + # catch when IV underidentified + @test_throws "Model not identified. There must be at least as many ivs as endogeneneous variables" reg(df, @formula(Sales ~ Price + (NDI + Pop ~ NDI))) + + + # Make sure all coefficients are estimated + p = [100.0, -40.0, 30.0, 20.0] + df_r = DataFrame(y = p, x = p.^4) + result = reg(df_r, @formula(y ~ x)) + @test sum(abs.(coef(result)) .> 0) == 2 +end + ############################################################################## + ## + ## std errors + ## + ############################################################################## +@testset "standard errors" begin + + df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv"))) + df.StateC = categorical(df.State) + df.YearC = categorical(df.Year) + + # Simple - matches stata + m = @formula Sales ~ Price + x = reg(df, m) + @test stderror(x) ≈ [1.521269, 0.0188963] atol = 1e-6 + # Stata ivreg - ivreg2 discrepancy - matches with df_add=-2 + m = @formula Sales ~ (Price ~ Pimin) + x = reg(df, m) + @test stderror(x) ≈ [1.53661, 0.01915] atol = 1e-4 + # Stata areg + m = @formula Sales ~ Price + fe(State) + x = reg(df, m) + @test stderror(x) ≈ [0.0098003] atol = 1e-7 + + # White + # Stata reg - matches stata + m = @formula Sales ~ Price + x = reg(df, m, Vcov.robust()) + @test stderror(x) ≈ [1.686791, 0.0167042] atol = 1e-6 + # Stata ivreg - ivreg2 discrepancy - matches with df_add=-2 + m = @formula Sales ~ (Price ~ Pimin) + x = reg(df, m, Vcov.robust()) + @test stderror(x) ≈ [1.63305, 0.01674] atol = 1e-4 + # Stata areg - matches areg + m = @formula Sales ~ Price + fe(State) + x = reg(df, m, Vcov.robust()) + @test stderror(x) ≈ [0.0110005] atol = 1e-7 + + # Clustering models + # cluster - matches stata + m = @formula Sales ~ Price + x = reg(df, m, Vcov.cluster(:State)) + @test stderror(x)[2] ≈ 0.0379228 atol = 1e-7 + # cluster with fe - matches areg & reghdfe + m = @formula Sales ~ Price + fe(State) + x = reg(df, m, Vcov.cluster(:Year)) + @test stderror(x) ≈ [0.0220563] atol = 1e-7 + # stata reghxe - matches reghdfe (not areg) + m = @formula Sales ~ Price + fe(State) + x = reg(df, m, Vcov.cluster(:State)) + @test stderror(x) ≈ [0.0357498] atol = 1e-7 + # iv + fe + cluster - matches ivreghdfe + m = @formula Sales ~ NDI + (Price ~Pimin) + fe(State) + x = reg(df, m, Vcov.cluster(:State)) + @test stderror(x) ≈ [0.0019704, 0.1893396] atol = 1e-7 + # iv + fe + cluster + weights - matches ivreghdfe + m = @formula Sales ~ NDI + (Price ~ Pimin) + fe(State) + x = reg(df, m, Vcov.cluster(:State), weights = :Pop) + @test stderror(x) ≈ [0.000759, 0.070836] atol = 1e-6 + # iv + fe + cluster + weights - matches ivreghdfe + m = @formula Sales ~ (Price ~ Pimin) + fe(State) + x = reg(df, m, Vcov.cluster(:State), weights = :Pop) + @test stderror(x) ≈ [0.0337439] atol = 1e-7 + # multiway clustering - matches reghdfe + m = @formula Sales ~ Price + x = reg(df, m, Vcov.cluster(:State, :Year)) + @test stderror(x) ≈ [6.196362, 0.0403469] atol = 1e-6 + # multiway clustering - matches reghdfe + m = @formula Sales ~ Price + x = reg(df, m, Vcov.cluster(:State, :Year)) + @test stderror(x) ≈ [6.196362, 0.0403469] atol = 1e-6 + # fe + multiway clustering - matches reghdfe + m = @formula Sales ~ Price + fe(State) + x = reg(df, m, Vcov.cluster(:State, :Year)) + @test stderror(x) ≈ [0.0405335] atol = 1e-7 + m = @formula Sales ~ Price + fe(State) + x = reg(df, m, Vcov.cluster(:State, :Year)) + @test stderror(x) ≈ [0.0405335] atol = 1e-7 + # fe + clustering on interactions - matches reghdfe + #m = @formula Sales ~ Price + fe(State) vcov = cluster(State&id2) + #x = reg(df, m) + #@test stderror(x) ≈ [0.0110005] atol = 1e-7 + # fe partially nested in interaction clusters - matches reghdfe + #m=@formula Sales ~ Price + fe(State) vcov=cluster(State&id2) + #x = reg(df, m) + #@test stderror(x) ≈ [0.0110005] atol = 1e-7 + # regressor partially nested in interaction clusters - matches reghdfe + #m=@formula Sales ~ Price + StateC vcov=cluster(State&id2) + #x = reg(df, m) + #@test stderror(x)[1:2] ≈ [3.032187, 0.0110005] atol=1e-5 + + #check palue printed is correct + #m = @formula Sales ~ Price + fe(State) + #x = reg(df, m, Vcov.cluster(:State)) + #@test coeftable(x).mat[1, 4] ≈ 4.872723900371927e-7 atol = 1e-7 +end -m = @formula Sales ~ Price + StateC -Price = reg(df, m, subset = df.State .<= 30) -@test length(Price.esample) == size(df, 1) -@test coef(x0) ≈ coef(Price) atol = 1e-4 -@test vcov(x0) ≈ vcov(Price) atol = 1e-4 + ############################################################################## + ## + ## subset + ## + ############################################################################## +@testset "subset" begin + df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv"))) + df.StateC = categorical(df.State) + df.YearC = categorical(df.Year) -df.State_missing = ifelse.(df.State .<= 30, df.State, missing) -df.StateC_missing = categorical(df.State_missing) -m = @formula Sales ~ Price + StateC_missing -NDI = reg(df, m) -@test length(NDI.esample) == size(df, 1) -@test coef(x0) ≈ coef(NDI) atol = 1e-4 -@test vcov(x0) ≈ vcov(NDI) atol = 1e-2 + m = @formula Sales ~ Price + StateC + x0 = reg(df[df.State .<= 30, :], m) -# missing weights -df.Price_missing = ifelse.(df.State .<= 30, df.Price, missing) -m = @formula Sales ~ NDI + StateC -x = reg(df, m, weights = :Price_missing) -@test length(x.esample) == size(df, 1) + m = @formula Sales ~ Price + StateC + Price = reg(df, m, subset = df.State .<= 30) + @test length(Price.esample) == size(df, 1) + @test coef(x0) ≈ coef(Price) atol = 1e-4 + @test vcov(x0) ≈ vcov(Price) atol = 1e-4 -# missing interaction -m = @formula Sales ~ NDI + fe(State)&Price_missing -x = reg(df, m) -@test nobs(x) == count(.!ismissing.(df.Price_missing)) + df.State_missing = ifelse.(df.State .<= 30, df.State, missing) + df.StateC_missing = categorical(df.State_missing) + m = @formula Sales ~ Price + StateC_missing + NDI = reg(df, m) + @test length(NDI.esample) == size(df, 1) + @test coef(x0) ≈ coef(NDI) atol = 1e-4 + @test vcov(x0) ≈ vcov(NDI) atol = 1e-2 + # missing weights + df.Price_missing = ifelse.(df.State .<= 30, df.Price, missing) + m = @formula Sales ~ NDI + StateC + x = reg(df, m, weights = :Price_missing) + @test length(x.esample) == size(df, 1) -m = @formula Sales ~ Price + fe(State) -x3 = reg(df, m, subset = df.State .<= 30) -@test length(x3.esample) == size(df, 1) -@test coef(x0)[2] ≈ coef(x3)[1] atol = 1e-4 -m = @formula Sales ~ Price + fe(State_missing) -x4 = reg(df, m) -@test coef(x0)[2] ≈ coef(x4)[1] atol = 1e-4 + # missing interaction + m = @formula Sales ~ NDI + fe(State)&Price_missing + x = reg(df, m) + @test nobs(x) == count(.!ismissing.(df.Price_missing)) + m = @formula Sales ~ Price + fe(State) + x3 = reg(df, m, subset = df.State .<= 30) + @test length(x3.esample) == size(df, 1) + @test coef(x0)[2] ≈ coef(x3)[1] atol = 1e-4 -# categorical variable as fixed effects -m = @formula Sales ~ Price + fe(State) -x5 = reg(df, m, subset = df.State .>= 30) + m = @formula Sales ~ Price + fe(State_missing) + x4 = reg(df, m) + @test coef(x0)[2] ≈ coef(x4)[1] atol = 1e-4 -#Error reported by Erik -m = @formula Sales ~ Pimin + CPI -x = reg(df, m, Vcov.cluster(:State), subset = df.State .>= 30) -@test diag(x.vcov) ≈ [130.7464887, 0.0257875, 0.0383939] atol = 1e-4 + # categorical variable as fixed effects + m = @formula Sales ~ Price + fe(State) + x5 = reg(df, m, subset = df.State .>= 30) -############################################################################## -## -## R2 -## -############################################################################## -m = @formula Sales ~ Price -x = reg(df, m) -@test r2(x) ≈ 0.0969 atol = 1e-4 -@test adjr2(x) ≈ 0.09622618 atol = 1e-4 + #Error reported by Erik + m = @formula Sales ~ Pimin + CPI + x = reg(df, m, Vcov.cluster(:State), subset = df.State .>= 30) + @test diag(x.vcov) ≈ [130.7464887, 0.0257875, 0.0383939] atol = 1e-4 +end -############################################################################## -## -## F Stat -## -############################################################################## -m = @formula Sales ~ Price -x = reg(df, m) -@test x.F ≈ 147.82425 atol = 1e-4 -m = @formula Sales ~ Price + fe(State) -x = reg(df, m) -@test x.F ≈ 458.45825 atol = 1e-4 -m = @formula Sales ~ (Price ~ Pimin) -x = reg(df, m) -@test x.F ≈ 117.17329 atol = 1e-4 -m = @formula Sales ~ Price + Pop -x = reg(df, m) -@test x.F ≈ 79.091576 atol = 1e-4 -m = @formula Sales ~ Price -x = reg(df, m, Vcov.cluster(:State)) -@test x.F ≈ 36.70275 atol = 1e-4 -m = @formula Sales ~ (Price ~ Pimin) -x = reg(df, m, Vcov.cluster(:State)) -@test x.F ≈ 39.67227 atol = 1e-4 -# xtivreg2 -m = @formula Sales ~ (Price ~ Pimin) + fe(State) -x = reg(df, m) -@test x.F ≈ 422.46444 atol = 1e-4 - -# p value -m = @formula Pop ~ Pimin -x = reg(df, m) -@test x.p ≈ 0.003998718283554043 atol = 1e-4 -m = @formula Pop ~ Pimin + Price -x = reg(df, m) -@test x.p ≈ 3.369423076033613e-14 atol = 1e-4 - - - - -# Fstat https://github.com/FixedEffects/FixedEffectModels.jl/issues/150 -df_example = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Ftest.csv"))) -x = reg(df_example, @formula(Y ~ fe(id) + (X1 + X2 ~ Z1 + Z2) ), Vcov.robust() ) -@test x.F_kp ≈ 14.5348449357 atol = 1e-4 +@testset "statistics" begin + + df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv"))) + df.StateC = categorical(df.State) + df.YearC = categorical(df.Year) + ############################################################################## + ## + ## R2 + ## + ############################################################################## + m = @formula Sales ~ Price + x = reg(df, m) + @test r2(x) ≈ 0.0969 atol = 1e-4 + @test adjr2(x) ≈ 0.09622618 atol = 1e-4 + + + ############################################################################## + ## + ## F Stat + ## + ############################################################################## + m = @formula Sales ~ Price + x = reg(df, m) + @test x.F ≈ 147.82425 atol = 1e-4 + m = @formula Sales ~ Price + fe(State) + x = reg(df, m) + @test x.F ≈ 458.45825 atol = 1e-4 + m = @formula Sales ~ (Price ~ Pimin) + x = reg(df, m) + @test x.F ≈ 117.17329 atol = 1e-4 + m = @formula Sales ~ Price + Pop + x = reg(df, m) + @test x.F ≈ 79.091576 atol = 1e-4 + m = @formula Sales ~ Price + x = reg(df, m, Vcov.cluster(:State)) + @test x.F ≈ 36.70275 atol = 1e-4 + m = @formula Sales ~ (Price ~ Pimin) + x = reg(df, m, Vcov.cluster(:State)) + @test x.F ≈ 39.67227 atol = 1e-4 + # xtivreg2 + m = @formula Sales ~ (Price ~ Pimin) + fe(State) + x = reg(df, m) + @test x.F ≈ 422.46444 atol = 1e-4 + + # p value + m = @formula Pop ~ Pimin + x = reg(df, m) + @test x.p ≈ 0.003998718283554043 atol = 1e-4 + m = @formula Pop ~ Pimin + Price + x = reg(df, m) + @test x.p ≈ 3.369423076033613e-14 atol = 1e-4 + + + + + # Fstat https://github.com/FixedEffects/FixedEffectModels.jl/issues/150 + df_example = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Ftest.csv"))) + x = reg(df_example, @formula(Y ~ fe(id) + (X1 + X2 ~ Z1 + Z2) ), Vcov.robust() ) + @test x.F_kp ≈ 14.5348449357 atol = 1e-4 + + + ############################################################################## + ## + ## F_kp r_kp statistics for IV. Difference degrees of freedom. + ## + ############################################################################## + m = @formula Sales ~ (Price ~ Pimin) + x = reg(df, m) + @test x.F_kp ≈ 52248.79247 atol = 1e-4 + m = @formula Sales ~ NDI + (Price ~ Pimin) + x = reg(df, m) + @test x.F_kp ≈ 5159.812208193612 atol = 1e-4 + m = @formula Sales ~ (Price ~ Pimin + CPI) + x = reg(df, m) + @test x.F_kp ≈ 27011.44080 atol = 1e-4 + m = @formula Sales ~ (Price ~ Pimin) + fe(State) + x = reg(df, m) + @test x.F_kp ≈ 100927.75710 atol = 1e-4 + + # exactly same with ivreg2 + m = @formula Sales ~ (Price ~ Pimin) + x = reg(df, m, Vcov.robust()) + @test x.F_kp ≈ 23160.06350 atol = 1e-4 + m = @formula Sales ~ (Price ~ Pimin) + fe(State) + x = reg(df, m, Vcov.robust()) + @test x.F_kp ≈ 37662.82808 atol = 1e-4 + m = @formula Sales ~ CPI + (Price ~ Pimin) + x = reg(df, m, Vcov.robust()) + @test x.F_kp ≈ 2093.46609 atol = 1e-4 + m = @formula Sales ~ (Price ~ Pimin + CPI) + x = reg(df, m, Vcov.robust()) + @test x.F_kp ≈ 16418.21196 atol = 1e-4 + + + + # like in ivreg2 but += 5 difference. combination iv difference, degrees of freedom difference? + # iv + cluster - F_kp varies from ivreg2 about 7e-4 (SEs off by more than 2 df) + m = @formula Sales ~ (Price ~ Pimin) + x = reg(df, m, Vcov.cluster(:State)) + @test x.F_kp ≈ 7249.88606 atol = 1e-4 + # iv + cluster - F_kp varies from ivreg2 about 5e-6 (SEs off by more than 2 df) + m = @formula Sales ~ CPI + (Price ~ Pimin) + x = reg(df, m, Vcov.cluster(:State)) + @test x.F_kp ≈ 538.40393 atol = 1e-4 + # Modified test values below after multiway clustering update + # iv + 2way clustering - F_kp matches ivreg2 (SEs match with df_add=-2) + m = @formula Sales ~ CPI + (Price ~ Pimin) + x = reg(df, m, Vcov.cluster(:State, :Year)) + @test x.F_kp ≈ 421.9651 atol = 1e-4 + # multivariate iv + clustering - F_kp varies from ivreg2 about 3 (SEs off by more than 2 df) + m = @formula Sales ~ (Price ~ Pimin + CPI) + x = reg(df, m, Vcov.cluster(:State)) + @test x.F_kp ≈ 4080.66081 atol = 1e-4 + # multivariate iv + multiway clustering - F_kp varies from ivreg2 about 2 (SEs off by more than 2 df) + m = @formula Sales ~ (Price ~ Pimin + CPI) + x = reg(df, m, Vcov.cluster(:State, :Year)) + @test x.F_kp ≈ 2873.1405 atol = 1e-4 +end -############################################################################## -## -## F_kp r_kp statistics for IV. Difference degrees of freedom. -## -############################################################################## -m = @formula Sales ~ (Price ~ Pimin) -x = reg(df, m) -@test x.F_kp ≈ 52248.79247 atol = 1e-4 -m = @formula Sales ~ NDI + (Price ~ Pimin) -x = reg(df, m) -@test x.F_kp ≈ 5159.812208193612 atol = 1e-4 -m = @formula Sales ~ (Price ~ Pimin + CPI) -x = reg(df, m) -@test x.F_kp ≈ 27011.44080 atol = 1e-4 -m = @formula Sales ~ (Price ~ Pimin) + fe(State) -x = reg(df, m) -@test x.F_kp ≈ 100927.75710 atol = 1e-4 - -# exactly same with ivreg2 -m = @formula Sales ~ (Price ~ Pimin) -x = reg(df, m, Vcov.robust()) -@test x.F_kp ≈ 23160.06350 atol = 1e-4 -m = @formula Sales ~ (Price ~ Pimin) + fe(State) -x = reg(df, m, Vcov.robust()) -@test x.F_kp ≈ 37662.82808 atol = 1e-4 -m = @formula Sales ~ CPI + (Price ~ Pimin) -x = reg(df, m, Vcov.robust()) -@test x.F_kp ≈ 2093.46609 atol = 1e-4 -m = @formula Sales ~ (Price ~ Pimin + CPI) -x = reg(df, m, Vcov.robust()) -@test x.F_kp ≈ 16418.21196 atol = 1e-4 - - - -# like in ivreg2 but += 5 difference. combination iv difference, degrees of freedom difference? -# iv + cluster - F_kp varies from ivreg2 about 7e-4 (SEs off by more than 2 df) -m = @formula Sales ~ (Price ~ Pimin) -x = reg(df, m, Vcov.cluster(:State)) -@test x.F_kp ≈ 7249.88606 atol = 1e-4 -# iv + cluster - F_kp varies from ivreg2 about 5e-6 (SEs off by more than 2 df) -m = @formula Sales ~ CPI + (Price ~ Pimin) -x = reg(df, m, Vcov.cluster(:State)) -@test x.F_kp ≈ 538.40393 atol = 1e-4 -# Modified test values below after multiway clustering update -# iv + 2way clustering - F_kp matches ivreg2 (SEs match with df_add=-2) -m = @formula Sales ~ CPI + (Price ~ Pimin) -x = reg(df, m, Vcov.cluster(:State, :Year)) -@test x.F_kp ≈ 421.9651 atol = 1e-4 -# multivariate iv + clustering - F_kp varies from ivreg2 about 3 (SEs off by more than 2 df) -m = @formula Sales ~ (Price ~ Pimin + CPI) -x = reg(df, m, Vcov.cluster(:State)) -@test x.F_kp ≈ 4080.66081 atol = 1e-4 -# multivariate iv + multiway clustering - F_kp varies from ivreg2 about 2 (SEs off by more than 2 df) -m = @formula Sales ~ (Price ~ Pimin + CPI) -x = reg(df, m, Vcov.cluster(:State, :Year)) -@test x.F_kp ≈ 2873.1405 atol = 1e-4 +@testset "singletons" begin + df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv"))) + df.StateC = categorical(df.State) + df.YearC = categorical(df.Year) -############################################################################## -## -## Test singleton -## -## -############################################################################## -df.n = max.(1:size(df, 1), 60) -df.pn = categorical(df.n) -m = @formula Sales ~ Price + fe(pn) -x = reg(df, m, Vcov.cluster(:State)) -@test x.nobs == 60 + df.n = max.(1:size(df, 1), 60) + df.pn = categorical(df.n) + m = @formula Sales ~ Price + fe(pn) + x = reg(df, m, Vcov.cluster(:State)) + @test x.nobs == 60 -m = @formula Sales ~ Price + fe(pn) -x = reg(df, m, Vcov.cluster(:State), drop_singletons = false) -@test x.nobs == 1380 -############################################################################## -## -## Test unbalanced panel -## -## corresponds to abdata in Stata, for instance reghxe wage emp [w=indoutpt], a(id year) -## -############################################################################## -df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/EmplUK.csv"))) - -m = @formula Wage ~ Emp + fe(Firm) -x = reg(df, m) -@test coef(x) ≈ [- 0.11981270017206136] atol = 1e-4 -m = @formula Wage ~ Emp + fe(Firm)&Year -x = reg(df, m) -@test coef(x) ≈ [-315.0000747500431,- 0.07633636891202833] atol = 1e-4 -m = @formula Wage ~ Emp + Year&fe(Firm) -x = reg(df, m) -@test coef(x) ≈ [-315.0000747500431,- 0.07633636891202833] atol = 1e-4 -m = @formula Wage ~ 1 + Year&fe(Firm) -x = reg(df, m) -@test coef(x) ≈ [- 356.40430526316396] atol = 1e-4 -m = @formula Wage ~ Emp + fe(Firm) -x = reg(df, m, weights = :Output) -@test coef(x) ≈ [- 0.11514363590574725] atol = 1e-4 - -# absorb + weights -m = @formula Wage ~ Emp + fe(Firm) + fe(Year) -x = reg(df, m) -@test coef(x) ≈ [- 0.04683333721137311] atol = 1e-4 -m = @formula Wage ~ Emp + fe(Firm) + fe(Year) -x = reg(df, m, weights = :Output) -@test coef(x) ≈ [- 0.043475472188120416] atol = 1e-3 - -## the last two ones test an ill conditioned model matrix -# SSR does not work well here -m = @formula Wage ~ Emp + fe(Firm) + fe(Firm)&Year -x = reg(df, m) -@test coef(x) ≈ [- 0.122354] atol = 1e-4 -@test x.iterations <= 30 - -# SSR does not work well here -m = @formula Wage ~ Emp + fe(Firm) + fe(Firm)&Year -x = reg(df, m, weights = :Output) -@test coef(x) ≈ [- 0.11752306001586807] atol = 1e-4 -@test x.iterations <= 50 - -methods_vec = [:cpu] -if FixedEffectModels.FixedEffects.has_CUDA() - push!(methods_vec, :gpu) + m = @formula Sales ~ Price + fe(pn) + x = reg(df, m, Vcov.cluster(:State), drop_singletons = false) + @test x.nobs == 1380 end -for method in methods_vec - # same thing with float32 precision - local m = @formula Wage ~ Emp + fe(Firm) - local x = reg(df, m, method = method, double_precision = false) - @test coef(x) ≈ [- 0.11981270017206136] rtol = 1e-4 - local m = @formula Wage ~ Emp + fe(Firm)&Year - local x = reg(df, m, method = method, double_precision = false) - @test coef(x) ≈ [-315.0000747500431,- 0.07633636891202833] rtol = 1e-4 - local m = @formula Wage ~ Emp + Year&fe(Firm) - local x = reg(df, m, method = method, double_precision = false) - @test coef(x) ≈ [-315.0000747500431,- 0.07633636891202833] rtol = 1e-4 - local m = @formula Wage ~ 1 + Year&fe(Firm) - local x = reg(df, m, method = method, double_precision = false) - @test coef(x) ≈ [- 356.40430526316396] rtol = 1e-4 - local m = @formula Wage ~ Emp + fe(Firm) - local x = reg(df, m, weights = :Output, method = method, double_precision = false) - @test coef(x) ≈ [- 0.11514363590574725] rtol = 1e-4 - local m = @formula Wage ~ Emp + fe(Firm) + fe(Year) - local x = reg(df, m, method = method, double_precision = false) - @test coef(x) ≈ [- 0.04683333721137311] rtol = 1e-4 - local m = @formula Wage ~ Emp + fe(Firm) + fe(Year) - local x = reg(df, m, weights = :Output, method = method, double_precision = false) - @test coef(x) ≈ [- 0.043475472188120416] atol = 1e-3 -end - -# add tests with missing fixed effects -df.Firm_missing = ifelse.(df.Firm .<= 30, missing, df.Firm) -## test with missing fixed effects -m = @formula Wage ~ Emp + fe(Firm_missing) -x = reg(df, m) -@test coef(x) ≈ [-.1093657] atol = 1e-4 -@test stderror(x) ≈ [.032949 ] atol = 1e-4 -@test r2(x) ≈ 0.8703 atol = 1e-2 -@test adjr2(x) ≈ 0.8502 atol = 1e-2 -@test x.nobs == 821 - -## test with missing interaction -df.Year2 = df.Year .>= 1980 -m = @formula Wage ~ Emp + fe(Firm_missing) & fe(Year2) -x = reg(df, m) -@test coef(x) ≈ [-0.100863] atol = 1e-4 -@test stderror(x) ≈ [0.04149] atol = 1e-4 -@test x.nobs == 821 - - -############################################################################## -## -## Missing dependent / independent variables -## -############################################################################## +@testset "unbalanced panel" begin + + df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/EmplUK.csv"))) + + m = @formula Wage ~ Emp + fe(Firm) + x = reg(df, m) + @test coef(x) ≈ [- 0.11981270017206136] atol = 1e-4 + m = @formula Wage ~ Emp + fe(Firm)&Year + x = reg(df, m) + @test coef(x) ≈ [-315.0000747500431,- 0.07633636891202833] atol = 1e-4 + m = @formula Wage ~ Emp + Year&fe(Firm) + x = reg(df, m) + @test coef(x) ≈ [-315.0000747500431,- 0.07633636891202833] atol = 1e-4 + m = @formula Wage ~ 1 + Year&fe(Firm) + x = reg(df, m) + @test coef(x) ≈ [- 356.40430526316396] atol = 1e-4 + m = @formula Wage ~ Emp + fe(Firm) + x = reg(df, m, weights = :Output) + @test coef(x) ≈ [- 0.11514363590574725] atol = 1e-4 + + # absorb + weights + m = @formula Wage ~ Emp + fe(Firm) + fe(Year) + x = reg(df, m) + @test coef(x) ≈ [- 0.04683333721137311] atol = 1e-4 + m = @formula Wage ~ Emp + fe(Firm) + fe(Year) + x = reg(df, m, weights = :Output) + @test coef(x) ≈ [- 0.043475472188120416] atol = 1e-3 + ## the last two ones test an ill conditioned model matrix + # SSR does not work well here + m = @formula Wage ~ Emp + fe(Firm) + fe(Firm)&Year + x = reg(df, m) + @test coef(x) ≈ [- 0.122354] atol = 1e-4 + @test x.iterations <= 30 + + # SSR does not work well here + m = @formula Wage ~ Emp + fe(Firm) + fe(Firm)&Year + x = reg(df, m, weights = :Output) + @test coef(x) ≈ [- 0.11752306001586807] atol = 1e-4 + @test x.iterations <= 50 + + methods_vec = [:cpu] + if FixedEffectModels.FixedEffects.has_CUDA() + push!(methods_vec, :gpu) + end + for method in methods_vec + # same thing with float32 precision + local m = @formula Wage ~ Emp + fe(Firm) + local x = reg(df, m, method = method, double_precision = false) + @test coef(x) ≈ [- 0.11981270017206136] rtol = 1e-4 + local m = @formula Wage ~ Emp + fe(Firm)&Year + local x = reg(df, m, method = method, double_precision = false) + @test coef(x) ≈ [-315.0000747500431,- 0.07633636891202833] rtol = 1e-4 + local m = @formula Wage ~ Emp + Year&fe(Firm) + local x = reg(df, m, method = method, double_precision = false) + @test coef(x) ≈ [-315.0000747500431,- 0.07633636891202833] rtol = 1e-4 + local m = @formula Wage ~ 1 + Year&fe(Firm) + local x = reg(df, m, method = method, double_precision = false) + @test coef(x) ≈ [- 356.40430526316396] rtol = 1e-4 + local m = @formula Wage ~ Emp + fe(Firm) + local x = reg(df, m, weights = :Output, method = method, double_precision = false) + @test coef(x) ≈ [- 0.11514363590574725] rtol = 1e-4 + local m = @formula Wage ~ Emp + fe(Firm) + fe(Year) + local x = reg(df, m, method = method, double_precision = false) + @test coef(x) ≈ [- 0.04683333721137311] rtol = 1e-4 + local m = @formula Wage ~ Emp + fe(Firm) + fe(Year) + local x = reg(df, m, weights = :Output, method = method, double_precision = false) + @test coef(x) ≈ [- 0.043475472188120416] atol = 1e-3 + end + + + # add tests with missing fixed effects + df.Firm_missing = ifelse.(df.Firm .<= 30, missing, df.Firm) + + + ## test with missing fixed effects + m = @formula Wage ~ Emp + fe(Firm_missing) + x = reg(df, m) + @test coef(x) ≈ [-.1093657] atol = 1e-4 + @test stderror(x) ≈ [.032949 ] atol = 1e-4 + @test r2(x) ≈ 0.8703 atol = 1e-2 + @test adjr2(x) ≈ 0.8502 atol = 1e-2 + @test x.nobs == 821 + + ## test with missing interaction + df.Year2 = df.Year .>= 1980 + m = @formula Wage ~ Emp + fe(Firm_missing) & fe(Year2) + x = reg(df, m) + @test coef(x) ≈ [-0.100863] atol = 1e-4 + @test stderror(x) ≈ [0.04149] atol = 1e-4 + @test x.nobs == 821 +end -# Missing -df1 = DataFrame(a=[1.0, 2.0, 3.0, 4.0], b=[5.0, 7.0, 11.0, 13.0]) -df2 = DataFrame(a=[1.0, missing, 3.0, 4.0], b=[5.0, 7.0, 11.0, 13.0]) -x = reg(df1, @formula(a ~ b)) -@test coef(x) ≈ [ -0.6500000000000004, 0.35000000000000003] atol = 1e-4 -x = reg(df1, @formula(b ~ a)) -@test coef(x) ≈ [2.0, 2.8] atol = 1e-4 -x = reg(df2,@formula(a ~ b)) -@test coef(x) ≈ [ -0.8653846153846163, 0.3653846153846155] atol = 1e-4 -x = reg(df2,@formula(b ~ a)) -@test coef(x) ≈ [ 2.4285714285714253, 2.7142857142857157] atol = 1e-4 - -# Works with Integers -df1 = DataFrame(a=[1, 2, 3, 4], b=[5, 7, 11, 13], c = categorical([1, 1, 2, 2])) -x = reg(df1, @formula(a ~ b)) -@test coef(x) ≈ [-0.65, 0.35] atol = 1e-4 -x = reg(df1, @formula(a ~ b + fe(c))) -@test coef(x) ≈ [0.5] atol = 1e-4 +@testset "missings" begin + + # Missing + df1 = DataFrame(a=[1.0, 2.0, 3.0, 4.0], b=[5.0, 7.0, 11.0, 13.0]) + df2 = DataFrame(a=[1.0, missing, 3.0, 4.0], b=[5.0, 7.0, 11.0, 13.0]) + x = reg(df1, @formula(a ~ b)) + @test coef(x) ≈ [ -0.6500000000000004, 0.35000000000000003] atol = 1e-4 + x = reg(df1, @formula(b ~ a)) + @test coef(x) ≈ [2.0, 2.8] atol = 1e-4 + x = reg(df2,@formula(a ~ b)) + @test coef(x) ≈ [ -0.8653846153846163, 0.3653846153846155] atol = 1e-4 + x = reg(df2,@formula(b ~ a)) + @test coef(x) ≈ [ 2.4285714285714253, 2.7142857142857157] atol = 1e-4 + + # Works with Integers + df1 = DataFrame(a=[1, 2, 3, 4], b=[5, 7, 11, 13], c = categorical([1, 1, 2, 2])) + x = reg(df1, @formula(a ~ b)) + @test coef(x) ≈ [-0.65, 0.35] atol = 1e-4 + x = reg(df1, @formula(a ~ b + fe(c))) + @test coef(x) ≈ [0.5] atol = 1e-4 +end diff --git a/test/formula.jl b/test/formula.jl index fc91412f..b75b50fe 100644 --- a/test/formula.jl +++ b/test/formula.jl @@ -4,61 +4,61 @@ using FixedEffectModels: parse_fixedeffect, _parse_fixedeffect, _multiply using FixedEffects import Base: == -==(x::FixedEffect{R,I}, y::FixedEffect{R,I}) where {R,I} = +function ==(x::FixedEffect, y::FixedEffect) x.refs == y.refs && x.interaction == y.interaction && x.n == y.n +end + +csvfile = CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv")) +df = DataFrame(csvfile) -@testset "parse_fixedeffect" begin - csvfile = CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv")) - df = DataFrame(csvfile) - # Any table type supporting the Tables.jl interface should work - for data in [df, csvfile] - @test _parse_fixedeffect(data, term(:Price)) === nothing - @test _parse_fixedeffect(data, ConstantTerm(1)) === nothing - @test _parse_fixedeffect(data, fe(:State)) == (FixedEffect(data.State), :fe_State, [:State]) - - @test _parse_fixedeffect(data, fe(:State)&term(:Year)) == - (FixedEffect(data.State, interaction=_multiply(data, [:Year])), Symbol("fe_State&Year"), [:State]) - @test _parse_fixedeffect(data, fe(:State)&fe(:Year)) == - (FixedEffect(data.State, data.Year), Symbol("fe_State&fe_Year"), [:State, :Year]) +# Any table type supporting the Tables.jl interface should work +for data in [df, csvfile] + @test _parse_fixedeffect(data, term(:Price)) === nothing + @test _parse_fixedeffect(data, ConstantTerm(1)) === nothing + @test _parse_fixedeffect(data, fe(:State)) == (FixedEffect(data.State), :fe_State, [:State]) + + @test _parse_fixedeffect(data, fe(:State)&term(:Year)) == + (FixedEffect(data.State, interaction=_multiply(data, [:Year])), Symbol("fe_State&Year"), [:State]) + @test _parse_fixedeffect(data, fe(:State)&fe(:Year)) == + (FixedEffect(data.State, data.Year), Symbol("fe_State&fe_Year"), [:State, :Year]) - @test parse_fixedeffect(data, ()) == (FixedEffect[], Symbol[], Symbol[], ()) - - f = @formula(y ~ 1 + Price) - ts1 = f.rhs - ts2 = term(1) + term(:Price) - @test parse_fixedeffect(data, f) == (FixedEffect[], Symbol[], Symbol[], f) - @test parse_fixedeffect(data, ts1) == (FixedEffect[], Symbol[], Symbol[], ts1) - @test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1) + @test parse_fixedeffect(data, ()) == (FixedEffect[], Symbol[], Symbol[], ()) + + f = @formula(y ~ 1 + Price) + ts1 = f.rhs + ts2 = term(1) + term(:Price) + @test parse_fixedeffect(data, f) == (FixedEffect[], Symbol[], Symbol[], f) + @test parse_fixedeffect(data, ts1) == (FixedEffect[], Symbol[], Symbol[], ts1) + @test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1) - fparsed = term(:y) ~ InterceptTerm{false}() + term(:Price) - tsparsed = (InterceptTerm{false}(), term(:Price)) + fparsed = term(:y) ~ InterceptTerm{false}() + term(:Price) + tsparsed = (InterceptTerm{false}(), term(:Price)) - f = @formula(y ~ 1 + Price + fe(State)) - ts1 = f.rhs - ts2 = term(1) + term(:Price) + fe(:State) - @test parse_fixedeffect(data, f) == ([FixedEffect(data.State)], [:fe_State], [:State], fparsed) - @test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State)], [:fe_State], [:State], tsparsed) - @test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1) + f = @formula(y ~ 1 + Price + fe(State)) + ts1 = f.rhs + ts2 = term(1) + term(:Price) + fe(:State) + @test parse_fixedeffect(data, f) == ([FixedEffect(data.State)], [:fe_State], [:State], fparsed) + @test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State)], [:fe_State], [:State], tsparsed) + @test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1) - f = @formula(y ~ Price + fe(State) + fe(Year)) - ts1 = f.rhs - ts2 = term(:Price) + fe(:State) + fe(:Year) - @test parse_fixedeffect(data, f) == ([FixedEffect(data.State), FixedEffect(data.Year)], [:fe_State, :fe_Year], [:State, :Year], fparsed) - @test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State), FixedEffect(data.Year)], [:fe_State, :fe_Year], [:State, :Year], tsparsed) - @test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1) + f = @formula(y ~ Price + fe(State) + fe(Year)) + ts1 = f.rhs + ts2 = term(:Price) + fe(:State) + fe(:Year) + @test parse_fixedeffect(data, f) == ([FixedEffect(data.State), FixedEffect(data.Year)], [:fe_State, :fe_Year], [:State, :Year], fparsed) + @test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State), FixedEffect(data.Year)], [:fe_State, :fe_Year], [:State, :Year], tsparsed) + @test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1) - f = @formula(y ~ Price + fe(State)&Year) - ts1 = f.rhs - ts2 = term(:Price) + fe(:State)&term(:Year) - @test parse_fixedeffect(data, f) == ([FixedEffect(data.State, interaction=_multiply(data, [:Year]))], [Symbol("fe_State&Year")], [:State], term(:y) ~ (term(:Price),)) - @test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State, interaction=_multiply(data, [:Year]))], [Symbol("fe_State&Year")], [:State], (term(:Price),)) - @test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1) + f = @formula(y ~ Price + fe(State)&Year) + ts1 = f.rhs + ts2 = term(:Price) + fe(:State)&term(:Year) + @test parse_fixedeffect(data, f) == ([FixedEffect(data.State, interaction=_multiply(data, [:Year]))], [Symbol("fe_State&Year")], [:State], term(:y) ~ (term(:Price),)) + @test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State, interaction=_multiply(data, [:Year]))], [Symbol("fe_State&Year")], [:State], (term(:Price),)) + @test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1) - f = @formula(y ~ Price + fe(State)*fe(Year)) - ts1 = f.rhs - ts2 = term(:Price) + fe(:State) + fe(:Year) + fe(:State)&fe(:Year) - @test parse_fixedeffect(data, f) == ([FixedEffect(data.State), FixedEffect(data.Year), FixedEffect(data.State, data.Year)], [:fe_State, :fe_Year, Symbol("fe_State&fe_Year")], [:State, :Year], fparsed) - @test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State), FixedEffect(data.Year), FixedEffect(data.State, data.Year)], [:fe_State, :fe_Year, Symbol("fe_State&fe_Year")], [:State, :Year], tsparsed) - @test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1) - end + f = @formula(y ~ Price + fe(State)*fe(Year)) + ts1 = f.rhs + ts2 = term(:Price) + fe(:State) + fe(:Year) + fe(:State)&fe(:Year) + @test parse_fixedeffect(data, f) == ([FixedEffect(data.State), FixedEffect(data.Year), FixedEffect(data.State, data.Year)], [:fe_State, :fe_Year, Symbol("fe_State&fe_Year")], [:State, :Year], fparsed) + @test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State), FixedEffect(data.Year), FixedEffect(data.State, data.Year)], [:fe_State, :fe_Year, Symbol("fe_State&fe_Year")], [:State, :Year], tsparsed) + @test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1) end diff --git a/test/partial_out.jl b/test/partial_out.jl index ddc01514..d8da23f7 100644 --- a/test/partial_out.jl +++ b/test/partial_out.jl @@ -1,24 +1,26 @@ using FixedEffectModels, DataFrames, Statistics, CSV, Test -df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv"))) +@testset "partial out" begin + df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv"))) -@test Matrix(partial_out(df, @formula(Sales + Price ~ NDI))[1])[1:5, :] ≈ [ -37.2108 9.72654; -35.5599 9.87628; -32.309 8.82602; -34.2826 9.64653; -35.0526 8.84143] atol = 1e-3 -@test Matrix(partial_out(df, @formula(Sales + Price ~ NDI + fe(State)))[1])[1:5, :] ≈ [ -21.3715 -0.642783; -19.6571 -0.540335; -16.3427 -1.63789; -18.2631 -0.856975; -18.9784 -1.70283] atol = 1e-3 -@test Matrix(partial_out(df, @formula(Sales + Price ~ 1 + fe(State)))[1])[1:5, :] ≈ [-13.5767 -40.5467; -12.0767 -39.3467; -8.97667 -39.3467; -11.0767 -37.6467; -11.9767 -37.5467] atol = 1e-3 -@test Matrix(partial_out(df, @formula(Sales + Price ~ 1))[1])[1:5, :] ≈ [ -30.0509 -40.0999; -28.5509 -38.8999; -25.4509 -38.8999; -27.5509 -37.1999; -28.4509 -37.0999] atol = 1e-3 -@test mean(Matrix(partial_out(df, @formula(Sales + Price ~ NDI), add_mean = true)[1]), dims = 1[1]) ≈ [123.951 68.6999] atol = 1e-3 -@test mean(Matrix(partial_out(df, @formula(Sales + Price ~ NDI + fe(State)), add_mean = true)[1]), dims = 1[1]) ≈ [123.951 68.6999] atol = 1e-3 -@test mean(Matrix(partial_out(df, @formula(Sales + Price ~ 1 + fe(State)), add_mean = true)[1]), dims = 1[1]) ≈ [123.951 68.6999] atol = 1e-3 -@test mean(Matrix(partial_out(df, @formula(Sales + Price ~ 1), add_mean = true)[1]), dims = 1[1]) ≈ [123.951 68.6999] atol = 1e-3 -@test Matrix(partial_out(df, @formula(Sales + Price ~ NDI), weights = :Pop)[1])[1:5, :] ≈[ -37.5296 11.8467; -35.8224 11.9922; -32.5151 10.9377; -34.4416 11.7546; -35.163 10.9459] atol = 1e-3 + @test Matrix(partial_out(df, @formula(Sales + Price ~ NDI))[1])[1:5, :] ≈ [ -37.2108 9.72654; -35.5599 9.87628; -32.309 8.82602; -34.2826 9.64653; -35.0526 8.84143] atol = 1e-3 + @test Matrix(partial_out(df, @formula(Sales + Price ~ NDI + fe(State)))[1])[1:5, :] ≈ [ -21.3715 -0.642783; -19.6571 -0.540335; -16.3427 -1.63789; -18.2631 -0.856975; -18.9784 -1.70283] atol = 1e-3 + @test Matrix(partial_out(df, @formula(Sales + Price ~ 1 + fe(State)))[1])[1:5, :] ≈ [-13.5767 -40.5467; -12.0767 -39.3467; -8.97667 -39.3467; -11.0767 -37.6467; -11.9767 -37.5467] atol = 1e-3 + @test Matrix(partial_out(df, @formula(Sales + Price ~ 1))[1])[1:5, :] ≈ [ -30.0509 -40.0999; -28.5509 -38.8999; -25.4509 -38.8999; -27.5509 -37.1999; -28.4509 -37.0999] atol = 1e-3 + @test mean(Matrix(partial_out(df, @formula(Sales + Price ~ NDI), add_mean = true)[1]), dims = 1[1]) ≈ [123.951 68.6999] atol = 1e-3 + @test mean(Matrix(partial_out(df, @formula(Sales + Price ~ NDI + fe(State)), add_mean = true)[1]), dims = 1[1]) ≈ [123.951 68.6999] atol = 1e-3 + @test mean(Matrix(partial_out(df, @formula(Sales + Price ~ 1 + fe(State)), add_mean = true)[1]), dims = 1[1]) ≈ [123.951 68.6999] atol = 1e-3 + @test mean(Matrix(partial_out(df, @formula(Sales + Price ~ 1), add_mean = true)[1]), dims = 1[1]) ≈ [123.951 68.6999] atol = 1e-3 + @test Matrix(partial_out(df, @formula(Sales + Price ~ NDI), weights = :Pop)[1])[1:5, :] ≈[ -37.5296 11.8467; -35.8224 11.9922; -32.5151 10.9377; -34.4416 11.7546; -35.163 10.9459] atol = 1e-3 -@test Matrix(partial_out(df, @formula(Sales + Price ~ NDI), weights = :Pop, add_mean = true)[1])[1:5, :] ≈ [82.7448 85.3569; 84.4521 85.5024; 87.7593 84.4479; 85.8329 85.2649; 85.1115 84.4561] atol = 1e-3 + @test Matrix(partial_out(df, @formula(Sales + Price ~ NDI), weights = :Pop, add_mean = true)[1])[1:5, :] ≈ [82.7448 85.3569; 84.4521 85.5024; 87.7593 84.4479; 85.8329 85.2649; 85.1115 84.4561] atol = 1e-3 -@test Matrix(partial_out(df, @formula(Sales + Price ~ NDI + fe(State)), weights = :Pop)[1])[1:5, :] ≈ [ -22.2429 -1.2635 ; -20.5296 -1.1515 ; -17.2164 -2.23949; -19.1378 -1.45057; -19.854 -2.28819] atol = 1e-3 -@test Matrix(partial_out(df, @formula(Sales + Price ~ 1 + fe(State)), weights = :Pop)[1])[1:5, :] ≈ [ -14.0383 -43.1224; -12.5383 -41.9224; -9.43825 -41.9224; -11.5383 -40.2224; -12.4383 -40.1224] atol = 1e-3 -@test Matrix(partial_out(df, @formula(Sales + Price ~ 1), weights = :Pop)[1])[1:5, :] ≈ [ -26.3745 -44.9103; -24.8745 -43.7103; -21.7745 -43.7103; -23.8745 -42.0103; -24.7745 -41.9103] atol = 1e-3 - + @test Matrix(partial_out(df, @formula(Sales + Price ~ NDI + fe(State)), weights = :Pop)[1])[1:5, :] ≈ [ -22.2429 -1.2635 ; -20.5296 -1.1515 ; -17.2164 -2.23949; -19.1378 -1.45057; -19.854 -2.28819] atol = 1e-3 + @test Matrix(partial_out(df, @formula(Sales + Price ~ 1 + fe(State)), weights = :Pop)[1])[1:5, :] ≈ [ -14.0383 -43.1224; -12.5383 -41.9224; -9.43825 -41.9224; -11.5383 -40.2224; -12.4383 -40.1224] atol = 1e-3 + @test Matrix(partial_out(df, @formula(Sales + Price ~ 1), weights = :Pop)[1])[1:5, :] ≈ [ -26.3745 -44.9103; -24.8745 -43.7103; -21.7745 -43.7103; -23.8745 -42.0103; -24.7745 -41.9103] atol = 1e-3 +end + diff --git a/test/predict.jl b/test/predict.jl index 1989b960..6d600e5f 100644 --- a/test/predict.jl +++ b/test/predict.jl @@ -1,312 +1,315 @@ using FixedEffectModels, DataFrames, CategoricalArrays, CSV, Test -df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv"))) -df.StateC = categorical(df.State) -############################################################################## -## -## Printing Results -## -############################################################################## +@testset "print results" begin -model = @formula Sales ~ NDI -result = reg(df, model) -show(result) -predict(result, df) -residuals(result, df) -@test responsename(result) == "Sales" + df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv"))) + df.StateC = categorical(df.State) + model = @formula Sales ~ NDI + result = reg(df, model) + show(result) + predict(result, df) + residuals(result, df) + @test responsename(result) == "Sales" -model = @formula Sales ~ CPI + (Price ~ Pimin) -result = reg(df, model) -coeftable(result) -show(result) -predict(result, df) -residuals(result, df) -@test nobs(result) == 1380 -@test vcov(result)[1] ≈ 3.5384578251636785 - -# predict with interactions -model = @formula Sales ~ CPI * Pop -result = reg(df, model) -@test predict(result, df)[1] ≈ 131.92991 + model = @formula Sales ~ CPI + (Price ~ Pimin) + result = reg(df, model) + coeftable(result) + show(result) + predict(result, df) + residuals(result, df) + @test nobs(result) == 1380 + @test vcov(result)[1] ≈ 3.5384578251636785 -model = @formula Sales ~ Price + fe(State) -result = reg(df, model) -show(result) -model = @formula Sales ~ CPI + (Price ~ Pimin) + fe(State) -result = reg(df, model) -show(result) - - -model = @formula Sales ~ Price + StateC -result = reg(df, model) -@test predict(result, df)[1] ≈ 115.9849874 - -#model = @formula Sales ~ Price + fe(State) -#result = reg(df, model, save = :fe) -#@test predict(result)[1] ≈ 115.9849874 - -model = @formula Sales ~ Price * Pop + StateC -result = reg(df, model) -@test predict(result, df)[1] ≈ 115.643985352 - -#model = @formula Sales ~ Price * Pop + fe(State) -#result = reg(df, model, save = :fe) -#@test predict(result, df)[1] ≈ 115.643985352 - -model = @formula Sales ~ Price + Pop + Price & Pop + StateC -result = reg(df, model) -@test predict(result, df)[1] ≈ 115.643985352 - -#model = @formula Sales ~ Price + Pop + Price & Pop + fe(State) -#result = reg(df, model, save = :fe) -#@test predict(result, df)[1] ≈ 115.643985352 - - - - - -# Tests for predict method -# Test that predicting from model without saved FE test throws -model = @formula Sales ~ Price + fe(State) -result = reg(df, model) -@test_throws "No estimates for fixed effects found. Fixed effects need to be estimated using the option save = :fe or :all for prediction to work." predict(result, df) - -# Test basic functionality - adding 1 to price should increase prediction by coef -#model = @formula Sales ~ Price + fe(State) -#result = reg(df, model, save = :fe) -#x = predict(result, DataFrame(Price = [1.0, 2.0], State = [1, 1])) -#@test last(x) - first(x) ≈ only(result.coef) - -# Missing variables in covariates should yield missing prediction -#x = predict(result, DataFrame(Price = [1.0, missing], State = [1, 1])) -#@test ismissing(last(x)) - -# Missing variables in fixed effects should yield missing prediction -#x = predict(result, DataFrame(Price = [1.0, 2.0], State = [1, missing])) -#@test ismissing(last(x)) - -# Fixed effect levels not in the estimation data should yield missing prediction -#x = predict(result, DataFrame(Price = [1.0, 2.0], State = [1, 111])) -#@test ismissing(last(x)) - -############################################################################## -## -## Saved Residuals -## -############################################################################## - -model = @formula Sales ~ Price -result = reg(df, model) -@test residuals(result, df)[1:10] ≈ [-39.2637, -37.48801, -34.38801, -36.09743, -36.97446, -43.15547, -41.22573, -40.83648, -34.52427, -28.91617] atol = 1e-4 -@test r2(result) ≈ 0.0968815737054879 atol = 1e-4 -@test adjr2(result) ≈ 0.0962261902321246 atol = 1e-4 -@test result.nobs == 1380 -@test result.F ≈ 147.8242550248069 atol= 1e-4 - -#weights -model = @formula Sales ~ CPI -result = reg(df, model, weights = :Pop) -@test residuals(result, df)[1:3] ≈ [ -35.641449, -34.0611538, -30.860784] atol = 1e-4 - -# iv -model = @formula Sales ~ CPI + (Price ~ Pimin) -result = reg(df, model) -@test residuals(result, df)[1:3] ≈ [ -33.047390, -30.9518422, -28.1371048] atol = 1e-4 - -# iv with exo after endo -model = @formula Sales ~ (Price ~ Pimin) + CPI -result = reg(df, model) -@test residuals(result, df)[1:3] ≈ [ -33.047390, -30.9518422, -28.1371048] atol = 1e-4 - -# iv and weights -model = @formula Sales ~ CPI + (Price ~ Pimin) -result = reg(df, model, weights = :Pop) -@test residuals(result, df)[1:3] ≈ [ -30.2284549, -28.09507, -25.313248] atol = 1e-4 - -# iv, weights and subset of states -model = @formula Sales ~ CPI + (Price ~ Pimin) -result = reg(df, model, subset = df.State .<= 30, weights = :Pop) -@test residuals(result, df)[1:3] ≈ [ -34.081720, -31.906020, -29.131738] atol = 1e-4 - - -# fixed effects -model = @formula Sales ~ Price + fe(State) -result = reg(df, model, save = true) -@test residuals(result)[1:3] ≈ [-22.08499, -20.33318, -17.23318] atol = 1e-4 -@test result.nobs == 1380 -@test r2(result) ≈ 0.7682403747044817 atol = 1e-4 -@test adjr2(result) ≈ 0.7602426682051615 atol = 1e-4 -@test result.F ≈ 458.4582526109375 atol = 1e-4 - -# fixed effects and weights -model = @formula Sales ~ Price + fe(State) -result = reg(df, model, weights = :Pop, save = true) -@test residuals(result)[1:3] ≈ [ -23.413793, -21.65289, -18.55289] atol = 1e-4 - -# fixed effects and iv -#TO CHECK WITH IVREGHDFE, NO SUPPORT RIGHT NOW -model = @formula Sales ~ CPI + (Price ~ Pimin) + fe(State) -result = reg(df, model, save = true) -@test residuals(result)[1:3] ≈ [ -16.925748, -14.835710, -12.017037] atol = 1e-4 - -#r2 with weights when saving residuals -m = @formula Sales ~ Price -result = reg(df, save = :residuals, weights = :Pop, m) -@test r2(result) ≈ 0.24654260 atol = 1e-4 - - -# test different arguments for the keyword argument save -model = @formula Sales ~ Price + fe(State) -result = reg(df, model, save = true) -@test residuals(result) !== nothing -@test "fe_State" ∈ names(fe(result)) - -model = @formula Sales ~ Price + fe(State) -result = reg(df, model, save = :residuals) -@test residuals(result) !== nothing -@test "fe_State" ∉ names(fe(result)) - -model = @formula Sales ~ Price + fe(State) -result = reg(df, model, save = :fe) -@test "fe_State" ∈ names(fe(result)) - - -# iv recategorized -df.Pimin2 = df.Pimin -m = @formula Sales ~ (Pimin2 + Price ~ NDI + Pimin) -result = reg(df, m) -yhat = predict(result, df) -res = residuals(result, df) - -m2 = @formula Sales ~ Pimin2 + (Price ~ NDI + Pimin) -result2 = reg(df, m2) -yhat2 = predict(result2, df) -res2 = residuals(result2, df) -@test yhat ≈ yhat2 -@test res ≈ res2 - -m3 = @formula Sales ~ Pimin2 + (Price ~ NDI) -result3 = reg(df, m3) -yhat3 = predict(result3, df) -res3 = residuals(result3, df) -@test yhat ≈ yhat3 -@test res ≈ res3 - -m4 = @formula Sales ~ (Price + Pimin2 ~ NDI + Pimin) -result4 = reg(df, m4) -yhat4 = predict(result4, df) -res4 = residuals(result4, df) -@test yhat ≈ yhat4 -@test res ≈ res4 - -m5 = @formula Sales ~ (Price ~ NDI + Pimin) + Pimin2 -result5 = reg(df, m5) -yhat5 = predict(result5, df) -res5 = residuals(result5, df) -@test yhat ≈ yhat5 -@test res ≈ res5 - - - -############################################################################## -## -## Saved FixedEffects -## -############################################################################## -# check save does not change r2 -model1 = @formula Sales ~ Price -result1 = reg(df, model1, weights = :Pop) -model2 = @formula Sales ~ Price -result2 = reg(df, model2, weights = :Pop) -@test r2(result1) ≈ r2(result2) - - - -model = @formula Sales ~ Price + fe(Year) -result = reg(df, model, save = true) -@test fe(result)[1, :fe_Year] ≈ 164.77833189721005 -@test size(fe(result), 2) == 1 -@test size(fe(result, keepkeys = true), 2) == 2 - -model = @formula Sales ~ Price + fe(Year) + fe(State) -result = reg(df, model, save = true) -@test fe(result)[1, :fe_Year] + fe(result)[1, :fe_State] ≈ 140.6852 atol = 1e-3 - -model = @formula Sales ~ Price + Year&fe(State) -result = reg(df, model, save = true) -@test fe(result)[1, Symbol("fe_State&Year")] ≈ 1.742779 atol = 1e-3 - -model = @formula Sales ~ Price + fe(State) + Year&fe(State) -result = reg(df, model, save = true) -@test fe(result)[1, :fe_State] ≈ -91.690635 atol = 1e-1 - -model = @formula Sales ~ Price + fe(State) -result = reg(df, model, subset = df.State .<= 30, save = true) -@test fe(result)[1, :fe_State] ≈ 124.913976 atol = 1e-1 -@test ismissing(fe(result)[1380 , :fe_State]) - -model = @formula Sales ~ Price + fe(Year) -result = reg(df, model, weights = :Pop, save = true) -@test fe(result)[2, :fe_Year] - fe(result)[1, :fe_Year] ≈ -3.0347149502496222 - -# fixed effects -df.Price2 = df.Price -model = @formula Sales ~ Price + Price2 + fe(Year) -result = reg(df, model, save = true) -@test fe(result)[1, :fe_Year] ≈ 164.77833189721005 - -# iv -model = @formula Sales ~ (State ~ Price) + fe(Year) -result = reg(df, model, save = true) -@test fe(result)[1, :fe_Year] ≈ -167.48093490413623 - -# weights -model = @formula Sales ~ Price + fe(Year) -result = reg(df, model, weights = :Pop, save = true) -@test fe(result)[2, :fe_Year] - fe(result)[1, :fe_Year] ≈ -3.0347149502496222 - -# IV and weights -model = @formula Sales ~ (Price ~ Pimin) + fe(Year) -result = reg(df, model, weights = :Pop, save = true) -@test fe(result)[1, :fe_Year] ≈ 168.24688 atol = 1e-4 - - -# IV, weights and both year and state fixed effects -model = @formula Sales ~ (Price ~ Pimin) + fe(State) + fe(Year) -result = reg(df, model, weights = :Pop, save = true) -@test fe(result)[1, :fe_Year] + fe(result)[1, :fe_State]≈ 147.84145 atol = 1e-4 - - -# subset with IV -model = @formula Sales ~ (Price ~ Pimin) + fe(Year) -result = reg(df, model, subset = df.State .<= 30, save = true) -@test fe(result)[1, :fe_Year] ≈ 164.05245824240276 atol = 1e-4 -@test ismissing(fe(result)[811, :fe_Year]) - - -# subset with IV, weights and year fixed effects -model = @formula Sales ~ (Price ~ Pimin) + fe(Year) -result = reg(df, model, subset = df.State .<= 30, weights = :Pop, save = true) -@test fe(result)[1, :fe_Year] ≈ 182.71915 atol = 1e-4 - -# subset with IV, weights and year fixed effects -model = @formula Sales ~ (Price ~ Pimin) + fe(State) + fe(Year) -result = reg(df, model, subset = df.State .<= 30, weights = :Pop, save = true) -@test fe(result)[1, :fe_Year] + fe(result)[1, :fe_State] ≈ 158.91798 atol = 1e-4 - -methods_vec = [:cpu] -if FixedEffectModels.FixedEffects.has_CUDA() - push!(methods_vec, :gpu) + # predict with interactions + model = @formula Sales ~ CPI * Pop + result = reg(df, model) + @test predict(result, df)[1] ≈ 131.92991 + + + model = @formula Sales ~ Price + fe(State) + result = reg(df, model) + show(result) + model = @formula Sales ~ CPI + (Price ~ Pimin) + fe(State) + result = reg(df, model) + show(result) end -for method in methods_vec - local model = @formula Sales ~ Price + fe(Year) - local result = reg(df, model, save = true, method = method, double_precision = false) - @test fe(result)[1, :fe_Year] ≈ 164.7 atol = 1e-1 + +@testset "predict" begin + + df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv"))) + df.StateC = categorical(df.State) + + model = @formula Sales ~ Price + StateC + result = reg(df, model) + @test predict(result, df)[1] ≈ 115.9849874 + + #model = @formula Sales ~ Price + fe(State) + #result = reg(df, model, save = :fe) + #@test predict(result)[1] ≈ 115.9849874 + + model = @formula Sales ~ Price * Pop + StateC + result = reg(df, model) + @test predict(result, df)[1] ≈ 115.643985352 + + #model = @formula Sales ~ Price * Pop + fe(State) + #result = reg(df, model, save = :fe) + #@test predict(result, df)[1] ≈ 115.643985352 + + model = @formula Sales ~ Price + Pop + Price & Pop + StateC + result = reg(df, model) + @test predict(result, df)[1] ≈ 115.643985352 + + #model = @formula Sales ~ Price + Pop + Price & Pop + fe(State) + #result = reg(df, model, save = :fe) + #@test predict(result, df)[1] ≈ 115.643985352 + + + + + + # Tests for predict method + # Test that predicting from model without saved FE test throws + model = @formula Sales ~ Price + fe(State) + result = reg(df, model) + @test_throws "No estimates for fixed effects found. Fixed effects need to be estimated using the option save = :fe or :all for prediction to work." predict(result, df) + + # Test basic functionality - adding 1 to price should increase prediction by coef + #model = @formula Sales ~ Price + fe(State) + #result = reg(df, model, save = :fe) + #x = predict(result, DataFrame(Price = [1.0, 2.0], State = [1, 1])) + #@test last(x) - first(x) ≈ only(result.coef) + + # Missing variables in covariates should yield missing prediction + #x = predict(result, DataFrame(Price = [1.0, missing], State = [1, 1])) + #@test ismissing(last(x)) + + # Missing variables in fixed effects should yield missing prediction + #x = predict(result, DataFrame(Price = [1.0, 2.0], State = [1, missing])) + #@test ismissing(last(x)) + + # Fixed effect levels not in the estimation data should yield missing prediction + #x = predict(result, DataFrame(Price = [1.0, 2.0], State = [1, 111])) + #@test ismissing(last(x)) end +@testset "residuals" begin + + df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv"))) + df.StateC = categorical(df.State) + + model = @formula Sales ~ Price + result = reg(df, model) + @test residuals(result, df)[1:10] ≈ [-39.2637, -37.48801, -34.38801, -36.09743, -36.97446, -43.15547, -41.22573, -40.83648, -34.52427, -28.91617] atol = 1e-4 + @test r2(result) ≈ 0.0968815737054879 atol = 1e-4 + @test adjr2(result) ≈ 0.0962261902321246 atol = 1e-4 + @test result.nobs == 1380 + @test result.F ≈ 147.8242550248069 atol= 1e-4 + + #weights + model = @formula Sales ~ CPI + result = reg(df, model, weights = :Pop) + @test residuals(result, df)[1:3] ≈ [ -35.641449, -34.0611538, -30.860784] atol = 1e-4 + + # iv + model = @formula Sales ~ CPI + (Price ~ Pimin) + result = reg(df, model) + @test residuals(result, df)[1:3] ≈ [ -33.047390, -30.9518422, -28.1371048] atol = 1e-4 + + # iv with exo after endo + model = @formula Sales ~ (Price ~ Pimin) + CPI + result = reg(df, model) + @test residuals(result, df)[1:3] ≈ [ -33.047390, -30.9518422, -28.1371048] atol = 1e-4 + + # iv and weights + model = @formula Sales ~ CPI + (Price ~ Pimin) + result = reg(df, model, weights = :Pop) + @test residuals(result, df)[1:3] ≈ [ -30.2284549, -28.09507, -25.313248] atol = 1e-4 + + # iv, weights and subset of states + model = @formula Sales ~ CPI + (Price ~ Pimin) + result = reg(df, model, subset = df.State .<= 30, weights = :Pop) + @test residuals(result, df)[1:3] ≈ [ -34.081720, -31.906020, -29.131738] atol = 1e-4 + + + # fixed effects + model = @formula Sales ~ Price + fe(State) + result = reg(df, model, save = true) + @test residuals(result)[1:3] ≈ [-22.08499, -20.33318, -17.23318] atol = 1e-4 + @test result.nobs == 1380 + @test r2(result) ≈ 0.7682403747044817 atol = 1e-4 + @test adjr2(result) ≈ 0.7602426682051615 atol = 1e-4 + @test result.F ≈ 458.4582526109375 atol = 1e-4 + + # fixed effects and weights + model = @formula Sales ~ Price + fe(State) + result = reg(df, model, weights = :Pop, save = true) + @test residuals(result)[1:3] ≈ [ -23.413793, -21.65289, -18.55289] atol = 1e-4 + + # fixed effects and iv + #TO CHECK WITH IVREGHDFE, NO SUPPORT RIGHT NOW + model = @formula Sales ~ CPI + (Price ~ Pimin) + fe(State) + result = reg(df, model, save = true) + @test residuals(result)[1:3] ≈ [ -16.925748, -14.835710, -12.017037] atol = 1e-4 + + #r2 with weights when saving residuals + m = @formula Sales ~ Price + result = reg(df, save = :residuals, weights = :Pop, m) + @test r2(result) ≈ 0.24654260 atol = 1e-4 + + + # test different arguments for the keyword argument save + model = @formula Sales ~ Price + fe(State) + result = reg(df, model, save = true) + @test residuals(result) !== nothing + @test "fe_State" ∈ names(fe(result)) + + model = @formula Sales ~ Price + fe(State) + result = reg(df, model, save = :residuals) + @test residuals(result) !== nothing + @test "fe_State" ∉ names(fe(result)) + + model = @formula Sales ~ Price + fe(State) + result = reg(df, model, save = :fe) + @test "fe_State" ∈ names(fe(result)) + + + # iv recategorized + df.Pimin2 = df.Pimin + m = @formula Sales ~ (Pimin2 + Price ~ NDI + Pimin) + result = reg(df, m) + yhat = predict(result, df) + res = residuals(result, df) + + m2 = @formula Sales ~ Pimin2 + (Price ~ NDI + Pimin) + result2 = reg(df, m2) + yhat2 = predict(result2, df) + res2 = residuals(result2, df) + @test yhat ≈ yhat2 + @test res ≈ res2 + + m3 = @formula Sales ~ Pimin2 + (Price ~ NDI) + result3 = reg(df, m3) + yhat3 = predict(result3, df) + res3 = residuals(result3, df) + @test yhat ≈ yhat3 + @test res ≈ res3 + + m4 = @formula Sales ~ (Price + Pimin2 ~ NDI + Pimin) + result4 = reg(df, m4) + yhat4 = predict(result4, df) + res4 = residuals(result4, df) + @test yhat ≈ yhat4 + @test res ≈ res4 + + m5 = @formula Sales ~ (Price ~ NDI + Pimin) + Pimin2 + result5 = reg(df, m5) + yhat5 = predict(result5, df) + res5 = residuals(result5, df) + @test yhat ≈ yhat5 + @test res ≈ res5 +end + + +@testset "saved fixed effects" begin + + df = DataFrame(CSV.File(joinpath(dirname(pathof(FixedEffectModels)), "../dataset/Cigar.csv"))) + df.StateC = categorical(df.State) + + # check save does not change r2 + model1 = @formula Sales ~ Price + result1 = reg(df, model1, weights = :Pop) + model2 = @formula Sales ~ Price + result2 = reg(df, model2, weights = :Pop) + @test r2(result1) ≈ r2(result2) + + + + model = @formula Sales ~ Price + fe(Year) + result = reg(df, model, save = true) + @test fe(result)[1, :fe_Year] ≈ 164.77833189721005 + @test size(fe(result), 2) == 1 + @test size(fe(result, keepkeys = true), 2) == 2 + + model = @formula Sales ~ Price + fe(Year) + fe(State) + result = reg(df, model, save = true) + @test fe(result)[1, :fe_Year] + fe(result)[1, :fe_State] ≈ 140.6852 atol = 1e-3 + + model = @formula Sales ~ Price + Year&fe(State) + result = reg(df, model, save = true) + @test fe(result)[1, Symbol("fe_State&Year")] ≈ 1.742779 atol = 1e-3 + + model = @formula Sales ~ Price + fe(State) + Year&fe(State) + result = reg(df, model, save = true) + @test fe(result)[1, :fe_State] ≈ -91.690635 atol = 1e-1 + + model = @formula Sales ~ Price + fe(State) + result = reg(df, model, subset = df.State .<= 30, save = true) + @test fe(result)[1, :fe_State] ≈ 124.913976 atol = 1e-1 + @test ismissing(fe(result)[1380 , :fe_State]) + + model = @formula Sales ~ Price + fe(Year) + result = reg(df, model, weights = :Pop, save = true) + @test fe(result)[2, :fe_Year] - fe(result)[1, :fe_Year] ≈ -3.0347149502496222 + + # fixed effects + df.Price2 = df.Price + model = @formula Sales ~ Price + Price2 + fe(Year) + result = reg(df, model, save = true) + @test fe(result)[1, :fe_Year] ≈ 164.77833189721005 + + # iv + model = @formula Sales ~ (State ~ Price) + fe(Year) + result = reg(df, model, save = true) + @test fe(result)[1, :fe_Year] ≈ -167.48093490413623 + + # weights + model = @formula Sales ~ Price + fe(Year) + result = reg(df, model, weights = :Pop, save = true) + @test fe(result)[2, :fe_Year] - fe(result)[1, :fe_Year] ≈ -3.0347149502496222 + + # IV and weights + model = @formula Sales ~ (Price ~ Pimin) + fe(Year) + result = reg(df, model, weights = :Pop, save = true) + @test fe(result)[1, :fe_Year] ≈ 168.24688 atol = 1e-4 + + + # IV, weights and both year and state fixed effects + model = @formula Sales ~ (Price ~ Pimin) + fe(State) + fe(Year) + result = reg(df, model, weights = :Pop, save = true) + @test fe(result)[1, :fe_Year] + fe(result)[1, :fe_State]≈ 147.84145 atol = 1e-4 + + + # subset with IV + model = @formula Sales ~ (Price ~ Pimin) + fe(Year) + result = reg(df, model, subset = df.State .<= 30, save = true) + @test fe(result)[1, :fe_Year] ≈ 164.05245824240276 atol = 1e-4 + @test ismissing(fe(result)[811, :fe_Year]) + + + # subset with IV, weights and year fixed effects + model = @formula Sales ~ (Price ~ Pimin) + fe(Year) + result = reg(df, model, subset = df.State .<= 30, weights = :Pop, save = true) + @test fe(result)[1, :fe_Year] ≈ 182.71915 atol = 1e-4 + + # subset with IV, weights and year fixed effects + model = @formula Sales ~ (Price ~ Pimin) + fe(State) + fe(Year) + result = reg(df, model, subset = df.State .<= 30, weights = :Pop, save = true) + @test fe(result)[1, :fe_Year] + fe(result)[1, :fe_State] ≈ 158.91798 atol = 1e-4 + + methods_vec = [:cpu] + if FixedEffectModels.FixedEffects.has_CUDA() + push!(methods_vec, :gpu) + end + for method in methods_vec + local model = @formula Sales ~ Price + fe(Year) + local result = reg(df, model, save = true, method = method, double_precision = false) + @test fe(result)[1, :fe_Year] ≈ 164.7 atol = 1e-1 + end +end + diff --git a/test/runtests.jl b/test/runtests.jl index cf458e85..eebbe167 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,27 +1,7 @@ -using FixedEffectModels +using FixedEffectModels, Test -tests = [ - "formula.jl", - "fit.jl", - "predict.jl", - "partial_out.jl", - "collinearity.jl" - ] - -println("Running tests:") - - -for test in tests - try - include(test) - println("\t\033[1m\033[32mPASSED\033[0m: $(test)") - catch e - println("\t\033[1m\033[31mFAILED\033[0m: $(test)") - showerror(stdout, e, backtrace()) - rethrow(e) - end -end - - -using Test -@test Vcov.pinvertible(Symmetric([1.0 1.0; 1.0 1.0])) ≈ [1.0 1.0; 1.0 1.0] \ No newline at end of file +@testset "formula" begin include("formula.jl") end +@testset "fit" begin include("fit.jl") end +@testset "predict" begin include("predict.jl") end +@testset "partial out" begin include("partial_out.jl") end +@testset "collinearity" begin include("collinearity.jl") end