Skip to content

Conversation

franceslinyc
Copy link

@franceslinyc franceslinyc commented Aug 12, 2025

This PR adds a new post titled "Create your own score class object" for filtro in developer tools.

  • Finalize
  • Have Hannah review once finalizing
  • Revise based on Hannah's feedback
  • Ready to merge

@franceslinyc
Copy link
Author

Error:
! Can't find method for `fit(<class_score_aov>)`.
Hide Traceback
    ▆
 1. ├─filtro::fit(score_aov_pval, Sale_Price ~ ., data = ames_subset)
 2. │ └─S7::S7_dispatch()
 3. └─S7:::method_lookup_error("fit", `<named list>`)

I think this is related to PR #162 in filtro

@franceslinyc franceslinyc marked this pull request as ready for review August 18, 2025 23:06
@franceslinyc
Copy link
Author

Do users need to install S7?

@franceslinyc
Copy link
Author

@topepo @EmilHvitfeldt Just want to know if there is anything that is missing from the page.

Still trying to see why fit() throws error here. It won't let me Preview nor Render so I commented it out currently.

@franceslinyc
Copy link
Author

Error in `UseMethod()`:
! no applicable method for 'fit' applied to an object of class "c('class_score_aov', 'filtro::class_score', 'S7_object')"
Hide Traceback
    ▆
 1. └─generics::fit(score_aov_pval, Sale_Price ~ ., data = ames_subset)

@EmilHvitfeldt
Copy link
Member

Do users need to install S7?

Nope, it should be installed as they install {filtro}, as it is in Imports

@franceslinyc franceslinyc requested a review from hfrick August 20, 2025 16:51
Copy link
Member

@hfrick hfrick left a comment

Choose a reason for hiding this comment

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

Looks like you made a very cool package with filtro! I would add a little bit more context in the article.

You don't have to write a novel about what filtro does (that's what the filtro website is for) but just a few sentences to situate a reader who's possibly coming from the developer category on tidymodels.org and not directly from the filtro page.

I'd also add some broad strokes about how filtro works as context for the concrete steps of what to actually do when implementing a new scoring method. And then always show concrete examples, like for the docs.


`r article_req_pkgs(pkgs)`

You can construct new scoring objects using `class_score()`. This article is a guide to creating new scoring objects.
Copy link
Member

Choose a reason for hiding this comment

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

Can you give readers some context for scoring objects, e.g., via a brief intro to filtro? See the other articles in the "developer tools" category for examples: https://www.tidymodels.org/learn/#category=developer%20tools


## Scoring object

All subclasses specific to the scoring method have a parent class named `class_score`. There are a few properties (attributes) for this object:
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a little bit more context on the package structure?

args(class_score)
```

- `outcome_type`: What types of outcome can the method handle? The options are `numeric`, `factor`, or both.
Copy link
Member

Choose a reason for hiding this comment

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

This is a pretty long list. Does the reader need to know about all of them, at this point? Or could you highlight some and point them to the docs for the rest?


## Scoring object specific to the scoring method

We demonstrate how to create a custom scoring object specific to the given scoring method.
Copy link
Member

Choose a reason for hiding this comment

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

Does this mean that a scoring object is the implementation of a scoring method? The use of "specific to" is throwing me a little here.


We demonstrate how to create a custom scoring object specific to the given scoring method.

As an example, let’s consider the ANOVA F-test filter.
Copy link
Member

Choose a reason for hiding this comment

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

A sentence on the stats aspect of this would be a nice bonus.

)
```

## Fitting (or estimating) score
Copy link
Member

Choose a reason for hiding this comment

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

Does this use the fit method for the parent class? I was expecting to see some class-specific method implementations in this section.


Documentation for S7 methods is still a work in progress, and it seems no one currently knows the right approach. Here’s how we tackle it:

We re-export the `fit()` generic from another package. Instead of documenting each individual `fit()` method, we provide the details in the "Details" section and the "Estimating the scores" subsection of the documentation for the corresponding object.
Copy link
Member

Choose a reason for hiding this comment

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

I would show examples of that. The call to the help pages below shows users how to access it and that's helpful context but it less focused on "how to implement your own scoring class".

@franceslinyc
Copy link
Author

All super important points, thanks @hfrick! I'll revise according.

Copy link
Member

@hfrick hfrick left a comment

Choose a reason for hiding this comment

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

Thank you for the added context! I have a much better orientation in this now. 🐸

My understanding is that the core things to know about adding your own scoring method are:

