Skip to content

Commit 75ac403

Browse files
committed
after pull
2 parents a4a4d73 + 95776f1 commit 75ac403

99 files changed

Lines changed: 196 additions & 24260 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,12 @@
11

2-
[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://pat-alt.github.io/CounterfactualExplanations.jl/stable) [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://pat-alt.github.io/CounterfactualExplanations.jl/dev) [![Build Status](https://github.com/pat-alt/CounterfactualExplanations.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/pat-alt/CounterfactualExplanations.jl/actions/workflows/CI.yml?query=branch%3Amain) [![Coverage](https://codecov.io/gh/pat-alt/CounterfactualExplanations.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/pat-alt/CounterfactualExplanations.jl)
2+
[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://pat-alt.github.io/CounterfactualExplanations.jl/stable) [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://pat-alt.github.io/CounterfactualExplanations.jl/dev) [![Build Status](https://github.com/pat-alt/CounterfactualExplanations.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/pat-alt/CounterfactualExplanations.jl/actions/workflows/CI.yml?query=branch%3Amain) [![Coverage](https://codecov.io/gh/pat-alt/CounterfactualExplanations.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/pat-alt/CounterfactualExplanations.jl) [![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle) [![ColPrac: Contributor’s Guide on Collaborative Practices for Community Packages](https://img.shields.io/badge/ColPrac-Contributor's%20Guide-blueviolet.png)](https://github.com/SciML/ColPrac) [![Twitter Badge](https://img.shields.io/twitter/url/https/twitter.com/paltmey.svg?style=social&label=Follow%20%40paltmey)](https://twitter.com/paltmey)
33

44
# AlgorithmicRecourseDynamics
55

6-
`AlgorithmicRecourseDynamics.jl` is a Julia package for modelling Algorithmic Recourse Dynamics.
6+
`AlgorithmicRecourseDynamics.jl` is a Julia package for modeling Algorithmic Recourse Dynamics.
77

8-
## Research Paper 📝
9-
10-
**Note** ⚠: You are browsing the (anonymised) [`#original-paper`](https://anonymous.4open.science/r/AlgorithmicRecourseDynamics/README.md) branch of `AlgorithmicRecourseDynamics.jl`. This branch is a static artifact corresponding to the state of the package at the time the paper was first published. It can be used to replicate the original findings of the paper. Only this branch is currently accessible as an anonymised git repository. The main repository is private and will will be open-sourced after the review process.
11-
12-
## At a Glance
13-
14-
The paper titles **Endogenous Macrodynamics in Algorithmic Recourse** is currently under review and not yet published. You can find a preprint along with other resources right here on this branch of the repository:
15-
16-
- [Paper](paper/paper.pdf)
17-
- [Notebooks](dev/notebooks/)
18-
- [Supplementary Appendix](build/dev/notebooks/appendix.html) generated from notebooks (download the HTML and view in browser)
19-
- [Artifacts]() (including data and experimental results; link currently exluded due to double-blind review process)
8+
## Related Research Paper 📝
209

2110
In this work we investigate what happens if Algorithmic Recourse is actually implemented by a large number of individuals. The chart below illustrates what we mean by Endogenous Macrodynamics in Algorithmic Recourse: (a) we have a simple linear classifier trained for binary classification where samples from the negative class (y=0) are marked in blue and samples of the positive class (y=1) are marked in orange; (b) the implementation of AR for a random subset of individuals leads to a noticable domain shift; (c) as the classifier is retrained we observe a corresponding model shift; (d) as this process is repeated, the decision boundary moves away from the target class.
2211

2312
![](paper/www/poc.png)
24-
25-
## Paper Abstract
26-
27-
Existing work on Counterfactual Explanations (CE) and Algorithmic Recourse (AR) has largely been limited to the static setting and focused on single individuals: given some estimated model, the goal is to find valid counterfactuals for an individual instance that fulfill various desiderata. The ability of such counterfactuals to handle dynamics like data and model drift remains a largely unexplored research challenge at this point. There has also been surprisingly little work on the related question of how the actual implementation of recourse by one individual may affect other individuals. Through this work we aim to close that gap by systematizing and extending existing knowledge. We first show that many of the existing methodologies can be collectively described by a generalized framework. We then argue that the existing framework fails to account for a hidden external cost of recourse, that only reveals itself when studying the endogenous dynamics of recourse at the group level. Through simulation experiments involving various state-of-the-art counterfactual generators and several benchmark datasets, we generate large numbers of counterfactuals and study the resulting domain and model shifts. We find that the induced shifts are substantial enough to likely impede the applicability of Algorithmic Recourse in situations that involve competition for scarce resources. Fortunately, we find various potential mitigation strategies that can be used in combination with existing approaches. Our simulation framework for studying recourse dynamics is fast and open-sourced.

README.qmd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ jupyter: julia-1.8
2525

2626
# AlgorithmicRecourseDynamics
2727

28+
{{< include docs/src/_intro.qmd >}}

dev/Project.toml

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,6 @@
11
[deps]
2-
AlgorithmicRecourseDynamics = "3d1ede72-abb8-4340-bf8e-2ae06849b5ec"
3-
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
4-
Cairo = "159f3aea-2a34-519c-b102-8c37f9878175"
5-
CounterfactualExplanations = "2f13d31b-18db-44c1-bc43-ebaf2cff0be0"
6-
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
7-
Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c"
8-
Gadfly = "c91e804a-d5a3-530f-b6f0-dfbca275c004"
9-
LaplaceRedux = "c52c1a26-f7c5-402b-80be-ba1e638ad478"
10-
LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433"
11-
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
12-
MLDataUtils = "cc2ba9b6-d476-5e6d-8eaf-a92d5412d41d"
13-
MLUtils = "f1d291b0-491e-4a28-83b9-f70985020b54"
14-
NNlib = "872c559c-99b0-510c-b3b7-b6c96a88d5cd"
15-
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
16-
PlotThemes = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a"
17-
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
18-
ProgressBars = "49802e3a-d2f1-5c88-81d8-b72133a6f568"
19-
RCall = "6f49c342-dc21-5d91-9882-a32aef131414"
2+
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
3+
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
4+
Luxor = "ae8d54c2-7ccd-5906-9d76-62fc9837b5bc"
205
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
21-
SliceMap = "82cb661a-3f19-5665-9e27-df437c7e54c8"
226
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
23-
ghr_jll = "07c12ed4-43bc-5495-8a2a-d5838ef8d533"

dev/artifacts/generate_artifacts.jl

Lines changed: 0 additions & 76 deletions
This file was deleted.

dev/logo/logo.jl

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
2+
using Pkg;
3+
Pkg.activate("dev");
4+
5+
using AlgorithmicRecourseDynamics
6+
using Colors
7+
using CounterfactualExplanations
8+
using Distributions
9+
using Flux
10+
using Luxor
11+
using StatsBase: sample
12+
using Random
13+
14+
const julia_colors = Dict(
15+
:blue => Luxor.julia_blue,
16+
:red => Luxor.julia_red,
17+
:green => Luxor.julia_green,
18+
:purple => Luxor.julia_purple,
19+
)
20+
21+
function get_data(N=1000, xmax=2)
22+
X, ys = make_blobs(
23+
N, 2;
24+
centers=2, as_table=false, center_box=(-xmax => xmax), cluster_std=0.1
25+
)
26+
ys .= ys .== 2
27+
X = X'
28+
xs = Flux.unstack(X, 2)
29+
data = zip(xs, ys)
30+
counterfactual_data = CounterfactualData(X, ys')
31+
return counterfactual_data, data
32+
end
33+
plot()
34+
scatter!(counterfactual_data)
35+
36+
function logo_picture(;
37+
ndots=3,
38+
frame_size=500,
39+
ms=frame_size // 10,
40+
mcolor=(:red, :green, :purple),
41+
margin=0.1,
42+
fun=f(x) = x * cos(x),
43+
xmax=2.5,
44+
noise=0.5,
45+
ged_data=get_data,
46+
ntrue=50,
47+
gt_color=julia_colors[:blue],
48+
gt_stroke_size=5,
49+
interval_color=julia_colors[:blue],
50+
interval_alpha=0.2,
51+
seed=2022
52+
)
53+
54+
# Setup
55+
n_mcolor = length(mcolor)
56+
mcolor = getindex.(Ref(julia_colors), mcolor)
57+
Random.seed!(seed)
58+
59+
# Data
60+
x, y = get_data(xmax=xmax, noise=noise, fun=fun)
61+
train, test = partition(eachindex(y), 0.4, 0.4, shuffle=true)
62+
xtrue = range(-xmax, xmax, ntrue)
63+
ytrue = fun.(xtrue)
64+
65+
# Conformal Prediction
66+
Model = @load LinearRegressor pkg = MLJLinearModels
67+
degree_polynomial = 5
68+
polynomial_features(x, degree::Int) = reduce(hcat, map(i -> x .^ i, 1:degree))
69+
pipe = (x -> MLJBase.table(polynomial_features(x, degree_polynomial))) |> Model()
70+
conf_model = conformal_model(pipe; coverage=0.95)
71+
mach = machine(conf_model, x, y)
72+
fit!(mach, rows=train)
73+
yhat = predict(mach, x[test])
74+
y_lb = [y[1] for y in yhat]
75+
y_ub = [y[2] for y in yhat]
76+
77+
# Logo
78+
idx = sample(test, ndots, replace=false)
79+
xplot, yplot = (x[idx], y[idx])
80+
_scale = (frame_size / (2 * maximum(x))) * (1 - margin)
81+
82+
# Ground truth:
83+
setline(gt_stroke_size)
84+
sethue(gt_color)
85+
true_points = [Point((_scale .* (x, y))...) for (x, y) in zip(xtrue, ytrue)]
86+
poly(true_points[1:(end-1)], action=:stroke)
87+
88+
# Data
89+
data_plot = zip(xplot, yplot)
90+
for i = 1:length(data_plot)
91+
_x, _y = _scale .* collect(data_plot)[i]
92+
color_idx = i % n_mcolor == 0 ? n_mcolor : i % n_mcolor
93+
sethue(mcolor[color_idx]...)
94+
circle(Point(_x, _y), ms, action=:fill)
95+
end
96+
97+
# Prediction interval:
98+
_order_lb = sortperm(x[test])
99+
_order_ub = reverse(_order_lb)
100+
lb = [
101+
Point((_scale .* (x, y))...) for (x, y) in zip(x[test][_order_lb], y_lb[_order_lb])
102+
]
103+
ub = [
104+
Point((_scale .* (x, y))...) for (x, y) in zip(x[test][_order_ub], y_ub[_order_ub])
105+
]
106+
setcolor(sethue(interval_color)..., interval_alpha)
107+
poly(vcat(lb, ub), action=:fill)
108+
109+
end
110+
111+
function draw_small_logo(filename="docs/src/assets/logo.svg"; width=500)
112+
frame_size = width
113+
Drawing(frame_size, frame_size, filename)
114+
origin()
115+
logo_picture(frame_size=frame_size)
116+
finish()
117+
preview()
118+
end
119+
120+
function draw_wide_logo_new(
121+
filename="docs/src/assets/wide_logo.png";
122+
_pkg_name="Conformal Prediction",
123+
font_size=150,
124+
font_family="Tamil MN",
125+
font_fill="transparent",
126+
font_color=Luxor.julia_blue,
127+
bg_color="transparent",
128+
picture_kwargs...
129+
)
130+
131+
# Setup:
132+
height = Int(round(font_size * 2.4))
133+
fontsize(font_size)
134+
fontface(font_family)
135+
strs = split(_pkg_name)
136+
text_col_width = Int(round(maximum(map(str -> textextents(str)[3], strs)) * 1.05))
137+
width = Int(round(height + text_col_width))
138+
cw = [height, text_col_width]
139+
cells = Luxor.Table(height, cw)
140+
ms = Int(round(height / 10))
141+
gt_stroke_size = Int(round(height / 50))
142+
143+
Drawing(width, height, filename)
144+
origin()
145+
background(bg_color)
146+
147+
# Picture:
148+
@layer begin
149+
translate(cells[1])
150+
logo_picture(
151+
frame_size=height,
152+
margin=0.1,
153+
ms=ms,
154+
gt_stroke_size=gt_stroke_size,
155+
picture_kwargs...,
156+
)
157+
end
158+
159+
# Text:
160+
@layer begin
161+
translate(cells[2])
162+
fontsize(font_size)
163+
fontface(font_family)
164+
tiles = Tiler(cells.colwidths[2], height, length(strs), 1)
165+
for (pos, n) in tiles
166+
@layer begin
167+
translate(pos)
168+
setline(Int(round(gt_stroke_size / 5)))
169+
sethue(font_fill)
170+
textoutlines(strs[n], O, :path, valign=:middle, halign=:center)
171+
sethue(font_color)
172+
strokepath()
173+
end
174+
end
175+
end
176+
177+
finish()
178+
preview()
179+
end
180+
181+
draw_wide_logo_new()

dev/logo/small_logo.png

16.2 KB
Loading

dev/logo/wide_logo.png

68.4 KB
Loading

dev/notebooks/appendix.qmd

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)