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
plot_roc_curves(scores, y_true, sensitive_features) #758
Comments
I agree this is pretty useful, and for whoever would like to take it up, here's what I've used in my scripts. Feel free to take and generalize it. import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve, auc
from sklearn.metrics import RocCurveDisplay
def plot_auc(df, name, ax=None):
y_pred = np.asarray(df.y_pred).astype(float)
y_true = np.asarray(df.FraudLabel == "true").astype(int)
fpr, tpr, _ = roc_curve(y_true, y_pred, pos_label=1)
roc_auc = auc(fpr, tpr)
display = RocCurveDisplay(fpr=fpr, tpr=tpr, roc_auc=roc_auc, estimator_name=name)
display.plot(ax=ax)
return display
plt.figure(figsize=(8, 6))
ax = plt.gca()
overall = plot_auc(df, "overall", ax)
women = plot_auc(df_women, "women", ax)
men = plot_auc(df_men, "men", ax)
nans = plot_auc(df_nan, "unspecified", ax) And the corresponding one for precision recall curves: import numpy as np
from sklearn.metrics import (precision_recall_curve,
PrecisionRecallDisplay,
average_precision_score)
def plot_precision_recall(df, name, ax=None):
y_pred = np.asarray(df.y_pred).astype(float)
y_true = np.asarray(df.FraudLabel == "true").astype(int)
precision, recall, _ = precision_recall_curve(y_true, y_pred)
average_precision = average_precision_score(y_true, y_pred)
disp = PrecisionRecallDisplay(
precision=precision, recall=recall,
average_precision=average_precision,
estimator_name=name
)
disp.plot(ax=ax)
return display
plt.figure(figsize=(8, 6))
ax = plt.gca()
overall = plot_precision_recall(df, "overall", ax=ax)
women = plot_precision_recall(df_women, "women", ax)
men = plot_precision_recall(df_men, "men", ax)
nans = plot_precision_recall(df_nan, "unspecified", ax) |
Yes, we should definitely include this! Perhaps adding a separate |
Thanks! There is just one piece I am missing Given I have a dataframe (df) with labeled sensitive features, I want to create dataframes that are a subset of this, using sensitive features. Something like: create_sensitive_dfs(df, sensitive_features) Example: sensitive_features = [age, race] This would then allow me to use your script above |
Resolved: Combining all of the above @kstohr here's my workaround solution for cartesian product to create the smaller data frames - though how it would be embedded in the MetricFrame or where this same product happens is something I could not find. Where sensitive features is a list of the sensitive feature columns (defined elsewhere)
|
Hi all! Here is some code that splits pdfs based on a list of columns (the columns must only have boolean values) Example use: Example in colab notebook:
|
Reopening since we need a nice method in fairlearn to do this. |
I agree that this is a super useful addition. I also like @hildeweerts suggestion to consider extending both sklearn's roc_curve and plot_roc_curve. Until we find somebody willing to implement this, can we tease out the API we would like to see? We would use the same pattern for other curves that we discussed and come up in fairness literature like For plotting, my preference is to have API of the following form:
For just getting
|
I agree, let's get the conversation started (which may be relevant to the other plotting functionality as well? @romanlutz )
Is there a specific reason you want to put |
Re. compatibility with sklearn, I noticed that they use Re.
|
@romanlutz Ok, I'll pick this up. Not sure how long it will take me, but will dive in and work through it. |
Sounds great! Let us know if you have questions! We're happy to help! I'll also keep checking Discord during the sprint, of course. |
Quick note: There are contexts in which no negative samples in the ground truth ("No negative samples in y_true"), in which case an ROC curve can not be created |
@michaelamoako ok, so you want me to detect those cases and handle them with a try/except? Any other cases that would arise that we should handle? |
@romanlutz @michaelamoako New to this repo.. Couple quick q's before I build out this class/function.
|
@kstohr The ROC curves would be more general than the The postprocessing method has a lot of custom code that we can switch out at a later point. For now, I would completely ignore it since it has somewhat more comprehensive requirements. Same for the plotting capabilities of that module, they're very custom to the mitigation technique used there. Let's ignore it for now. You're right about the dashboard moving away (in fact #766 deletes it), so this should just be its own standlone utility. We're all in favor of reusing things that already exist. Perhaps some of the code snippets from this thread are useful to have ROC plots with multiple lines (one per group)? I think matplotlib supports this already by just passing in the same axes to subsequent plotting commands. |
@romanlutz Ok great. So, I'll build a module under metrics: Yep. Saw the code snippets and incorporating as needed. In terms of plotting, you can use plt.subplots() to add traces to the figure, but sklearn already has a class RocCurveDisplay and it looks like you can pass an existing ax to the function which should enable us to add traces to the figure for the each of the sensitive values. |
Yes! I was hoping we could reuse what |
Another very closely related but also valuable curve: Threshold vs. [Metric]. For example, to plot Threshold vs. [Recall]: Assume y_true/y_pred defined elsewhere
the ROC curve function similarly also returns threshold, so there is potential to plot that on the X-axis there too |
@michaelamoako Yep. Will keep precision_recall_curve in mind. Assuming you'd also do that for each sensitive value (i.e. by group.) |
@kstohr feel free to stick with one thing per PR. Smaller PRs are easier to review and typically get merged significantly faster 😄 @michaelamoako 's suggestion can be another PR unless it's just a few extra lines. Keep in mind that adding ROC curves should include at least a small unit test and a short user guide section, so this is already quite a bit. |
Agreed with @romanlutz - and yes (per group) |
@romanlutz @michaelamoako Roger that. I think Michael was just pointing out that the code should ideally be easy to adapt for this other purpose. At least that's how I undrestood it. |
…766) With a slight delay (originally targeted for April) I'm finally removing the `FairlearnDashboard` since a newer version already exists in `raiwidgets`. The documentation is updated to instead use the plots @MiroDudik created with a single line directly from the `MetricFrame`. In the future we want to add more kinds of plots as already mentioned in #758 #666 and #668 . Specifically, the model comparison plots do not yet have a replacement yet. Note that the "example" added to the `examples` directory is not shown under "Example notebooks" on the webpage, which is intentional since it's technically not a notebook. This also makes #561 mostly redundant, which I'll close shortly. #667 is also directly addressed with this PR as the examples illustrate. Signed-off-by: Roman Lutz <rolutz@microsoft.com>
@michaelamoako @romanlutz Yokee, the ref code snippets are up and running. Quick and dirty plot: I am thinking of testing out using the existing MetricFrame to do the data splitting. The benefit of using MetricFrame is that this would be a standard way of handling splitting the data on sensitive values. You get the 'overall' curve as well as the curves for the sensitive values, and any methods we add to the MetricFrame class would be inherited by the ROC Curve class. The downside is that the |
I don't think that a dependency on |
Hoping just to import it. I'll dig into it. However, I need to pass y_test,
y_pred, y_pred_proba. In addition, we need not just the sensitive values
for multiple features but the cartesian product of the sensitive value
features. So, it may not work.
…On Fri, May 21, 2021 at 9:30 AM Richard Edgar ***@***.***> wrote:
I don't think that a dependency on MetricFrame would be a bad thing. Or
do you mean to inherit from it?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#758 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AB6WM57VBVG4TTCA73RZC63TO2DBDANCNFSM43X6STVQ>
.
--
Kas Stohr
+1 646 554 7671
|
@kstohr I just tagged you in a comment above related to Cartesian product -though where this process is happening in the MetricFrame is not clear to me |
@kstohr : my suggestions:
def metric(y_true, y_pred):
return (y_true, y_pred)
mf = MetricFrame(metric, y_true, y_pred, sensitive_features=sensitive_features)
mf.by_group # this will contain the split of the data y_true and y_pred
# and consider all combinations if multiple sensitive features are provided |
@MiroDudik Yep that's exactly what I had in mind, but I have to pass a third parameter...looks like that's permitted in the docs and it will split on it and extract the groups. Not sure if it will handle the cartesian split. Still getting up to speed on this codebase. |
Cartesian split is already handled I believe, you just provide two columns of sensitive features and it'll consider all combinations. See here. MetricFrame also supports additional column parameters, e.g.: def metric(y_true, y_pred, y_pred_proba):
return (y_true, y_pred, y_pred_proba)
mf = MetricFrame(metric, y_true, y_pred, sensitive_features=sensitive_features,
sample_params = {'y_pred_proba': y_pred_proba})
mf.by_group # this will contain the split of the data y_true, y_pred, and y_pred_proba
# and consider all combinations if multiple sensitive features are provided |
This would be tied to the NaN issue in MetricFrame right @MiroDudik ? |
@michaelamoako : I think you're thinking about #800? We don't have to worry about that here, because we're not using mf.difference() or other aggregates (so the above code shouldn't raise any errors). |
@miro Well, there you go. `mf.by_group` should work then. Tks.
…On Fri, May 21, 2021 at 10:19 AM MiroDudik ***@***.***> wrote:
Cartesian split is already handled I believe, you just provide two columns
of sensitive features and it'll consider all combinations. See here
<https://fairlearn.org/v0.6.2/auto_examples/plot_new_metrics.html#intersections-of-features>
.
MetricFrame also supports additional column parameters, e.g.:
def metric(y_true, y_pred, y_pred_proba):
return (y_true, y_pred, y_pred_proba)mf = MetricFrame(metric, y_true, y_pred, sensitive_features=sensitive_features,
sample_params = {'y_pred_proba': y_pred_proba})mf.by_group # this will contain the split of the data y_true, y_pred, and y_pred_proba
# and consider all combinations if multiple sensitive features are provided
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#758 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AB6WM5ZSJGK7HT336WLYCH3TO2I2FANCNFSM43X6STVQ>
.
--
Kas Stohr
+1 646 554 7671
|
y'all this is coming along: I think the best course of action is to build on MetricFrame as it will create a common language across the Fairlearn API. So you can split by group and use the series index to plot a subset of the sensitive feature subgroups (remember is the cartesian product, so selecting a smaller set to plot will be important.) TODO:
|
Tagging @alexquach who has started work on #235 . Both of these are generically looking at 'plots for MetricFrame' so we should try to have a common API pattern (even if the details will be different) |
Totally agree. Was going to write a generic method to establish the plot
trace. @alexquach if you have something going, please share.
…On Thu, Jun 3, 2021 at 6:38 PM Richard Edgar ***@***.***> wrote:
Tagging @alexquach <https://github.com/alexquach> who has started work on
#235 <#235> . Both of these
are generically looking at 'plots for MetricFrame' so we should try to have
a common API pattern (even if the details will be different)
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#758 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AB6WM57TNKUGECFJWTRKWG3TRAVCHANCNFSM43X6STVQ>
.
|
@alexquach only just started this week, so there's still some playing around with 'how to plot this' before we get too far into the API design. I do like your approach of having a separate class, since there's no need to deal with We can meet to talk, if you think that would be helpful (and we can figure out timezones). |
What I was thinking was to build a standard plot that is “styled” the
Fairlearn way, whatever that is...and then call that and instantiate it if
the user does not pass their own fig, ax. I was going to do it as part of
the class.
If it’s in the class and then somebody looks at the code, It should be
clear to them how they can change the defaults to suit their needs by
either passing their own figure or updating parameters of the class
instance. And, for those not familiar with plotting or just needing a
convenience method, the defaults should meet their basic needs.
In the ROC case, we’re taking advantage of scikit to provide default axis
labels, etc. So even less to configure...
Thoughts...
…On Fri, Jun 4, 2021 at 9:05 AM Richard Edgar ***@***.***> wrote:
@alexquach <https://github.com/alexquach> only just started this week, so
there's still some playing around with 'how to plot this' before we get too
far into the API design. I do like your approach of having a separate
class, since there's no need to deal with MetricFrame internals. For the
error bar case, it's more likely to be 'bring the MetricFrame precomputed
and tell us the mapping of metrics to error bars' though.
We can meet to talk, if you think that would be helpful (and we can figure
out timezones).
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#758 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AB6WM5ZZ5YDHAVB73MOSB23TRD2TRANCNFSM43X6STVQ>
.
|
Styling shouldn't be done in this code I think. That's why people can provide their own Although, now I'm not sure you actually meant "styling" (as in https://matplotlib.org/stable/gallery/style_sheets/style_sheets_reference.html) or just axis labels, legend, etc.. Regardless, it shouldn't be preconfigured and people should be allowed to configure it however they like. I don't think that will affect the implementation in any way, other than the fact that we shouldn't set any options internally (e.g., figure size, shouldn't be set in our code). |
@romanlutz Here's how scikit learn handles it. What they have done is offered the full convenience of a labeled plot. However, those who want to provide their own fig, ax, or overwrite the defaults can. They use the default figure size, font, etc. Notice how they return the figure and the axis as 'self' which enables the user to configure the plot further if they wish. I am following the above approach. In fact, in the case of Roc Curves, I am actually using RocCurveDisplay. Because plotting is specific to the metric you are plotting, I think providing labeled plotting functions that add convenience to the relevant modules makes more sense than building a generic shared plotting utility. Basically adapting common metrics to return data by subgroup and the associated plots to handle plotting sensitive features by subgroup. |
Hi all. I pushed a draft PR for this last night. Take a look at the basic approach and give me feedback when you have a moment. https://github.com/fairlearn/fairlearn/pull/869/files It's not passin CI/CD checks yet, still needs.
|
Is your feature request related to a problem? Please describe.
In producing Fairness Assessment results, seeing metrics given a fixed threshold is useful. In addition to that, it helps to also see an ROC curve (TPR vs. FPR) across different thresholds.
Describe the solution you'd like
A function like "plot_roc_curves(scores, y_true, sensitive_features)" that takes in sensitive_features (list of sensitive features) as a parameter, and plots roc curves across subgroups
Describe alternatives you've considered, if relevant
sklearn plot_roc_curve, though this does not support plot via sensitive groups
Additional context
Creating a Fairness Assessment using Fairlearn, ROC curve plots are missing
The text was updated successfully, but these errors were encountered: