diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 667c2fe92..655738c3f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -24,7 +24,7 @@ jobs: - uses: julia-actions/setup-julia@v2 with: version: "1" - - uses: julia-actions/cache@v1 + - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 - name: Benchmark run run: | diff --git a/.github/workflows/current.yml b/.github/workflows/current.yml index 0e8a34743..7e2172e36 100644 --- a/.github/workflows/current.yml +++ b/.github/workflows/current.yml @@ -30,7 +30,7 @@ jobs: - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.julia-version }} - - uses: julia-actions/cache@v1 + - uses: julia-actions/cache@v2 with: cache-compiled: "true" - uses: julia-actions/julia-buildpkg@v1 diff --git a/.github/workflows/documenter.yml b/.github/workflows/documenter.yml index 81a8ccd6a..483fc4b8f 100644 --- a/.github/workflows/documenter.yml +++ b/.github/workflows/documenter.yml @@ -25,7 +25,7 @@ jobs: - uses: julia-actions/setup-julia@v2 with: version: 1.8 - - uses: julia-actions/cache@v1 + - uses: julia-actions/cache@v2 with: cache-compiled: "true" - uses: julia-actions/julia-buildpkg@latest diff --git a/.github/workflows/minimum.yml b/.github/workflows/minimum.yml index 80be46af9..607edc6be 100644 --- a/.github/workflows/minimum.yml +++ b/.github/workflows/minimum.yml @@ -22,13 +22,13 @@ jobs: matrix: julia-version: [1.8] julia-arch: [x64] - os: [ubuntu-22.04, macos-11, windows-2019] + os: [ubuntu-22.04, macos-12, windows-2019] steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.julia-version }} - - uses: julia-actions/cache@v1 + - uses: julia-actions/cache@v2 with: cache-compiled: "true" - uses: julia-actions/julia-buildpkg@v1 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 26e85fc56..1f667ca2b 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -28,7 +28,7 @@ jobs: - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.julia-version }} - - uses: julia-actions/cache@v1 + - uses: julia-actions/cache@v2 with: cache-compiled: true - uses: julia-actions/julia-runtest@v1 diff --git a/.gitignore b/.gitignore index 7cf172276..808865dee 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ settings.json docs/jmd LocalPreferences.toml benchmark.md +lcov.info +coverage/ diff --git a/NEWS.md b/NEWS.md index cc2f91e03..1a53f74ee 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,34 @@ +MixedModels v4.25.3 Release Notes +============================== +- Fix a bug in the handling of rank deficiency in the `simulate[!]` code. This has important correctness implications for bootstrapping models with rank-deficient fixed effects (as can happen in the case of partial crossing of the fixed effects / missing cells). [#778] + +MixedModels v4.25.2 Release Notes +============================== +- Use `public` keyword so that users don't see unnecessary docstring warnings on 1.11+. [#776] +- Fix accidental export of `dataset` and `datasets` and make them `public` instead. [#776] + +MixedModels v4.25.1 Release Notes +============================== +- Use more sophisticated checks on property names in `restoreoptsum` to allow for optsums saved by pre-v4.25 versions to be used with this version and later. [#775] + +MixedModels v4.25 Release Notes +============================== +- Add type notations in `pwrss(::LinearMixedModel)` and `logdet(::LinearMixedModel)` to enhance type inference. [#773] +- Take advantage of type parameter for `StatsAPI.weights(::LinearMixedModel{T})`. [#772] +- Fix use of kwargs in `fit!((::LinearMixedModel)`: [#772] + - user-specified `σ` is actually used, defaulting to existing value + - `REML` defaults to model's already specified REML value. +- Clean up code of keyword convenience constructor for `OptSummary`. [#772] +- Refactor thresholding parameters for forcing near-zero parameter values into `OptSummary`. [#772] + +MixedModels v4.24.1 Release Notes +============================== +- Re-export accidentally dropped export `lrtest`. [#769] + +MixedModels v4.24.0 Release Notes +============================== +* Properties for `GeneralizedLinearMixedModel` now default to delegation to the internal weighted `LinearMixedModel` when that property is not explicitly handled by `GeneralizedLinearMixedModel`. Previously, properties were delegated on an explicit basis, which meant that they had to be added manually as use cases were discovered. The downside to the new approach is that it is now possible to access properties whose definition in the LMM case doesn't match the GLMM definition when the GLMM definition hasn't been explicitly been implemented. [#767] + MixedModels v4.23.1 Release Notes ============================== * Fix for `simulate!` when only the estimable coefficients for a rank-deficient model are provided. [#756] @@ -516,3 +547,10 @@ Package dependencies [#748]: https://github.com/JuliaStats/MixedModels.jl/issues/748 [#755]: https://github.com/JuliaStats/MixedModels.jl/issues/755 [#756]: https://github.com/JuliaStats/MixedModels.jl/issues/756 +[#767]: https://github.com/JuliaStats/MixedModels.jl/issues/767 +[#769]: https://github.com/JuliaStats/MixedModels.jl/issues/769 +[#772]: https://github.com/JuliaStats/MixedModels.jl/issues/772 +[#773]: https://github.com/JuliaStats/MixedModels.jl/issues/773 +[#775]: https://github.com/JuliaStats/MixedModels.jl/issues/775 +[#776]: https://github.com/JuliaStats/MixedModels.jl/issues/776 +[#778]: https://github.com/JuliaStats/MixedModels.jl/issues/778 diff --git a/Project.toml b/Project.toml index 7020d82ac..122ab02b0 100644 --- a/Project.toml +++ b/Project.toml @@ -1,11 +1,12 @@ name = "MixedModels" uuid = "ff71e718-51f3-5ec2-a782-8ffcbfa3c316" author = ["Phillip Alday ", "Douglas Bates ", "Jose Bayoan Santiago Calderon "] -version = "4.23.1" +version = "4.25.3" [deps] Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45" BSplineKit = "093aae92-e908-43d7-9660-e50ee39d5a0a" +Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" GLM = "38e38edf-8417-5370-95a0-9cbb8c7f171a" @@ -33,6 +34,7 @@ TypedTables = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9" Aqua = "0.8" Arrow = "1, 2" BSplineKit = "0.14, 0.15, 0.16, 0.17" +Compat = "4.10" DataAPI = "1" DataFrames = "1" Distributions = "0.21, 0.22, 0.23, 0.24, 0.25" diff --git a/README.md b/README.md index 148959a1c..faa0846d3 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Typical distribution forms are _Bernoulli_ for binary data or _Poisson_ for coun |Linux | Ubuntu 20.04 | x64 |v1.8 | |Linux | Ubuntu 20.04 | x64 |current release | |Linux | Ubuntu 20.04 | x64 |nightly | -|macOS | Catalina 10.15| x64 |v1.8 | +|macOS | Monterey 12 | x64 |v1.8 | |Windows | Server 2019 | x64 |v1.8 | Note that previous releases still support older Julia versions. diff --git a/issues/780/Manifest.toml b/issues/780/Manifest.toml new file mode 100644 index 000000000..117b80340 --- /dev/null +++ b/issues/780/Manifest.toml @@ -0,0 +1,836 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.10.4" +manifest_format = "2.0" +project_hash = "ee9452c19ab51ba0816b484826fde7c9c38fea0f" + +[[deps.Adapt]] +deps = ["LinearAlgebra", "Requires"] +git-tree-sha1 = "6a55b747d1812e699320963ffde36f1ebdda4099" +uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" +version = "4.0.4" +weakdeps = ["StaticArrays"] + + [deps.Adapt.extensions] + AdaptStaticArraysExt = "StaticArrays" + +[[deps.AliasTables]] +deps = ["PtrArrays", "Random"] +git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff" +uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8" +version = "1.1.3" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" + +[[deps.ArrayLayouts]] +deps = ["FillArrays", "LinearAlgebra"] +git-tree-sha1 = "ce2ca959f932f5dad70697dd93133d1167cf1e4e" +uuid = "4c555306-a7a7-4459-81d9-ec55ddd5c99a" +version = "1.10.2" +weakdeps = ["SparseArrays"] + + [deps.ArrayLayouts.extensions] + ArrayLayoutsSparseArraysExt = "SparseArrays" + +[[deps.Arrow]] +deps = ["ArrowTypes", "BitIntegers", "CodecLz4", "CodecZstd", "ConcurrentUtilities", "DataAPI", "Dates", "EnumX", "LoggingExtras", "Mmap", "PooledArrays", "SentinelArrays", "Tables", "TimeZones", "TranscodingStreams", "UUIDs"] +git-tree-sha1 = "f8d411d1b45459368567dc51f683ed78a919d795" +uuid = "69666777-d1a9-59fb-9406-91d4454c9d45" +version = "2.7.2" + +[[deps.ArrowTypes]] +deps = ["Sockets", "UUIDs"] +git-tree-sha1 = "404265cd8128a2515a81d5eae16de90fdef05101" +uuid = "31f734f8-188a-4ce0-8406-c8a06bd891cd" +version = "2.3.0" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[deps.BSplineKit]] +deps = ["ArrayLayouts", "BandedMatrices", "FastGaussQuadrature", "ForwardDiff", "LinearAlgebra", "PrecompileTools", "Random", "Reexport", "SparseArrays", "Static", "StaticArrays", "StaticArraysCore"] +git-tree-sha1 = "79dba2b0d60f225f4660075cb0e9b4da9960042a" +uuid = "093aae92-e908-43d7-9660-e50ee39d5a0a" +version = "0.17.6" + +[[deps.BandedMatrices]] +deps = ["ArrayLayouts", "FillArrays", "LinearAlgebra", "PrecompileTools"] +git-tree-sha1 = "71f605effb24081b09cae943ba39ef9ca90c04f4" +uuid = "aae01518-5342-5314-be14-df237901396f" +version = "1.7.2" +weakdeps = ["SparseArrays"] + + [deps.BandedMatrices.extensions] + BandedMatricesSparseArraysExt = "SparseArrays" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[deps.BitIntegers]] +deps = ["Random"] +git-tree-sha1 = "a55462dfddabc34bc97d3a7403a2ca2802179ae6" +uuid = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1" +version = "0.3.1" + +[[deps.CSV]] +deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "PrecompileTools", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings", "WorkerUtilities"] +git-tree-sha1 = "6c834533dc1fabd820c1db03c839bf97e45a3fab" +uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +version = "0.10.14" + +[[deps.CodecLz4]] +deps = ["Lz4_jll", "TranscodingStreams"] +git-tree-sha1 = "0db0c70ca94c0a79cadad269497f25ca88b9fa91" +uuid = "5ba52731-8f18-5e0d-9241-30f10d1ec561" +version = "0.4.5" + +[[deps.CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.6" + +[[deps.CodecZstd]] +deps = ["TranscodingStreams", "Zstd_jll"] +git-tree-sha1 = "5e41a52bec3b0881a7eb54f5391b779994504186" +uuid = "6b39b394-51ab-5f42-8807-6242bab2b4c2" +version = "0.8.5" + +[[deps.CommonSubexpressions]] +deps = ["MacroTools", "Test"] +git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7" +uuid = "bbf7d656-a473-5ed7-a52c-81e309532950" +version = "0.3.0" + +[[deps.CommonWorldInvalidations]] +git-tree-sha1 = "ae52d1c52048455e85a387fbee9be553ec2b68d0" +uuid = "f70d9fcc-98c5-4d4a-abd7-e4cdeebd8ca8" +version = "1.0.0" + +[[deps.Compat]] +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.16.0" +weakdeps = ["Dates", "LinearAlgebra"] + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.1.1+0" + +[[deps.ConcurrentUtilities]] +deps = ["Serialization", "Sockets"] +git-tree-sha1 = "ea32b83ca4fefa1768dc84e504cc0a94fb1ab8d1" +uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" +version = "2.4.2" + +[[deps.Crayons]] +git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15" +uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f" +version = "4.1.1" + +[[deps.DataAPI]] +git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.16.0" + +[[deps.DataFrames]] +deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "REPL", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] +git-tree-sha1 = "04c738083f29f86e62c8afc341f0967d8717bdb8" +uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +version = "1.6.1" + +[[deps.DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.20" + +[[deps.DataValueInterfaces]] +git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" +uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" +version = "1.0.0" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.Dictionaries]] +deps = ["Indexing", "Random", "Serialization"] +git-tree-sha1 = "35b66b6744b2d92c778afd3a88d2571875664a2a" +uuid = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" +version = "0.4.2" + +[[deps.DiffResults]] +deps = ["StaticArraysCore"] +git-tree-sha1 = "782dd5f4561f5d267313f23853baaaa4c52ea621" +uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5" +version = "1.1.0" + +[[deps.DiffRules]] +deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"] +git-tree-sha1 = "23163d55f885173722d1e4cf0f6110cdbaf7e272" +uuid = "b552c78f-8df3-52c6-915a-8e097449b14b" +version = "1.15.1" + +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[deps.Distributions]] +deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"] +git-tree-sha1 = "0e0a1264b0942f1f3abb2b30891f2a590cc652ac" +uuid = "31c24e10-a181-5473-b8eb-7969acd0382f" +version = "0.25.110" + + [deps.Distributions.extensions] + DistributionsChainRulesCoreExt = "ChainRulesCore" + DistributionsDensityInterfaceExt = "DensityInterface" + DistributionsTestExt = "Test" + + [deps.Distributions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + DensityInterface = "b429d917-457f-4dbc-8f4c-0cc954292b1d" + Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.DocStringExtensions]] +deps = ["LibGit2"] +git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.3" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.EnumX]] +git-tree-sha1 = "bdb1942cd4c45e3c678fd11569d5cccd80976237" +uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56" +version = "1.0.4" + +[[deps.ExprTools]] +git-tree-sha1 = "27415f162e6028e81c72b82ef756bf321213b6ec" +uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" +version = "0.1.10" + +[[deps.FastGaussQuadrature]] +deps = ["LinearAlgebra", "SpecialFunctions", "StaticArrays"] +git-tree-sha1 = "fd923962364b645f3719855c88f7074413a6ad92" +uuid = "442a2c76-b920-505d-bb47-c5924d526838" +version = "1.0.2" + +[[deps.FilePathsBase]] +deps = ["Compat", "Dates", "Mmap", "Printf", "Test", "UUIDs"] +git-tree-sha1 = "9f00e42f8d99fdde64d40c8ea5d14269a2e2c1aa" +uuid = "48062228-2e41-5def-b9a4-89aafe57970f" +version = "0.9.21" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + +[[deps.FillArrays]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "0653c0a2396a6da5bc4766c43041ef5fd3efbe57" +uuid = "1a297f60-69ca-5386-bcde-b61e274b549b" +version = "1.11.0" +weakdeps = ["PDMats", "SparseArrays", "Statistics"] + + [deps.FillArrays.extensions] + FillArraysPDMatsExt = "PDMats" + FillArraysSparseArraysExt = "SparseArrays" + FillArraysStatisticsExt = "Statistics" + +[[deps.ForwardDiff]] +deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions"] +git-tree-sha1 = "cf0fe81336da9fb90944683b8c41984b08793dad" +uuid = "f6369f11-7733-5829-9624-2563aa707210" +version = "0.10.36" +weakdeps = ["StaticArrays"] + + [deps.ForwardDiff.extensions] + ForwardDiffStaticArraysExt = "StaticArrays" + +[[deps.Future]] +deps = ["Random"] +uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" + +[[deps.GLM]] +deps = ["Distributions", "LinearAlgebra", "Printf", "Reexport", "SparseArrays", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns", "StatsModels"] +git-tree-sha1 = "273bd1cd30768a2fddfa3fd63bbc746ed7249e5f" +uuid = "38e38edf-8417-5370-95a0-9cbb8c7f171a" +version = "1.9.0" + +[[deps.HypergeometricFunctions]] +deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"] +git-tree-sha1 = "7c4195be1649ae622304031ed46a2f4df989f1eb" +uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a" +version = "0.3.24" + +[[deps.IfElse]] +git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" +uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" +version = "0.1.1" + +[[deps.Indexing]] +git-tree-sha1 = "ce1566720fd6b19ff3411404d4b977acd4814f9f" +uuid = "313cdc1a-70c2-5d6a-ae34-0150d3930a38" +version = "1.1.1" + +[[deps.InlineStrings]] +git-tree-sha1 = "45521d31238e87ee9f9732561bfee12d4eebd52d" +uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" +version = "1.4.2" +weakdeps = ["ArrowTypes", "Parsers"] + + [deps.InlineStrings.extensions] + ArrowTypesExt = "ArrowTypes" + ParsersExt = "Parsers" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[deps.InvertedIndices]] +git-tree-sha1 = "0dc7b50b8d436461be01300fd8cd45aa0274b038" +uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" +version = "1.3.0" + +[[deps.IrrationalConstants]] +git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2" +uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" +version = "0.2.2" + +[[deps.IteratorInterfaceExtensions]] +git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" +uuid = "82899510-4779-5014-852e-03e436cf321d" +version = "1.0.0" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.5.0" + +[[deps.JSON3]] +deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"] +git-tree-sha1 = "eb3edce0ed4fa32f75a0a11217433c31d56bd48b" +uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +version = "1.14.0" +weakdeps = ["ArrowTypes"] + + [deps.JSON3.extensions] + JSON3ArrowExt = ["ArrowTypes"] + +[[deps.LaTeXStrings]] +git-tree-sha1 = "50901ebc375ed41dbf8058da26f9de442febbbec" +uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +version = "1.3.1" + +[[deps.LazyArtifacts]] +deps = ["Artifacts", "Pkg"] +uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.4.0+0" + +[[deps.LibGit2]] +deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.6.4+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.0+1" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[deps.LogExpFunctions]] +deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] +git-tree-sha1 = "a2d09619db4e765091ee5c6ffe8872849de0feea" +uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" +version = "0.3.28" + + [deps.LogExpFunctions.extensions] + LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" + LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" + LogExpFunctionsInverseFunctionsExt = "InverseFunctions" + + [deps.LogExpFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[deps.LoggingExtras]] +deps = ["Dates", "Logging"] +git-tree-sha1 = "c1dd6d7978c12545b4179fb6153b9250c96b0075" +uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" +version = "1.0.3" + +[[deps.Lz4_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "7f26c8fc5229e68484e0b3447312c98e16207d11" +uuid = "5ced341a-0733-55b8-9ab6-a4889d929147" +version = "1.10.0+0" + +[[deps.MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.13" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.2+1" + +[[deps.Missings]] +deps = ["DataAPI"] +git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" +uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" +version = "1.2.0" + +[[deps.MixedModels]] +deps = ["Arrow", "BSplineKit", "Compat", "DataAPI", "Distributions", "GLM", "JSON3", "LinearAlgebra", "Markdown", "MixedModelsDatasets", "NLopt", "PooledArrays", "PrecompileTools", "ProgressMeter", "Random", "SparseArrays", "StaticArrays", "Statistics", "StatsAPI", "StatsBase", "StatsFuns", "StatsModels", "StructTypes", "Tables", "TypedTables"] +git-tree-sha1 = "55b9364f382b37c0794d7e8065aefceaa225c04c" +uuid = "ff71e718-51f3-5ec2-a782-8ffcbfa3c316" +version = "4.25.3" + +[[deps.MixedModelsDatasets]] +deps = ["Arrow", "Artifacts", "LazyArtifacts"] +git-tree-sha1 = "5f508af97ecf39645febed8ba2fabf5cfdc682e0" +uuid = "7e9fb7ac-9f67-43bf-b2c8-96ba0796cbb6" +version = "0.1.1" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.Mocking]] +deps = ["Compat", "ExprTools"] +git-tree-sha1 = "ead0dbb33b6808578c385ffaab20d9b57053661b" +uuid = "78c3b35d-d492-501b-9361-3d52fe80e533" +version = "0.8.0" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2023.1.10" + +[[deps.NLopt]] +deps = ["NLopt_jll"] +git-tree-sha1 = "3b887e2ef56be3309e68d2546c6178e2d2fa9a60" +uuid = "76087f3c-5699-56af-9a33-bf431cd00edd" +version = "1.0.2" + + [deps.NLopt.extensions] + NLoptMathOptInterfaceExt = ["MathOptInterface"] + + [deps.NLopt.weakdeps] + MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" + +[[deps.NLopt_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "3d1eefa627faeabc59ab9edbf00d17f70123ad69" +uuid = "079eb43e-fd8e-5478-9966-2cf3e3edb778" +version = "2.8.0+0" + +[[deps.NaNMath]] +deps = ["OpenLibm_jll"] +git-tree-sha1 = "0877504529a3e5c3343c6f8b4c0381e57e4387e4" +uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" +version = "1.0.2" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.23+4" + +[[deps.OpenLibm_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "05823500-19ac-5b8b-9628-191a04bc5112" +version = "0.8.1+2" + +[[deps.OpenSpecFun_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1" +uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" +version = "0.5.5+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.6.3" + +[[deps.PDMats]] +deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"] +git-tree-sha1 = "949347156c25054de2db3b166c52ac4728cbad65" +uuid = "90014a1f-27ba-587c-ab20-58faa44d9150" +version = "0.11.31" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.1" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.10.0" + +[[deps.PooledArrays]] +deps = ["DataAPI", "Future"] +git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3" +uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" +version = "1.4.3" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.3" + +[[deps.PrettyTables]] +deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "Reexport", "StringManipulation", "Tables"] +git-tree-sha1 = "66b20dd35966a748321d3b2537c4584cf40387c7" +uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" +version = "2.3.2" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.ProgressMeter]] +deps = ["Distributed", "Printf"] +git-tree-sha1 = "8f6bc219586aef8baf0ff9a5fe16ee9c70cb65e4" +uuid = "92933f4c-e287-5a05-a399-4b506db050ca" +version = "1.10.2" + +[[deps.PtrArrays]] +git-tree-sha1 = "f011fbb92c4d401059b2212c05c0601b70f8b759" +uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" +version = "1.2.0" + +[[deps.QuadGK]] +deps = ["DataStructures", "LinearAlgebra"] +git-tree-sha1 = "e237232771fdafbae3db5c31275303e056afaa9f" +uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" +version = "2.10.1" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.Reexport]] +git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.2.2" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.0" + +[[deps.Rmath]] +deps = ["Random", "Rmath_jll"] +git-tree-sha1 = "f65dcb5fa46aee0cf9ed6274ccbd597adc49aa7b" +uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa" +version = "0.7.1" + +[[deps.Rmath_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e60724fd3beea548353984dc61c943ecddb0e29a" +uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f" +version = "0.4.3+0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Scratch]] +deps = ["Dates"] +git-tree-sha1 = "3bac05bc7e74a75fd9cba4295cde4045d9fe2386" +uuid = "6c6a2e73-6563-6170-7368-637461726353" +version = "1.2.1" + +[[deps.SentinelArrays]] +deps = ["Dates", "Random"] +git-tree-sha1 = "ff11acffdb082493657550959d4feb4b6149e73a" +uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" +version = "1.4.5" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.ShiftedArrays]] +git-tree-sha1 = "503688b59397b3307443af35cd953a13e8005c16" +uuid = "1277b4bf-5013-50f5-be3d-901d8477a67a" +version = "2.0.0" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[deps.SortingAlgorithms]] +deps = ["DataStructures"] +git-tree-sha1 = "66e0a8e672a0bdfca2c3f5937efb8538b9ddc085" +uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" +version = "1.2.1" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.10.0" + +[[deps.SpecialFunctions]] +deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] +git-tree-sha1 = "2f5d4697f21388cbe1ff299430dd169ef97d7e14" +uuid = "276daf66-3868-5448-9aa4-cd146d93841b" +version = "2.4.0" + + [deps.SpecialFunctions.extensions] + SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" + + [deps.SpecialFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + +[[deps.SplitApplyCombine]] +deps = ["Dictionaries", "Indexing"] +git-tree-sha1 = "c06d695d51cfb2187e6848e98d6252df9101c588" +uuid = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" +version = "1.2.3" + +[[deps.Static]] +deps = ["CommonWorldInvalidations", "IfElse", "PrecompileTools"] +git-tree-sha1 = "87d51a3ee9a4b0d2fe054bdd3fc2436258db2603" +uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" +version = "1.1.1" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "eeafab08ae20c62c44c8399ccb9354a04b80db50" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.7" + + [deps.StaticArrays.extensions] + StaticArraysChainRulesCoreExt = "ChainRulesCore" + StaticArraysStatisticsExt = "Statistics" + + [deps.StaticArrays.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[deps.StaticArraysCore]] +git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.3" + +[[deps.Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.10.0" + +[[deps.StatsAPI]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "1ff449ad350c9c4cbc756624d6f8a8c3ef56d3ed" +uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" +version = "1.7.0" + +[[deps.StatsBase]] +deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] +git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21" +uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" +version = "0.34.3" + +[[deps.StatsFuns]] +deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"] +git-tree-sha1 = "cef0472124fab0695b58ca35a77c6fb942fdab8a" +uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c" +version = "1.3.1" + + [deps.StatsFuns.extensions] + StatsFunsChainRulesCoreExt = "ChainRulesCore" + StatsFunsInverseFunctionsExt = "InverseFunctions" + + [deps.StatsFuns.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.StatsModels]] +deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Printf", "REPL", "ShiftedArrays", "SparseArrays", "StatsAPI", "StatsBase", "StatsFuns", "Tables"] +git-tree-sha1 = "5cf6c4583533ee38639f73b880f35fc85f2941e0" +uuid = "3eaba693-59b7-5ba5-a881-562e759f1c8d" +version = "0.7.3" + +[[deps.StringManipulation]] +deps = ["PrecompileTools"] +git-tree-sha1 = "a04cabe79c5f01f4d723cc6704070ada0b9d46d5" +uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e" +version = "0.3.4" + +[[deps.StructTypes]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "ca4bccb03acf9faaf4137a9abc1881ed1841aa70" +uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" +version = "1.10.0" + +[[deps.SuiteSparse]] +deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"] +uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.2.1+1" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.TZJData]] +deps = ["Artifacts"] +git-tree-sha1 = "1607ad46cf8d642aa779a1d45af1c8620dbf6915" +uuid = "dc5dba14-91b3-4cab-a142-028a31da12f7" +version = "1.2.0+2024a" + +[[deps.TableTraits]] +deps = ["IteratorInterfaceExtensions"] +git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" +uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" +version = "1.0.1" + +[[deps.Tables]] +deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"] +git-tree-sha1 = "598cd7c1f68d1e205689b1c2fe65a9f85846f297" +uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" +version = "1.12.0" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.TimeZones]] +deps = ["Dates", "Downloads", "InlineStrings", "Mocking", "Printf", "Scratch", "TZJData", "Unicode", "p7zip_jll"] +git-tree-sha1 = "b92aebdd3555f3a7e3267cf17702033c2814ef48" +uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53" +version = "1.18.0" + + [deps.TimeZones.extensions] + TimeZonesRecipesBaseExt = "RecipesBase" + + [deps.TimeZones.weakdeps] + RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" + +[[deps.TranscodingStreams]] +git-tree-sha1 = "d73336d81cafdc277ff45558bb7eaa2b04a8e472" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.10.10" +weakdeps = ["Random", "Test"] + + [deps.TranscodingStreams.extensions] + TestExt = ["Test", "Random"] + +[[deps.TypedTables]] +deps = ["Adapt", "Dictionaries", "Indexing", "SplitApplyCombine", "Tables", "Unicode"] +git-tree-sha1 = "84fd7dadde577e01eb4323b7e7b9cb51c62c60d4" +uuid = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9" +version = "1.4.6" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.WeakRefStrings]] +deps = ["DataAPI", "InlineStrings", "Parsers"] +git-tree-sha1 = "b1be2855ed9ed8eac54e5caff2afcdb442d52c23" +uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5" +version = "1.4.2" + +[[deps.WorkerUtilities]] +git-tree-sha1 = "cd1659ba0d57b71a464a29e64dbc67cfe83d54e7" +uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60" +version = "1.6.1" + +[[deps.ZipFile]] +deps = ["Libdl", "Printf", "Zlib_jll"] +git-tree-sha1 = "f492b7fe1698e623024e873244f10d89c95c340a" +uuid = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea" +version = "0.10.1" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+1" + +[[deps.Zstd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.6+0" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.8.0+1" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.52.0+1" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+2" diff --git a/issues/780/Project.toml b/issues/780/Project.toml new file mode 100644 index 000000000..72052ff9f --- /dev/null +++ b/issues/780/Project.toml @@ -0,0 +1,10 @@ +[deps] +Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45" +CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +MixedModels = "ff71e718-51f3-5ec2-a782-8ffcbfa3c316" +Scratch = "6c6a2e73-6563-6170-7368-637461726353" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +ZipFile = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea" diff --git a/issues/780/issue.jl b/issues/780/issue.jl new file mode 100644 index 000000000..2603b111b --- /dev/null +++ b/issues/780/issue.jl @@ -0,0 +1,155 @@ +module IssueData + using Arrow + using CSV + using DataFrames + using Downloads + using Scratch + using ZipFile + + export get_data + + const CACHE = Ref("") + const URL = "https://github.com/user-attachments/files/16604579/testdataforjulia_bothcase.zip" + + function extract_csv(zipfile, fname; delim=',', header=1, kwargs...) + file = only(filter(f -> endswith(f.name, fname), zipfile.files)) + return CSV.read(file, DataFrame; delim, header, kwargs...) + end + + function get_data() + path = joinpath(CACHE[], "780.arrow") + + isfile(path) && return DataFrame(Arrow.Table(path); copycols=true) + + @info "downloading..." + data = open(Downloads.download(URL), "r") do io + zipfile = ZipFile.Reader(io) + @info "extracting..." + return extract_csv( + zipfile, + "testdataforjulia_bothcase.csv"; + missingstring=["NA"], + downcast=true, + types=Dict( + :case => Bool, + :individual_local_identifier => String15, + ) + ) + end + + Arrow.write(path, data) + return data + end + + clear_scratchspaces!() = rm.(readdir(CACHE[])) + + function __init__() + CACHE[] = get_scratch!(Main, "780") + return nothing + end +end + +using DataFrames +using .IssueData +using LinearAlgebra +using MixedModels +using Statistics + +data = get_data() + +# check for complete separation of response within levels of columns used as predictors + +println( + unstack( + combine(groupby(data, [:Analysisclass, :case]), nrow => :n), + :case, + :n + ), +) + +println( + unstack( + combine(groupby(data, [:individual_local_identifier, :case]), nrow => :n), + :case, + :n, + ), +) + +println( + unstack( + combine(groupby(data, [:cropyear, :case]), nrow => :n), + :case, + :n, + ), +) + +m0form = @formula(case ~ 0 + Analysisclass + (1|cropyear/individual_local_identifier)) + +# fails +model = fit(MixedModel, m0form, data, Bernoulli(); + wts=float.(data.weights), + contrasts= Dict(:Analysisclass => DummyCoding(; base="aRice_Wet_day")), + fast=false, + progress=true, + verbose=false) + +# works on amd64, non singular, FE look okay +model = fit(MixedModel, m0form, data, Bernoulli(); + wts=float.(data.weights), + contrasts= Dict(:Analysisclass => DummyCoding(; base="aRice_Wet_day")), + init_from_lmm=[:θ], + fast=false, + progress=true, + verbose=false) + +# works on m1, singular and has questionable FE +m0fast = fit(MixedModel, m0form, data, Bernoulli(); + wts=float.(data.weights), + contrasts= Dict(:Analysisclass => DummyCoding(; base="aRice_Wet_day")), + fast=true, + progress=true, + verbose=false) + +# this model is singular in cropyear, but it looks like there is proper nesting: +groups = select(data, :cropyear, :individual_local_identifier) +unique(groups) +unique(groups, :cropyear) +unique(groups, :individual_local_identifier) + +# the estimates for `Nonhabitat_Wet_day` and `Nonhabitat_Wet_night` are identical, +# which seems suspicious, and they have very large standard errors. I think +# this hints at undetected collinearity. +X = modelmatrix(m0fast) +rank(X) # =12 +idx = findall(coefnames(m0fast)) do x + return x in ("Analysisclass: Nonhabitat_Wet_day", "Analysisclass: Nonhabitat_Wet_night") +end + +cols = X[:, idx] +# AHA 98% of values are identical because these measurements are very sparse +mean(cols[:, 1] .== cols[:, 2]) +mean(cols[:, 1]) +mean(cols[:, 2]) + +counts = sort!(combine(groupby(data, :Analysisclass), nrow => :n), :n) +transform!(counts, :n => ByRow(x -> round(100x / sum(counts.n); digits=1)) => "%") + +# let's try reparameterizing + +transform!(data, :Analysisclass => ByRow(ac -> NamedTuple{(:habitat, :wet, :time)}(split(ac, "_"))) => AsTable) + +m1form = @formula(case ~ 0 + habitat * wet * time + (1|cropyear & individual_local_identifier)) + +# fails really fast with a PosDefException +m1fast = fit(MixedModel, m1form, data, Bernoulli(); + wts=float.(data.weights), + fast=true, + progress=true, + verbose=false) + +# still fails +m1 = fit(MixedModel, m1form, data, Bernoulli(); + wts=float.(data.weights), + fast=false, + progress=true, + verbose=false) diff --git a/src/MixedModels.jl b/src/MixedModels.jl index 664b5db0a..db3911bfe 100644 --- a/src/MixedModels.jl +++ b/src/MixedModels.jl @@ -4,9 +4,10 @@ using Arrow: Arrow using Base: Ryu, require_one_based_indexing using BSplineKit: BSplineKit, BSplineOrder, Natural, Derivative, SplineInterpolation using BSplineKit: interpolate +using Compat: @compat using DataAPI: DataAPI, levels, refpool, refarray, refvalue using Distributions: Distributions, Bernoulli, Binomial, Chisq, Distribution, Gamma -using Distributions: InverseGaussian, Normal, Poisson, ccdf, estimate +using Distributions: InverseGaussian, Normal, Poisson, ccdf using GLM: GLM, GeneralizedLinearModel, IdentityLink, InverseLink, LinearModel using GLM: Link, LogLink, LogitLink, ProbitLink, SqrtLink using GLM: canonicallink, glm, linkinv, dispersion, dispersion_parameter @@ -16,10 +17,10 @@ using LinearAlgebra: Diagonal, Hermitian, HermOrSym, I, LAPACK, LowerTriangular using LinearAlgebra: PosDefException, SVD, SymTridiagonal, Symmetric using LinearAlgebra: UpperTriangular, cond, diag, diagind, dot, eigen, isdiag using LinearAlgebra: ldiv!, lmul!, logdet, mul!, norm, normalize, normalize!, qr -using LinearAlgebra: rank, rdiv!, rmul!, svd, tr, tril! +using LinearAlgebra: rank, rdiv!, rmul!, svd, tril! using Markdown: Markdown using MixedModelsDatasets: dataset, datasets -using NLopt: NLopt, Opt, ftol_abs, ftol_rel, initial_step, maxtime, xtol_abs, xtol_rel +using NLopt: NLopt, Opt using PooledArrays: PooledArrays, PooledArray using PrecompileTools: PrecompileTools, @setup_workload, @compile_workload using ProgressMeter: ProgressMeter, Progress, ProgressUnknown, finish!, next! @@ -38,9 +39,9 @@ using StatsModels: StatsModels, AbstractContrasts, AbstractTerm, CategoricalTerm using StatsModels: ConstantTerm, DummyCoding, EffectsCoding, FormulaTerm, FunctionTerm using StatsModels: HelmertCoding, HypothesisCoding, InteractionTerm, InterceptTerm using StatsModels: MatrixTerm, SeqDiffCoding, TableRegressionModel, Term -using StatsModels: apply_schema, drop_term, formula, modelcols, term, @formula +using StatsModels: apply_schema, drop_term, formula, lrtest, modelcols, term, @formula using StructTypes: StructTypes -using Tables: Tables, columntable, rows +using Tables: Tables, columntable using TypedTables: TypedTables, DictTable, FlexTable, Table export @formula, @@ -89,8 +90,6 @@ export @formula, condVar, condVartables, confint, - dataset, - datasets, deviance, dispersion, dispersion_parameter, @@ -114,6 +113,7 @@ export @formula, logdet, loglikelihood, lowerbd, + lrtest, meanresponse, modelmatrix, model_response, @@ -159,6 +159,8 @@ export @formula, # TODO: move this to the correct spot in list once we've decided on name export savereplicates, restorereplicates +@compat public rePCA, PCA, dataset, datasets + """ MixedModel @@ -204,11 +206,12 @@ include("mimeshow.jl") include("serialization.jl") include("profile/profile.jl") +# COV_EXCL_START @setup_workload begin # Putting some things in `setup` can reduce the size of the # precompile file and potentially make loading faster. - sleepstudy = MixedModels.dataset(:sleepstudy) - contra = MixedModels.dataset(:contra) + sleepstudy = dataset(:sleepstudy) + contra = dataset(:contra) progress = false io = IOBuffer() @compile_workload begin @@ -230,5 +233,6 @@ include("profile/profile.jl") progress) end end +# COV_EXCL_STOP end # module diff --git a/src/bootstrap.jl b/src/bootstrap.jl index 269c5567f..c90dc33a7 100644 --- a/src/bootstrap.jl +++ b/src/bootstrap.jl @@ -177,7 +177,7 @@ end """ parametricbootstrap([rng::AbstractRNG], nsamp::Integer, m::MixedModel{T}, ftype=T; - β = coef(m), σ = m.σ, θ = m.θ, progress=true, optsum_overrides=(;)) + β = fixef(m), σ = m.σ, θ = m.θ, progress=true, optsum_overrides=(;)) Perform `nsamp` parametric bootstrap replication fits of `m`, returning a `MixedModelBootstrap`. @@ -214,7 +214,7 @@ function parametricbootstrap( n::Integer, morig::MixedModel{T}, ftype::Type{<:AbstractFloat}=T; - β::AbstractVector=coef(morig), + β::AbstractVector=fixef(morig), σ=morig.σ, θ::AbstractVector=morig.θ, use_threads::Bool=false, @@ -232,9 +232,13 @@ function parametricbootstrap( if σ !== missing σ = T(σ) end - β, θ = convert(Vector{T}, β), convert(Vector{T}, θ) - βsc, θsc = similar(ftype.(β)), similar(ftype.(θ)) - p, k = length(β), length(θ) + β = convert(Vector{T}, β) + θ = convert(Vector{T}, θ) + # scratch -- note that this is the length of the unpivoted coef vector + βsc = coef(morig) + θsc = zeros(ftype, length(θ)) + p = length(βsc) + k = length(θsc) m = deepcopy(morig) for (key, val) in pairs(optsum_overrides) setfield!(m.optsum, key, val) @@ -251,7 +255,6 @@ function parametricbootstrap( samp = replicate(n; progress) do simulate!(rng, m; β, σ, θ) refit!(m; progress=false) - # @info "" m.optsum.feval ( objective=ftype.(m.objective), σ=ismissing(m.σ) ? missing : ftype(m.σ), diff --git a/src/generalizedlinearmixedmodel.jl b/src/generalizedlinearmixedmodel.jl index e33429b81..cfc06a778 100644 --- a/src/generalizedlinearmixedmodel.jl +++ b/src/generalizedlinearmixedmodel.jl @@ -309,13 +309,13 @@ function StatsAPI.fit!( ## check if very small parameter values bounded below by zero can be set to zero xmin_ = copy(xmin) for i in eachindex(xmin_) - if iszero(optsum.lowerbd[i]) && zero(T) < xmin_[i] < T(0.001) + if iszero(optsum.lowerbd[i]) && zero(T) < xmin_[i] < optsum.xtol_zero_abs xmin_[i] = zero(T) end end loglength = length(fitlog) if xmin ≠ xmin_ - if (zeroobj = obj(xmin_, T[])) ≤ (fmin + 1.e-5) + if (zeroobj = obj(xmin_, T[])) ≤ (fmin + optsum.ftol_zero_abs) fmin = zeroobj copyto!(xmin, xmin_) elseif length(fitlog) > loglength @@ -351,7 +351,7 @@ function GeneralizedLinearMixedModel( tbl, d::Distribution, l::Type; - kwargs... + kwargs..., ) throw(ArgumentError("Expected a Link instance (`$l()`), got a type (`$l`).")) end @@ -376,7 +376,7 @@ function GeneralizedLinearMixedModel( tbl::Tables.ColumnTable, d::Normal, l::IdentityLink; - kwargs... + kwargs..., ) return throw( ArgumentError("use LinearMixedModel for Normal distribution with IdentityLink") @@ -489,12 +489,12 @@ function Base.getproperty(m::GeneralizedLinearMixedModel, s::Symbol) σs(m) elseif s == :σρs σρs(m) - elseif s ∈ (:A, :L, :optsum, :reterms, :Xymat, :feterm, :formula, :parmap) - getfield(m.LMM, s) - elseif s ∈ (:dims, :λ, :lowerbd, :corr, :PCA, :rePCA, :X) - getproperty(m.LMM, s) elseif s == :y m.resp.y + elseif !hasfield(GeneralizedLinearMixedModel, s) && s ∈ propertynames(m.LMM, true) + # automatically delegate as much as possible to the internal local linear approximation + # NB: the !hasfield call has to be first since we're calling getproperty() with m.LMM... + getproperty(m.LMM, s) else getfield(m, s) end diff --git a/src/likelihoodratiotest.jl b/src/likelihoodratiotest.jl index 335cbaa3f..4e2bf7287 100644 --- a/src/likelihoodratiotest.jl +++ b/src/likelihoodratiotest.jl @@ -219,7 +219,7 @@ function _iscomparable(m::LinearMixedModel...) if any(getproperty.(getproperty.(m, :optsum), :REML)) isconstant(coefnames.(m)) || throw( ArgumentError( - "Likelihood-ratio tests for REML-fitted models are only valid when the fixed-effects specifications are identical", + "Likelihood-ratio tests for REML-fitted models are only valid when the fixed-effects specifications are identical" ), ) end diff --git a/src/linalg/logdet.jl b/src/linalg/logdet.jl index e4e084a72..5f2c5a564 100644 --- a/src/linalg/logdet.jl +++ b/src/linalg/logdet.jl @@ -25,11 +25,11 @@ lower Cholesky factor. """ function LinearAlgebra.logdet(m::LinearMixedModel{T}) where {T} L = m.L - @inbounds s = sum(j -> LD(L[kp1choose2(j)]), axes(m.reterms, 1)) + @inbounds s = sum(j -> LD(L[kp1choose2(j)])::T, axes(m.reterms, 1)) if m.optsum.REML - lastL = last(L) + lastL = last(L)::Matrix{T} s += LD(lastL) # this includes the log of sqrtpwrss s -= log(last(lastL)) # so we need to subtract it from the sum end - return s + s # multiply by 2 b/c the desired det is of the symmetric mat, not the factor + return (s + s)::T # multiply by 2 b/c the desired det is of the symmetric mat, not the factor end diff --git a/src/linalg/rankUpdate.jl b/src/linalg/rankUpdate.jl index e6f1aacd6..705f87d53 100644 --- a/src/linalg/rankUpdate.jl +++ b/src/linalg/rankUpdate.jl @@ -12,7 +12,7 @@ function rankUpdate! end function rankUpdate!(C::AbstractMatrix, a::AbstractArray, α, β) return error( - "We haven't implemented a method for $(typeof(C)), $(typeof(a)). Please file an issue on GitHub.", + "We haven't implemented a method for $(typeof(C)), $(typeof(a)). Please file an issue on GitHub." ) end diff --git a/src/linearmixedmodel.jl b/src/linearmixedmodel.jl index cc95b1a1a..14f297835 100644 --- a/src/linearmixedmodel.jl +++ b/src/linearmixedmodel.jl @@ -36,8 +36,8 @@ struct LinearMixedModel{T<:AbstractFloat} <: MixedModel{T} sqrtwts::Vector{T} parmap::Vector{NTuple{3,Int}} dims::NamedTuple{(:n, :p, :nretrms),NTuple{3,Int}} - A::Vector{AbstractMatrix{T}} # cross-product blocks - L::Vector{AbstractMatrix{T}} + A::Vector{<:AbstractMatrix{T}} # cross-product blocks + L::Vector{<:AbstractMatrix{T}} optsum::OptSummary{T} end @@ -50,7 +50,7 @@ function LinearMixedModel( end const _MISSING_RE_ERROR = ArgumentError( - "Formula contains no random effects; this isn't a mixed model. Perhaps you want to use GLM.jl?", + "Formula contains no random effects; this isn't a mixed model. Perhaps you want to use GLM.jl?" ) function LinearMixedModel( @@ -62,7 +62,7 @@ function LinearMixedModel( fvars ⊆ tvars || throw( ArgumentError( - "The following formula variables are not present in the table: $(setdiff(fvars, tvars))", + "The following formula variables are not present in the table: $(setdiff(fvars, tvars))" ), ) @@ -175,7 +175,7 @@ function LinearMixedModel( A, L = createAL(reterms, Xy) lbd = foldl(vcat, lowerbd(c) for c in reterms) θ = foldl(vcat, getθ(c) for c in reterms) - optsum = OptSummary(θ, lbd, :LN_BOBYQA; ftol_rel=T(1.0e-12), ftol_abs=T(1.0e-8)) + optsum = OptSummary(θ, lbd) optsum.sigma = isnothing(σ) ? nothing : T(σ) fill!(optsum.xtol_abs, 1.0e-10) return LinearMixedModel( @@ -226,7 +226,7 @@ end function _offseterr() return throw( ArgumentError( - "Offsets are not supported in linear models. You can simply shift the response by the offset.", + "Offsets are not supported in linear models. You can simply shift the response by the offset." ), ) end @@ -372,7 +372,7 @@ function StatsBase.confint(m::MixedModel{T}; level=0.95) where {T} return DictTable(; coef=coefnames(m), lower=β .- cutoff .* std, - upper=β .+ cutoff .* std + upper=β .+ cutoff .* std, ) end @@ -408,7 +408,7 @@ function createAL(reterms::Vector{<:AbstractReMat{T}}, Xy::FeMat{T}) where {T} end end end - return A, L + return identity.(A), identity.(L) end StatsAPI.deviance(m::LinearMixedModel) = objective(m) @@ -431,8 +431,8 @@ function feL(m::LinearMixedModel) end """ - fit!(m::LinearMixedModel; progress::Bool=true, REML::Bool=false, - σ::Union{Real, Nothing}=nothing, + fit!(m::LinearMixedModel; progress::Bool=true, REML::Bool=m.optsum.REML, + σ::Union{Real, Nothing}=m.optsum.sigma, thin::Int=typemax(Int)) Optimize the objective of a `LinearMixedModel`. When `progress` is `true` a @@ -445,8 +445,8 @@ saved in `m.optsum.fitlog`. function StatsAPI.fit!( m::LinearMixedModel{T}; progress::Bool=true, - REML::Bool=false, - σ::Union{Real,Nothing}=nothing, + REML::Bool=m.optsum.REML, + σ::Union{Real,Nothing}=m.optsum.sigma, thin::Int=typemax(Int), ) where {T} optsum = m.optsum @@ -461,6 +461,7 @@ function StatsAPI.fit!( end opt = Opt(optsum) optsum.REML = REML + optsum.sigma = σ prog = ProgressUnknown(; desc="Minimizing", showspeed=true) # start from zero for the initial call to obj before optimization iter = 0 @@ -511,13 +512,13 @@ function StatsAPI.fit!( xmin_ = copy(xmin) lb = optsum.lowerbd for i in eachindex(xmin_) - if iszero(lb[i]) && zero(T) < xmin_[i] < T(0.001) + if iszero(lb[i]) && zero(T) < xmin_[i] < optsum.xtol_zero_abs xmin_[i] = zero(T) end end loglength = length(fitlog) if xmin ≠ xmin_ - if (zeroobj = obj(xmin_, T[])) ≤ (fmin + 1.e-5) + if (zeroobj = obj(xmin_, T[])) ≤ (fmin + optsum.ftol_zero_abs) fmin = zeroobj copyto!(xmin, xmin_) elseif length(fitlog) > loglength @@ -895,7 +896,7 @@ end The penalized, weighted residual sum-of-squares. """ -pwrss(m::LinearMixedModel) = abs2(last(last(m.L))) +pwrss(m::LinearMixedModel{T}) where {T} = abs2(last(last(m.L)::Matrix{T})) """ ranef!(v::Vector{Matrix{T}}, m::MixedModel{T}, β, uscale::Bool) where {T} diff --git a/src/mixedmodel.jl b/src/mixedmodel.jl index f57b4c669..3b145f959 100644 --- a/src/mixedmodel.jl +++ b/src/mixedmodel.jl @@ -80,7 +80,7 @@ function StatsAPI.fit( tbl, d::Type, args...; - kwargs... + kwargs..., ) throw(ArgumentError("Expected a Distribution instance (`$d()`), got a type (`$d`).")) end @@ -91,7 +91,7 @@ function StatsAPI.fit( tbl, d::Distribution, l::Type; - kwargs... + kwargs..., ) throw(ArgumentError("Expected a Link instance (`$l()`), got a type (`$l`).")) end diff --git a/src/optsummary.jl b/src/optsummary.jl index e79fb4557..2ee69144c 100644 --- a/src/optsummary.jl +++ b/src/optsummary.jl @@ -19,75 +19,55 @@ Summary of an `NLopt` optimization * `feval`: the number of function evaluations * `optimizer`: the name of the optimizer used, as a `Symbol` * `returnvalue`: the return value, as a `Symbol` +* `xtol_zero_abs`: the tolerance for a near zero parameter to be considered practically zero +* `ftol_zero_abs`: the tolerance for change in the objective for setting a near zero parameter to zero +* `fitlog`: A vector of tuples of parameter and objectives values from steps in the optimization * `nAGQ`: number of adaptive Gauss-Hermite quadrature points in deviance evaluation for GLMMs * `REML`: use the REML criterion for LMM fits * `sigma`: a priori value for the residual standard deviation for LMM -* `fitlog`: A vector of tuples of parameter and objectives values from steps in the optimization -The latter four fields are MixedModels functionality and not related directly to the `NLopt` package or algorithms. +The last three fields are MixedModels functionality and not related directly to the `NLopt` package or algorithms. !!! note The internal storage of the parameter values within `fitlog` may change in the future to use a different subtype of `AbstractVector` (e.g., `StaticArrays.SVector`) for each snapshot without being considered a breaking change. """ -mutable struct OptSummary{T<:AbstractFloat} +Base.@kwdef mutable struct OptSummary{T<:AbstractFloat} initial::Vector{T} lowerbd::Vector{T} - finitial::T - ftol_rel::T - ftol_abs::T - xtol_rel::T - xtol_abs::Vector{T} - initial_step::Vector{T} - maxfeval::Int - maxtime::T - feval::Int - final::Vector{T} - fmin::T - optimizer::Symbol - returnvalue::Symbol - nAGQ::Integer # don't really belong here but I needed a place to store them - REML::Bool - sigma::Union{T,Nothing} - fitlog::Vector{Tuple{Vector{T},T}} # not SVector because we would need to parameterize on size (which breaks GLMM) + # the @kwdef macro isn't quite smart enough for us to use the type parameter + # for the default values, but we can fake it + finitial::T = Inf * one(eltype(initial)) + ftol_rel::T = eltype(initial)(1.0e-12) + ftol_abs::T = eltype(initial)(1.0e-8) + xtol_rel::T = zero(eltype(initial)) + xtol_abs::Vector{T} = zero(initial) .+ 1e-10 + initial_step::Vector{T} = empty(initial) + maxfeval::Int = -1 + maxtime::T = -one(eltype(initial)) + feval::Int = -1 + final::Vector{T} = copy(initial) + fmin::T = Inf * one(eltype(initial)) + optimizer::Symbol = :LN_BOBYQA + returnvalue::Symbol = :FAILURE + xtol_zero_abs::T = eltype(initial)(0.001) + ftol_zero_abs::T = eltype(initial)(1.e-5) + # not SVector because we would need to parameterize on size (which breaks GLMM) + fitlog::Vector{Tuple{Vector{T},T}} = [(initial, fmin)] + # don't really belong here but I needed a place to store them + nAGQ::Int = 1 + REML::Bool = false + sigma::Union{T,Nothing} = nothing end function OptSummary( initial::Vector{T}, - lowerbd::Vector{T}, - optimizer::Symbol; - ftol_rel::T=zero(T), - ftol_abs::T=zero(T), - xtol_rel::T=zero(T), - xtol_abs::Vector{T}=zero(initial) .+ 1e-10, - initial_step::Vector{T}=T[], - maxfeval=-1, - maxtime=T(-1), -) where {T<:AbstractFloat} - fitlog = [(initial, T(Inf))] - - return OptSummary( - initial, - lowerbd, - T(Inf), - ftol_rel, - ftol_abs, - xtol_rel, - xtol_abs, - initial_step, - maxfeval, - maxtime, - -1, - copy(initial), - T(Inf), - optimizer, - :FAILURE, - 1, - false, - nothing, - fitlog, - ) + lowerbd::Vector{S}, + optimizer::Symbol=:LN_BOBYQA; kwargs..., +) where {T<:AbstractFloat,S<:AbstractFloat} + TS = promote_type(T, S) + return OptSummary{TS}(; initial, lowerbd, optimizer, kwargs...) end """ diff --git a/src/predict.jl b/src/predict.jl index 5a2246ac6..1cf1c144f 100644 --- a/src/predict.jl +++ b/src/predict.jl @@ -115,7 +115,7 @@ function _predict(m::MixedModel{T}, newdata, β; new_re_levels) where {T} any(any(ismissing, Tables.getcolumn(newdata, col)) for col in respvars) throw( ArgumentError( - "Response column must be initialized to a non-missing numeric value.", + "Response column must be initialized to a non-missing numeric value." ), ) end diff --git a/src/profile/thetapr.jl b/src/profile/thetapr.jl index 62f9473b2..aeb6ebf15 100644 --- a/src/profile/thetapr.jl +++ b/src/profile/thetapr.jl @@ -10,7 +10,7 @@ function optsumj(os::OptSummary, j::Integer) return OptSummary( deleteat!(copy(os.final), j), deleteat!(copy(os.lowerbd), j), - os.optimizer + os.optimizer, ) end diff --git a/src/randomeffectsterm.jl b/src/randomeffectsterm.jl index 432f9af9a..95beee5e7 100644 --- a/src/randomeffectsterm.jl +++ b/src/randomeffectsterm.jl @@ -174,7 +174,7 @@ function StatsModels.apply_schema( if !(typeof(first) <: CategoricalTerm) throw( ArgumentError( - "nesting terms requires categorical grouping term, got $first. Manually specify $first as `CategoricalTerm` in hints/contrasts", + "nesting terms requires categorical grouping term, got $first. Manually specify $first as `CategoricalTerm` in hints/contrasts" ), ) end diff --git a/src/remat.jl b/src/remat.jl index 74d92ff88..ee40c53e6 100644 --- a/src/remat.jl +++ b/src/remat.jl @@ -586,7 +586,7 @@ end function copyscaleinflate!( Ljj::UniformBlockDiagonal{T}, Ajj::UniformBlockDiagonal{T}, - Λj::ReMat{T,S} + Λj::ReMat{T,S}, ) where {T,S} λ = Λj.λ dind = diagind(S, S) @@ -604,7 +604,7 @@ end function copyscaleinflate!( Ljj::Matrix{T}, Ajj::UniformBlockDiagonal{T}, - Λj::ReMat{T,S} + Λj::ReMat{T,S}, ) where {T,S} copyto!(Ljj, Ajj) n = LinearAlgebra.checksquare(Ljj) diff --git a/src/serialization.jl b/src/serialization.jl index 525a0e55c..52ec1dafe 100644 --- a/src/serialization.jl +++ b/src/serialization.jl @@ -10,11 +10,24 @@ function restoreoptsum!( ) where {T} dict = JSON3.read(io) ops = m.optsum - okay = - (setdiff(propertynames(ops), keys(dict)) == [:lowerbd]) && - all(ops.lowerbd .≤ dict.initial) && - all(ops.lowerbd .≤ dict.final) - if !okay + allowed_missing = ( + :lowerbd, # never saved, -Inf not allowed in JSON + :xtol_zero_abs, # added in v4.25.0 + :ftol_zero_abs, # added in v4.25.0 + :sigma, # added in v4.1.0 + :fitlog, # added in v4.1.0 + ) + nmdiff = setdiff( + propertynames(ops), # names in freshly created optsum + union!(Set(keys(dict)), allowed_missing), # names in saved optsum plus those we allow to be missing + ) + if !isempty(nmdiff) + throw(ArgumentError(string("optsum names: ", nmdiff, " not found in io"))) + end + if length(setdiff(allowed_missing, keys(dict))) > 1 # 1 because :lowerbd + @warn "optsum was saved with an older version of MixedModels.jl: consider resaving." + end + if any(ops.lowerbd .> dict.initial) || any(ops.lowerbd .> dict.final) throw(ArgumentError("initial or final parameters in io do not satisfy lowerbd")) end for fld in (:feval, :finitial, :fmin, :ftol_rel, :ftol_abs, :maxfeval, :nAGQ, :REML) @@ -33,12 +46,16 @@ function restoreoptsum!( end ops.optimizer = Symbol(dict.optimizer) ops.returnvalue = Symbol(dict.returnvalue) - # provides compatibility with fits saved before the introduction of fixed sigma + # compatibility with fits saved before the introduction of various extensions + for prop in [:xtol_zero_abs, :ftol_zero_abs] + fallback = getproperty(ops, prop) + setproperty!(ops, prop, get(dict, prop, fallback)) + end ops.sigma = get(dict, :sigma, nothing) fitlog = get(dict, :fitlog, nothing) ops.fitlog = if isnothing(fitlog) # compat with fits saved before fitlog - [(ops.initial, ops.finitial, ops.final, ops.fmin)] + [(ops.initial, ops.finitial), (ops.final, ops.fmin)] else [(convert(Vector{T}, first(entry)), T(last(entry))) for entry in fitlog] end diff --git a/src/simulate.jl b/src/simulate.jl index 1b4159e1e..035a8c02c 100644 --- a/src/simulate.jl +++ b/src/simulate.jl @@ -18,13 +18,17 @@ function simulate(m::MixedModel, args...; kwargs...) end """ - simulate!(rng::AbstractRNG, m::MixedModel{T}; β=m.β, σ=m.σ, θ=T[]) - simulate!(m::MixedModel; β=m.β, σ=m.σ, θ=m.θ) + simulate!(rng::AbstractRNG, m::MixedModel{T}; β=fixef(m), σ=m.σ, θ=T[]) + simulate!(m::MixedModel; β=fixef(m), σ=m.σ, θ=m.θ) Overwrite the response (i.e. `m.trms[end]`) with a simulated response vector from model `m`. This simulation includes sampling new values for the random effects. +`β` can be specified either as a pivoted, full rank coefficient vector (cf. [`fixef`](@ref)) +or as an unpivoted full dimension coefficient vector (cf. [`coef`](@ref)), where the entries +corresponding to redundant columns will be ignored. + !!! note Note that `simulate!` methods with a `y::AbstractVector` as the first argument (besides the RNG) and `simulate` methods return the simulated response. This is @@ -32,7 +36,7 @@ This simulation includes sampling new values for the random effects. which modify the model's response and return the entire modified model. """ function simulate!( - rng::AbstractRNG, m::LinearMixedModel{T}; β=coef(m), σ=m.σ, θ=T[] + rng::AbstractRNG, m::LinearMixedModel{T}; β=fixef(m), σ=m.σ, θ=T[] ) where {T} # XXX should we add support for doing something with weights? simulate!(rng, m.y, m; β, σ, θ) @@ -40,7 +44,7 @@ function simulate!( end function simulate!( - rng::AbstractRNG, m::GeneralizedLinearMixedModel{T}; β=coef(m), σ=m.σ, θ=T[] + rng::AbstractRNG, m::GeneralizedLinearMixedModel{T}; β=fixef(m), σ=m.σ, θ=T[] ) where {T} # note that these m.resp.y and m.LMM.y will later be synchronized in (re)fit!() # but for now we use them as distinct scratch buffers to avoid allocations @@ -85,7 +89,7 @@ function _rand(rng::AbstractRNG, d::Distribution, location, scale=NaN, n=1) return rand(rng, dist) / n end -function simulate!(m::MixedModel{T}; β=coef(m), σ=m.σ, θ=T[]) where {T} +function simulate!(m::MixedModel{T}; β=fixef(m), σ=m.σ, θ=T[]) where {T} return simulate!(Random.GLOBAL_RNG, m; β, σ, θ) end @@ -121,7 +125,7 @@ function simulate!( y::AbstractVector, m::LinearMixedModel, newdata::Tables.ColumnTable; - β=m.β, + β=fixef(m), σ=m.σ, θ=m.θ, ) @@ -147,9 +151,9 @@ function simulate!( end function simulate!( - rng::AbstractRNG, y::AbstractVector, m::LinearMixedModel{T}; β=m.β, σ=m.σ, θ=m.θ + rng::AbstractRNG, y::AbstractVector, m::LinearMixedModel{T}; β=fixef(m), σ=m.σ, θ=m.θ ) where {T} - length(β) == length(pivot(m)) || length(β) == m.feterm.rank || + length(β) == length(pivot(m)) || length(β) == rank(m) || throw(ArgumentError("You must specify all (non-singular) βs")) β = convert(Vector{T}, β) @@ -157,10 +161,8 @@ function simulate!( θ = convert(Vector{T}, θ) isempty(θ) || setθ!(m, θ) - if length(β) ≠ length(pivot(m)) - padding = length(pivot(m)) - rank(m) - append!(β, fill(-0.0, padding)) - invpermute!(β, pivot(m)) + if length(β) == length(pivot(m)) + β = view(view(β, pivot(m)), 1:rank(m)) end # initialize y to standard normal @@ -172,7 +174,7 @@ function simulate!( end # scale by σ and add fixed-effects contribution - return mul!(y, m.X, β, one(T), σ) + return mul!(y, fullrankx(m), β, one(T), σ) end function simulate!( @@ -180,7 +182,7 @@ function simulate!( y::AbstractVector, m::GeneralizedLinearMixedModel, newdata::Tables.ColumnTable; - β=m.β, + β=fixef(m), σ=m.σ, θ=m.θ, ) @@ -209,7 +211,7 @@ function simulate!( rng::AbstractRNG, y::AbstractVector, m::GeneralizedLinearMixedModel{T}; - β=m.β, + β=fixef(m), σ=m.σ, θ=m.θ, ) where {T} @@ -236,7 +238,7 @@ function _simulate!( ismissing(σ) || throw( ArgumentError( - "You must not specify a dispersion parameter for model families without a dispersion parameter", + "You must not specify a dispersion parameter for model families without a dispersion parameter" ), ) @@ -250,7 +252,7 @@ function _simulate!( if length(β) == length(pivot(m)) # unlike LMM, GLMM stores the truncated, pivoted vector directly - β = view(β, view(pivot(m), 1:(rank(m)))) + β = view(view(β, pivot(m)), 1:rank(m)) end fast = (length(m.θ) == length(m.optsum.final)) setpar! = fast ? setθ! : setβθ! diff --git a/src/utilities.jl b/src/utilities.jl index 5394a1fe3..413413b7c 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -136,7 +136,7 @@ function replicate( ) use_threads && Base.depwarn( "use_threads is deprecated and will be removed in a future release", - :replicate + :replicate, ) if !isnothing(hide_progress) Base.depwarn( diff --git a/test/bootstrap.jl b/test/bootstrap.jl index 576aad918..49c28441f 100644 --- a/test/bootstrap.jl +++ b/test/bootstrap.jl @@ -226,7 +226,7 @@ end rng = MersenneTwister(0); x = rand(rng, 100); data = (x = x, x2 = 1.5 .* x, y = rand(rng, [0,1], 100), z = repeat('A':'T', 5)) - @testset "$family" for family in [Normal(), Bernoulli()] + @testset "$family" for family in [Normal(), Bernoulli()] model = @suppress fit(MixedModel, @formula(y ~ x + x2 + (1|z)), data, family; progress=false) boot = quickboot(model, 10) @@ -242,6 +242,33 @@ end yf = simulate(StableRNG(1), model; β=fixef(model)) @test all(x -> isapprox(x...), zip(yc, yf)) end + + @testset "partial crossing" begin + id = lpad.(string.(1:40), 2, "0") + B = ["b0", "b1", "b2"] + C = ["c0", "c1", "c2", "c3", "c4"] + df = DataFrame(reshape(collect(Iterators.product(B, C, id)), :), [:b, :c, :id]) + df[!, :y] .= 0 + filter!(df) do row + b = last(row.b) + c = last(row.c) + return b != c + end + + m = LinearMixedModel(@formula(y ~ 1 + b * c + (1|id)), df) + β = 1:rank(m) + σ = 1 + simulate!(StableRNG(628), m; β, σ) + fit!(m) + + boot = parametricbootstrap(StableRNG(271828), 1000, m); + bootci = DataFrame(shortestcovint(boot)) + filter!(:group => ismissing, bootci) + select!(bootci, :names => disallowmissing => :coef, :lower, :upper) + transform!(bootci, [:lower, :upper] => ByRow(middle) => :mean) + + @test all(x -> isapprox(x[1], x[2]; atol=0.1), zip(coef(m), bootci.mean)) + end end end diff --git a/test/pls.jl b/test/pls.jl index 6dd8e9583..c0a540278 100644 --- a/test/pls.jl +++ b/test/pls.jl @@ -543,10 +543,110 @@ end @test loglikelihood(fm) ≈ loglikelihood(m) @test bic(fm) ≈ bic(m) @test coef(fm) ≈ coef(m) + + # check restoreoptsum from older versions + m = LinearMixedModel( + @formula(reaction ~ 1 + days + (1 + days | subj)), + MixedModels.dataset(:sleepstudy), + ) + iob = IOBuffer( +""" +{ + "initial":[1.0,0.0,1.0], + "finitial":1784.642296192436, + "ftol_rel":1.0e-12, + "ftol_abs":1.0e-8, + "xtol_rel":0.0, + "xtol_abs":[1.0e-10,1.0e-10,1.0e-10], + "initial_step":[0.75,1.0,0.75], + "maxfeval":-1, + "maxtime":-1.0, + "feval":57, + "final":[0.9292213195402981,0.01816837807519162,0.22264487477788353], + "fmin":1751.9393444646712, + "optimizer":"LN_BOBYQA", + "returnvalue":"FTOL_REACHED", + "nAGQ":1, + "REML":false +} +""" + ) + @test_logs((:warn, + r"optsum was saved with an older version of MixedModels.jl: consider resaving"), + restoreoptsum!(m, seekstart(iob))) + @test loglikelihood(fm) ≈ loglikelihood(m) + @test bic(fm) ≈ bic(m) + @test coef(fm) ≈ coef(m) + iob = IOBuffer( +""" +{ + "initial":[1.0,0.0,1.0], + "finitial":1784.642296192436, + "ftol_rel":1.0e-12, + "xtol_rel":0.0, + "xtol_abs":[1.0e-10,1.0e-10,1.0e-10], + "initial_step":[0.75,1.0,0.75], + "maxfeval":-1, + "maxtime":-1.0, + "feval":57, + "final":[0.9292213195402981,0.01816837807519162,0.22264487477788353], + "fmin":1751.9393444646712, + "optimizer":"LN_BOBYQA", + "returnvalue":"FTOL_REACHED", + "nAGQ":1, + "REML":false, + "sigma":null, + "fitlog":[[[1.0,0.0,1.0],1784.642296192436]] +} +""" + ) + @test_throws(ArgumentError("optsum names: [:ftol_abs] not found in io"), + restoreoptsum!(m, seekstart(iob))) + + iob = IOBuffer( +""" +{ + "initial":[1.0,0.0,1.0], + "finitial":1784.642296192436, + "ftol_rel":1.0e-12, + "ftol_abs":1.0e-8, + "xtol_rel":0.0, + "xtol_abs":[1.0e-10,1.0e-10,1.0e-10], + "initial_step":[0.75,1.0,0.75], + "maxfeval":-1, + "maxtime":-1.0, + "feval":57, + "final":[-0.9292213195402981,0.01816837807519162,0.22264487477788353], + "fmin":1751.9393444646712, + "optimizer":"LN_BOBYQA", + "returnvalue":"FTOL_REACHED", + "nAGQ":1, + "REML":false, + "sigma":null, + "fitlog":[[[1.0,0.0,1.0],1784.642296192436]] +} +""" + ) + @test_throws(ArgumentError("initial or final parameters in io do not satisfy lowerbd"), + @suppress restoreoptsum!(m, seekstart(iob))) + + # make sure new fields are correctly restored + mktemp() do path, io + m = deepcopy(last(models(:sleepstudy))) + m.optsum.xtol_zero_abs = 0.5 + m.optsum.ftol_zero_abs = 0.5 + saveoptsum(io, m) + m.optsum.xtol_zero_abs = 1.0 + m.optsum.ftol_zero_abs = 1.0 + @suppress restoreoptsum!(m, seekstart(io)) + @test m.optsum.xtol_zero_abs == 0.5 + @test m.optsum.ftol_zero_abs == 0.5 + end + end @testset "profile" begin - pr = profile(last(models(:sleepstudy))) + pr = @suppress profile(last(models(:sleepstudy))) tbl = pr.tbl @test length(tbl) >= 122 ci = confint(pr) diff --git a/test/predict.jl b/test/predict.jl index a55272bbc..8307bd5c7 100644 --- a/test/predict.jl +++ b/test/predict.jl @@ -143,7 +143,7 @@ end slp1 = subset(slp, :days => ByRow(>(0))) # this model probably doesn't make much sense, but it has two # variables on the left hand side in a FunctionTerm - m = fit(MixedModel, @formula(reaction / days ~ 1 + (1|subj)), slp1) + m = @suppress fit(MixedModel, @formula(reaction / days ~ 1 + (1|subj)), slp1) # make sure that we're getting the transformation @test response(m) ≈ slp1.reaction ./ slp1.days @test_throws ArgumentError predict(m, slp[:, Not(:reaction)]) @@ -154,7 +154,7 @@ end @test predict(m, slp1) ≈ fitted(m) - m = fit(MixedModel, @formula(log10(reaction) ~ 1 + days + (1|subj)), slp1) + m = @suppress fit(MixedModel, @formula(log10(reaction) ~ 1 + days + (1|subj)), slp1) # make sure that we're getting the transformation @test response(m) ≈ log10.(slp1.reaction) @test_throws ArgumentError predict(m, slp[:, Not(:reaction)])