There is a base class for scoring objects. You add a scoring method by

  1. Creating a subclass of that base class
  2. Writing a fit() method for the subclass

And for the docs, you get guidance on how to document an S7 method.

Assuming I got that correct, I think the article could make that just a little clearer and then we're there!


filtro is tidy tools to apply filter-based supervised feature selection methods. It provides functions to rank and select a specified proportion or a fixed number of features using built-in methods and the desirability function.

Currently, there are 6 filters in filtro and many existing score objects. A list of existing scoring objects [can be found here](https://filtro.tidymodels.org/articles/filtro.html#available-score-objects-and-filter-methods). However, you might need to define your own scoring objects. This article serves as a guide to creating new scoring objects and computing feature scores before performing ranking and selection.
Copy link
Member

Choose a reason for hiding this comment

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

What's the purpose of a scoring object? So far, the text only mentions filters.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, some explanation coming at the end of the paragraph. Could we reshuffle that a little?

Copy link
Author

Choose a reason for hiding this comment

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

Just saw these now, adding (revising)! Thanks!


The general procedure is to:

1. Create a parent scoring object `class_score`, specifying fixed properties that are shared across all custom scoring objects.
Copy link
Member

Choose a reason for hiding this comment

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

This class is already implemented in filtro, right? If so, we should make it clearer that this is the infrastructure we build on when we make our own score class object.


1. Create a parent scoring object `class_score`, specifying fixed properties that are shared across all custom scoring objects.

2. Construct a custom scoring object `class_score_*`, adding additional, implementation-specific properties.
Copy link
Member

Choose a reason for hiding this comment

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

I'd rephrase this to say that we are making a subclass, inheriting from class_score. The info that this is the place to specify aspects specific to this score (as opposed to the general characteristics already covered by the parent class) is proabaly helpful for orientating readers less familar with object-oriented programming.


2. Construct a custom scoring object `class_score_*`, adding additional, implementation-specific properties.

3. Define the scoring method in `fit()`, which computes feature score. `fit()` refers to the custom scoring object from step 2 to use the appropriate method.
Copy link
Member

Choose a reason for hiding this comment

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

If you think it's helpful, you could clarify that defining the subclass corresponds to defining characteristics of your score and writing the fit() method corresponds to writing down how to calculate it.


- `results`: A slot for the results once the method is fitted. Initially, this is an empty data frame.

For details on its constructor and its remaining properties, please refer to the package documentation.
Copy link
Member

Choose a reason for hiding this comment

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

It'b be nice to have a link here directly to the relevant docs.

case_weights = NULL,
...
) {
# TODO Finish the rest of the function
Copy link
Member

Choose a reason for hiding this comment

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

Is this meant as a TODO for you now or do you plan to leave it in for the reader?


## Documenting S7 methods

Documentation for S7 methods is still a work in progress, and it seems no one currently knows the right approach. Here’s how we tackle it:
Copy link
Member

Choose a reason for hiding this comment

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

Maybe something along the lines of "Documentation for S7 methods is still a work in progress, but our current best approach is as follows"?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, I quoted Max too directly. 😅

Comment on lines 272 to 288
```{r}
#| eval: false
#' @export
S7::method(fit, class_score_aov) <- function(
object,
formula,
data,
case_weights = NULL,
...
) {
# TODO Finish the rest of the function using lm() and anova()

object@results <- res
object
}
```

Copy link
Member

Choose a reason for hiding this comment

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

I know I asked for examples previously but I'm leaning towards removing this one here because it's just one line that's relevant here. I think we can capture that in the text above. What do you think?

Copy link
Author

Choose a reason for hiding this comment

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

I truncated it down further, but I think it may still good to include the skeleton code here? Point of view from someone who is still navigating roxygen.

}
```

Instead, documentation is provided in the "Details" section and the "Estimating the scores" subsection of the documentation for the `score_aov_pval` object.
Copy link
Member

Choose a reason for hiding this comment

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

The example for this one I find super helpful!

```{r}
#| label: "si"
#| echo: false
small_session(pkgs)
Copy link
Member

Choose a reason for hiding this comment

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

For the final rendering, could you update filtro to the latest CRAN version? We have a convention of using only CRAN versions for these articles :)

@franceslinyc
Copy link
Author

Still working!

@franceslinyc
Copy link
Author

Thanks so much for the feedback @hfrick, I hope I didn't miss anything. @topepo or @EmilHvitfeldt, would you like to take a final look?

@franceslinyc franceslinyc requested a review from topepo August 29, 2025 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants