Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hierarchical predictors #623

Merged
merged 20 commits into from
Mar 15, 2024
Merged

Conversation

FBruzzesi
Copy link
Collaborator

@FBruzzesi FBruzzesi commented Feb 29, 2024

Description

  • Introduces HierarchicalPredictor, HierarchicalClassifier and HierarchicalRegressor - Partially fixes HierarchicalPredictor and HierarchicalTransformer #620
  • Introduces ShrinkageMixin to abstract such shared functionalities from HierarchicalPredictor and GroupedPredictor
  • Adds a new shrinkage built-in function equal_shrinkage

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist:

  • My code follows the style guidelines (flake8)
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (also to the readme.md)
  • I have added tests that prove my fix is effective or that my feature works
  • I have added tests to check whether the new feature adheres to the sklearn convention
  • New and existing unit tests pass locally with my changes

@@ -0,0 +1,33 @@
# Shrinkage
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Deserve their own page, at least in API

@@ -177,6 +177,50 @@ This transformer also has use-cases beyond fairness. You could use this transfor

For example, for predicting house prices, using the surface of a house relatively to houses in the same neighborhood could be a more relevant feature than the surface relative to all houses.

## Hierarchical Prediction

!!! info "New in version 0.8.0"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Again, totally made up version number. Need to be fixed accordingly

Copy link
Owner

Choose a reason for hiding this comment

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

I'm fine with 0.8.0. Just gotta get this reviewed.

Comment on lines +73 to +86
def equal_shrinkage(group_sizes) -> np.ndarray:
"""Each group is weighed equally.

Parameters
----------
group_sizes : array-like
The number of observations in each group, must implement the `__len__` method.

Returns
-------
np.ndarray
The weights for each group.
"""
return np.ones(len(group_sizes))
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not sure if we want this to exist, but it felt like an easy one to add

@FBruzzesi
Copy link
Collaborator Author

FBruzzesi commented Feb 29, 2024

Forgot to mention a couple of ideas:

  • estimators_ attribute is something like the following dictionary:

    {
        (1,): LogisticRegression(),
        (1, 'A'): LogisticRegression(),
        (1, 'B'): LogisticRegression(),
        (1, 'A', 'X'): LogisticRegression(),
        (1, 'B', 'Y'): LogisticRegression(),
    }
    

    which is ok-ish, but not super interpretable. One idea could be to have a namedtuple:

    from collections import namedtuple
    
    groups, grp_values = ["col1", "col2"], [1,2]
    estimator = namedtuple("estimator", groups)
    
    estimator(*grp_values)
    # estimator(col1=1, col2=2)

    This hits edge cases if a group has a "_" prefix (maybe we can trim that), but the end result would be:

     {
         estimator(sklego_global_estimator=1): LogisticRegression(),
         estimator(sklego_global_estimator=1, col1="A"): LogisticRegression(),
         estimator(sklego_global_estimator=1, col1="B"): LogisticRegression(),
         estimator(sklego_global_estimator=1, col1="A", col2="X"): LogisticRegression(),
         estimator(sklego_global_estimator=1, col1="B", col2="Y"): LogisticRegression(),
     }
    

    dataclasses would also work similarly.

  • Possibility of accessing an estimator from its dictionary directly, namely being able to do hierarchical_predictor[(1, "A", "X")] in place of hierarchical_predictor.estimators_[(1, "A", "X")]

  • Related to both the above, as adding the 1 for the global model is not very ergonomic, we can create shortcuts by aliasing externally what is in the first point as:

     {
         "__sklego_global_estimator__": LogisticRegression(),
         ('A', ): LogisticRegression(),
         ('B', ): LogisticRegression(),
         ('A', 'X'): LogisticRegression(),
         ('B', 'Y'): LogisticRegression(),
     }
    

    and all the associated "conversions" from the first and/or second point, if accepted

@koaning
Copy link
Owner

koaning commented Mar 2, 2024

At the moment I'd be fine with this:

{
    (1,): LogisticRegression(),
    (1, 'A'): LogisticRegression(),
    (1, 'B'): LogisticRegression(),
    (1, 'A', 'X'): LogisticRegression(),
    (1, 'B', 'Y'): LogisticRegression(),
}

But it would be preferable to have this documented in the code with comments. I agree that it's merely "ok", but it feels clear enough if the comment is there, no?

@FBruzzesi
Copy link
Collaborator Author

But it would be preferable to have this documented in the code with comments. I agree that it's merely "ok", but it feels clear enough if the comment is there, no?

Just added a few comments on those. Here is how it looks like:
image

@koaning
Copy link
Owner

koaning commented Mar 4, 2024

Ah yeah, that's even nicer. Just in the comments would've been sufficient but adding it in the docs for sure is a nice touch.

Copy link
Owner

@koaning koaning left a comment

Choose a reason for hiding this comment

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

My main observation is that we might want to add an extra test that uses a dummy model to help predict the values that we'd expect. Other than that; this looks great! Good work :)

@FBruzzesi
Copy link
Collaborator Author

My main observation is that we might want to add an extra test that uses a dummy model to help predict the values that we'd expect. Other than that; this looks great! Good work :)

I am thinking out loud here but...maybe the easiest way to check this is to use a deterministic/fake predictions model. Does that sound reasonable?

@koaning
Copy link
Owner

koaning commented Mar 9, 2024

Oh that was totally in line with what I had in mind. I've used Dummy models for this in the past but you're also free to pick another method. As long as we just have a test that our assumptions on how we shrink play out.

@FBruzzesi FBruzzesi requested a review from koaning March 13, 2024 16:43
Copy link
Owner

@koaning koaning left a comment

Choose a reason for hiding this comment

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

LGTM

@koaning koaning merged commit b7e9f77 into koaning:main Mar 15, 2024
14 checks passed
@FBruzzesi FBruzzesi deleted the feature/hierarchical branch April 10, 2024 08:42
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.

HierarchicalPredictor and HierarchicalTransformer
2 participants