Skip to content

Commit

Permalink
Initial Release (#3)
Browse files Browse the repository at this point in the history
* YASG is part of me now

* start work on tests

* tests of powers

* CI

* all the tests + nested nesting

* warn about limitations

* comment out example for now

* metadata + documenter

* 100% test coverage or bust

* indentation

* docs deps and compat

* formulae, plural

* more doc fixing

* remove travis

* finish removing travis

* add error checking for negative exponentiation

* bump compat to 1.6

* Update src/nesting.jl

Co-authored-by: Dave Kleinschmidt <dave.f.kleinschmidt@gmail.com>

Co-authored-by: Dave Kleinschmidt <dave.f.kleinschmidt@gmail.com>
  • Loading branch information
palday and kleinschmidt authored Oct 22, 2021
1 parent 4ab90fb commit f3e8daf
Show file tree
Hide file tree
Showing 19 changed files with 395 additions and 62 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: CI
on:
push:
branches:
- main
- master
paths-ignore:
- 'LICENSE.md'
- 'README.md'
pull_request:
branches:
- main
- master
paths-ignore:
- 'LICENSE.md'
- 'README.md'
jobs:
ci:
runs-on: ${{ matrix.os }}
strategy:
matrix:
julia-version: [1, 1.6]
julia-arch: [x64]
os: [ubuntu-18.04, macos-10.15, windows-2019]
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
with:
version: ${{ matrix.julia-version }}
- uses: julia-actions/julia-buildpkg@v0.1
- uses: julia-actions/julia-runtest@v0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: julia-actions/julia-uploadcodecov@v0.1
if: ${{ startsWith(matrix.os, 'Ubuntu') && startsWith(matrix.julia-version, '1.6') }}
16 changes: 16 additions & 0 deletions .github/workflows/CompatHelper.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: CompatHelper
on:
schedule:
- cron: 43 7 * * *
workflow_dispatch:
jobs:
CompatHelper:
runs-on: ubuntu-latest
steps:
- name: Pkg.add("CompatHelper")
run: julia -e 'using Pkg; Pkg.add("CompatHelper")'
- name: CompatHelper.main()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }}
run: julia -e 'using CompatHelper; CompatHelper.main()'
23 changes: 23 additions & 0 deletions .github/workflows/Documenter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Documenter
on:
push:
branches: [main, master]
tags: [v*]
pull_request:
branches:
- main
- master
jobs:
Documenter:
name: Documentation
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
with:
version: 1.6
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-docdeploy@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
15 changes: 15 additions & 0 deletions .github/workflows/TagBot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: TagBot
on:
issue_comment:
types:
- created
workflow_dispatch:
jobs:
TagBot:
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
runs-on: ubuntu-latest
steps:
- uses: JuliaRegistries/TagBot@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
ssh: ${{ secrets.DOCUMENTER_KEY }} # see https://juliadocs.github.io/Documenter.jl/stable/man/hosting/#GitHub-Actions
25 changes: 0 additions & 25 deletions .travis.yml

This file was deleted.

9 changes: 6 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
name = "RegressionFormulae"
uuid = "545c379f-4ec2-4339-9aea-38f2fb6a8ba2"
authors = ["Dave Kleinschmidt"]
authors = ["Dave Kleinschmidt", "Phillip Alday"]
version = "0.1.0"

[deps]
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
StatsModels = "3eaba693-59b7-5ba5-a881-562e759f1c8d"

[compat]
StatsBase = "0.33"
StatsModels = "0.6.7"
julia = "1"
julia = "1.6"

[extras]
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
StatsModels = "3eaba693-59b7-5ba5-a881-562e759f1c8d"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]
test = ["StatsBase", "StatsModels", "Test"]
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@

[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://kleinschmidt.github.io/RegressionFormulae.jl/stable)
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://kleinschmidt.github.io/RegressionFormulae.jl/dev)
[![Build Status](https://travis-ci.com/kleinschmidt/RegressionFormulae.jl.svg?branch=master)](https://travis-ci.com/kleinschmidt/RegressionFormulae.jl)
[![Codecov](https://codecov.io/gh/kleinschmidt/RegressionFormulae.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/kleinschmidt/RegressionFormulae.jl)

Extended [StatsModels.jl
`@formula`](https://www.github.com/JuliaStats/StatsModels.jl) syntax for
regression modeling.

Note that the functionality in this package is very new: please verify that the resulting schematized formulae and model coefficient (names) are what you were expecting, especially if you are combining multiple "advanced" formula features.

<!--
## Examples
```julia
using RegressionFormulae, StatsModels, GLM, DataFrames
```
-->

## Supported syntax ##

Expand All @@ -29,6 +32,8 @@ using RegressionFormulae, StatsModels, GLM, DataFrames
Generate all main effects and interactions up to the specified order. For
instance, `(a+b+c)^2` generates `a + b + c + a&b + a&c + b&c`, but not `a&b&c`.

**NB:** The presence of interaction terms within the base will result in redundant terms and is currently unsupported.

## Approach

Extended syntax is supported at two levels. First, RegressionFormulae.jl
Expand Down
4 changes: 4 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
RegressionFormulae = "545c379f-4ec2-4339-9aea-38f2fb6a8ba2"

[compat]
Documenter = "0.27"
15 changes: 7 additions & 8 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
using Documenter, RegressionFormula
using Documenter, RegressionFormulae

makedocs(;
modules=[RegressionFormula],
format=Documenter.HTML(),
pages=[
"Home" => "index.md",
],
repo="https://github.com/kleinschmidt/RegressionFormula.jl/blob/{commit}{path}#L{line}",
sitename="RegressionFormula.jl",
authors="Dave Kleinschmidt",
assets=String[],
repo="https://github.com/kleinschmidt/RegressionFormulae.jl/blob/{commit}{path}#L{line}",
sitename="RegressionFormulae.jl",
authors="Dave Kleinschmidt and Phillip Alday",
)

deploydocs(;
repo="github.com/kleinschmidt/RegressionFormula.jl",
repo="github.com/kleinschmidt/RegressionFormulae.jl",
devbranch = "main",
push_preview = true
)
4 changes: 2 additions & 2 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# RegressionFormula.jl
# RegressionFormulae.jl

```@index
```

```@autodocs
Modules = [RegressionFormula]
Modules = [RegressionFormulae]
```
2 changes: 1 addition & 1 deletion src/RegressionFormulae.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ using StatsModels
using Combinatorics
using Base.Iterators

import StatsModels: apply_schema
using StatsModels: apply_schema

const TermTuple = NTuple{N, AbstractTerm} where N
const Schemas = Union{StatsModels.Schema, StatsModels.FullRank}
Expand Down
3 changes: 0 additions & 3 deletions src/fulldummy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,3 @@ function fulldummy(t::CategoricalTerm)
)
t = CategoricalTerm(t.sym, new_contrasts)
end

fulldummy(x) =
throw(ArgumentError("fulldummy isn't supported outside of a MixedModel formula"))
41 changes: 27 additions & 14 deletions src/nesting.jl
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
# TODO: handle nested grouping. This parses as (a / b) / c instead of
# (/)(a,b,c) so need to do the reduction manually

_isfulldummy(x::AbstractTerm) = false
function _isfulldummy(x::CategoricalTerm)
return isa(x.contrasts, StatsModels.ContrastsMatrix{StatsModels.FullDummyCoding})
end

"""
group / term
Generate predictors for `term` within each level of `group`. Implemented as
`group + fulldummy(group) & term`.
"""
function Base.:(/)(args::AbstractTerm...)
groups = (&)(args[1:end-1]...)
term = last(args)
return groups + fulldummy(groups) & term
function Base.:(/)(outer::CategoricalTerm, inner::AbstractTerm)
return outer + fulldummy(outer) & inner
end

function Base.:(/)(outer::TermTuple, inner::AbstractTerm)
return outer[1:end-1] + last(outer) / inner
end

function Base.:(/)(outer::InteractionTerm, inner::AbstractTerm)
# we should only get here via expansion where the interaction term,
# but who knows what devious things users will try
all(_isfulldummy, outer.terms[1:end-1]) ||
throw(ArgumentError("Outer interactions in a nesting must consist only " *
" of categorical terms with FullDummyCoding, got $outer"))
return outer + outer & inner
end

function Base.:(/)(outer::AbstractTerm, inner::AbstractTerm)
throw(ArgumentError("nesting terms requires categorical grouping term, got $outer / $inner " *
"Manually specify $outer as `CategoricalTerm` in hints/contrasts"))
end

function StatsModels.apply_schema(
Expand All @@ -19,15 +38,9 @@ function StatsModels.apply_schema(
Mod::Type{<:RegressionModel},
)
length(t.args_parsed) == 2 ||
throw(ArgumentError("malformed nesting term: $t (Exactly two arguments required"))
throw(ArgumentError("malformed nesting term: $t (Exactly two arguments required)"))

args = apply_schema.(t.args_parsed, Ref(sch), Mod)

map(args[1:end-1]) do arg
typeof(arg) <: CategoricalTerm ||
throw(ArgumentError("nesting terms requires categorical grouping term, got $arg. "*
"Manually specify $first as `CategoricalTerm` in hints/contrasts"))
end

return (/)(args...)
return first(args) / last(args)
end
15 changes: 12 additions & 3 deletions src/power.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@ combinations_upto(x, n) = Iterators.flatten(combinations(x, i) for i in 1:n)
(term1, term2, ...) ^ n
Generate all interactions of terms up to order ``n``.
!!! warning
If any term is an `InteractionTerm`, then nonsensical interactions may
arise, e.g. `a & a & b`.
"""
function Base.:(^)(args::TermTuple, deg::ConstantTerm)
function Base.:(^)(args::TermTuple, deg::ConstantTerm{<:Integer})
deg.n > 0 || throw(ArgumentError("power should be greater than zero (got $deg)"))
tuple(((&)(terms...) for terms in combinations_upto(args, deg.n))...)
end

function Base.:(^)(::TermTuple, deg::AbstractTerm)
throw(ArgumentError("power should be an integer constant (got $deg)"))
end

function StatsModels.apply_schema(
t::FunctionTerm{typeof(^)},
sch::StatsModels.FullRank,
Expand All @@ -17,7 +26,7 @@ function StatsModels.apply_schema(
length(t.args_parsed) == 2 ||
throw(ArgumentError("invalid term $t: should have exactly two arguments"))
first, second = t.args_parsed
second isa ConstantTerm ||
throw(ArgumentError("invalid term $t: power should be a number (got $second)"))
second isa ConstantTerm{<:Integer} ||
throw(ArgumentError("invalid term $t: power should be an integer (got $second)"))
apply_schema.(first^second, Ref(sch), ctx)
end
Loading

3 comments on commit f3e8daf

@palday
Copy link
Collaborator Author

@palday palday commented on f3e8daf Oct 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@palday
Copy link
Collaborator Author

@palday palday commented on f3e8daf Oct 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/47299

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.1.0 -m "<description of version>" f3e8daf00198431fea591c718a7a625ec38c3c2b
git push origin v0.1.0

Please sign in to comment.