Adding tidiers to broom

2020-04-20

Adding new tidiers to broom

Thank you for your interest in contributing to broom! This document is a work in progress describing the conventions that you should follow when adding tidiers to broom.

General guidelines:

If you are just getting into open source development, broom is an excellent place to get started and we are more than happy to help. We recommend you start contributing by improving the documentation, writing issues with reproducible errors, or taking on issues tagged beginner-friendly.

Which package does my tidier belong in?

Ideally, tidying methods should live in the packages of their associated modelling functions. That is, if you have some object my_object produced bymy_package, the functions tidy.my_object, glance.my_object and augment.my_object should live in my_package, provided there are sensible ways to define these tidiers for my_object.

We are currently working on an appropriate way to split tidiers into several domain specific tidying packages. For now, if you don’t own my_package, you should add the tidiers to broom. There are some exceptions:

We will keep you updated as we work towards a final solution.

Implementing new tiders

NOTE: okay to write tidyverse code to tidy and wrap it in a function. encouraged, in fact.

We encourage you to develop new tidiers using your favorite tidyverse tools. Pipes are welcome, as is any code that you might write for tidyverse-style interactive data manipulation.

If you are implementing a new tidier, we recommend taking a look at the internals of the tidying methods for betareg and rq objects and using those as a starting point.

You should also be aware of the following helper functions:

Documenting new tidiers

All new tidiers should be fully documented following the tidyverse code documentation guidelines. Documentation should use full sentences with appropriate punctation. Documentation should also contain at least one but potentially several examples of how the tidiers can be used.

Documentation should be written in R markdown as much as possible.

There’ll be a major overhaul of documentation later this summer, at which point this portion of the vignette will also get some major updates.

Testing new tidiers

Basic checks

Your tests should include:

The tests for augment are rapidly evolving at the moment, and we’ll follow up with more details on them soon.

If any of your tests use random number generation, you should call set.seed() in the body of the test.

In general, we prefer informative errors to magical behaviors or untested success.

More complete checks

Package dependencies

Catching edge cases

You should test new tidiers on a representative set of my_object objects. At a minimum, you should have a test for each distinct type of fit that appears in the examples for a particular model (if we working with stats::arima models, the tidiers should work for seasonal and non-seasonal models).

It’s important to test your tidiers for fits estimated with different algorithms (i.e. stats::arima tidier should be tested for method = "CSS-ML", method = "ML" and method = "ML"). As another example, good tests for glm tidying methods would test tidiers on glm objects fit for all acceptable values of family.

In short: be sure that you’ve tested your tidiers on models fit with all the major modelling options (both statistical options, and estimation options).

Before submitting your pull request

broom doesn’t currently pass all of these. If you are adding new tidiers at the moment, it’s enough for these to throw no warnings for the files you’ve changed.

Defining tidiers

The big picture:

Oftentimes it doesn’t make sense to define one or more of these methods for a particular model. In this case, just implement the methods that do make sense.

glance

The glance(x, ...) method accepts a model object x and returns a tibble with exactly one row containing model level summary information.

augment

The augment(x, data = NULL, ...) method accepts a model object and optionally a data frame data and adds columns of observation level information to data. augment returns a tibble with the same number of rows as data.

The data argument can be any of the following:

Any other inputs should result in an error. This will eventually be checked by the validate_augment_input() function.

Many augment methods will also provide an optional newdata argument that should also default to NULL. Users should only ever specify one of data or newdata. Providing both data and newdata should result in an error. newdata should accept both data.frames and tibbles and should be tested with both.

Data given to the data argument must have both the original predictors and the original response. Data given to the newdata argument only needs to have the original predictors. This is important because there may be important information associated with training data that is not associated with test data, for example, leverages (.hat below) in the case in linear regression:

This means that many augment(model, data = original_data) should provide .fitted and .resid columns in most cases, whereas augment(model, data = test_data) only needs to a .fitted column, even if the response is present in test_data.

If the data or newdata is specified as a data.frame with rownames, augment should return them in a column called .rownames.

For observations where no fitted values or summaries are available (where there’s missing data, for example) return NA.

Added column names should begin with . to avoid overwriting columns in the original data.

tidy

The tidy(x, ...) method accepts a model object x and returns a tibble with one row per model component. A model component might be a single term in a regression, a single test, or one cluster/class. Exactly what a component is varies across models but is usually self-evident.

Sometimes a model will have different types of components. For example, in mixed models, there is different information associated with fixed effects and random effects, since this information doesn’t have the same interpretation, it doesn’t make sense to summarize the fixed and random effects in the same table. In cases like this you should add an argument that allows the user to specify which type of information they want. For example, you might implement an interface along the lines of:

Common arguments to tidy methods: