Spaces:
Running
Running
mervenoyan
commited on
Commit
•
30e9731
1
Parent(s):
1f8e178
commit files to HF hub
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +118 -0
- .gitignore +14 -0
- CONTRIBUTING.md +28 -0
- LICENSE +202 -0
- README.md +6 -6
- package.json +18 -0
- public/anonymization/annotations.js +38 -0
- public/anonymization/index.html +268 -0
- public/anonymization/init.js +77 -0
- public/anonymization/make-axii.js +86 -0
- public/anonymization/make-estimates.js +227 -0
- public/anonymization/make-gs.js +105 -0
- public/anonymization/make-sel.js +78 -0
- public/anonymization/make-sliders.js +139 -0
- public/anonymization/make-slides.js +98 -0
- public/anonymization/make-students.js +184 -0
- public/anonymization/style-graph-scroll.css +160 -0
- public/anonymization/style.css +344 -0
- public/base-rate/script.js +317 -0
- public/base-rate/sliders.js +103 -0
- public/base-rate/style.css +134 -0
- public/data-leak/face.png +3 -0
- public/data-leak/index.html +170 -0
- public/data-leak/players0.js +456 -0
- public/data-leak/script.js +296 -0
- public/data-leak/style.css +176 -0
- public/dataset-worldviews/README.md +6 -0
- public/dataset-worldviews/img/confusing_pointiness.png +3 -0
- public/dataset-worldviews/img/confusing_pointiness.svg +203 -0
- public/dataset-worldviews/img/confusing_shape_name.png +3 -0
- public/dataset-worldviews/img/confusing_shape_name.svg +228 -0
- public/dataset-worldviews/img/confusing_size.png +3 -0
- public/dataset-worldviews/img/confusing_size.svg +203 -0
- public/dataset-worldviews/img/data_labelers.png +3 -0
- public/dataset-worldviews/img/data_labelers.svg +221 -0
- public/dataset-worldviews/img/dataset-worldviews-shareimg.png +3 -0
- public/dataset-worldviews/img/interface_default.png +3 -0
- public/dataset-worldviews/img/interface_default.svg +204 -0
- public/dataset-worldviews/img/interface_shape_name_false.png +3 -0
- public/dataset-worldviews/img/interface_shape_name_false.svg +234 -0
- public/dataset-worldviews/img/interface_shape_name_true.png +3 -0
- public/dataset-worldviews/img/interface_shape_name_true.svg +214 -0
- public/dataset-worldviews/img/labels_1.png +3 -0
- public/dataset-worldviews/img/labels_1.svg +265 -0
- public/dataset-worldviews/img/labels_2.png +3 -0
- public/dataset-worldviews/img/labels_2.svg +301 -0
- public/dataset-worldviews/img/labels_3.png +3 -0
- public/dataset-worldviews/img/labels_3.svg +137 -0
- public/dataset-worldviews/img/labels_4.png +3 -0
- public/dataset-worldviews/img/labels_4.svg +265 -0
.gitattributes
CHANGED
@@ -25,3 +25,121 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
25 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
26 |
*.zstandard filter=lfs diff=lfs merge=lfs -text
|
27 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
26 |
*.zstandard filter=lfs diff=lfs merge=lfs -text
|
27 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
public/data-leak/face.png filter=lfs diff=lfs merge=lfs -text
|
29 |
+
public/dataset-worldviews/img/confusing_pointiness.png filter=lfs diff=lfs merge=lfs -text
|
30 |
+
public/dataset-worldviews/img/confusing_shape_name.png filter=lfs diff=lfs merge=lfs -text
|
31 |
+
public/dataset-worldviews/img/confusing_size.png filter=lfs diff=lfs merge=lfs -text
|
32 |
+
public/dataset-worldviews/img/data_labelers.png filter=lfs diff=lfs merge=lfs -text
|
33 |
+
public/dataset-worldviews/img/dataset-worldviews-shareimg.png filter=lfs diff=lfs merge=lfs -text
|
34 |
+
public/dataset-worldviews/img/interface_default.png filter=lfs diff=lfs merge=lfs -text
|
35 |
+
public/dataset-worldviews/img/interface_shape_name_false.png filter=lfs diff=lfs merge=lfs -text
|
36 |
+
public/dataset-worldviews/img/interface_shape_name_true.png filter=lfs diff=lfs merge=lfs -text
|
37 |
+
public/dataset-worldviews/img/labels_1.png filter=lfs diff=lfs merge=lfs -text
|
38 |
+
public/dataset-worldviews/img/labels_2.png filter=lfs diff=lfs merge=lfs -text
|
39 |
+
public/dataset-worldviews/img/labels_3.png filter=lfs diff=lfs merge=lfs -text
|
40 |
+
public/dataset-worldviews/img/labels_4.png filter=lfs diff=lfs merge=lfs -text
|
41 |
+
public/dataset-worldviews/img/newspapers_01.png filter=lfs diff=lfs merge=lfs -text
|
42 |
+
public/dataset-worldviews/img/newspapers_pointiness.png filter=lfs diff=lfs merge=lfs -text
|
43 |
+
public/dataset-worldviews/img/newspapers_shape_name.png filter=lfs diff=lfs merge=lfs -text
|
44 |
+
public/dataset-worldviews/img/newspapers_size.png filter=lfs diff=lfs merge=lfs -text
|
45 |
+
public/dataset-worldviews/img/person_0.png filter=lfs diff=lfs merge=lfs -text
|
46 |
+
public/dataset-worldviews/img/person_1.png filter=lfs diff=lfs merge=lfs -text
|
47 |
+
public/dataset-worldviews/img/person_2.png filter=lfs diff=lfs merge=lfs -text
|
48 |
+
public/dataset-worldviews/img/person_3.png filter=lfs diff=lfs merge=lfs -text
|
49 |
+
public/dataset-worldviews/img/seattle.png filter=lfs diff=lfs merge=lfs -text
|
50 |
+
public/dataset-worldviews/img/seattle_first_tags.png filter=lfs diff=lfs merge=lfs -text
|
51 |
+
public/dataset-worldviews/img/seattle_second_tags.png filter=lfs diff=lfs merge=lfs -text
|
52 |
+
public/dataset-worldviews/img/woman_washing_clothes.jpeg filter=lfs diff=lfs merge=lfs -text
|
53 |
+
public/fill-in-the-blank/img/wiki-years-black.png filter=lfs diff=lfs merge=lfs -text
|
54 |
+
public/fill-in-the-blank/img/wiki-years.png filter=lfs diff=lfs merge=lfs -text
|
55 |
+
public/hidden-bias/over.png filter=lfs diff=lfs merge=lfs -text
|
56 |
+
public/images/anonymization.png filter=lfs diff=lfs merge=lfs -text
|
57 |
+
public/images/dataset-worldviews-shareimg.png filter=lfs diff=lfs merge=lfs -text
|
58 |
+
public/images/dataset-worldviews.png filter=lfs diff=lfs merge=lfs -text
|
59 |
+
public/images/fairness.png filter=lfs diff=lfs merge=lfs -text
|
60 |
+
public/images/fill-in-the-blank-abstract.png filter=lfs diff=lfs merge=lfs -text
|
61 |
+
public/images/fill-in-the-blank.png filter=lfs diff=lfs merge=lfs -text
|
62 |
+
public/images/hidden-bias.png filter=lfs diff=lfs merge=lfs -text
|
63 |
+
public/images/measuring-diversity.png filter=lfs diff=lfs merge=lfs -text
|
64 |
+
public/images/measuring-fairness.png filter=lfs diff=lfs merge=lfs -text
|
65 |
+
public/images/medical-fairness.gif filter=lfs diff=lfs merge=lfs -text
|
66 |
+
public/images/model-inversion.png filter=lfs diff=lfs merge=lfs -text
|
67 |
+
public/images/pair_logo_full.png filter=lfs diff=lfs merge=lfs -text
|
68 |
+
public/images/private-and-fair-abstract.png filter=lfs diff=lfs merge=lfs -text
|
69 |
+
public/images/private-and-fair.png filter=lfs diff=lfs merge=lfs -text
|
70 |
+
public/images/uncertainty-calibration-abstract.png filter=lfs diff=lfs merge=lfs -text
|
71 |
+
public/images/uncertainty-calibration.png filter=lfs diff=lfs merge=lfs -text
|
72 |
+
public/measuring-diversity/img/blue0.png filter=lfs diff=lfs merge=lfs -text
|
73 |
+
public/measuring-diversity/img/blue1.png filter=lfs diff=lfs merge=lfs -text
|
74 |
+
public/measuring-diversity/img/blue_doctor.jpg filter=lfs diff=lfs merge=lfs -text
|
75 |
+
public/measuring-diversity/img/bright_blue.png filter=lfs diff=lfs merge=lfs -text
|
76 |
+
public/measuring-diversity/img/construction.jpg filter=lfs diff=lfs merge=lfs -text
|
77 |
+
public/measuring-diversity/img/construction.png filter=lfs diff=lfs merge=lfs -text
|
78 |
+
public/measuring-diversity/img/green0.png filter=lfs diff=lfs merge=lfs -text
|
79 |
+
public/measuring-diversity/img/green2.png filter=lfs diff=lfs merge=lfs -text
|
80 |
+
public/measuring-diversity/img/green_doctor.png filter=lfs diff=lfs merge=lfs -text
|
81 |
+
public/measuring-diversity/img/white0.png filter=lfs diff=lfs merge=lfs -text
|
82 |
+
public/measuring-diversity/img/white1.png filter=lfs diff=lfs merge=lfs -text
|
83 |
+
public/measuring-diversity/img/white2.png filter=lfs diff=lfs merge=lfs -text
|
84 |
+
public/measuring-diversity/img/white3.png filter=lfs diff=lfs merge=lfs -text
|
85 |
+
public/measuring-diversity/img/white4.png filter=lfs diff=lfs merge=lfs -text
|
86 |
+
public/measuring-diversity/img/white5.png filter=lfs diff=lfs merge=lfs -text
|
87 |
+
source/data-leak/face.png filter=lfs diff=lfs merge=lfs -text
|
88 |
+
source/dataset-worldviews/img/confusing_pointiness.png filter=lfs diff=lfs merge=lfs -text
|
89 |
+
source/dataset-worldviews/img/confusing_shape_name.png filter=lfs diff=lfs merge=lfs -text
|
90 |
+
source/dataset-worldviews/img/confusing_size.png filter=lfs diff=lfs merge=lfs -text
|
91 |
+
source/dataset-worldviews/img/data_labelers.png filter=lfs diff=lfs merge=lfs -text
|
92 |
+
source/dataset-worldviews/img/dataset-worldviews-shareimg.png filter=lfs diff=lfs merge=lfs -text
|
93 |
+
source/dataset-worldviews/img/interface_default.png filter=lfs diff=lfs merge=lfs -text
|
94 |
+
source/dataset-worldviews/img/interface_shape_name_false.png filter=lfs diff=lfs merge=lfs -text
|
95 |
+
source/dataset-worldviews/img/interface_shape_name_true.png filter=lfs diff=lfs merge=lfs -text
|
96 |
+
source/dataset-worldviews/img/labels_1.png filter=lfs diff=lfs merge=lfs -text
|
97 |
+
source/dataset-worldviews/img/labels_2.png filter=lfs diff=lfs merge=lfs -text
|
98 |
+
source/dataset-worldviews/img/labels_3.png filter=lfs diff=lfs merge=lfs -text
|
99 |
+
source/dataset-worldviews/img/labels_4.png filter=lfs diff=lfs merge=lfs -text
|
100 |
+
source/dataset-worldviews/img/newspapers_01.png filter=lfs diff=lfs merge=lfs -text
|
101 |
+
source/dataset-worldviews/img/newspapers_pointiness.png filter=lfs diff=lfs merge=lfs -text
|
102 |
+
source/dataset-worldviews/img/newspapers_shape_name.png filter=lfs diff=lfs merge=lfs -text
|
103 |
+
source/dataset-worldviews/img/newspapers_size.png filter=lfs diff=lfs merge=lfs -text
|
104 |
+
source/dataset-worldviews/img/person_0.png filter=lfs diff=lfs merge=lfs -text
|
105 |
+
source/dataset-worldviews/img/person_1.png filter=lfs diff=lfs merge=lfs -text
|
106 |
+
source/dataset-worldviews/img/person_2.png filter=lfs diff=lfs merge=lfs -text
|
107 |
+
source/dataset-worldviews/img/person_3.png filter=lfs diff=lfs merge=lfs -text
|
108 |
+
source/dataset-worldviews/img/seattle.png filter=lfs diff=lfs merge=lfs -text
|
109 |
+
source/dataset-worldviews/img/seattle_first_tags.png filter=lfs diff=lfs merge=lfs -text
|
110 |
+
source/dataset-worldviews/img/seattle_second_tags.png filter=lfs diff=lfs merge=lfs -text
|
111 |
+
source/dataset-worldviews/img/woman_washing_clothes.jpeg filter=lfs diff=lfs merge=lfs -text
|
112 |
+
source/fill-in-the-blank/img/wiki-years-black.png filter=lfs diff=lfs merge=lfs -text
|
113 |
+
source/fill-in-the-blank/img/wiki-years.png filter=lfs diff=lfs merge=lfs -text
|
114 |
+
source/hidden-bias/over.png filter=lfs diff=lfs merge=lfs -text
|
115 |
+
source/images/anonymization.png filter=lfs diff=lfs merge=lfs -text
|
116 |
+
source/images/dataset-worldviews-shareimg.png filter=lfs diff=lfs merge=lfs -text
|
117 |
+
source/images/dataset-worldviews.png filter=lfs diff=lfs merge=lfs -text
|
118 |
+
source/images/fairness.png filter=lfs diff=lfs merge=lfs -text
|
119 |
+
source/images/fill-in-the-blank-abstract.png filter=lfs diff=lfs merge=lfs -text
|
120 |
+
source/images/fill-in-the-blank.png filter=lfs diff=lfs merge=lfs -text
|
121 |
+
source/images/hidden-bias.png filter=lfs diff=lfs merge=lfs -text
|
122 |
+
source/images/measuring-diversity.png filter=lfs diff=lfs merge=lfs -text
|
123 |
+
source/images/measuring-fairness.png filter=lfs diff=lfs merge=lfs -text
|
124 |
+
source/images/medical-fairness.gif filter=lfs diff=lfs merge=lfs -text
|
125 |
+
source/images/model-inversion.png filter=lfs diff=lfs merge=lfs -text
|
126 |
+
source/images/pair_logo_full.png filter=lfs diff=lfs merge=lfs -text
|
127 |
+
source/images/private-and-fair-abstract.png filter=lfs diff=lfs merge=lfs -text
|
128 |
+
source/images/private-and-fair.png filter=lfs diff=lfs merge=lfs -text
|
129 |
+
source/images/uncertainty-calibration-abstract.png filter=lfs diff=lfs merge=lfs -text
|
130 |
+
source/images/uncertainty-calibration.png filter=lfs diff=lfs merge=lfs -text
|
131 |
+
source/measuring-diversity/img/blue0.png filter=lfs diff=lfs merge=lfs -text
|
132 |
+
source/measuring-diversity/img/blue1.png filter=lfs diff=lfs merge=lfs -text
|
133 |
+
source/measuring-diversity/img/blue_doctor.jpg filter=lfs diff=lfs merge=lfs -text
|
134 |
+
source/measuring-diversity/img/bright_blue.png filter=lfs diff=lfs merge=lfs -text
|
135 |
+
source/measuring-diversity/img/construction.jpg filter=lfs diff=lfs merge=lfs -text
|
136 |
+
source/measuring-diversity/img/construction.png filter=lfs diff=lfs merge=lfs -text
|
137 |
+
source/measuring-diversity/img/green0.png filter=lfs diff=lfs merge=lfs -text
|
138 |
+
source/measuring-diversity/img/green2.png filter=lfs diff=lfs merge=lfs -text
|
139 |
+
source/measuring-diversity/img/green_doctor.png filter=lfs diff=lfs merge=lfs -text
|
140 |
+
source/measuring-diversity/img/white0.png filter=lfs diff=lfs merge=lfs -text
|
141 |
+
source/measuring-diversity/img/white1.png filter=lfs diff=lfs merge=lfs -text
|
142 |
+
source/measuring-diversity/img/white2.png filter=lfs diff=lfs merge=lfs -text
|
143 |
+
source/measuring-diversity/img/white3.png filter=lfs diff=lfs merge=lfs -text
|
144 |
+
source/measuring-diversity/img/white4.png filter=lfs diff=lfs merge=lfs -text
|
145 |
+
source/measuring-diversity/img/white5.png filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
node_modules
|
2 |
+
yarn.lock
|
3 |
+
watch-doc/credentials.json
|
4 |
+
watch-doc/token.json
|
5 |
+
server-side/fill-in-the-blank/cache/
|
6 |
+
server-side/fill-in-the-blank/env/
|
7 |
+
server-side/fill-in-the-blank/py/bert-large-uncased-whole-word-masking/pytorch_model.bin
|
8 |
+
server-side/fill-in-the-blank/py/zari-bert-cda/pytorch_model.bin
|
9 |
+
server-side/fill-in-the-blank/py/bert-large-uncased/pytorch_modecl.bin
|
10 |
+
server-side/fill-in-the-blank/zari-convert/raw
|
11 |
+
server-side/dp-model/cns-cache
|
12 |
+
*.pytorch_modecl.bin
|
13 |
+
*.DS_Store
|
14 |
+
.vscode/*
|
CONTRIBUTING.md
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# How to Contribute
|
2 |
+
|
3 |
+
We'd love to accept your patches and contributions to this project. There are
|
4 |
+
just a few small guidelines you need to follow.
|
5 |
+
|
6 |
+
## Contributor License Agreement
|
7 |
+
|
8 |
+
Contributions to this project must be accompanied by a Contributor License
|
9 |
+
Agreement. You (or your employer) retain the copyright to your contribution;
|
10 |
+
this simply gives us permission to use and redistribute your contributions as
|
11 |
+
part of the project. Head over to <https://cla.developers.google.com/> to see
|
12 |
+
your current agreements on file or to sign a new one.
|
13 |
+
|
14 |
+
You generally only need to submit a CLA once, so if you've already submitted one
|
15 |
+
(even if it was for a different project), you probably don't need to do it
|
16 |
+
again.
|
17 |
+
|
18 |
+
## Code reviews
|
19 |
+
|
20 |
+
All submissions, including submissions by project members, require review. We
|
21 |
+
use GitHub pull requests for this purpose. Consult
|
22 |
+
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
23 |
+
information on using pull requests.
|
24 |
+
|
25 |
+
## Community Guidelines
|
26 |
+
|
27 |
+
This project follows [Google's Open Source Community
|
28 |
+
Guidelines](https://opensource.google.com/conduct/).
|
LICENSE
ADDED
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
Apache License
|
3 |
+
Version 2.0, January 2004
|
4 |
+
http://www.apache.org/licenses/
|
5 |
+
|
6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
7 |
+
|
8 |
+
1. Definitions.
|
9 |
+
|
10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
12 |
+
|
13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
14 |
+
the copyright owner that is granting the License.
|
15 |
+
|
16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
17 |
+
other entities that control, are controlled by, or are under common
|
18 |
+
control with that entity. For the purposes of this definition,
|
19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
20 |
+
direction or management of such entity, whether by contract or
|
21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
23 |
+
|
24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
25 |
+
exercising permissions granted by this License.
|
26 |
+
|
27 |
+
"Source" form shall mean the preferred form for making modifications,
|
28 |
+
including but not limited to software source code, documentation
|
29 |
+
source, and configuration files.
|
30 |
+
|
31 |
+
"Object" form shall mean any form resulting from mechanical
|
32 |
+
transformation or translation of a Source form, including but
|
33 |
+
not limited to compiled object code, generated documentation,
|
34 |
+
and conversions to other media types.
|
35 |
+
|
36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
37 |
+
Object form, made available under the License, as indicated by a
|
38 |
+
copyright notice that is included in or attached to the work
|
39 |
+
(an example is provided in the Appendix below).
|
40 |
+
|
41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
42 |
+
form, that is based on (or derived from) the Work and for which the
|
43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
45 |
+
of this License, Derivative Works shall not include works that remain
|
46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
47 |
+
the Work and Derivative Works thereof.
|
48 |
+
|
49 |
+
"Contribution" shall mean any work of authorship, including
|
50 |
+
the original version of the Work and any modifications or additions
|
51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
55 |
+
means any form of electronic, verbal, or written communication sent
|
56 |
+
to the Licensor or its representatives, including but not limited to
|
57 |
+
communication on electronic mailing lists, source code control systems,
|
58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
60 |
+
excluding communication that is conspicuously marked or otherwise
|
61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
62 |
+
|
63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
65 |
+
subsequently incorporated within the Work.
|
66 |
+
|
67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
72 |
+
Work and such Derivative Works in Source or Object form.
|
73 |
+
|
74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
77 |
+
(except as stated in this section) patent license to make, have made,
|
78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
79 |
+
where such license applies only to those patent claims licensable
|
80 |
+
by such Contributor that are necessarily infringed by their
|
81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
83 |
+
institute patent litigation against any entity (including a
|
84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
85 |
+
or a Contribution incorporated within the Work constitutes direct
|
86 |
+
or contributory patent infringement, then any patent licenses
|
87 |
+
granted to You under this License for that Work shall terminate
|
88 |
+
as of the date such litigation is filed.
|
89 |
+
|
90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
91 |
+
Work or Derivative Works thereof in any medium, with or without
|
92 |
+
modifications, and in Source or Object form, provided that You
|
93 |
+
meet the following conditions:
|
94 |
+
|
95 |
+
(a) You must give any other recipients of the Work or
|
96 |
+
Derivative Works a copy of this License; and
|
97 |
+
|
98 |
+
(b) You must cause any modified files to carry prominent notices
|
99 |
+
stating that You changed the files; and
|
100 |
+
|
101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
102 |
+
that You distribute, all copyright, patent, trademark, and
|
103 |
+
attribution notices from the Source form of the Work,
|
104 |
+
excluding those notices that do not pertain to any part of
|
105 |
+
the Derivative Works; and
|
106 |
+
|
107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
108 |
+
distribution, then any Derivative Works that You distribute must
|
109 |
+
include a readable copy of the attribution notices contained
|
110 |
+
within such NOTICE file, excluding those notices that do not
|
111 |
+
pertain to any part of the Derivative Works, in at least one
|
112 |
+
of the following places: within a NOTICE text file distributed
|
113 |
+
as part of the Derivative Works; within the Source form or
|
114 |
+
documentation, if provided along with the Derivative Works; or,
|
115 |
+
within a display generated by the Derivative Works, if and
|
116 |
+
wherever such third-party notices normally appear. The contents
|
117 |
+
of the NOTICE file are for informational purposes only and
|
118 |
+
do not modify the License. You may add Your own attribution
|
119 |
+
notices within Derivative Works that You distribute, alongside
|
120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
121 |
+
that such additional attribution notices cannot be construed
|
122 |
+
as modifying the License.
|
123 |
+
|
124 |
+
You may add Your own copyright statement to Your modifications and
|
125 |
+
may provide additional or different license terms and conditions
|
126 |
+
for use, reproduction, or distribution of Your modifications, or
|
127 |
+
for any such Derivative Works as a whole, provided Your use,
|
128 |
+
reproduction, and distribution of the Work otherwise complies with
|
129 |
+
the conditions stated in this License.
|
130 |
+
|
131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
133 |
+
by You to the Licensor shall be under the terms and conditions of
|
134 |
+
this License, without any additional terms or conditions.
|
135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
136 |
+
the terms of any separate license agreement you may have executed
|
137 |
+
with Licensor regarding such Contributions.
|
138 |
+
|
139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
141 |
+
except as required for reasonable and customary use in describing the
|
142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
143 |
+
|
144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
145 |
+
agreed to in writing, Licensor provides the Work (and each
|
146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
148 |
+
implied, including, without limitation, any warranties or conditions
|
149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
151 |
+
appropriateness of using or redistributing the Work and assume any
|
152 |
+
risks associated with Your exercise of permissions under this License.
|
153 |
+
|
154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
155 |
+
whether in tort (including negligence), contract, or otherwise,
|
156 |
+
unless required by applicable law (such as deliberate and grossly
|
157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
158 |
+
liable to You for damages, including any direct, indirect, special,
|
159 |
+
incidental, or consequential damages of any character arising as a
|
160 |
+
result of this License or out of the use or inability to use the
|
161 |
+
Work (including but not limited to damages for loss of goodwill,
|
162 |
+
work stoppage, computer failure or malfunction, or any and all
|
163 |
+
other commercial damages or losses), even if such Contributor
|
164 |
+
has been advised of the possibility of such damages.
|
165 |
+
|
166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
169 |
+
or other liability obligations and/or rights consistent with this
|
170 |
+
License. However, in accepting such obligations, You may act only
|
171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
172 |
+
of any other Contributor, and only if You agree to indemnify,
|
173 |
+
defend, and hold each Contributor harmless for any liability
|
174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
175 |
+
of your accepting any such warranty or additional liability.
|
176 |
+
|
177 |
+
END OF TERMS AND CONDITIONS
|
178 |
+
|
179 |
+
APPENDIX: How to apply the Apache License to your work.
|
180 |
+
|
181 |
+
To apply the Apache License to your work, attach the following
|
182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
183 |
+
replaced with your own identifying information. (Don't include
|
184 |
+
the brackets!) The text should be enclosed in the appropriate
|
185 |
+
comment syntax for the file format. We also recommend that a
|
186 |
+
file or class name and description of purpose be included on the
|
187 |
+
same "printed page" as the copyright notice for easier
|
188 |
+
identification within third-party archives.
|
189 |
+
|
190 |
+
Copyright [yyyy] [name of copyright owner]
|
191 |
+
|
192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
193 |
+
you may not use this file except in compliance with the License.
|
194 |
+
You may obtain a copy of the License at
|
195 |
+
|
196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
197 |
+
|
198 |
+
Unless required by applicable law or agreed to in writing, software
|
199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
201 |
+
See the License for the specific language governing permissions and
|
202 |
+
limitations under the License.
|
README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: static
|
7 |
pinned: false
|
|
|
|
|
8 |
---
|
9 |
-
|
10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces#reference
|
|
|
1 |
---
|
2 |
+
title: private-and-fair
|
3 |
+
emoji: 🪄
|
4 |
+
colorFrom: green
|
5 |
+
colorTo: purple
|
6 |
sdk: static
|
7 |
pinned: false
|
8 |
+
license: apache-2.0
|
9 |
+
app_file: public/private-and-fair/index.html
|
10 |
---
|
|
|
|
package.json
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "explorable-ml",
|
3 |
+
"dependencies": {
|
4 |
+
"chokidar": "^1.7.0",
|
5 |
+
"doc2txt": "^0.0.6",
|
6 |
+
"googleapis": "39",
|
7 |
+
"highlight.js": "^9.12.0",
|
8 |
+
"hot-server": "^0.0.18",
|
9 |
+
"marked": "^0.3.6",
|
10 |
+
"scrape-stl": "^1.0.3"
|
11 |
+
},
|
12 |
+
"scripts": {
|
13 |
+
"start": "mkdir -p public && node source/third_party/index.js --watch & sleep 1 && cd public/ && hot-server --port=2344",
|
14 |
+
"pub": "mkdir -p public && node source/third_party/index.js && gcloud --quiet app deploy --project=google.com:explorable-ml",
|
15 |
+
"watch": "bin/watch-doc.sh"
|
16 |
+
},
|
17 |
+
"license": "MIT"
|
18 |
+
}
|
public/anonymization/annotations.js
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
var annotations =
|
2 |
+
|
3 |
+
[
|
4 |
+
]
|
5 |
+
|
6 |
+
|
7 |
+
|
8 |
+
|
9 |
+
function addSwoop(c){
|
10 |
+
var swoopy = d3.swoopyDrag()
|
11 |
+
.x(d => c.x(d.x))
|
12 |
+
.y(d => c.y(d.y))
|
13 |
+
.draggable(0)
|
14 |
+
.annotations(annotations)
|
15 |
+
|
16 |
+
var swoopySel = c.svg.append('g.annotations').call(swoopy)
|
17 |
+
|
18 |
+
c.svg.append('marker#arrow')
|
19 |
+
.attr('viewBox', '-10 -10 20 20')
|
20 |
+
.attr('markerWidth', 20)
|
21 |
+
.attr('markerHeight', 20)
|
22 |
+
.attr('orient', 'auto')
|
23 |
+
.append('path').at({d: 'M-6.75,-6.75 L 0,0 L -6.75,6.75'})
|
24 |
+
|
25 |
+
|
26 |
+
swoopySel.selectAll('path').attr('marker-end', 'url(#arrow)')
|
27 |
+
window.annotationSel = swoopySel.selectAll('g')
|
28 |
+
.st({fontSize: 12, opacity: d => d.slide == 0 ? 1 : 0})
|
29 |
+
|
30 |
+
swoopySel.selectAll('text')
|
31 |
+
.each(function(d){
|
32 |
+
d3.select(this)
|
33 |
+
.text('') //clear existing text
|
34 |
+
.tspans(d3.wordwrap(d.text, d.width || 20), 12) //wrap after 20 char
|
35 |
+
})
|
36 |
+
}
|
37 |
+
|
38 |
+
|
public/anonymization/index.html
ADDED
@@ -0,0 +1,268 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!--
|
2 |
+
@license
|
3 |
+
Copyright 2020 Google. All Rights Reserved.
|
4 |
+
|
5 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6 |
+
you may not use this file except in compliance with the License.
|
7 |
+
You may obtain a copy of the License at
|
8 |
+
|
9 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
10 |
+
|
11 |
+
Unless required by applicable law or agreed to in writing, software
|
12 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14 |
+
See the License for the specific language governing permissions and
|
15 |
+
limitations under the License.
|
16 |
+
-->
|
17 |
+
|
18 |
+
<!DOCTYPE html>
|
19 |
+
|
20 |
+
<html>
|
21 |
+
<head>
|
22 |
+
<meta charset="utf-8">
|
23 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
24 |
+
|
25 |
+
<link rel="apple-touch-icon" sizes="180x180" href="https://pair.withgoogle.com/images/favicon/apple-touch-icon.png">
|
26 |
+
<link rel="icon" type="image/png" sizes="32x32" href="https://pair.withgoogle.com/images/favicon/favicon-32x32.png">
|
27 |
+
<link rel="icon" type="image/png" sizes="16x16" href="https://pair.withgoogle.com/images/favicon/favicon-16x16.png">
|
28 |
+
<link rel="mask-icon" href="https://pair.withgoogle.com/images/favicon/safari-pinned-tab.svg" color="#00695c">
|
29 |
+
<link rel="shortcut icon" href="https://pair.withgoogle.com/images/favicon.ico">
|
30 |
+
|
31 |
+
<script>
|
32 |
+
!(function(){
|
33 |
+
var url = window.location.href
|
34 |
+
if (url.split('#')[0].split('?')[0].slice(-1) != '/' && !url.includes('.html')) window.location = url + '/'
|
35 |
+
})()
|
36 |
+
</script>
|
37 |
+
|
38 |
+
<title>How randomized response can help collect sensitive information responsibly</title>
|
39 |
+
<meta property="og:title" content="How randomized response can help collect sensitive information responsibly">
|
40 |
+
<meta property="og:url" content="https://pair.withgoogle.com/explorables/anonymization/">
|
41 |
+
|
42 |
+
<meta name="og:description" content="The availability of giant datasets and faster computers is making it harder to collect and study private information without inadvertently violating people's privacy.">
|
43 |
+
<meta property="og:image" content="https://pair.withgoogle.com/explorables/images/anonymization.png">
|
44 |
+
<meta name="twitter:card" content="summary_large_image">
|
45 |
+
|
46 |
+
<link rel="stylesheet" type="text/css" href="../style.css">
|
47 |
+
|
48 |
+
<link href='https://fonts.googleapis.com/css?family=Roboto+Slab:400,500,700|Roboto:700,500,300' rel='stylesheet' type='text/css'>
|
49 |
+
<link href="https://fonts.googleapis.com/css?family=Google+Sans:400,500,700" rel="stylesheet">
|
50 |
+
|
51 |
+
<meta name="viewport" content="width=device-width">
|
52 |
+
</head>
|
53 |
+
<body>
|
54 |
+
<div class='header'>
|
55 |
+
<div class='header-left'>
|
56 |
+
<a href='https://pair.withgoogle.com/'>
|
57 |
+
<img src='../images/pair-logo.svg' style='width: 100px'></img>
|
58 |
+
</a>
|
59 |
+
<a href='../'>Explorables</a>
|
60 |
+
</div>
|
61 |
+
</div>
|
62 |
+
|
63 |
+
<h1 class='headline'>How randomized response can help collect sensitive information responsibly</h1>
|
64 |
+
<div class="post-summary">Giant datasets are revealing new patterns in <a href='https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5070532/'>cancer</a>, <a href='https://opportunityinsights.org/national_trends/'>income inequality</a> and other important areas. However, the widespread availability of fast computers that can cross reference public data is making it harder to collect private information without inadvertently violating people's privacy. Modern randomization techniques can help preserve anonymity. </div>
|
65 |
+
<link rel="stylesheet" href="style.css">
|
66 |
+
<link rel="stylesheet" href="style-graph-scroll.css">
|
67 |
+
|
68 |
+
<div id='container' class='container-1'>
|
69 |
+
<div id='graph'></div>
|
70 |
+
<div id='sections'>
|
71 |
+
<div>
|
72 |
+
|
73 |
+
<h3>Anonymous Data</h3>
|
74 |
+
|
75 |
+
<p>Let's pretend we're analysts at a small college, looking at anonymous survey data about plagiarism.
|
76 |
+
|
77 |
+
<p>We've gotten responses from the entire student body, reporting if they've ever <span class='highlight purple'>plagiarized</span> or <span class='highlight grey'>not</span>. To encourage them to respond honestly, names were not collected.
|
78 |
+
<p>
|
79 |
+
|
80 |
+
<p class='note'>The data here has been randomly generated</p>
|
81 |
+
</div>
|
82 |
+
|
83 |
+
|
84 |
+
<div>
|
85 |
+
<p>On the survey students also report several bits of information about themselves, like their age...
|
86 |
+
</div>
|
87 |
+
|
88 |
+
|
89 |
+
<div>
|
90 |
+
<p>...and what state they're from.
|
91 |
+
|
92 |
+
<p>This additional information is critical to finding potential patterns in the data—why have so many first-years from New Hampshire plagiarized?
|
93 |
+
</div>
|
94 |
+
|
95 |
+
|
96 |
+
<div>
|
97 |
+
<h3>Revealed Information</h3>
|
98 |
+
<p>But granular information comes with a cost.
|
99 |
+
|
100 |
+
<p>One student has a <span class='highlight box square orange'>unique</span> age/home state combination. By searching another student database for a 19-year old from Vermont we can identify one of the plagiarists from supposedly anonymous survey data.
|
101 |
+
</div>
|
102 |
+
|
103 |
+
|
104 |
+
<div>
|
105 |
+
<p>Increasing granularity exacerbates the problem. If the students reported slightly more about their ages by including what season they were born in, we'd be able to <span class='highlight box square orange'>identify</span> about a sixth of them.
|
106 |
+
|
107 |
+
<p>This isn't just a hypothetical: A <a href="https://cpg.doc.ic.ac.uk/individual-risk/">birthday / gender / zip code combination</a> uniquely identifies 83% of the people in the United States.
|
108 |
+
|
109 |
+
<p>With the spread of large datasets, it is increasingly difficult to release detailed information without inadvertently revealing someone's identity. A week of a person's location data could <a href='https://www.nytimes.com/interactive/2018/12/10/business/location-data-privacy-apps.html'>reveal</a> a home and work address—possibly enough to find a name using public records.
|
110 |
+
</div>
|
111 |
+
|
112 |
+
|
113 |
+
<div>
|
114 |
+
<h3>Randomization</h3>
|
115 |
+
<p>One solution is to randomize responses so each student has plausible deniability. This lets us buy privacy at the cost of some uncertainty in our estimation of plagiarism rates.
|
116 |
+
|
117 |
+
<p><b>Step 1:</b> Each student flips a coin and looks at it without showing anyone.
|
118 |
+
</div>
|
119 |
+
|
120 |
+
|
121 |
+
<div>
|
122 |
+
<p><b>Step 2:</b> Students who flip heads <span class='highlight purple-box box'>report plagiarism</span>, even if they haven't plagiarized.
|
123 |
+
|
124 |
+
<p>Students that flipped tails report the truth, secure with the knowledge that even if their response is linked back to their name, they can claim they flipped heads.
|
125 |
+
</div>
|
126 |
+
|
127 |
+
|
128 |
+
<div>
|
129 |
+
<p>With a little bit of math, we can approximate the rate of plagiarism from these randomized responses. We'll skip the algebra, but doubling the reported non-plagiarism rate gives a good estimate of the actual non-plagiarism rate.
|
130 |
+
|
131 |
+
<p class='rand-text'></p>
|
132 |
+
|
133 |
+
<div class='button-outer'>
|
134 |
+
<div class='button-container flip-coins-once'>
|
135 |
+
Flip coins
|
136 |
+
</div>
|
137 |
+
</div>
|
138 |
+
|
139 |
+
</div>
|
140 |
+
|
141 |
+
|
142 |
+
<div>
|
143 |
+
<h3>How far off can we be?</h3>
|
144 |
+
|
145 |
+
<p>If we simulate this coin flipping lots of times, we can see the distribution of errors.
|
146 |
+
|
147 |
+
<p>The estimates are close most of the time, but errors can be quite large.
|
148 |
+
|
149 |
+
<div class='button-outer'>
|
150 |
+
<div class='button-container flip-coins'>
|
151 |
+
Flip coins 200 times
|
152 |
+
</div>
|
153 |
+
</div>
|
154 |
+
|
155 |
+
</div>
|
156 |
+
|
157 |
+
|
158 |
+
<div>
|
159 |
+
<p>Reducing the random noise (by reducing the number of students who flip heads) increases the accuracy of our estimate, but risks leaking information about students.
|
160 |
+
|
161 |
+
<p>If the coin is heavily weighted towards tails, identified students can't credibly claim they reported plagiarizing because they flipped heads.
|
162 |
+
|
163 |
+
<div class="slider-outer">
|
164 |
+
<div class="slide-container-heads-prob"></div>
|
165 |
+
<div class='pointer'><div></div></div>
|
166 |
+
</div>
|
167 |
+
|
168 |
+
</div>
|
169 |
+
|
170 |
+
|
171 |
+
<div>
|
172 |
+
<p>One surprising way out of this accuracy-privacy tradeoff: carefully collect information from even more people.
|
173 |
+
|
174 |
+
<p>If we got students from other schools to fill out this survey, we could accurately measure plagiarism while protecting everyone's privacy. With enough students, we could even start comparing plagiarism across different age groups again—safely this time.
|
175 |
+
|
176 |
+
<div class="slider-outer">
|
177 |
+
<div class="slide-container-population"></div>
|
178 |
+
|
179 |
+
<div class="slide-container-heads-prob"></div>
|
180 |
+
</div>
|
181 |
+
</div>
|
182 |
+
|
183 |
+
|
184 |
+
|
185 |
+
</div>
|
186 |
+
</div>
|
187 |
+
|
188 |
+
<h3>Conclusion</h3>
|
189 |
+
|
190 |
+
<p>Aggregate statistics about private information are valuable, but can be risky to collect. We want researchers to be able to study things like the connection between demographics and health outcomes without revealing our entire medical history to our neighbors. The coin flipping technique in this article, called <a href='https://en.wikipedia.org/wiki/Randomized_response'>randomized response</a>, makes it possible to safely study private information.
|
191 |
+
|
192 |
+
<p>You might wonder if coin flipping is the only way to do this. It's not—<a href='https://desfontain.es/privacy/differential-privacy-in-more-detail.html'>differential privacy</a> can add targeted bits of random noise to a dataset and guarantee privacy. More flexible than randomized response, the 2020 Census will use it to <a href='https://www.youtube.com/watch?v=pT19VwBAqKA'>protect respondents' privacy</a>. In addition to randomizing responses, differential privacy also limits the impact any one response can have on the released data.
|
193 |
+
|
194 |
+
|
195 |
+
<h3>Credits</h3>
|
196 |
+
|
197 |
+
<p>Adam Pearce and Ellen Jiang // September 2020
|
198 |
+
|
199 |
+
<p>Thanks to Carey Radebaugh, Fernanda Viégas, Emily Reif, Hal Abelson, Jess Holbrook, Kristen Olson, Mahima Pushkarna, Martin Wattenberg, Michael Terry, Miguel Guevara, Rebecca Salois, Yannick Assogba, Zan Armstrong and our other colleagues at Google for their help with this piece.
|
200 |
+
|
201 |
+
</div>
|
202 |
+
|
203 |
+
|
204 |
+
<h3>More Explorables</h3>
|
205 |
+
|
206 |
+
<p id='recirc'></p>
|
207 |
+
|
208 |
+
<div id='end'></div>
|
209 |
+
|
210 |
+
<script src='../third_party/seedrandom.min.js'></script>
|
211 |
+
<script src='../third_party/d3_.js'></script>
|
212 |
+
<script src='../third_party/swoopy-drag.js'></script>
|
213 |
+
<script src='../third_party/misc.js'></script>
|
214 |
+
<script src='annotations.js'></script>
|
215 |
+
|
216 |
+
|
217 |
+
<script src='make-axii.js'></script>
|
218 |
+
<script src='make-students.js'></script>
|
219 |
+
<script src='make-sel.js'></script>
|
220 |
+
<script src='make-estimates.js'></script>
|
221 |
+
<script src='make-sliders.js'></script>
|
222 |
+
<script src='make-slides.js'></script>
|
223 |
+
<script src='make-gs.js'></script>
|
224 |
+
<script src='init.js'></script>
|
225 |
+
|
226 |
+
<script src='../third_party/recirc.js'></script>
|
227 |
+
|
228 |
+
</body>
|
229 |
+
|
230 |
+
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-138505774-1"></script>
|
231 |
+
<script>
|
232 |
+
if (window.location.origin === 'https://pair.withgoogle.com'){
|
233 |
+
window.dataLayer = window.dataLayer || [];
|
234 |
+
function gtag(){dataLayer.push(arguments);}
|
235 |
+
gtag('js', new Date());
|
236 |
+
gtag('config', 'UA-138505774-1');
|
237 |
+
}
|
238 |
+
</script>
|
239 |
+
|
240 |
+
<script>
|
241 |
+
// Tweaks for displaying in an iframe
|
242 |
+
if (window !== window.parent){
|
243 |
+
|
244 |
+
// Open links in a new tab
|
245 |
+
Array.from(document.querySelectorAll('a'))
|
246 |
+
.forEach(e => {
|
247 |
+
// skip anchor links
|
248 |
+
if (e.href && e.href[0] == '#') return
|
249 |
+
|
250 |
+
e.setAttribute('target', '_blank')
|
251 |
+
e.setAttribute('rel', 'noopener noreferrer')
|
252 |
+
})
|
253 |
+
|
254 |
+
// Remove recirc h3
|
255 |
+
Array.from(document.querySelectorAll('h3'))
|
256 |
+
.forEach(e => {
|
257 |
+
if (e.textContent != 'More Explorables') return
|
258 |
+
|
259 |
+
e.parentNode.removeChild(e)
|
260 |
+
})
|
261 |
+
|
262 |
+
// Remove recirc container
|
263 |
+
var recircEl = document.querySelector('#recirc')
|
264 |
+
recircEl.parentNode.removeChild(recircEl)
|
265 |
+
}
|
266 |
+
</script>
|
267 |
+
|
268 |
+
</html>
|
public/anonymization/init.js
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
d3.select('body').selectAppend('div.tooltip.tooltip-hidden')
|
2 |
+
|
3 |
+
window.ages = '18 19 20 21 22'.split(' ')
|
4 |
+
window.states = 'RI NH NY CT VT'.split(' ')
|
5 |
+
|
6 |
+
window.init = function(){
|
7 |
+
// console.clear()
|
8 |
+
var graphSel = d3.select('#graph').html('').append('div')
|
9 |
+
window.c = d3.conventions({
|
10 |
+
sel: graphSel,
|
11 |
+
width: 460,
|
12 |
+
height: 460,
|
13 |
+
})
|
14 |
+
|
15 |
+
function sizeGraphSel(){
|
16 |
+
var clientWidth = d3.select('body').node().clientWidth
|
17 |
+
|
18 |
+
window.scale = d3.clamp(1, (c.totalWidth + 35)/(clientWidth - 10), 2) // off by one, s is 35
|
19 |
+
|
20 |
+
graphSel.st({
|
21 |
+
transform: `scale(${1/scale})`,
|
22 |
+
transformOrigin: `0px 0px`,
|
23 |
+
})
|
24 |
+
|
25 |
+
d3.select('#graph').st({height: scale == 1 ? 500 : 710})
|
26 |
+
}
|
27 |
+
sizeGraphSel()
|
28 |
+
d3.select(window).on('resize', sizeGraphSel)
|
29 |
+
|
30 |
+
|
31 |
+
c.svg = c.svg.append('g').translate([.5, .5])
|
32 |
+
|
33 |
+
window.axii = makeAxii()
|
34 |
+
window.sliders = makeSliders()
|
35 |
+
window.students = makeStudents()
|
36 |
+
window.sel = makeSel()
|
37 |
+
window.slides = makeSlides()
|
38 |
+
window.estimates = makeEstimates()
|
39 |
+
|
40 |
+
|
41 |
+
|
42 |
+
|
43 |
+
var error = 0
|
44 |
+
while (error < .02 || error > .05){
|
45 |
+
estimates.flipCoin()
|
46 |
+
error = Math.abs(estimates.active.val - .5)
|
47 |
+
}
|
48 |
+
|
49 |
+
makeGS()
|
50 |
+
}
|
51 |
+
|
52 |
+
init()
|
53 |
+
|
54 |
+
|
55 |
+
|
56 |
+
|
57 |
+
|
58 |
+
|
59 |
+
|
60 |
+
|
61 |
+
|
62 |
+
|
63 |
+
|
64 |
+
|
65 |
+
|
66 |
+
|
67 |
+
|
68 |
+
|
69 |
+
|
70 |
+
|
71 |
+
|
72 |
+
|
73 |
+
|
74 |
+
|
75 |
+
|
76 |
+
|
77 |
+
|
public/anonymization/make-axii.js
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
window.makeAxii = function(){
|
2 |
+
|
3 |
+
var stateScale = d3.scaleBand().domain(states).range(c.x.range())
|
4 |
+
var stateAxis = c.svg.append('g.axis.state.init-hidden')
|
5 |
+
|
6 |
+
var bw = stateScale.bandwidth()/2
|
7 |
+
|
8 |
+
stateAxis.appendMany('text', states)
|
9 |
+
.translate(d => [stateScale(d) + bw, c.height + 22])
|
10 |
+
.text(d => d)
|
11 |
+
.at({
|
12 |
+
textAnchor: 'middle',
|
13 |
+
})
|
14 |
+
.st({fill: '#444'})
|
15 |
+
|
16 |
+
stateAxis.appendMany('path', d3.range(ages.length + 1))
|
17 |
+
.at({
|
18 |
+
d: d => ['M', d*c.width/(ages.length), '0 V', c.height].join(' '),
|
19 |
+
stroke: '#aaa',
|
20 |
+
})
|
21 |
+
|
22 |
+
stateAxis.append('text.bold').text('Home State')
|
23 |
+
.translate([c.width/2, c.height + 45])
|
24 |
+
.at({textAnchor: 'middle'})
|
25 |
+
|
26 |
+
var ageScale = d3.scaleBand().domain(ages.slice().reverse()).range(c.x.range())
|
27 |
+
var ageAxis = c.svg.append('g.axis.age.init-hidden')
|
28 |
+
|
29 |
+
ageAxis.appendMany('text', ages)
|
30 |
+
.translate(d => [-30, ageScale(d) + bw])
|
31 |
+
.text(d => d)
|
32 |
+
.at({dy: '.33em'})
|
33 |
+
.st({fill: '#444'})
|
34 |
+
|
35 |
+
ageAxis.appendMany('path', d3.range(ages.length + 1))
|
36 |
+
.at({
|
37 |
+
d: d => ['M 0', d*c.width/(ages.length), 'H', c.width].join(' '),
|
38 |
+
stroke: '#aaa',
|
39 |
+
})
|
40 |
+
|
41 |
+
if (scale == 1){
|
42 |
+
ageAxis
|
43 |
+
.append('g').translate([-43, c.height/2])
|
44 |
+
.append('text.bold').text('Age')
|
45 |
+
.at({textAnchor: 'middle', transform: 'rotate(-90)'})
|
46 |
+
} else {
|
47 |
+
ageAxis
|
48 |
+
.append('g').translate([-22, 14])
|
49 |
+
.append('text.bold').text('Age')
|
50 |
+
.at({textAnchor: 'middle'})
|
51 |
+
}
|
52 |
+
|
53 |
+
var seasonAxis = c.svg.append('g.axis.state.init-hidden').lower()
|
54 |
+
seasonAxis.appendMany('g', ages)
|
55 |
+
.translate(d => ageScale(d), 1)
|
56 |
+
.appendMany('path', d3.range(1, 4))
|
57 |
+
.at({
|
58 |
+
d: d => ['M 0', d*bw/4*2, 'H', c.width].join(' '),
|
59 |
+
stroke: '#ddd',
|
60 |
+
})
|
61 |
+
|
62 |
+
var headAxis = c.svg.append('g.axis.state.init-hidden')
|
63 |
+
headAxis.appendMany('text.bold', ['Heads', 'Tails'])
|
64 |
+
.text(d => d)
|
65 |
+
.translate((d, i) => [i ? c.width/4*3 + 20 : c.width/4 - 20, 88])
|
66 |
+
.at({textAnchor: 'middle'})
|
67 |
+
|
68 |
+
|
69 |
+
var headCaptionAxis = c.svg.append('g.axis.state.init-hidden')
|
70 |
+
headCaptionAxis.appendMany('text', ['reports plagiarism', 'reports truth'])
|
71 |
+
.text(d => d)
|
72 |
+
.translate((d, i) => [i ? c.width/4*3 + 20 : c.width/4 - 20, 88 + 15])
|
73 |
+
.at({textAnchor: 'middle'})
|
74 |
+
.st({fill: '#444'})
|
75 |
+
|
76 |
+
|
77 |
+
return {stateScale, stateAxis, headAxis, headCaptionAxis, ageScale, ageAxis, bw, seasonAxis}
|
78 |
+
}
|
79 |
+
|
80 |
+
|
81 |
+
|
82 |
+
|
83 |
+
|
84 |
+
|
85 |
+
|
86 |
+
if (window.init) window.init()
|
public/anonymization/make-estimates.js
ADDED
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
window.makeEstimates = function(){
|
2 |
+
var estimateScale = d3.scaleLinear()
|
3 |
+
.domain([.5 - .15, .5 + .15]).range([0, c.width])
|
4 |
+
.interpolate(d3.interpolateRound)
|
5 |
+
|
6 |
+
var jitterHeight = 90
|
7 |
+
var rs = 4 // rect size
|
8 |
+
|
9 |
+
var estimates = students[0].coinVals.map(d => ({val: .5, pctHead: .25, x: c.width/2, y: c.height - jitterHeight/2}))
|
10 |
+
var simulation = d3.forceSimulation(estimates)
|
11 |
+
.force('collide', d3.forceCollide(rs).strength(.1))
|
12 |
+
.stop()
|
13 |
+
|
14 |
+
function updateEstimates(){
|
15 |
+
var selectedStudents = students.all.slice(0, sliders.population)
|
16 |
+
|
17 |
+
selectedStudents[0].coinVals.map((_, i) => {
|
18 |
+
estimates[i].pctHead = d3.mean(selectedStudents, d => (d.coinVals[i] < sliders.headsProb) || d.plagerized)
|
19 |
+
|
20 |
+
estimates[i].val = (1 - estimates[i].pctHead)/(1 - sliders.headsProb)
|
21 |
+
})
|
22 |
+
updateSimulation(60)
|
23 |
+
}
|
24 |
+
updateEstimates()
|
25 |
+
|
26 |
+
function updateSimulation(ticks=80, yStrength=.005){
|
27 |
+
var variance = d3.variance(estimates, d => d.val)
|
28 |
+
var xStength = variance < .0005 ? .3 : .1
|
29 |
+
|
30 |
+
estimates.forEach(d => d.targetX = estimateScale(d.val))
|
31 |
+
|
32 |
+
simulation
|
33 |
+
.force('x', d3.forceX(d => d.targetX).strength(xStength))
|
34 |
+
.force('y', d3.forceY(c.height - jitterHeight/2).strength(yStrength))
|
35 |
+
.alpha(1)
|
36 |
+
// .alphaDecay(1 - Math.pow(0.001, 1/ticks))
|
37 |
+
|
38 |
+
for (var i = 0; i < ticks; ++i) simulation.tick()
|
39 |
+
|
40 |
+
estimates.forEach(d => {
|
41 |
+
d.x = Math.round(d.x)
|
42 |
+
d.y = Math.round(d.y)
|
43 |
+
})
|
44 |
+
}
|
45 |
+
updateSimulation(80, 1)
|
46 |
+
updateSimulation(80, .005)
|
47 |
+
|
48 |
+
|
49 |
+
// Set up DOM
|
50 |
+
var histogramSel = c.svg.append('g').translate([0, -25])
|
51 |
+
var axisSel = histogramSel.append('g.axis.state.init-hidden')
|
52 |
+
var histogramAxis = axisSel.append('g')
|
53 |
+
|
54 |
+
var numTicks = 6
|
55 |
+
var xAxis = d3.axisTop(estimateScale).ticks(numTicks).tickFormat(d3.format('.0%')).tickSize(100)
|
56 |
+
|
57 |
+
histogramAxis.call(xAxis).translate([.5, c.height + 5])
|
58 |
+
middleTick = histogramAxis.selectAll('g').filter((d, i) => i === 3)
|
59 |
+
middleTick.select('text').classed('bold', 1)
|
60 |
+
middleTick.select('line').st({stroke: '#000'})
|
61 |
+
|
62 |
+
histogramAxis.append('text.bold')
|
63 |
+
.text('actual non-plagiarism rate')
|
64 |
+
.translate([c.width/2, 11])
|
65 |
+
.st({fontSize: '10px'})
|
66 |
+
|
67 |
+
var containerSel = histogramSel.append('g#histogram').translate([0.5, .5])
|
68 |
+
|
69 |
+
|
70 |
+
// Selection overlay to highlight individual estimates.
|
71 |
+
var selectSize = rs*2 + 2
|
72 |
+
var selectColor = '#007276'
|
73 |
+
var rectFill = '#007276'
|
74 |
+
|
75 |
+
var activeSel = histogramSel.append('g.active.init-hidden.axis')
|
76 |
+
.st({pointerEvents: 'none'})
|
77 |
+
|
78 |
+
activeSel.append('rect')
|
79 |
+
.at({width: selectSize, height: selectSize, stroke: selectColor, fill: 'none', strokeWidth: 3})
|
80 |
+
.translate([-selectSize/2, -selectSize/2])
|
81 |
+
|
82 |
+
var activeTextHighlight = activeSel.append('rect')
|
83 |
+
.at({x: -32, width: 32*2, height: 18, y: -25, fill: 'rgba(255,255,255,.6)', rx: 10, ry: 10, xfill: 'red'})
|
84 |
+
|
85 |
+
var activeTextSel = activeSel.append('text.est-text.bold')
|
86 |
+
.text('34%')
|
87 |
+
.at({textAnchor: 'middle', textAnchor: 'middle', y: '-1em'})
|
88 |
+
.st({fill: selectColor})
|
89 |
+
|
90 |
+
var activePathSel = activeSel.append('path')
|
91 |
+
.st({stroke: selectColor, strokeWidth: 3})
|
92 |
+
|
93 |
+
|
94 |
+
// Update highlight DOM with current highlight
|
95 |
+
var curDrawData = {pctHead: .25, val: .5, x: c.width/2, y: c.height - jitterHeight/2}
|
96 |
+
function setActive(active, dur=0){
|
97 |
+
if (active !== estimates.active){
|
98 |
+
estimates.forEach(d => {
|
99 |
+
d.active = d == active
|
100 |
+
d.fy = d.active ? d.y : null
|
101 |
+
})
|
102 |
+
estimates.active = active
|
103 |
+
}
|
104 |
+
|
105 |
+
students.updateHeadsPos()
|
106 |
+
|
107 |
+
|
108 |
+
sel.flipCircle
|
109 |
+
.transition().duration(0).delay(d => d.i*5*(dur > 0 ? 1 : 0))
|
110 |
+
.at({transform: d => slides && slides.curSlide && slides.curSlide.showFlipCircle && d.coinVals[active.index] < sliders.headsProb ?
|
111 |
+
'scale(1)' : 'scale(.1)'})
|
112 |
+
|
113 |
+
|
114 |
+
flipCoinTimer.stop()
|
115 |
+
if (dur){
|
116 |
+
var objI = d3.interpolateObject(curDrawData, active)
|
117 |
+
|
118 |
+
flipCoinTimer = d3.timer(ms => {
|
119 |
+
var t = d3.easeCubicInOut(d3.clamp(0, ms/dur, 1))
|
120 |
+
drawData(objI(t))
|
121 |
+
if (t == 1) flipCoinTimer.stop()
|
122 |
+
})
|
123 |
+
} else{
|
124 |
+
drawData(active)
|
125 |
+
}
|
126 |
+
|
127 |
+
function drawData({pctHead, val, x, y}){
|
128 |
+
activeSel.translate([x + rs/2, y + rs/2])
|
129 |
+
activeTextSel.text('est. ' + d3.format('.1%')(val))
|
130 |
+
activePathSel.at({d: `M ${selectSize/2*Math.sign(c.width/2 - x)} -1 H ${c.width/2 - x}`})
|
131 |
+
|
132 |
+
var error = Math.abs(val - .5)
|
133 |
+
var fmt = d3.format(".1%")
|
134 |
+
var pop = sliders.population
|
135 |
+
d3.select('.rand-text')
|
136 |
+
// .html(`${fmt(1 - pctHead)} of students said they had never plagerized. Since about half the students flipped heads and automatically reported plagerizism, we double that to <span class='highlight square blue-box box'>estimate ${fmt(val)}</span> of students haven't plagerized—${error > .1 ? '' : error > .07 ? 'a little ' : 'not '}far from the actual rate of ${fmt(.5)}`)
|
137 |
+
// .html(`${Math.round((1 - pctHead)*pop)} of ${pop} students said they had never plagiarized. Since about half the students flipped heads and automatically reported plagiarism, we double that rate to <span class='highlight square blue-box box'>estimate ${fmt(val)}</span> of students haven't plagiarized—${error > .4 ? '' : error > .07 ? 'a little ' : 'not '}far from the actual rate of ${fmt(.5)}`)
|
138 |
+
.html(`Here, ${fmt(1 - pctHead)} students said they had never plagiarized. Doubling that, we <span class='highlight square blue-box box'>estimate ${fmt(val)}</span> of students haven't plagiarized—${error > .1 ? 'quite ' : error > .07 ? 'a little ' : 'not '}far from the actual rate of ${fmt(.5)}`)
|
139 |
+
|
140 |
+
curDrawData = {pctHead, val, x, y}
|
141 |
+
}
|
142 |
+
}
|
143 |
+
window.flipCoinTimer = d3.timer(d => d)
|
144 |
+
|
145 |
+
|
146 |
+
|
147 |
+
var estimateSel = containerSel.appendMany('rect.estimate', estimates)
|
148 |
+
.at({width: rs, height: rs, stroke: '#fff', fill: rectFill, strokeWidth: .5})
|
149 |
+
.st({fill: rectFill})
|
150 |
+
.translate([rs/2, rs/2])
|
151 |
+
.on('mouseover', (d, i) => {
|
152 |
+
if (window.slides.curSlide.showHistogram) {
|
153 |
+
setActive(d)
|
154 |
+
}
|
155 |
+
})
|
156 |
+
|
157 |
+
function setSelectorOpacity(textOpacity, strokeOpacity) {
|
158 |
+
activeTextSel.st({opacity: textOpacity})
|
159 |
+
activeSel.st({opacity: strokeOpacity})
|
160 |
+
activePathSel.st({opacity: strokeOpacity})
|
161 |
+
}
|
162 |
+
|
163 |
+
function render(transition=false){
|
164 |
+
estimateSel.translate(d => [d.x, d.y])
|
165 |
+
setActive(estimates.active)
|
166 |
+
|
167 |
+
if (transition){
|
168 |
+
if (window.flipAllCoinsTimer) window.flipAllCoinsTimer.stop()
|
169 |
+
window.flipAllCoinsTimer = d3.timer(ms => {
|
170 |
+
var t = d3.easeExpIn(d3.clamp(0, ms/5000, 1), 20)
|
171 |
+
if (flipAllCoinsTimer.forceEnd) t = 1
|
172 |
+
|
173 |
+
if (t > .028) {
|
174 |
+
setSelectorOpacity(textOpacity=0, strokeOpacity=0.7)
|
175 |
+
}
|
176 |
+
|
177 |
+
var index = Math.floor((estimates.length - 2)*t) + 1
|
178 |
+
estimateSel.classed('active', (d, i) => i <= index)
|
179 |
+
|
180 |
+
setActive(estimates[index])
|
181 |
+
// flipCoinsSel.text('Flip coins ' + d3.format('03')(index < 100 ? index : index + 1) + ' times')
|
182 |
+
flipCoinsSel.text('Flip coins 200 times')
|
183 |
+
|
184 |
+
if (t == 1) {
|
185 |
+
flipAllCoinsTimer.stop()
|
186 |
+
setSelectorOpacity(textOpacity=1, strokeOpacity=1)
|
187 |
+
}
|
188 |
+
})
|
189 |
+
} else {
|
190 |
+
setSelectorOpacity(textOpacity=1, strokeOpacity=1)
|
191 |
+
flipCoinsSel
|
192 |
+
}
|
193 |
+
}
|
194 |
+
window.flipAllCoinsTimer = d3.timer(d => d)
|
195 |
+
|
196 |
+
|
197 |
+
var flipCoinsSel = d3.select('.flip-coins').on('click', () => {
|
198 |
+
students.all.forEach(student => {
|
199 |
+
student.coinVals = student.coinVals.map(j => Math.random())
|
200 |
+
})
|
201 |
+
|
202 |
+
updateEstimates()
|
203 |
+
render(true)
|
204 |
+
})
|
205 |
+
|
206 |
+
d3.select('.flip-coins-once').on('click', flipCoin)
|
207 |
+
function flipCoin(){
|
208 |
+
active = estimates[0]
|
209 |
+
|
210 |
+
students.all.forEach(student => {
|
211 |
+
student.coinVals = student.coinVals.map(j => Math.random())
|
212 |
+
})
|
213 |
+
|
214 |
+
active.fy = active.y = c.height - jitterHeight/2
|
215 |
+
updateEstimates()
|
216 |
+
|
217 |
+
estimateSel.translate(d => [d.x, d.y])
|
218 |
+
estimates.active = null
|
219 |
+
setActive(active, 1000)
|
220 |
+
}
|
221 |
+
|
222 |
+
Object.assign(estimates, {updateEstimates, setActive, render, flipCoin, axisSel, containerSel, estimateSel, activeSel})
|
223 |
+
|
224 |
+
return estimates
|
225 |
+
}
|
226 |
+
|
227 |
+
if (window.init) window.init()
|
public/anonymization/make-gs.js
ADDED
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
window.makeGS = function(){
|
2 |
+
var prevSlideIndex = -1
|
3 |
+
function updateSlide(i){
|
4 |
+
var slide = slides[i]
|
5 |
+
if (!slide) return
|
6 |
+
|
7 |
+
d3.select('.tooltip').classed('tooltip-hidden', true)
|
8 |
+
|
9 |
+
var dur = 500
|
10 |
+
|
11 |
+
sel.student.transition('xKey').duration(dur).delay(dur ? slide.circleDelayFn : 0)
|
12 |
+
.translate(d => (d.isAdditionalStudent && slide.xKey != 'plagerizedShifted') ? [0,0]: d.pos[slide.xKey])
|
13 |
+
|
14 |
+
|
15 |
+
if (sel.rectAt[slide.xKey]){
|
16 |
+
sel.uniqueBox.transition('at').duration(dur)
|
17 |
+
.delay(d => dur ? slide.circleDelayFn(d.d0) : 0)
|
18 |
+
.at(sel.rectAt[slide.xKey])
|
19 |
+
.translate(d => d.d0.group[slide.xKey].pos)
|
20 |
+
}
|
21 |
+
|
22 |
+
sel.uniqueBox.transition().duration(dur)
|
23 |
+
.st({opacity: slide.showUniqueBox ? 1 : 0})
|
24 |
+
|
25 |
+
sel.uniqueSeasonBox.transition()
|
26 |
+
.delay((d, i) => slide.showUniqueSeasonBox ? dur*2 + i*40 : 0).duration(slide.showUniqueSeasonBox ? 0 : dur)
|
27 |
+
.st({opacity: slide.showUniqueSeasonBox ? 1 : 0})
|
28 |
+
|
29 |
+
|
30 |
+
if (sliders.headsProb != slide.headsProbTarget && slide.animateHeadsProbSlider != -1){
|
31 |
+
var headI = d3.interpolate(sliders.headsProb, slide.headsProbTarget)
|
32 |
+
if (window.headSliderTimer) window.headSliderTimer.stop()
|
33 |
+
window.headSliderTimer = d3.timer(ms => {
|
34 |
+
var dur = slide.animateHeadsProbSlider ? 2000 : 1
|
35 |
+
var t = d3.easeCubicInOut(d3.clamp(0, ms/dur, 1))
|
36 |
+
sliders.updateHeadsProb(headI(t))
|
37 |
+
if (t == 1) headSliderTimer.stop()
|
38 |
+
})
|
39 |
+
}
|
40 |
+
|
41 |
+
if (sliders.population != slide.populationTarget){
|
42 |
+
var popI = d3.interpolate(sliders.population, slide.populationTarget)
|
43 |
+
if (window.popSliderTimer) window.popSliderTimer.stop()
|
44 |
+
window.popSliderTimer = d3.timer(ms => {
|
45 |
+
var dur = slide.animatePopulationSlider ? 2000 : 1
|
46 |
+
var t = d3.easeCubicInOut(d3.clamp(0, ms/dur, 1))
|
47 |
+
sliders.updatePopulation(Math.round(popI(t)/2)*2)
|
48 |
+
if (t == 1) popSliderTimer.stop()
|
49 |
+
})
|
50 |
+
}
|
51 |
+
|
52 |
+
axii.stateAxis.transition().duration(dur/2)
|
53 |
+
.st({opacity: slide.showStateAxis ? 1 : 0})
|
54 |
+
axii.ageAxis.transition().duration(dur/2)
|
55 |
+
.st({opacity: slide.showAgeAxis ? 1 : 0})
|
56 |
+
axii.seasonAxis.transition().duration(dur/2)
|
57 |
+
.st({opacity: slide.showSeasonAxis ? 1 : 0})
|
58 |
+
axii.headAxis.transition().duration(dur/2)
|
59 |
+
.st({opacity: slide.showHeadAxis ? 1 : 0})
|
60 |
+
axii.headCaptionAxis.transition().duration(dur/2)
|
61 |
+
.st({opacity: slide.showHeadCaptionAxis ? 1 : 0})
|
62 |
+
estimates.axisSel.transition().delay(dur).duration(dur/2)
|
63 |
+
.st({opacity: slide.showHistogramAxis ? 1 : 0})
|
64 |
+
estimates.activeSel.transition().delay(dur).duration(dur/2)
|
65 |
+
.st({opacity: slide.showHistogramAxis ? 1 : 0})
|
66 |
+
// axii.estimateAxis.transition().delay(dur).duration(dur/2)
|
67 |
+
// .st({opacity: slide.showEstimate && !slide.enterHistogram ? 1 : 0})
|
68 |
+
// axii.plagerizedAxis.transition().delay(dur).duration(dur/2)
|
69 |
+
// .st({opacity: slide.showPlagerizedAxis ? 1 : 0})
|
70 |
+
|
71 |
+
|
72 |
+
annotationSel.transition().duration(dur/2)
|
73 |
+
.st({opacity: d => i == d.slide ? 1 : 0})
|
74 |
+
|
75 |
+
estimates.containerSel.transition('xKey').duration(dur/2)
|
76 |
+
.st({opacity: slide.showHistogram ? 1 : 0})
|
77 |
+
|
78 |
+
if (slide.enterHistogram){
|
79 |
+
estimates.render(true)
|
80 |
+
} else {
|
81 |
+
window.flipAllCoinsTimer._time = Infinity
|
82 |
+
}
|
83 |
+
if (slide.enterHistogram === 0) estimates.estimateSel.classed('active', 1)
|
84 |
+
|
85 |
+
|
86 |
+
// Display the default coin flip state if the histogram is not visible.
|
87 |
+
sel.flipCircle.transition().duration(dur)
|
88 |
+
.at({transform: d => {
|
89 |
+
return slide.showFlipCircle && d.coinVals[estimates.active.index] < sliders.headsProb ? 'scale(1)' : 'scale(.1)'}})
|
90 |
+
|
91 |
+
prevSlideIndex = i
|
92 |
+
slides.curSlide = slide
|
93 |
+
}
|
94 |
+
|
95 |
+
var gs = d3.graphScroll()
|
96 |
+
.container(d3.select('.container-1'))
|
97 |
+
.graph(d3.selectAll('container-1 #graph'))
|
98 |
+
.eventId('uniqueId1')
|
99 |
+
.sections(d3.selectAll('.container-1 #sections > div'))
|
100 |
+
.offset(300)
|
101 |
+
.on('active', updateSlide)
|
102 |
+
}
|
103 |
+
|
104 |
+
|
105 |
+
if (window.init) window.init()
|
public/anonymization/make-sel.js
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
window.makeSel = function(){
|
2 |
+
function ttFmt(d){
|
3 |
+
var ttSel = d3.select('.tooltip').html('')
|
4 |
+
|
5 |
+
var ageStr = d.age + ' year old'
|
6 |
+
if (slides.curSlide.index == 4){
|
7 |
+
ageStr = ageStr + ' born in the ' + ['spring', 'summer', 'fall', 'winter'][d.season]
|
8 |
+
}
|
9 |
+
ttSel.append('div').html(`
|
10 |
+
${ageStr} from ${d.state} who
|
11 |
+
${d.plagerized ?
|
12 |
+
'<span class="highlight purple">plagiarized</span>' :
|
13 |
+
'<span class="highlight grey">never plagiarized</span>'}
|
14 |
+
`)
|
15 |
+
|
16 |
+
if (slides.curSlide.index < 6) return
|
17 |
+
|
18 |
+
var isHeads = d.coinVals[estimates.active.index] < sliders.headsProb
|
19 |
+
ttSel.append('div').html(`
|
20 |
+
They flipped
|
21 |
+
${isHeads ? 'heads' : 'tails'}
|
22 |
+
and said they had
|
23 |
+
${d.plagerized || isHeads ?
|
24 |
+
'<span class="highlight purple-box box">plagiarized</span>' :
|
25 |
+
'<span class="highlight grey-box box">never plagiarized</span>'}
|
26 |
+
`)
|
27 |
+
.st({marginTop: 10})
|
28 |
+
}
|
29 |
+
|
30 |
+
var rectAt = {}
|
31 |
+
var rs = (axii.bw - 10)*2
|
32 |
+
rectAt.ageState = {width: rs, height: rs, x: -rs/2, y: -rs/2}
|
33 |
+
var uniqueBox = c.svg.appendMany('rect.unique.init-hidden', students.byAgeState.filter(d => d.length == 1))
|
34 |
+
.translate(d => d.pos)
|
35 |
+
.at(rectAt.ageState)
|
36 |
+
|
37 |
+
var rs = axii.bw/4 + 5.5
|
38 |
+
rectAt.ageStateSeason = {width: rs, height: rs, x: Math.round(-rs/2), y: 4}
|
39 |
+
var uniqueSeasonBox = c.svg.appendMany(
|
40 |
+
'rect.unique.init-hidden',
|
41 |
+
students.byAgeStateSeason.filter(d => d.length == 1 && d[0].group.ageState.length > 1))
|
42 |
+
.translate(d => d.pos)
|
43 |
+
.at(rectAt.ageStateSeason)
|
44 |
+
|
45 |
+
// number of uniquely id'd students
|
46 |
+
// console.log(uniqueSeasonBox.size())
|
47 |
+
|
48 |
+
var studentGroup = c.svg.append('g')
|
49 |
+
.at({width: 500, height: 500})
|
50 |
+
|
51 |
+
var student = studentGroup.appendMany('g.student', students.all)
|
52 |
+
.call(d3.attachTooltip)
|
53 |
+
.on('mouseover', ttFmt)
|
54 |
+
.translate(d => d.isAdditionalStudent ? [0,0]: d.pos.grid)
|
55 |
+
.classed('inactive', d => d.isAdditionalStudent)
|
56 |
+
|
57 |
+
var rs = 16
|
58 |
+
var flipCircle = student.append('circle')
|
59 |
+
.at({transform: 'scale(.1)'})
|
60 |
+
.at({r: 9, fill: '#fff'})
|
61 |
+
.at({stroke: '#b0b' })
|
62 |
+
|
63 |
+
var circle = student.append('circle').at({
|
64 |
+
r: 5,
|
65 |
+
fill: d => d.plagerized ? '#f0f' : '#ccc',
|
66 |
+
stroke: d => d.plagerized ? '#b0b' : '#aaa',
|
67 |
+
strokeWidth: 1,
|
68 |
+
})
|
69 |
+
|
70 |
+
|
71 |
+
|
72 |
+
addSwoop(c)
|
73 |
+
|
74 |
+
return {student, studentGroup, circle, flipCircle, rectAt, uniqueBox, uniqueSeasonBox}
|
75 |
+
}
|
76 |
+
|
77 |
+
|
78 |
+
if (window.init) window.init()
|
public/anonymization/make-sliders.js
ADDED
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
window.makeSliders = function(){
|
2 |
+
var rv = {
|
3 |
+
population: 144,
|
4 |
+
headsProb: .5,
|
5 |
+
}
|
6 |
+
|
7 |
+
rv.updateHeadsProb = (headsProb) => {
|
8 |
+
rv.headsProb = headsProb
|
9 |
+
updateSliderPos()
|
10 |
+
|
11 |
+
|
12 |
+
estimates.updateEstimates()
|
13 |
+
estimates.render()
|
14 |
+
}
|
15 |
+
|
16 |
+
rv.updatePopulation = (population) => {
|
17 |
+
rv.population = population
|
18 |
+
updateSliderPos()
|
19 |
+
|
20 |
+
|
21 |
+
var scale = d3.clamp(0, 13 / Math.sqrt(population), 1)
|
22 |
+
sel.studentGroup.st({
|
23 |
+
transformOrigin: 'top',
|
24 |
+
transformOrigin: c.width/2 + 'px ' + 160 + 'px',
|
25 |
+
transform: `scale(${scale})`
|
26 |
+
})
|
27 |
+
|
28 |
+
estimates.updateEstimates()
|
29 |
+
estimates.render()
|
30 |
+
|
31 |
+
sel.student.classed('inactive',(d, i) => i >= population)
|
32 |
+
}
|
33 |
+
|
34 |
+
rv.updatePopulationSlider = (val) => {
|
35 |
+
rv.updatePopulation(val)
|
36 |
+
}
|
37 |
+
|
38 |
+
rv.updateNoiseSlider = (val) => {
|
39 |
+
rv.updateHeadsProb(val)
|
40 |
+
}
|
41 |
+
|
42 |
+
var updateSliderPos = (function(){
|
43 |
+
var width = d3.clamp(50, window.innerWidth/2 - 40, 145)
|
44 |
+
var height = 30
|
45 |
+
var color = '#007276'
|
46 |
+
|
47 |
+
var sliderVals = {
|
48 |
+
population: {
|
49 |
+
key: 'population',
|
50 |
+
textFn: d => rv.population + ' students' ,
|
51 |
+
r: [144, 756],
|
52 |
+
v: 144,
|
53 |
+
stepFn: d => rv.updatePopulation(Math.round(d.v/2)*2),
|
54 |
+
},
|
55 |
+
headsProb: {
|
56 |
+
key: 'headsProb',
|
57 |
+
textFn: d => d3.format('.1%')(rv.headsProb) + ' chance of heads',
|
58 |
+
r: [.2, .5],
|
59 |
+
v: .5,
|
60 |
+
stepFn: d => rv.updateHeadsProb(d.v),
|
61 |
+
}
|
62 |
+
}
|
63 |
+
var sliders = [sliderVals.headsProb, sliderVals.population, sliderVals.headsProb]
|
64 |
+
sliders.forEach(d => {
|
65 |
+
d.s = d3.scaleLinear().domain(d.r).range([0, width])
|
66 |
+
})
|
67 |
+
|
68 |
+
var sliderSel = d3.selectAll('.slide-container-population,.slide-container-heads-prob').html('')
|
69 |
+
.data(sliders)
|
70 |
+
.classed('slider', true)
|
71 |
+
.st({
|
72 |
+
display: 'inline-block',
|
73 |
+
width: width,
|
74 |
+
paddingRight: (d, i) => i == 1 ? 40 : 0,
|
75 |
+
marginTop: 20,
|
76 |
+
})
|
77 |
+
|
78 |
+
var textSel = sliderSel.append('div.slider-label-container')
|
79 |
+
.st({marginBottom: -5})
|
80 |
+
|
81 |
+
var svgSel = sliderSel.append('svg').at({width, height})
|
82 |
+
.on('click', function(d){
|
83 |
+
d.v = d.s.invert(d3.mouse(this)[0])
|
84 |
+
d.stepFn(d)
|
85 |
+
})
|
86 |
+
.st({
|
87 |
+
cursor: 'pointer'
|
88 |
+
})
|
89 |
+
.append('g').translate(height/2, 1)
|
90 |
+
svgSel.append('rect').at({width, height, y: -height/2, fill: 'rgba(0,0,0,0)'})
|
91 |
+
|
92 |
+
svgSel.append('path').at({
|
93 |
+
d: `M 0 -.5 H ${width}`,
|
94 |
+
stroke: color,
|
95 |
+
strokeWidth: 1
|
96 |
+
})
|
97 |
+
|
98 |
+
var leftPathSel = svgSel.append('path').at({
|
99 |
+
d: `M 0 -.5 H ${width}`,
|
100 |
+
stroke: color,
|
101 |
+
strokeWidth: 3
|
102 |
+
})
|
103 |
+
|
104 |
+
|
105 |
+
var drag = d3.drag()
|
106 |
+
.on('drag', function(d){
|
107 |
+
var x = d3.mouse(this)[0]
|
108 |
+
d.v = d3.clamp(d3.min(d.r), d.s.invert(x), d3.max(d.r))
|
109 |
+
d.stepFn(d)
|
110 |
+
})
|
111 |
+
|
112 |
+
var rectSel = svgSel.append('rect')
|
113 |
+
.at({
|
114 |
+
width: height/2 - 1,
|
115 |
+
height: height/2 - 1,
|
116 |
+
stroke: color,
|
117 |
+
strokeWidth: 3,
|
118 |
+
fill: '#fff',
|
119 |
+
})
|
120 |
+
.translate([-height/4, -height/4])
|
121 |
+
.call(drag)
|
122 |
+
|
123 |
+
return isDrag => {
|
124 |
+
rectSel.at({x: d => Math.round(d.s(rv[d.key]))})
|
125 |
+
textSel.text(d => d.textFn(d))
|
126 |
+
|
127 |
+
leftPathSel.at({d: d => `M 0 -.5 H ${d.s(rv[d.key])}`})
|
128 |
+
}
|
129 |
+
})()
|
130 |
+
updateSliderPos()
|
131 |
+
|
132 |
+
|
133 |
+
return rv
|
134 |
+
}
|
135 |
+
|
136 |
+
|
137 |
+
|
138 |
+
|
139 |
+
if (window.init) window.init()
|
public/anonymization/make-slides.js
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
window.makeSlides = function(){
|
2 |
+
var slides = [
|
3 |
+
{
|
4 |
+
xKey: 'grid',
|
5 |
+
circleDelayFn: d => axii.ageScale(d.age),
|
6 |
+
showFlipRect: 0,
|
7 |
+
populationTarget: 144,
|
8 |
+
headsProbTarget: .5,
|
9 |
+
},
|
10 |
+
{
|
11 |
+
xKey: 'age',
|
12 |
+
showAgeAxis: 1,
|
13 |
+
},
|
14 |
+
{
|
15 |
+
xKey: 'ageState',
|
16 |
+
showStateAxis: 1,
|
17 |
+
},
|
18 |
+
{
|
19 |
+
showUniqueBox: 1
|
20 |
+
},
|
21 |
+
{
|
22 |
+
xKey: 'ageStateSeason',
|
23 |
+
showUniqueBox: 1,
|
24 |
+
showUniqueSeasonBox: 1,
|
25 |
+
showSeasonAxis: 1,
|
26 |
+
},
|
27 |
+
{
|
28 |
+
xKey: 'heads',
|
29 |
+
showUniqueBox: 0,
|
30 |
+
showUniqueSeasonBox: 0,
|
31 |
+
showSeasonAxis: 0,
|
32 |
+
showAgeAxis: 0,
|
33 |
+
showStateAxis: 0,
|
34 |
+
showHeadAxis: 1,
|
35 |
+
},
|
36 |
+
{
|
37 |
+
showFlipCircle: 1,
|
38 |
+
showHeadCaptionAxis: 1,
|
39 |
+
},
|
40 |
+
|
41 |
+
// Flip coin
|
42 |
+
{
|
43 |
+
xKey: 'plagerizedShifted',
|
44 |
+
showHeadAxis: 0,
|
45 |
+
showHeadCaptionAxis: 0,
|
46 |
+
showHistogramAxis: 1,
|
47 |
+
},
|
48 |
+
|
49 |
+
// Exactly how far off can these estimates be after adding noise? Flip more coins to see the distribution.
|
50 |
+
{
|
51 |
+
enterHistogram: 1,
|
52 |
+
showHistogram: 1,
|
53 |
+
// showPlagerizedAxis: 0,
|
54 |
+
showEstimate: 1,
|
55 |
+
},
|
56 |
+
|
57 |
+
// Reducing the random noise increases our point estimate, but risks leaking information about students.
|
58 |
+
{
|
59 |
+
animateHeadsProbSlider: 1,
|
60 |
+
animatePopulationSlider: 1,
|
61 |
+
enterHistogram: 0,
|
62 |
+
name: 'noise',
|
63 |
+
headsProbTarget: .35,
|
64 |
+
},
|
65 |
+
|
66 |
+
// If we collect information from lots of people, we can have high accuracy and protect everyone's privacy.
|
67 |
+
{
|
68 |
+
showEstimate: 0,
|
69 |
+
showAllStudents: 1,
|
70 |
+
name: 'population',
|
71 |
+
animateHeadsProbSlider: -1,
|
72 |
+
animatePopulationSlider: 1,
|
73 |
+
populationTarget: 400,
|
74 |
+
},
|
75 |
+
|
76 |
+
]
|
77 |
+
|
78 |
+
var keys = []
|
79 |
+
slides.forEach((d, i) => {
|
80 |
+
keys = keys.concat(d3.keys(d))
|
81 |
+
d.index = i
|
82 |
+
})
|
83 |
+
_.uniq(keys).forEach(str => {
|
84 |
+
var prev = null
|
85 |
+
slides.forEach(d => {
|
86 |
+
if (typeof(d[str]) === 'undefined'){
|
87 |
+
d[str] = prev
|
88 |
+
}
|
89 |
+
prev = d[str]
|
90 |
+
})
|
91 |
+
})
|
92 |
+
|
93 |
+
return slides
|
94 |
+
}
|
95 |
+
|
96 |
+
|
97 |
+
|
98 |
+
if (window.init) window.init()
|
public/anonymization/make-students.js
ADDED
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
window.makeStudents = function(){
|
2 |
+
var seed = new Math.seedrandom('12fbsab56')
|
3 |
+
var rand = d3.randomUniform.source(seed)(0, 1)
|
4 |
+
|
5 |
+
var ncols = 12
|
6 |
+
|
7 |
+
var allStudents = d3.range(756).map(i => {
|
8 |
+
var age = ages[Math.floor(rand()*ages.length)]
|
9 |
+
var state = states[Math.floor(rand()*states.length)]
|
10 |
+
var season = Math.floor(rand()*4)
|
11 |
+
var heads = rand() < .5
|
12 |
+
|
13 |
+
if (rand() < .1) state = 'NY'
|
14 |
+
if (rand() < .5 && state == 'RI') state = states[Math.floor(rand()*states.length)]
|
15 |
+
if (rand() < .5 && state == 'CT') state = states[Math.floor(rand()*states.length)]
|
16 |
+
|
17 |
+
var coinVals = d3.range(300).map(rand).slice(0, 200)
|
18 |
+
|
19 |
+
return {age, state, i, pos: {}, group: {}, season, heads, coinVals, isAdditionalStudent: true}
|
20 |
+
})
|
21 |
+
|
22 |
+
var students = allStudents.slice(0, 144)
|
23 |
+
students.forEach(student => student.isAdditionalStudent = false)
|
24 |
+
|
25 |
+
students.all = allStudents
|
26 |
+
students.all.forEach((d, i) => {
|
27 |
+
var x = (i % 25)/25*c.width
|
28 |
+
var y = ~~(i/25)/25*c.width
|
29 |
+
d.pos.all = [x, y]
|
30 |
+
})
|
31 |
+
|
32 |
+
var {bw, ageScale, stateScale} = axii
|
33 |
+
_.sortBy(students, d => -d.age).forEach((d, i) => {
|
34 |
+
var x = (i % ncols)/(ncols - 1)*c.width
|
35 |
+
var y = ~~(i/ncols)/(ncols - 1)*c.width
|
36 |
+
d.pos.grid = [x, y]
|
37 |
+
scale = .6
|
38 |
+
d.pos.smallGrid = [x * scale + 90, y * scale]
|
39 |
+
})
|
40 |
+
|
41 |
+
// Set half the student to have plagerized.
|
42 |
+
var studentsPlagerizedArray = _.sortBy(d3.range(students.length).map(i => i % 2 == 0), () => rand())
|
43 |
+
// var remainingPlagerizedArray = _.sortBy(d3.range(allStudents.length - students.length).map(i => i % 2 == 0), () => rand())
|
44 |
+
remainingPlagerizedArray = d3.range(students.all.length).map(i => i % 2 == 1)
|
45 |
+
var plagerizedArray = studentsPlagerizedArray.concat(remainingPlagerizedArray)
|
46 |
+
students.all.forEach((d, i) => d.plagerized = plagerizedArray[i])
|
47 |
+
|
48 |
+
students.byAge = d3.nestBy(students, d => d.age)
|
49 |
+
students.byAge.forEach(age => {
|
50 |
+
age.forEach((d, i) => {
|
51 |
+
d.pos.age = [i*10, ageScale(d.age) + bw]
|
52 |
+
})
|
53 |
+
})
|
54 |
+
students.byAgeState = d3.nestBy(students, d => d.age + d.state)
|
55 |
+
students.byAgeState.forEach(group => {
|
56 |
+
var d0 = group.d0 = group[0]
|
57 |
+
group.pos = [bw + stateScale(d0.state), bw + ageScale(d0.age)]
|
58 |
+
|
59 |
+
var angle = Math.PI*(3 - Math.sqrt(5))*(1 + Math.random()*.05 - .05/2)
|
60 |
+
group.forEach((d, i) => {
|
61 |
+
d.pos.ageState = addVec(phyllotaxis(i, 10.5, angle), group.pos)
|
62 |
+
d.group.ageState = group
|
63 |
+
})
|
64 |
+
})
|
65 |
+
|
66 |
+
students.byAgeStateSeason = d3.nestBy(students, d => d.age + d.state + d.season)
|
67 |
+
students.byAgeStateSeason.forEach(group => {
|
68 |
+
var d0 = group.d0 = group[0]
|
69 |
+
group.pos = [bw + stateScale(d0.state), bw*d0.season/2 + ageScale(d0.age)]
|
70 |
+
|
71 |
+
group.forEach((d, i) => {
|
72 |
+
d.pos.ageStateSeason = addVec([i*11 - group.length*11/2 + 6, 12], group.pos)
|
73 |
+
d.group.ageStateSeason = group
|
74 |
+
})
|
75 |
+
})
|
76 |
+
|
77 |
+
|
78 |
+
students.updateHeadsPos = function(){
|
79 |
+
students.byHeads = d3.nestBy(students, d => d.coinVals[estimates.active.index] < sliders.headsProb)
|
80 |
+
students.byHeads.forEach(group => {
|
81 |
+
group.pos = [group.key == 'true' ? c.width/4 -15 : c.width/4*3 +15, c.height/2]
|
82 |
+
|
83 |
+
group.forEach((d, i) => {
|
84 |
+
d.pos.heads = addVec(phyllotaxis(i, 12), group.pos)
|
85 |
+
d.group.heads = group
|
86 |
+
})
|
87 |
+
})
|
88 |
+
}
|
89 |
+
|
90 |
+
students.plagerizedGroup = d3.nestBy(_.sortBy(students.all, d => d.plagerized), d => d.plagerized)
|
91 |
+
students.plagerizedGroup.forEach((group, groupIndex) => {
|
92 |
+
var d0 = group.d0 = group[0]
|
93 |
+
var offset = -20
|
94 |
+
group.pos = [(d0.plagerized ? c.width/2 + offset : c.width/2 - offset), c.height/2 - 80]
|
95 |
+
|
96 |
+
|
97 |
+
var getOrderedPositions = function() {
|
98 |
+
positions = []
|
99 |
+
|
100 |
+
var step = 25
|
101 |
+
var top = 0
|
102 |
+
var bottom = 0
|
103 |
+
var right = 0
|
104 |
+
|
105 |
+
var addAbove = function(dirPositive=true) {
|
106 |
+
var y = (top + 1) * step
|
107 |
+
var x = 0
|
108 |
+
while (x <= right * step) {
|
109 |
+
positions.push([dirPositive ? x: (right * step - x), y])
|
110 |
+
x += step
|
111 |
+
}
|
112 |
+
top++
|
113 |
+
}
|
114 |
+
|
115 |
+
var addRight = function(dirPositive=true) {
|
116 |
+
var x = (right + 1) * step
|
117 |
+
var y = bottom * step
|
118 |
+
while (y <= top * step) {
|
119 |
+
positions.push([x, dirPositive ? y: -y])
|
120 |
+
y += step
|
121 |
+
}
|
122 |
+
right++
|
123 |
+
}
|
124 |
+
|
125 |
+
var addBelow = function(dirPositive=true) {
|
126 |
+
var y = (bottom - 1) * step
|
127 |
+
var x = 0
|
128 |
+
while (x <= right * step) {
|
129 |
+
positions.push([dirPositive ? x: (right * step - x), y])
|
130 |
+
x += step
|
131 |
+
}
|
132 |
+
bottom--
|
133 |
+
}
|
134 |
+
|
135 |
+
var addForward = function() {
|
136 |
+
addAbove(true)
|
137 |
+
addRight(false)
|
138 |
+
addBelow(false)
|
139 |
+
}
|
140 |
+
|
141 |
+
var addBackward = function() {
|
142 |
+
addBelow(true)
|
143 |
+
addRight(true)
|
144 |
+
addAbove(false)
|
145 |
+
}
|
146 |
+
|
147 |
+
isForward = true
|
148 |
+
while(positions.length < students.all.length) {
|
149 |
+
if (positions.length === 0) {
|
150 |
+
positions.push([0, 0])
|
151 |
+
addRight()
|
152 |
+
addBelow()
|
153 |
+
} else {
|
154 |
+
if (isForward) {
|
155 |
+
addForward()
|
156 |
+
} else {
|
157 |
+
addBackward()
|
158 |
+
}
|
159 |
+
isForward = !isForward
|
160 |
+
}
|
161 |
+
}
|
162 |
+
return positions
|
163 |
+
}
|
164 |
+
|
165 |
+
var populationPositions = getOrderedPositions()
|
166 |
+
var reversePositions = populationPositions.map(pos => [-pos[0], pos[1]])
|
167 |
+
|
168 |
+
group.forEach((d, i) => {
|
169 |
+
var x = (i % 7)/20*c.width
|
170 |
+
var y = ~~(i/7)/20*c.width
|
171 |
+
// d.pos.plagerized = addVec([x, y], group.pos)
|
172 |
+
d.pos.plagerizedShifted = addVec([x, y - 50], group.pos)
|
173 |
+
d.group.plagerized = group
|
174 |
+
|
175 |
+
d.pos.plagerizedShifted = addVec((groupIndex === 0) ? populationPositions[i]: reversePositions[i], group.pos)
|
176 |
+
})
|
177 |
+
})
|
178 |
+
|
179 |
+
|
180 |
+
students.rand = rand
|
181 |
+
return students
|
182 |
+
}
|
183 |
+
|
184 |
+
if (window.init) window.init()
|
public/anonymization/style-graph-scroll.css
ADDED
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/** { border: 1px solid #f00; }*/
|
2 |
+
|
3 |
+
|
4 |
+
#container{
|
5 |
+
position: relative;
|
6 |
+
width: auto;
|
7 |
+
margin-left: -25px;
|
8 |
+
/*margin-bottom: 100px;*/
|
9 |
+
}
|
10 |
+
|
11 |
+
#sections{
|
12 |
+
width: 330px;
|
13 |
+
pointer-events: none;
|
14 |
+
}
|
15 |
+
|
16 |
+
#sections > div{
|
17 |
+
background: white;
|
18 |
+
opacity: .2;
|
19 |
+
margin-bottom: 400px;
|
20 |
+
line-height: 1.4em;
|
21 |
+
transition: opacity .2s;
|
22 |
+
pointer-events: all;
|
23 |
+
}
|
24 |
+
#sections > div:last-child{
|
25 |
+
height: 480px;
|
26 |
+
margin-bottom: 0px;
|
27 |
+
}
|
28 |
+
#sections > div.graph-scroll-active{
|
29 |
+
opacity: 1;
|
30 |
+
}
|
31 |
+
|
32 |
+
#graph{
|
33 |
+
margin-left: 40px;
|
34 |
+
width: 500px;
|
35 |
+
position: -webkit-sticky;
|
36 |
+
position: sticky;
|
37 |
+
top: 0px;
|
38 |
+
float: right;
|
39 |
+
height: 580px;
|
40 |
+
}
|
41 |
+
|
42 |
+
.slider-outer {
|
43 |
+
display: block;
|
44 |
+
max-width: 300px;
|
45 |
+
}
|
46 |
+
|
47 |
+
@media (max-width: 925px) {
|
48 |
+
#container{
|
49 |
+
margin-left: 0px;
|
50 |
+
}
|
51 |
+
|
52 |
+
#graph{
|
53 |
+
width: 100%;
|
54 |
+
float: none;
|
55 |
+
max-width: 500px;
|
56 |
+
margin: 0px auto;
|
57 |
+
}
|
58 |
+
|
59 |
+
#graph > div{
|
60 |
+
position: relative;
|
61 |
+
left:12px;
|
62 |
+
}
|
63 |
+
|
64 |
+
#sections{
|
65 |
+
width: auto;
|
66 |
+
position: relative;
|
67 |
+
margin: 0px auto;
|
68 |
+
}
|
69 |
+
|
70 |
+
#sections > div{
|
71 |
+
background: rgba(255,255,255,.8);
|
72 |
+
padding: 10px;
|
73 |
+
border-top: 1px solid;
|
74 |
+
border-bottom: 1px solid;
|
75 |
+
margin-bottom: 80vh;
|
76 |
+
width: calc(100vw - 20px);
|
77 |
+
margin-left: -5px;
|
78 |
+
}
|
79 |
+
|
80 |
+
#sections > div > *{
|
81 |
+
max-width: 750px;
|
82 |
+
}
|
83 |
+
|
84 |
+
#sections > div:first-child{
|
85 |
+
opacity: 1;
|
86 |
+
margin-top: -260px;
|
87 |
+
}
|
88 |
+
|
89 |
+
#sections > div:last-child{
|
90 |
+
height: auto;
|
91 |
+
}
|
92 |
+
|
93 |
+
#sections h3{
|
94 |
+
margin-top: .5em;
|
95 |
+
}
|
96 |
+
|
97 |
+
/* Adjust buttons for mobile. */
|
98 |
+
|
99 |
+
.button-container{
|
100 |
+
text-align: center;
|
101 |
+
left:0px;
|
102 |
+
}
|
103 |
+
|
104 |
+
/* Adjust sliders for mobile. */
|
105 |
+
input[type="range" i] {
|
106 |
+
width: 280px;
|
107 |
+
}
|
108 |
+
.slider-label-container{
|
109 |
+
width: 145px;
|
110 |
+
/* display: inline-block; */
|
111 |
+
}
|
112 |
+
|
113 |
+
.slide-container-heads-prob, .slide-container-population {
|
114 |
+
text-align: center;
|
115 |
+
}
|
116 |
+
|
117 |
+
.slider-container {
|
118 |
+
margin-bottom: 5px;
|
119 |
+
text-align: center;
|
120 |
+
width: 300px;
|
121 |
+
/* display:inline-block; */
|
122 |
+
}
|
123 |
+
|
124 |
+
.slider-outer {
|
125 |
+
text-align: center;
|
126 |
+
display: flex;
|
127 |
+
max-width: 300px;
|
128 |
+
}
|
129 |
+
|
130 |
+
.headsProb, .population {
|
131 |
+
margin-left: 15px;
|
132 |
+
}
|
133 |
+
|
134 |
+
.slide-container-population {
|
135 |
+
margin-bottom: -10px;
|
136 |
+
}
|
137 |
+
|
138 |
+
.pointer div {
|
139 |
+
left: 10px;
|
140 |
+
top: 37px;
|
141 |
+
}
|
142 |
+
|
143 |
+
/* Adjust post summary test for mobile. */
|
144 |
+
.post-summary{
|
145 |
+
margin-left: 8px;
|
146 |
+
margin-bottom: 60px;
|
147 |
+
margin-top: 40px;
|
148 |
+
}
|
149 |
+
|
150 |
+
}
|
151 |
+
|
152 |
+
#graph > div{
|
153 |
+
margin: 20 35px;
|
154 |
+
}
|
155 |
+
|
156 |
+
|
157 |
+
#end{
|
158 |
+
height: 15vh;
|
159 |
+
}
|
160 |
+
|
public/anonymization/style.css
ADDED
@@ -0,0 +1,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
.tooltip {
|
3 |
+
top: -1000px;
|
4 |
+
position: fixed;
|
5 |
+
padding: 10px;
|
6 |
+
background: rgba(255, 255, 255, .90);
|
7 |
+
border: 1px solid lightgray;
|
8 |
+
pointer-events: none;
|
9 |
+
font-size: 14px;
|
10 |
+
width: 267px;
|
11 |
+
}
|
12 |
+
.tooltip-hidden{
|
13 |
+
opacity: 0;
|
14 |
+
transition: all .3s;
|
15 |
+
transition-delay: .1s;
|
16 |
+
}
|
17 |
+
|
18 |
+
@media (max-width: 590px){
|
19 |
+
div.tooltip{
|
20 |
+
bottom: -1px;
|
21 |
+
width: calc(100%);
|
22 |
+
left: -1px !important;
|
23 |
+
right: -1px !important;
|
24 |
+
top: auto !important;
|
25 |
+
width: auto !important;
|
26 |
+
}
|
27 |
+
}
|
28 |
+
|
29 |
+
|
30 |
+
.domain{
|
31 |
+
display: none;
|
32 |
+
}
|
33 |
+
|
34 |
+
text{
|
35 |
+
/*pointer-events: none;*/
|
36 |
+
/*text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;*/
|
37 |
+
}
|
38 |
+
|
39 |
+
|
40 |
+
|
41 |
+
.note{
|
42 |
+
font-size: 12px;
|
43 |
+
color: #999;
|
44 |
+
margin-top: 60px;
|
45 |
+
}
|
46 |
+
|
47 |
+
h1{
|
48 |
+
font-weight: 100;
|
49 |
+
font-size: 34px;
|
50 |
+
margin-bottom: .5em;
|
51 |
+
line-height: 1.3em;
|
52 |
+
margin-top: 1.4em;
|
53 |
+
text-align: center;
|
54 |
+
font-family: "Google Sans", sans-serif;
|
55 |
+
}
|
56 |
+
|
57 |
+
.mono{
|
58 |
+
font-family: monospace;
|
59 |
+
}
|
60 |
+
|
61 |
+
|
62 |
+
svg{
|
63 |
+
overflow: visible;
|
64 |
+
}
|
65 |
+
|
66 |
+
|
67 |
+
|
68 |
+
|
69 |
+
.axis{
|
70 |
+
font-size: 12px;
|
71 |
+
pointer-events: none;
|
72 |
+
}
|
73 |
+
.axis{
|
74 |
+
color: #888;
|
75 |
+
|
76 |
+
}
|
77 |
+
.axis text, .slider-label-container{
|
78 |
+
fill: #888;
|
79 |
+
color: #888;
|
80 |
+
font-family: 'Roboto', Helvetica, sans-serif;
|
81 |
+
font-size: 12px;
|
82 |
+
}
|
83 |
+
|
84 |
+
.axis text.bold, .slider-label-container{
|
85 |
+
color: #3C4043;
|
86 |
+
fill: #3C4043;
|
87 |
+
font-weight: 500;
|
88 |
+
|
89 |
+
}
|
90 |
+
.axis line{
|
91 |
+
stroke: #ccc;
|
92 |
+
}
|
93 |
+
|
94 |
+
div.axis b{
|
95 |
+
margin-bottom: -10px;
|
96 |
+
display: block;
|
97 |
+
}
|
98 |
+
|
99 |
+
.init-hidden{
|
100 |
+
opacity: 0;
|
101 |
+
}
|
102 |
+
|
103 |
+
.slider-label-container{
|
104 |
+
font-weight: 500;
|
105 |
+
}
|
106 |
+
|
107 |
+
|
108 |
+
|
109 |
+
.highlight{
|
110 |
+
color: #fff;
|
111 |
+
padding-left: 3px;
|
112 |
+
padding-right: 3px;
|
113 |
+
padding-top: 1px;
|
114 |
+
padding-bottom: 1px;
|
115 |
+
border-radius: 3px;
|
116 |
+
}
|
117 |
+
|
118 |
+
.highlight.blue{ background: blue; }
|
119 |
+
.highlight.orange{ background: #ffd890; }
|
120 |
+
.highlight.yellow{ background: #ff0; color: #000; }
|
121 |
+
.highlight.purple{ background: #CB10CB; }
|
122 |
+
.highlight.purple{ background: #FF7AFF; color: #000;}
|
123 |
+
.highlight.grey{ background: #ccc; color: #000;}
|
124 |
+
.highlight.box{
|
125 |
+
border: 1px solid #ff6200;
|
126 |
+
border-radius: 5px;
|
127 |
+
color: #000;
|
128 |
+
padding-bottom: 2px;
|
129 |
+
white-space: nowrap;
|
130 |
+
}
|
131 |
+
.highlight.purple-box{
|
132 |
+
border: 1px solid #b0b;
|
133 |
+
}
|
134 |
+
.highlight.grey-box{
|
135 |
+
border: 1px solid #ccc;
|
136 |
+
}
|
137 |
+
.highlight.box.square{
|
138 |
+
border-radius: 0px;
|
139 |
+
}
|
140 |
+
.highlight.blue-box{ border: 2px solid #007276; }
|
141 |
+
|
142 |
+
|
143 |
+
|
144 |
+
.circle{
|
145 |
+
background: #eee;
|
146 |
+
border: 1px solid #ccc;
|
147 |
+
font-family: monospace;
|
148 |
+
padding-left: 4px;
|
149 |
+
padding-right: 4px;
|
150 |
+
padding-top: 1px;
|
151 |
+
padding-bottom: 1px;
|
152 |
+
|
153 |
+
border-radius: 100px;
|
154 |
+
}
|
155 |
+
|
156 |
+
|
157 |
+
.strikethrough{
|
158 |
+
text-decoration: line-through;
|
159 |
+
color: #000;
|
160 |
+
}
|
161 |
+
|
162 |
+
|
163 |
+
.annotations path{
|
164 |
+
fill: none;
|
165 |
+
stroke: black;
|
166 |
+
}
|
167 |
+
|
168 |
+
|
169 |
+
|
170 |
+
rect.unique{
|
171 |
+
stroke: #ff6200;
|
172 |
+
stroke-width: 1px;
|
173 |
+
fill: #ffd890;
|
174 |
+
|
175 |
+
animation-duration: 1s;
|
176 |
+
animation-name: xstrokeblink;
|
177 |
+
display: inline-block;
|
178 |
+
animation-iteration-count: infinite;
|
179 |
+
animation-direction: alternate;
|
180 |
+
}
|
181 |
+
|
182 |
+
|
183 |
+
@keyframes strokeblink {
|
184 |
+
from {
|
185 |
+
/*fill: black;*/
|
186 |
+
stroke-width: 1px;
|
187 |
+
}
|
188 |
+
|
189 |
+
to {
|
190 |
+
/*fill: green;*/
|
191 |
+
stroke-width: 1px;
|
192 |
+
}
|
193 |
+
}
|
194 |
+
|
195 |
+
|
196 |
+
|
197 |
+
|
198 |
+
|
199 |
+
.inline-line{
|
200 |
+
border: 1px #f0f solid;
|
201 |
+
width: 20px;
|
202 |
+
display: inline-block;
|
203 |
+
position: relative;
|
204 |
+
top: -5px;
|
205 |
+
}
|
206 |
+
|
207 |
+
.slider-label-container{
|
208 |
+
width: 240px;
|
209 |
+
}
|
210 |
+
.slider-label{
|
211 |
+
font-size: smaller;
|
212 |
+
margin-left: 2px;
|
213 |
+
}
|
214 |
+
|
215 |
+
.slider-text-label{
|
216 |
+
margin-left: 5px;
|
217 |
+
white-space: nowrap;
|
218 |
+
}
|
219 |
+
|
220 |
+
|
221 |
+
g.student:hover circle{
|
222 |
+
stroke-width: 2px;
|
223 |
+
}
|
224 |
+
|
225 |
+
g{
|
226 |
+
/*opacity: 1 !important;*/
|
227 |
+
}
|
228 |
+
|
229 |
+
.inactive{
|
230 |
+
opacity: 0 !important;
|
231 |
+
pointer-events: none;
|
232 |
+
}
|
233 |
+
|
234 |
+
input[type="range" i] {
|
235 |
+
background-color:#def5ef;
|
236 |
+
-webkit-appearance: none;
|
237 |
+
height:20px;
|
238 |
+
width:240px;
|
239 |
+
overflow: hidden;
|
240 |
+
}
|
241 |
+
|
242 |
+
input[type='range']::-webkit-slider-thumb {
|
243 |
+
-webkit-appearance: none;
|
244 |
+
width: 16px;
|
245 |
+
height: 20px;
|
246 |
+
cursor: ew-resize;
|
247 |
+
background: #007276;
|
248 |
+
box-shadow: -200px 0 0 200px #7ed3c9;
|
249 |
+
border: 1px solid #333;
|
250 |
+
}
|
251 |
+
|
252 |
+
input:focus {
|
253 |
+
outline-width: 0;
|
254 |
+
}
|
255 |
+
|
256 |
+
|
257 |
+
|
258 |
+
|
259 |
+
.estimate{
|
260 |
+
opacity: 0;
|
261 |
+
pointer-events: none
|
262 |
+
}
|
263 |
+
|
264 |
+
.estimate.active{
|
265 |
+
opacity: .70;
|
266 |
+
pointer-events: all;
|
267 |
+
}
|
268 |
+
|
269 |
+
.est-text{
|
270 |
+
text-shadow: 0 2px 0 rgba(255,255,255,1), 2px 0 0 rgba(255,255,255,1), 0 -2px 0 rgba(255,255,255,1), -2px 0 0 rgba(255,255,255,1);
|
271 |
+
}
|
272 |
+
|
273 |
+
|
274 |
+
|
275 |
+
|
276 |
+
@media (max-width: 590px){
|
277 |
+
text{
|
278 |
+
font-size: 120% !important;
|
279 |
+
}
|
280 |
+
}
|
281 |
+
|
282 |
+
|
283 |
+
.slider{
|
284 |
+
user-select: none;
|
285 |
+
-webkit-tap-highlight-color: transparent;
|
286 |
+
}
|
287 |
+
|
288 |
+
.button-container{
|
289 |
+
border: 1px solid #888;
|
290 |
+
display: inline-block;
|
291 |
+
padding: 10px 20px;
|
292 |
+
cursor: pointer;
|
293 |
+
text-align: center;
|
294 |
+
border-radius: 10px;
|
295 |
+
user-select: none;
|
296 |
+
-webkit-tap-highlight-color: transparent;
|
297 |
+
margin: 0px auto;
|
298 |
+
/* color: #888;
|
299 |
+
font-family: 'Roboto', Helvetica, sans-serif;
|
300 |
+
font-size: 12px;
|
301 |
+
font-weight: 500;*/
|
302 |
+
position: relative;
|
303 |
+
left: -20px;
|
304 |
+
}
|
305 |
+
|
306 |
+
.button-container:hover{
|
307 |
+
background: #ddd;
|
308 |
+
}
|
309 |
+
|
310 |
+
.button-outer{
|
311 |
+
text-align: center;
|
312 |
+
margin-top: 20px;
|
313 |
+
}
|
314 |
+
|
315 |
+
.pointer{
|
316 |
+
height: 0px;
|
317 |
+
position: relative;
|
318 |
+
}
|
319 |
+
.pointer div {
|
320 |
+
overflow: visible;
|
321 |
+
content: "";
|
322 |
+
background-image: url(https://pair-code.github.io/interpretability/bert-tree/pointer.svg);
|
323 |
+
width: 27px;
|
324 |
+
height: 27px;
|
325 |
+
position: absolute;
|
326 |
+
left: 165px;
|
327 |
+
top: -35px;
|
328 |
+
}
|
329 |
+
|
330 |
+
a{
|
331 |
+
color: rgb(60, 64, 67);
|
332 |
+
}
|
333 |
+
a:hover{
|
334 |
+
color: #000;
|
335 |
+
}
|
336 |
+
|
337 |
+
|
338 |
+
|
339 |
+
|
340 |
+
|
341 |
+
|
342 |
+
|
343 |
+
|
344 |
+
|
public/base-rate/script.js
ADDED
@@ -0,0 +1,317 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* Copyright 2020 Google LLC. All Rights Reserved.
|
2 |
+
|
3 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
you may not use this file except in compliance with the License.
|
5 |
+
You may obtain a copy of the License at
|
6 |
+
|
7 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
|
9 |
+
Unless required by applicable law or agreed to in writing, software
|
10 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
See the License for the specific language governing permissions and
|
13 |
+
limitations under the License.
|
14 |
+
==============================================================================*/
|
15 |
+
|
16 |
+
|
17 |
+
|
18 |
+
|
19 |
+
|
20 |
+
console.clear()
|
21 |
+
var ttSel = d3.select('body').selectAppend('div.tooltip.tooltip-hidden')
|
22 |
+
|
23 |
+
window.renderFns = []
|
24 |
+
|
25 |
+
window.m = (function(){
|
26 |
+
var rv = {b: .7, tpr: .8, fnr: .5, update, str: 'kids', titleStr: 'Children',}
|
27 |
+
|
28 |
+
function update(obj={}){
|
29 |
+
Object.assign(rv, obj)
|
30 |
+
window.renderFns.forEach(d => d())
|
31 |
+
}
|
32 |
+
|
33 |
+
return rv
|
34 |
+
})()
|
35 |
+
|
36 |
+
window.f = (function(){
|
37 |
+
var rv = {b: .3, tpr: .8, fnr: .5, update, str: 'adults', titleStr: 'Adults'}
|
38 |
+
|
39 |
+
function update(obj={}){
|
40 |
+
window.renderFns.forEach(d => d())
|
41 |
+
}
|
42 |
+
|
43 |
+
return rv
|
44 |
+
})()
|
45 |
+
|
46 |
+
|
47 |
+
var wLarge = d3.clamp(0, innerWidth/2 - 30, 300)
|
48 |
+
|
49 |
+
d3.select('#big-matrix').html('')
|
50 |
+
.appendMany('div.big-container', [{w: wLarge, s: f, isText: 1}, {w: wLarge, s: m, isText: 1}])
|
51 |
+
.each(drawMatrix)
|
52 |
+
|
53 |
+
|
54 |
+
addPattern(10, `pattern-${wLarge}-`)
|
55 |
+
addPattern(5, 'pattern-50-')
|
56 |
+
|
57 |
+
function addPattern(s, str){
|
58 |
+
var cColors = [colors.sick, colors.sick, colors.well, colors.well, lcolors.sick, lcolors.sick, lcolors.well, lcolors.well]
|
59 |
+
var rColors = [lcolors.sick, lcolors.well, lcolors.sick, lcolors.well, llcolors.sick, llcolors.well, llcolors.sick, llcolors.well]
|
60 |
+
|
61 |
+
d3.select('#big-matrix')
|
62 |
+
.append('svg')
|
63 |
+
.st({height: 0, position: 'absolute'})
|
64 |
+
.append('defs').appendMany('pattern', d3.range(8))
|
65 |
+
.at({ id: i => str + i, width: s, height: s})
|
66 |
+
.attr('patternUnits', 'userSpaceOnUse')
|
67 |
+
.append('rect')
|
68 |
+
.at({width: s, height: s, fill: i => rColors[i]})
|
69 |
+
.parent().append('circle')
|
70 |
+
.at({r: s == 10 ? 2.5 : 1.5, cx: s/2, cy: s/2, fill: i => cColors[i]})
|
71 |
+
}
|
72 |
+
|
73 |
+
|
74 |
+
var scale = d3.clamp(0, ((innerWidth - 50) / 3)/280, 1)
|
75 |
+
var isScaled = scale != 1
|
76 |
+
|
77 |
+
d3.select('#metrics').html('').st({height: 350*scale + 30})
|
78 |
+
.appendMany('div', [0, 1, 2])
|
79 |
+
.st({width: 280*scale, display: 'inline-block'})
|
80 |
+
.append('div')
|
81 |
+
.st({transform: `scale(${scale})`, transformOrigin: '0% 0%'})
|
82 |
+
.append('div.metrics-container').st({width: 280})
|
83 |
+
.each(drawMetric)
|
84 |
+
|
85 |
+
d3.selectAll('rect.drag')
|
86 |
+
.on('mouseover.style', d => d3.selectAll('rect.' + d).st({strokeWidth: 3, stroke: '#000'}))
|
87 |
+
.on('mouseout.style', d => d3.selectAll('rect.' + d).st({strokeWidth: 0}))
|
88 |
+
|
89 |
+
function drawMetric(i){
|
90 |
+
var sel = d3.select(this)
|
91 |
+
|
92 |
+
var text = [
|
93 |
+
// 'Percentage of <span style="background: #fcf">sick people</span><br> who <span style="background: #f0f">test positive<span>',
|
94 |
+
'Percentage of sick people<br> who test positive',
|
95 |
+
'Percentage of positive tests<br> who are actually sick',
|
96 |
+
'Percentage of well people <br>who test negative',
|
97 |
+
][i]
|
98 |
+
|
99 |
+
var percentFn = [
|
100 |
+
s => s.tpr,
|
101 |
+
s => s.b*s.tpr/(s.b*s.tpr + (1 - s.b)*(s.fnr)),
|
102 |
+
s => 1 - s.fnr,
|
103 |
+
][i]
|
104 |
+
|
105 |
+
var colors = [
|
106 |
+
['#f0f', '#fcf', '#fff', '#fff'],
|
107 |
+
['#f0f', '#fff', '#fcf', '#fff'],
|
108 |
+
['#fff', '#fff', '#fcf', '#f0f'],
|
109 |
+
][i]
|
110 |
+
|
111 |
+
sel.append('h3').st({marginBottom: 20, fontSize: isScaled ? 30 : 20}).html(isScaled ? text.replace('<br>', '') : text)
|
112 |
+
|
113 |
+
var h = 200
|
114 |
+
var width = 100
|
115 |
+
|
116 |
+
var fDiv = sel.append('div').st({position: 'relative', top: -h + 7})
|
117 |
+
.datum({w: 50, s: f, isText: 0, colors}).each(drawMatrix)
|
118 |
+
|
119 |
+
var svg = sel.append('svg')
|
120 |
+
.at({width, height: h})
|
121 |
+
.st({fontSize: 14, fontFamily: 'monospace'})
|
122 |
+
|
123 |
+
svg.append('path').at({stroke: '#ccc', d: `M ${width/2 + .5} 0 V ${h}`})
|
124 |
+
|
125 |
+
var errorSel = svg.append('path')
|
126 |
+
.translate(width/2 + .5, 0)
|
127 |
+
.at({stroke: 'orange', strokeWidth: 3})
|
128 |
+
|
129 |
+
var fSel = svg.append('g')
|
130 |
+
var mSel = svg.append('g')
|
131 |
+
|
132 |
+
mSel.append('circle').at({r: 4, cx: width/2 + .5, fill: 'none', stroke: '#000'})
|
133 |
+
fSel.append('circle').at({r: 4, cx: width/2 + .5, fill: 'none', stroke: '#000'})
|
134 |
+
|
135 |
+
var fTextSel = fSel.append('text').text('23%')
|
136 |
+
.at({dy: '.33em', textAnchor: 'middle', x: width/4 - 3, fontSize: isScaled ? 20 : 16})
|
137 |
+
var mTextSel = mSel.append('text').text('23%')
|
138 |
+
.at({dy: '.33em', textAnchor: 'middle', x: width/4*3 + 5, fontSize: isScaled ? 20 : 16})
|
139 |
+
|
140 |
+
fSel.append('text').text('Adults').st({fontSize: isScaled ? 18 : 12})
|
141 |
+
.at({textAnchor: 'middle', x: -23, y: -30})
|
142 |
+
mSel.append('text').text('Children').st({fontSize: isScaled ? 18 : 12})
|
143 |
+
.at({textAnchor: 'middle', x: 124, y: -30})
|
144 |
+
|
145 |
+
var mDiv = sel.append('div').st({position: 'relative', top: -h + 7})
|
146 |
+
.datum({w: 50, s: m, isText: 0, colors}).each(drawMatrix)
|
147 |
+
|
148 |
+
|
149 |
+
renderFns.push(() => {
|
150 |
+
var fPercent = percentFn(f)
|
151 |
+
fSel.translate(h - h*fPercent, 1)
|
152 |
+
fTextSel.text(d3.format('.0%')(fPercent))
|
153 |
+
|
154 |
+
var mPercent = percentFn(m)
|
155 |
+
mSel.translate(h - h*mPercent, 1)
|
156 |
+
mTextSel.text(d3.format('.0%')(mPercent))
|
157 |
+
|
158 |
+
fDiv.translate(h - h*fPercent, 1)
|
159 |
+
mDiv.translate(h - h*mPercent, 1)
|
160 |
+
|
161 |
+
errorSel.at({d: 'M 0 ' + (h - h*fPercent) + ' V ' + (h - h*mPercent) })
|
162 |
+
})
|
163 |
+
}
|
164 |
+
|
165 |
+
function drawMatrix({s, w, isText, colors}){
|
166 |
+
var svg = d3.select(this).append('svg')
|
167 |
+
.at({width: w, height: w})
|
168 |
+
|
169 |
+
|
170 |
+
svg.append('rect').at({width: w + 1, height: w + 1})
|
171 |
+
|
172 |
+
if (!colors) colors = ['#000', '#000', '#000', '#000']
|
173 |
+
|
174 |
+
var rects = [
|
175 |
+
{n: 'tp', x: 0, y: 0, width: _ => s.b*w, height: _ => s.tpr*w},
|
176 |
+
{n: 'fn', x: 0, y: _ => 1 + s.tpr*w, width: _ => s.b*w, height: _ => w - s.tpr*w},
|
177 |
+
{n: 'fp', x: _ => 1 + s.b*w, y: 0, width: _ => w - s.b*w, height: _ => s.fnr*w},
|
178 |
+
{n: 'tn', x: _ => 1 + s.b*w, y: _ => 1 + s.fnr*w, width: _ => w - s.b*w, height: _ => w - s.fnr*w},
|
179 |
+
]
|
180 |
+
rects.forEach((d, i) => d.i = i)
|
181 |
+
|
182 |
+
var rectSel = svg.appendMany('rect', rects)
|
183 |
+
.at({fill: d => `url(#pattern-${w}-${d.i}`})
|
184 |
+
// .at({opacity: d => colors[d.i] == '#fff' ? .5 : 1})
|
185 |
+
// .at({fill: d => `url(#pattern-${w}-${d.i + (colors[d.i] == '#ccc' ? 4 : 0)})`})
|
186 |
+
// .at({fill: d => colors[d.i] == '#ccc' ? '#000' : `url(#pattern-${w}-${d.i + (colors[d.i] == '#ccc' ? 4 : 0)})`})
|
187 |
+
.each(function(d){ d.sel = d3.select(this) })
|
188 |
+
rectSel.filter(d => colors[d.i] == '#fff').at({fill: '#eee'})
|
189 |
+
|
190 |
+
var bh = .5
|
191 |
+
svg.append('rect.tpr').at({height: bh}).translate(-bh/2, 1)
|
192 |
+
.datum('tpr')
|
193 |
+
|
194 |
+
svg.append('rect.fnr').at({height: bh}).translate(-bh/2, 1)
|
195 |
+
.datum('fnr')
|
196 |
+
|
197 |
+
svg.append('rect.b').at({width: bh, height: w}).translate(-bh/2, 0)
|
198 |
+
.datum('b')
|
199 |
+
|
200 |
+
var bh = 20
|
201 |
+
svg.append('rect.drag.tpr').at({height: bh}).translate(-bh/2, 1)
|
202 |
+
.call(makeDrag('tpr', 1)).datum('tpr').call(d3.attachTooltip).on('mouseover', ttFormat)
|
203 |
+
|
204 |
+
svg.append('rect.drag.fnr').at({height: bh}).translate(-bh/2, 1)
|
205 |
+
.call(makeDrag('fnr', 1)).datum('fnr').call(d3.attachTooltip).on('mouseover', ttFormat)
|
206 |
+
|
207 |
+
svg.append('rect.drag.b').at({width: bh, height: w}).translate(-bh/2, 0)
|
208 |
+
.call(makeDrag('b', 0)).datum('b').call(d3.attachTooltip).on('mouseover', ttFormat)
|
209 |
+
|
210 |
+
|
211 |
+
var tprRect = svg.selectAll('rect.tpr')
|
212 |
+
var fnrRect = svg.selectAll('rect.fnr')
|
213 |
+
var bRect = svg.selectAll('rect.b')
|
214 |
+
|
215 |
+
function ttFormat(str){
|
216 |
+
var html = ''
|
217 |
+
if (str == 'tpr') html = `${d3.format('.0%')(s.tpr)} of sick ${s.titleStr.toLowerCase()} test positive`
|
218 |
+
if (str == 'fnr') html = `${d3.format('.0%')(s.fnr)} of well ${s.titleStr.toLowerCase()} test negative`
|
219 |
+
if (str == 'b') html = `${d3.format('.0%')(s.b)} of ${s.titleStr.toLowerCase()} are sick`
|
220 |
+
ttSel.html(html)
|
221 |
+
}
|
222 |
+
|
223 |
+
function makeDrag(str, index){
|
224 |
+
|
225 |
+
return d3.drag()
|
226 |
+
.on('drag', function(){
|
227 |
+
var percent = d3.mouse(this)[index]/w
|
228 |
+
s[str] = d3.clamp(.15, percent, .85)
|
229 |
+
|
230 |
+
window.basetimer.stop()
|
231 |
+
s.update()
|
232 |
+
|
233 |
+
ttMove()
|
234 |
+
ttFormat(str)
|
235 |
+
})
|
236 |
+
.on('start', _ => svg.classed('dragging', 1))
|
237 |
+
.on('end', _ => svg.classed('dragging', 0))
|
238 |
+
}
|
239 |
+
|
240 |
+
renderFns.push(() => {
|
241 |
+
rectSel.each(d => d.sel.at(d))
|
242 |
+
|
243 |
+
tprRect.at({width: w*s.b, y: w*s.tpr})
|
244 |
+
fnrRect.at({x: w*s.b, width: w - w*s.b, y: w*s.fnr})
|
245 |
+
bRect.at({x: w*s.b})
|
246 |
+
|
247 |
+
// s => s.tpr,
|
248 |
+
// s => s.b*s.tpr/(s.b*s.tpr + (1 - s.b)*(s.fnr)),
|
249 |
+
// s => 1 - s.fnr,
|
250 |
+
if (!isText) return
|
251 |
+
})
|
252 |
+
|
253 |
+
|
254 |
+
if (!isText) return
|
255 |
+
|
256 |
+
svg.append('text').text(s.titleStr).at({textAnchor: 'middle', x: w/2, y: -8, fontSize: 20})
|
257 |
+
|
258 |
+
if (innerWidth < 800) return
|
259 |
+
// if (true)
|
260 |
+
|
261 |
+
svg.appendMany('text', d3.range(4)).each(function(i){
|
262 |
+
var isSick = i < 2
|
263 |
+
var isPos = i % 2
|
264 |
+
|
265 |
+
var pad = 5
|
266 |
+
d3.select(this)
|
267 |
+
.translate([isSick ? pad : w - pad, isPos ? 13 : w - 23])
|
268 |
+
.at({
|
269 |
+
textAnchor: isSick ? 'start' : 'end',
|
270 |
+
fill: '#000',
|
271 |
+
fontSize: 12,
|
272 |
+
fontFamily: 'monospace',
|
273 |
+
pointerEvents: 'none',
|
274 |
+
})
|
275 |
+
.tspans([
|
276 |
+
' test : ' + (isPos ? 'sick' : 'well'),
|
277 |
+
'truth: ' + (isSick ? 'sick' : 'well')])
|
278 |
+
})
|
279 |
+
}
|
280 |
+
|
281 |
+
|
282 |
+
if (window.basetimer) window.basetimer.stop()
|
283 |
+
window.basetimer = d3.timer(t => {
|
284 |
+
|
285 |
+
var val = t/1000 % (Math.PI*4)
|
286 |
+
|
287 |
+
if (val < Math.PI*2){
|
288 |
+
m.b = (Math.sin(val + Math.PI/2))/4 + .4
|
289 |
+
} else if (Math.PI*3 < val && val < Math.PI*5 || true){
|
290 |
+
f.tpr = (Math.sin(val + Math.PI/2))/4 + .4
|
291 |
+
}
|
292 |
+
m.update()
|
293 |
+
})
|
294 |
+
|
295 |
+
|
296 |
+
|
297 |
+
|
298 |
+
|
299 |
+
m.update()
|
300 |
+
|
301 |
+
|
302 |
+
|
303 |
+
function ttMove(d){
|
304 |
+
if (!ttSel.size()) return;
|
305 |
+
|
306 |
+
var e = d3.event.sourceEvent,
|
307 |
+
x = e.clientX,
|
308 |
+
y = e.clientY,
|
309 |
+
bb = ttSel.node().getBoundingClientRect(),
|
310 |
+
left = d3.clamp(20, (x-bb.width/2), window.innerWidth - bb.width - 20),
|
311 |
+
top = innerHeight > y + 20 + bb.height ? y + 20 : y - bb.height - 20;
|
312 |
+
|
313 |
+
ttSel
|
314 |
+
.style('left', left +'px')
|
315 |
+
.style('top', top + 'px');
|
316 |
+
}
|
317 |
+
|
public/base-rate/sliders.js
ADDED
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* Copyright 2020 Google LLC. All Rights Reserved.
|
2 |
+
|
3 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
you may not use this file except in compliance with the License.
|
5 |
+
You may obtain a copy of the License at
|
6 |
+
|
7 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
|
9 |
+
Unless required by applicable law or agreed to in writing, software
|
10 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
See the License for the specific language governing permissions and
|
13 |
+
limitations under the License.
|
14 |
+
==============================================================================*/
|
15 |
+
|
16 |
+
|
17 |
+
|
18 |
+
|
19 |
+
|
20 |
+
var sliderVals = {}
|
21 |
+
|
22 |
+
var sliders = [
|
23 |
+
{
|
24 |
+
key: 'fNoiseMag',
|
25 |
+
text: 'Feature Noise',
|
26 |
+
r: [0, 1],
|
27 |
+
v: .5
|
28 |
+
},
|
29 |
+
{
|
30 |
+
key: 'fBiasMag',
|
31 |
+
text: 'Feature Bias',
|
32 |
+
r: [0, 1],
|
33 |
+
v: .2
|
34 |
+
},
|
35 |
+
]
|
36 |
+
|
37 |
+
!(function(){
|
38 |
+
var width = 145
|
39 |
+
var height = 30
|
40 |
+
|
41 |
+
sliders.forEach(d => {
|
42 |
+
d.s = d3.scaleLinear().domain(d.r).range([0, width])
|
43 |
+
sliderVals[d.key] = d
|
44 |
+
})
|
45 |
+
|
46 |
+
var sliderSel = d3.select('.slider').html('')
|
47 |
+
.appendMany('div', sliders)
|
48 |
+
.at({class: d => d.key})
|
49 |
+
.st({
|
50 |
+
display: 'inline-block',
|
51 |
+
width: width,
|
52 |
+
paddingRight: 60,
|
53 |
+
marginTop: 20,
|
54 |
+
color: '#000'
|
55 |
+
})
|
56 |
+
|
57 |
+
sliderSel.append('div')
|
58 |
+
.text(d => d.text)
|
59 |
+
.st({marginBottom: height/2})
|
60 |
+
|
61 |
+
var svgSel = sliderSel.append('svg').at({width, height})
|
62 |
+
.on('click', function(d){
|
63 |
+
d.v = d.s.invert(d3.mouse(this)[0])
|
64 |
+
updatePos()
|
65 |
+
})
|
66 |
+
.st({
|
67 |
+
cursor: 'pointer'
|
68 |
+
})
|
69 |
+
.append('g').translate(height/2, 1)
|
70 |
+
svgSel.append('rect').at({width, height, y: -height/2, fill: '#fff'})
|
71 |
+
|
72 |
+
svgSel.append('path').at({
|
73 |
+
d: `M 0 0 H ${width}`,
|
74 |
+
stroke: '#000',
|
75 |
+
strokeWidth: 2
|
76 |
+
})
|
77 |
+
|
78 |
+
var drag = d3.drag()
|
79 |
+
.on('drag', function(d){
|
80 |
+
var x = d3.mouse(this)[0]
|
81 |
+
d.v = d3.clamp(d3.min(d.r), d.s.invert(x), d3.max(d.r))
|
82 |
+
|
83 |
+
updatePos()
|
84 |
+
})
|
85 |
+
|
86 |
+
var circleSel = svgSel.append('circle')
|
87 |
+
.at({
|
88 |
+
r: height/2,
|
89 |
+
stroke: '#000',
|
90 |
+
strokeWidth: 2,
|
91 |
+
fill: '#fff',
|
92 |
+
})
|
93 |
+
.call(drag)
|
94 |
+
|
95 |
+
|
96 |
+
function updatePos(){
|
97 |
+
circleSel.at({cx: d => d.s(d.v)})
|
98 |
+
if (sliderVals.onUpdate) sliderVals.onUpdate()
|
99 |
+
}
|
100 |
+
|
101 |
+
updatePos()
|
102 |
+
sliderVals.updatePos = updatePos
|
103 |
+
})()
|
public/base-rate/style.css
ADDED
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* Copyright 2020 Google LLC. All Rights Reserved.
|
2 |
+
|
3 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
you may not use this file except in compliance with the License.
|
5 |
+
You may obtain a copy of the License at
|
6 |
+
|
7 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
|
9 |
+
Unless required by applicable law or agreed to in writing, software
|
10 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
See the License for the specific language governing permissions and
|
13 |
+
limitations under the License.
|
14 |
+
==============================================================================*/
|
15 |
+
|
16 |
+
|
17 |
+
|
18 |
+
.tooltip {
|
19 |
+
top: -1000px;
|
20 |
+
position: fixed;
|
21 |
+
padding: 10px;
|
22 |
+
background: rgba(255, 255, 255, .90);
|
23 |
+
border: 1px solid lightgray;
|
24 |
+
pointer-events: none;
|
25 |
+
width: auto;
|
26 |
+
|
27 |
+
}
|
28 |
+
.tooltip-hidden{
|
29 |
+
opacity: 0;
|
30 |
+
transition: all .3s;
|
31 |
+
transition-delay: .1s;
|
32 |
+
}
|
33 |
+
|
34 |
+
@media (max-width: 590px){
|
35 |
+
div.tooltip{
|
36 |
+
bottom: -1px;
|
37 |
+
width: calc(100%);
|
38 |
+
left: -1px !important;
|
39 |
+
right: -1px !important;
|
40 |
+
top: auto !important;
|
41 |
+
width: auto !important;
|
42 |
+
}
|
43 |
+
}
|
44 |
+
|
45 |
+
svg{
|
46 |
+
overflow: visible;
|
47 |
+
}
|
48 |
+
|
49 |
+
.domain{
|
50 |
+
display: none;
|
51 |
+
}
|
52 |
+
|
53 |
+
#big-matrix text{
|
54 |
+
font-family: 'Google Sans', sans-serif;
|
55 |
+
/*pointer-events: none;*/
|
56 |
+
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
|
57 |
+
text-shadow: 0 1px 0 rgba(255,255,255, .6), 1px 0 0 rgba(255,255,255, .6), 0 -1px 0 rgba(255,255,255, .6), -1px 0 0 rgba(255,255,255, .6);
|
58 |
+
}
|
59 |
+
|
60 |
+
|
61 |
+
body{
|
62 |
+
max-width: 900px;
|
63 |
+
}
|
64 |
+
|
65 |
+
h1{
|
66 |
+
}
|
67 |
+
|
68 |
+
h1{
|
69 |
+
/*text-align: center;*/
|
70 |
+
}
|
71 |
+
|
72 |
+
h3{
|
73 |
+
font-size: 20px;
|
74 |
+
}
|
75 |
+
#big-matrix{
|
76 |
+
text-align: center;
|
77 |
+
margin-top: 40px;
|
78 |
+
font-family: 'Google Sans', sans-serif;
|
79 |
+
|
80 |
+
}
|
81 |
+
div.big-container{
|
82 |
+
display: inline-block;
|
83 |
+
margin: 10px;
|
84 |
+
}
|
85 |
+
|
86 |
+
#metrics{
|
87 |
+
text-align: center;
|
88 |
+
}
|
89 |
+
div.metrics-container{
|
90 |
+
display: inline-block;
|
91 |
+
margin: 10px;
|
92 |
+
}
|
93 |
+
|
94 |
+
div.metrics-container > div{
|
95 |
+
display: inline-block;
|
96 |
+
vertical-align: middle;
|
97 |
+
pointer-events: none;
|
98 |
+
}
|
99 |
+
|
100 |
+
|
101 |
+
|
102 |
+
|
103 |
+
.drag{
|
104 |
+
cursor: pointer;
|
105 |
+
fill-opacity: 0;
|
106 |
+
fill: #f0f;
|
107 |
+
stroke-opacity: 0;
|
108 |
+
}
|
109 |
+
|
110 |
+
svg.dragging{
|
111 |
+
cursor: pointer;
|
112 |
+
}
|
113 |
+
|
114 |
+
sl{
|
115 |
+
/*background: #000; */
|
116 |
+
color: #000;
|
117 |
+
border: 1px solid #eee;
|
118 |
+
width: 1em;
|
119 |
+
display: inline-block;
|
120 |
+
padding-left: 2px;
|
121 |
+
padding-right: 2px;
|
122 |
+
font-style: normal;
|
123 |
+
}
|
124 |
+
|
125 |
+
#instructions{
|
126 |
+
margin-top: 10px;
|
127 |
+
margin-bottom: 10px;
|
128 |
+
text-align: center;
|
129 |
+
}
|
130 |
+
|
131 |
+
|
132 |
+
|
133 |
+
|
134 |
+
|
public/data-leak/face.png
ADDED
Git LFS Details
|
public/data-leak/index.html
ADDED
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!--
|
2 |
+
@license
|
3 |
+
Copyright 2020 Google. All Rights Reserved.
|
4 |
+
|
5 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6 |
+
you may not use this file except in compliance with the License.
|
7 |
+
You may obtain a copy of the License at
|
8 |
+
|
9 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
10 |
+
|
11 |
+
Unless required by applicable law or agreed to in writing, software
|
12 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14 |
+
See the License for the specific language governing permissions and
|
15 |
+
limitations under the License.
|
16 |
+
-->
|
17 |
+
|
18 |
+
<!DOCTYPE html>
|
19 |
+
|
20 |
+
<html>
|
21 |
+
<head>
|
22 |
+
<meta charset="utf-8">
|
23 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
24 |
+
|
25 |
+
<link rel="apple-touch-icon" sizes="180x180" href="https://pair.withgoogle.com/images/favicon/apple-touch-icon.png">
|
26 |
+
<link rel="icon" type="image/png" sizes="32x32" href="https://pair.withgoogle.com/images/favicon/favicon-32x32.png">
|
27 |
+
<link rel="icon" type="image/png" sizes="16x16" href="https://pair.withgoogle.com/images/favicon/favicon-16x16.png">
|
28 |
+
<link rel="mask-icon" href="https://pair.withgoogle.com/images/favicon/safari-pinned-tab.svg" color="#00695c">
|
29 |
+
<link rel="shortcut icon" href="https://pair.withgoogle.com/images/favicon.ico">
|
30 |
+
|
31 |
+
<script>
|
32 |
+
!(function(){
|
33 |
+
var url = window.location.href
|
34 |
+
if (url.split('#')[0].split('?')[0].slice(-1) != '/' && !url.includes('.html')) window.location = url + '/'
|
35 |
+
})()
|
36 |
+
</script>
|
37 |
+
|
38 |
+
<title>Why Some Models Leak Data</title>
|
39 |
+
<meta property="og:title" content="Why Some Models Leak Data">
|
40 |
+
<meta property="og:url" content="https://pair.withgoogle.com/explorables/data-leak/">
|
41 |
+
|
42 |
+
<meta name="og:description" content="Machine learning models use large amounts of data, some of which can be sensitive. If they're not trained correctly, sometimes that data is inadvertently revealed.">
|
43 |
+
<meta property="og:image" content="https://pair.withgoogle.com/explorables/images/model-inversion.png">
|
44 |
+
<meta name="twitter:card" content="summary_large_image">
|
45 |
+
|
46 |
+
<link rel="stylesheet" type="text/css" href="../style.css">
|
47 |
+
|
48 |
+
<link href='https://fonts.googleapis.com/css?family=Roboto+Slab:400,500,700|Roboto:700,500,300' rel='stylesheet' type='text/css'>
|
49 |
+
<link href="https://fonts.googleapis.com/css?family=Google+Sans:400,500,700" rel="stylesheet">
|
50 |
+
|
51 |
+
<meta name="viewport" content="width=device-width">
|
52 |
+
</head>
|
53 |
+
<body>
|
54 |
+
<div class='header'>
|
55 |
+
<div class='header-left'>
|
56 |
+
<a href='https://pair.withgoogle.com/'>
|
57 |
+
<img src='../images/pair-logo.svg' style='width: 100px'></img>
|
58 |
+
</a>
|
59 |
+
<a href='../'>Explorables</a>
|
60 |
+
</div>
|
61 |
+
</div>
|
62 |
+
|
63 |
+
<h1 class='headline'>Why Some Models Leak Data</h1>
|
64 |
+
<div class="post-summary">Machine learning models use large amounts of data, some of which can be sensitive. If they're not trained correctly, sometimes that data is inadvertently revealed.</div>
|
65 |
+
<link rel="stylesheet" href="style.css">
|
66 |
+
|
67 |
+
|
68 |
+
<p>Let’s take a look at a game of soccer. </p>
|
69 |
+
<link rel="stylesheet" href="style.css">
|
70 |
+
|
71 |
+
<div id='field-grass' class='field'></div>
|
72 |
+
|
73 |
+
<p><br></br> </p>
|
74 |
+
<p>Using the position of each player as training data, we can teach a model to predict which team would get to a loose ball first at each spot on the field, indicated by the color of the pixel.</p>
|
75 |
+
<div id='field-prediction' class='field'></div>
|
76 |
+
|
77 |
+
<p>It updates in real-time—drag the players around to see the model change.</p>
|
78 |
+
<p><br></br> </p>
|
79 |
+
<p>This model reveals quite a lot about the data used to train it. Even without the actual positions of the players, it is simple to see where players might be. </p>
|
80 |
+
<div id='field-playerless' class='field'></div>
|
81 |
+
|
82 |
+
<p>Click this button to <span class="button" id="player-button">move the players</span> </p>
|
83 |
+
<p>Take a guess at where the yellow team’s goalie is now, then check their actual position. How close were you?</p>
|
84 |
+
<h3>Sensitive Salary Data</h3>
|
85 |
+
|
86 |
+
<p>In this specific soccer example, being able to make educated guesses about the data a model was trained on doesn’t matter too much. But what if our data points represent something more sensitive?</p>
|
87 |
+
<div id='field-scatter' class='field'></div>
|
88 |
+
|
89 |
+
<p>We’ve fed the same numbers into the model, but now they represent salary data instead of soccer data. Building models like this is a common technique to <a href="https://www.eeoc.gov/laws/guidance/section-10-compensation-discrimination#c.%20Using%20More%20Sophisticated%20Statistical%20Techniques%20to%20Evaluate">detect discrimination</a>. A union might test if a company is paying men and women fairly by building a salary model that takes into account years of experience. They can then <a href="https://postguild.org/2019-pay-study/">publish</a> the results to bring pressure for change or show improvement.</p>
|
90 |
+
<p>In this hypothetical salary study, even though no individual salaries have been published, it is easy to infer the salary of the newest male hire. And carefully cross referencing public start dates on LinkedIn with the model could almost perfectly reveal everyone’s salary.</p>
|
91 |
+
<p>Because the model here is so flexible (there are hundreds of square patches with independently calculated predictions) and we have so few data points (just 22 people), it is able to “memorize” individual data points. If we’re looking to share information about patterns in salaries, a simpler and more constrained model like a linear regression might be more appropriate. </p>
|
92 |
+
<div id='field-regression' class='field'></div>
|
93 |
+
|
94 |
+
<p>By boiling down the 22 data points to two lines we’re able to see broad trends without being able to guess anyone’s salary.</p>
|
95 |
+
<h3>Subtle Leaks</h3>
|
96 |
+
|
97 |
+
<p>Removing complexity isn’t a complete solution though. Depending on how the data is distributed, even a simple line can inadvertently reveal information.</p>
|
98 |
+
<div id='field-regression-leak' class='field'></div>
|
99 |
+
|
100 |
+
<p>In this company, almost all the men started several years ago, so the slope of the line is especially sensitive to the salary of the new hire. </p>
|
101 |
+
<p>Is their salary <span class="button" id="high-button">higher or lower</span> than average? Based on the line, we can make a pretty good guess.</p>
|
102 |
+
<p>Notice that changing the salary of someone with a more common tenure barely moves the line. In general, more typical data points are less susceptible to being leaked. This sets up a tricky trade off: we want models to learn about edge cases while being sure they haven’t memorized individual data points.</p>
|
103 |
+
<h3>Real World Data</h3>
|
104 |
+
|
105 |
+
<p>Models of real world data are often quite complex—this can improve accuracy, but makes them <a href="https://blog.tensorflow.org/2020/06/introducing-new-privacy-testing-library.html">more susceptible</a> to unexpectedly leaking information. Medical models have inadvertently revealed <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4827719/">patients’ genetic markers</a>. Language models have memorized <a href="https://bair.berkeley.edu/blog/2019/08/13/memorization/">credit card numbers</a>. Faces can even be <a href="https://rist.tech.cornell.edu/papers/mi-ccs.pdf">reconstructed</a> from image models: </p>
|
106 |
+
<div class='face-container'><img src='face.png'></div>
|
107 |
+
|
108 |
+
<p><a href="https://rist.tech.cornell.edu/papers/mi-ccs.pdf">Fredrikson et al</a> were able to extract the image on the left by repeatedly querying a facial recognition API. It isn’t an exact match with the individual’s actual face (on the right), but this attack only required access to the model’s predictions, not its internal state. </p>
|
109 |
+
<h3>Protecting Private Data</h3>
|
110 |
+
|
111 |
+
<p>Training models with <a href="http://www.cleverhans.io/privacy/2018/04/29/privacy-and-machine-learning.html">differential privacy</a> stops the training data from leaking by limiting how much the model can learn from any one data point. Differentially private models are still at the cutting edge of research, but they’re being packaged into <a href="https://blog.tensorflow.org/2019/03/introducing-tensorflow-privacy-learning.html">machine learning frameworks</a>, making them much easier to use. When it isn’t possible to train differentially private models, there are also tools that can <a href="https://github.com/tensorflow/privacy/tree/master/tensorflow_privacy/privacy/membership_inference_attack">measure</a> how much data is the model memorizing. Also, standard techniques such as aggregation and limiting how much data a single source can contribute are still useful and usually improve the privacy of the model.</p>
|
112 |
+
<p>As we saw in the <a href="https://pair.withgoogle.com/explorables/anonymization/">Collecting Sensitive Information Explorable</a>, adding enough random noise with differential privacy to protect outliers like the new hire can increase the amount of data required to reach a good level of accuracy. Depending on the application, the constraints of differential privacy could even improve the model—for instance, not learning too much from one data point can help prevent <a href="https://openreview.net/forum?id=r1xyx3R9tQ">overfitting</a>. </p>
|
113 |
+
<p>Given the increasing utility of machine learning models for many real-world tasks, it’s clear that more and more systems, devices and apps will be powered, to some extent, by machine learning in the future. While <a href="https://owasp.org/www-project-top-ten/">standard privacy best practices</a> developed for non-machine learning systems still apply to those with machine learning, the introduction of machine learning introduces new challenges, including the ability of the model to memorize some specific training data points and thus be vulnerable to privacy attacks that seek to extract this data from the model. Fortunately, techniques such as differential privacy exist that can be helpful in overcoming this specific challenge. Just as with other areas of <a href="https://ai.google/responsibilities/responsible-ai-practices/">Responsible AI</a>, it’s important to be aware of these new challenges that come along with machine learning and what steps can be taken to mitigate them. </p>
|
114 |
+
<h3>Credits</h3>
|
115 |
+
|
116 |
+
<p>Adam Pearce and Ellen Jiang // December 2020</p>
|
117 |
+
<p>Thanks to Andreas Terzis, Ben Wedin, Carey Radebaugh, David Weinberger, Emily Reif, Fernanda Viégas, Hal Abelson, Kristen Olson, Martin Wattenberg, Michael Terry, Miguel Guevara, Thomas Steinke, Yannick Assogba, Zan Armstrong and our other colleagues at Google for their help with this piece.</p>
|
118 |
+
<h3>More Explorables</h3>
|
119 |
+
|
120 |
+
<p id='recirc'></p>
|
121 |
+
|
122 |
+
|
123 |
+
<script src='../third_party/d3_.js'></script>
|
124 |
+
<script src='../third_party/simple-statistics.min.js'></script>
|
125 |
+
<script src='players0.js'></script>
|
126 |
+
<script src='script.js'></script>
|
127 |
+
|
128 |
+
|
129 |
+
<script src='../third_party/recirc.js'></script>
|
130 |
+
</body>
|
131 |
+
|
132 |
+
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-138505774-1"></script>
|
133 |
+
<script>
|
134 |
+
if (window.location.origin === 'https://pair.withgoogle.com'){
|
135 |
+
window.dataLayer = window.dataLayer || [];
|
136 |
+
function gtag(){dataLayer.push(arguments);}
|
137 |
+
gtag('js', new Date());
|
138 |
+
gtag('config', 'UA-138505774-1');
|
139 |
+
}
|
140 |
+
</script>
|
141 |
+
|
142 |
+
<script>
|
143 |
+
// Tweaks for displaying in an iframe
|
144 |
+
if (window !== window.parent){
|
145 |
+
|
146 |
+
// Open links in a new tab
|
147 |
+
Array.from(document.querySelectorAll('a'))
|
148 |
+
.forEach(e => {
|
149 |
+
// skip anchor links
|
150 |
+
if (e.href && e.href[0] == '#') return
|
151 |
+
|
152 |
+
e.setAttribute('target', '_blank')
|
153 |
+
e.setAttribute('rel', 'noopener noreferrer')
|
154 |
+
})
|
155 |
+
|
156 |
+
// Remove recirc h3
|
157 |
+
Array.from(document.querySelectorAll('h3'))
|
158 |
+
.forEach(e => {
|
159 |
+
if (e.textContent != 'More Explorables') return
|
160 |
+
|
161 |
+
e.parentNode.removeChild(e)
|
162 |
+
})
|
163 |
+
|
164 |
+
// Remove recirc container
|
165 |
+
var recircEl = document.querySelector('#recirc')
|
166 |
+
recircEl.parentNode.removeChild(recircEl)
|
167 |
+
}
|
168 |
+
</script>
|
169 |
+
|
170 |
+
</html>
|
public/data-leak/players0.js
ADDED
@@ -0,0 +1,456 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
var players0 = [
|
2 |
+
[
|
3 |
+
1.305925030229746,
|
4 |
+
38.016928657799276
|
5 |
+
],
|
6 |
+
[
|
7 |
+
20.894800483675937,
|
8 |
+
23.071342200725514
|
9 |
+
],
|
10 |
+
[
|
11 |
+
24.232164449818622,
|
12 |
+
50.35066505441355
|
13 |
+
],
|
14 |
+
[
|
15 |
+
37.29141475211608,
|
16 |
+
4.643288996372431
|
17 |
+
],
|
18 |
+
[
|
19 |
+
57.89600967351874,
|
20 |
+
25.24788391777509
|
21 |
+
],
|
22 |
+
[
|
23 |
+
41.20918984280532,
|
24 |
+
34.389359129383315
|
25 |
+
],
|
26 |
+
[
|
27 |
+
42.51511487303507,
|
28 |
+
54.26844014510278
|
29 |
+
],
|
30 |
+
[
|
31 |
+
31.77750906892382,
|
32 |
+
67.9081015719468
|
33 |
+
],
|
34 |
+
[
|
35 |
+
63.84522370012092,
|
36 |
+
54.41354292623942
|
37 |
+
],
|
38 |
+
[
|
39 |
+
70.37484885126965,
|
40 |
+
42.22490931076179
|
41 |
+
],
|
42 |
+
[
|
43 |
+
39.32285368802902,
|
44 |
+
56.44498186215236
|
45 |
+
],
|
46 |
+
[
|
47 |
+
35.550181378476424,
|
48 |
+
58.91172914147521
|
49 |
+
],
|
50 |
+
[
|
51 |
+
46.57799274486094,
|
52 |
+
52.8174123337364
|
53 |
+
],
|
54 |
+
[
|
55 |
+
39.6130592503023,
|
56 |
+
37.14631197097945
|
57 |
+
],
|
58 |
+
[
|
59 |
+
42.51511487303507,
|
60 |
+
30.90689238210399
|
61 |
+
],
|
62 |
+
[
|
63 |
+
50.64087061668682,
|
64 |
+
8.706166868198308
|
65 |
+
],
|
66 |
+
[
|
67 |
+
71.10036275695285,
|
68 |
+
8.996372430471585
|
69 |
+
],
|
70 |
+
[
|
71 |
+
75.01813784764208,
|
72 |
+
26.844014510278114
|
73 |
+
],
|
74 |
+
[
|
75 |
+
77.3397823458283,
|
76 |
+
47.44860943168077
|
77 |
+
],
|
78 |
+
[
|
79 |
+
76.17896009673518,
|
80 |
+
59.34703748488513
|
81 |
+
],
|
82 |
+
[
|
83 |
+
105.05441354292624,
|
84 |
+
39.177750906892385
|
85 |
+
],
|
86 |
+
[
|
87 |
+
59.34703748488513,
|
88 |
+
33.083434099153564
|
89 |
+
]
|
90 |
+
]
|
91 |
+
|
92 |
+
|
93 |
+
var players1 = [
|
94 |
+
[
|
95 |
+
6.819830713422007,
|
96 |
+
27.569528415961305
|
97 |
+
],
|
98 |
+
[
|
99 |
+
31.05199516324063,
|
100 |
+
30.03627569528416
|
101 |
+
],
|
102 |
+
[
|
103 |
+
28.440145102781138,
|
104 |
+
43.24062877871826
|
105 |
+
],
|
106 |
+
[
|
107 |
+
48.02902055622733,
|
108 |
+
13.639661426844015
|
109 |
+
],
|
110 |
+
[
|
111 |
+
62.249093107617895,
|
112 |
+
35.69528415961306
|
113 |
+
],
|
114 |
+
[
|
115 |
+
49.915356711003625,
|
116 |
+
26.553808948004836
|
117 |
+
],
|
118 |
+
[
|
119 |
+
53.68802902055623,
|
120 |
+
47.88391777509069
|
121 |
+
],
|
122 |
+
[
|
123 |
+
45.85247883917775,
|
124 |
+
54.123337363966144
|
125 |
+
],
|
126 |
+
[
|
127 |
+
72.8415961305925,
|
128 |
+
46.57799274486094
|
129 |
+
],
|
130 |
+
[
|
131 |
+
70.81015719467956,
|
132 |
+
23.216444981862153
|
133 |
+
],
|
134 |
+
[
|
135 |
+
35.98548972188634,
|
136 |
+
44.11124546553809
|
137 |
+
],
|
138 |
+
[
|
139 |
+
49.48004836759371,
|
140 |
+
59.92744860943168
|
141 |
+
],
|
142 |
+
[
|
143 |
+
46.86819830713422,
|
144 |
+
45.417170495767834
|
145 |
+
],
|
146 |
+
[
|
147 |
+
39.6130592503023,
|
148 |
+
37.14631197097945
|
149 |
+
],
|
150 |
+
[
|
151 |
+
42.37001209189843,
|
152 |
+
24.812575574365177
|
153 |
+
],
|
154 |
+
[
|
155 |
+
53.252720677146314,
|
156 |
+
9.721886336154776
|
157 |
+
],
|
158 |
+
[
|
159 |
+
73.5671100362757,
|
160 |
+
8.996372430471585
|
161 |
+
],
|
162 |
+
[
|
163 |
+
80.96735187424426,
|
164 |
+
26.698911729141475
|
165 |
+
],
|
166 |
+
[
|
167 |
+
85.75574365175332,
|
168 |
+
37.43651753325272
|
169 |
+
],
|
170 |
+
[
|
171 |
+
87.35187424425635,
|
172 |
+
47.88391777509069
|
173 |
+
],
|
174 |
+
[
|
175 |
+
112.59975816203143,
|
176 |
+
31.77750906892382
|
177 |
+
],
|
178 |
+
[
|
179 |
+
58.041112454655384,
|
180 |
+
25.97339782345828
|
181 |
+
]
|
182 |
+
]
|
183 |
+
|
184 |
+
var players2 = [
|
185 |
+
[
|
186 |
+
22.6360338573156,
|
187 |
+
36.27569528415961
|
188 |
+
],
|
189 |
+
[
|
190 |
+
49.48004836759371,
|
191 |
+
18.71825876662636
|
192 |
+
],
|
193 |
+
[
|
194 |
+
43.82103990326481,
|
195 |
+
34.82466747279323
|
196 |
+
],
|
197 |
+
[
|
198 |
+
94.89721886336154,
|
199 |
+
6.674727932285369
|
200 |
+
],
|
201 |
+
[
|
202 |
+
103.31318016928658,
|
203 |
+
24.522370012091898
|
204 |
+
],
|
205 |
+
[
|
206 |
+
82.12817412333736,
|
207 |
+
32.0677146311971
|
208 |
+
],
|
209 |
+
[
|
210 |
+
52.8174123337364,
|
211 |
+
56.009673518742446
|
212 |
+
],
|
213 |
+
[
|
214 |
+
91.26964933494558,
|
215 |
+
55.28415961305925
|
216 |
+
],
|
217 |
+
[
|
218 |
+
99.68561064087062,
|
219 |
+
40.33857315598549
|
220 |
+
],
|
221 |
+
[
|
222 |
+
105.19951632406288,
|
223 |
+
40.33857315598549
|
224 |
+
],
|
225 |
+
[
|
226 |
+
53.542926239419586,
|
227 |
+
43.966142684401454
|
228 |
+
],
|
229 |
+
[
|
230 |
+
49.48004836759371,
|
231 |
+
59.92744860943168
|
232 |
+
],
|
233 |
+
[
|
234 |
+
58.18621523579202,
|
235 |
+
37.87182587666263
|
236 |
+
],
|
237 |
+
[
|
238 |
+
86.91656590084644,
|
239 |
+
37.58162031438936
|
240 |
+
],
|
241 |
+
[
|
242 |
+
59.34703748488513,
|
243 |
+
18.137847642079805
|
244 |
+
],
|
245 |
+
[
|
246 |
+
96.34824667472793,
|
247 |
+
25.24788391777509
|
248 |
+
],
|
249 |
+
[
|
250 |
+
90.97944377267231,
|
251 |
+
8.996372430471585
|
252 |
+
],
|
253 |
+
[
|
254 |
+
104.47400241837968,
|
255 |
+
31.342200725513905
|
256 |
+
],
|
257 |
+
[
|
258 |
+
109.8428053204353,
|
259 |
+
28.295042321644498
|
260 |
+
],
|
261 |
+
[
|
262 |
+
105.05441354292624,
|
263 |
+
43.24062877871826
|
264 |
+
],
|
265 |
+
[
|
266 |
+
116.2273276904474,
|
267 |
+
25.538089480048367
|
268 |
+
],
|
269 |
+
[
|
270 |
+
86.62636033857315,
|
271 |
+
29.165659008464328
|
272 |
+
]
|
273 |
+
]
|
274 |
+
|
275 |
+
|
276 |
+
playersleakhigh = [
|
277 |
+
[
|
278 |
+
2.71764705882353,
|
279 |
+
22
|
280 |
+
],
|
281 |
+
[
|
282 |
+
38.11764705882353,
|
283 |
+
44.75294117647059
|
284 |
+
],
|
285 |
+
[
|
286 |
+
31.058823529411764,
|
287 |
+
53.22352941176471
|
288 |
+
],
|
289 |
+
[
|
290 |
+
52.94117647058824,
|
291 |
+
51.10588235294118
|
292 |
+
],
|
293 |
+
[
|
294 |
+
58.023529411764706,
|
295 |
+
50.11764705882353
|
296 |
+
],
|
297 |
+
[
|
298 |
+
46.305882352941175,
|
299 |
+
51.247058823529414
|
300 |
+
],
|
301 |
+
[
|
302 |
+
46.023529411764706,
|
303 |
+
42.635294117647064
|
304 |
+
],
|
305 |
+
[
|
306 |
+
41.082352941176474,
|
307 |
+
48.98823529411765
|
308 |
+
],
|
309 |
+
[
|
310 |
+
49.411764705882355,
|
311 |
+
43.76470588235294
|
312 |
+
],
|
313 |
+
[
|
314 |
+
59.71764705882353,
|
315 |
+
43.48235294117647
|
316 |
+
],
|
317 |
+
[
|
318 |
+
39.32285368802902,
|
319 |
+
56.44498186215236
|
320 |
+
],
|
321 |
+
[
|
322 |
+
67.76470588235294,
|
323 |
+
30.494117647058825
|
324 |
+
],
|
325 |
+
[
|
326 |
+
78.07058823529412,
|
327 |
+
48.28235294117647
|
328 |
+
],
|
329 |
+
[
|
330 |
+
69.60000000000001,
|
331 |
+
40.23529411764706
|
332 |
+
],
|
333 |
+
[
|
334 |
+
76.09411764705882,
|
335 |
+
23.152941176470588
|
336 |
+
],
|
337 |
+
[
|
338 |
+
85.9764705882353,
|
339 |
+
24.282352941176473
|
340 |
+
],
|
341 |
+
[
|
342 |
+
84.56470588235294,
|
343 |
+
48.98823529411765
|
344 |
+
],
|
345 |
+
[
|
346 |
+
74.68235294117648,
|
347 |
+
39.38823529411765
|
348 |
+
],
|
349 |
+
[
|
350 |
+
79.3529411764706,
|
351 |
+
22
|
352 |
+
],
|
353 |
+
[
|
354 |
+
93.1764705882353,
|
355 |
+
34.44705882352941
|
356 |
+
],
|
357 |
+
[
|
358 |
+
86.68235294117648,
|
359 |
+
33.45882352941177
|
360 |
+
],
|
361 |
+
[
|
362 |
+
81.74117647058824,
|
363 |
+
41.92941176470588
|
364 |
+
]
|
365 |
+
]
|
366 |
+
|
367 |
+
playersleaklow = [
|
368 |
+
[
|
369 |
+
2.71764705882353,
|
370 |
+
73.12941176470588
|
371 |
+
],
|
372 |
+
[
|
373 |
+
38.11764705882353,
|
374 |
+
44.75294117647059
|
375 |
+
],
|
376 |
+
[
|
377 |
+
31.058823529411764,
|
378 |
+
53.22352941176471
|
379 |
+
],
|
380 |
+
[
|
381 |
+
52.94117647058824,
|
382 |
+
51.10588235294118
|
383 |
+
],
|
384 |
+
[
|
385 |
+
58.023529411764706,
|
386 |
+
50.11764705882353
|
387 |
+
],
|
388 |
+
[
|
389 |
+
46.305882352941175,
|
390 |
+
51.247058823529414
|
391 |
+
],
|
392 |
+
[
|
393 |
+
46.023529411764706,
|
394 |
+
42.635294117647064
|
395 |
+
],
|
396 |
+
[
|
397 |
+
41.082352941176474,
|
398 |
+
48.98823529411765
|
399 |
+
],
|
400 |
+
[
|
401 |
+
49.411764705882355,
|
402 |
+
43.76470588235294
|
403 |
+
],
|
404 |
+
[
|
405 |
+
59.71764705882353,
|
406 |
+
43.48235294117647
|
407 |
+
],
|
408 |
+
[
|
409 |
+
39.32285368802902,
|
410 |
+
56.44498186215236
|
411 |
+
],
|
412 |
+
[
|
413 |
+
67.76470588235294,
|
414 |
+
30.494117647058825
|
415 |
+
],
|
416 |
+
[
|
417 |
+
78.07058823529412,
|
418 |
+
48.28235294117647
|
419 |
+
],
|
420 |
+
[
|
421 |
+
69.60000000000001,
|
422 |
+
40.23529411764706
|
423 |
+
],
|
424 |
+
[
|
425 |
+
76.09411764705882,
|
426 |
+
23.152941176470588
|
427 |
+
],
|
428 |
+
[
|
429 |
+
85.9764705882353,
|
430 |
+
24.282352941176473
|
431 |
+
],
|
432 |
+
[
|
433 |
+
84.56470588235294,
|
434 |
+
48.98823529411765
|
435 |
+
],
|
436 |
+
[
|
437 |
+
74.68235294117648,
|
438 |
+
39.38823529411765
|
439 |
+
],
|
440 |
+
[
|
441 |
+
79.3529411764706,
|
442 |
+
72.70588235294117
|
443 |
+
],
|
444 |
+
[
|
445 |
+
93.1764705882353,
|
446 |
+
34.44705882352941
|
447 |
+
],
|
448 |
+
[
|
449 |
+
86.68235294117648,
|
450 |
+
33.45882352941177
|
451 |
+
],
|
452 |
+
[
|
453 |
+
81.74117647058824,
|
454 |
+
41.92941176470588
|
455 |
+
]
|
456 |
+
]
|
public/data-leak/script.js
ADDED
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
console.clear()
|
2 |
+
|
3 |
+
var isMobile = innerWidth < 1000
|
4 |
+
d3.select('body').classed('is-mobile', isMobile)
|
5 |
+
|
6 |
+
var colors = ['#FDE100', '#EE2737' ]
|
7 |
+
var colors = ['#FDE100', '#8e068e' ]
|
8 |
+
// var colors = ['#2979FF', '#FF6D00']
|
9 |
+
// var colors = ['#2979FF', '#FDD835']
|
10 |
+
// var colors = ['#f1a340', '#998ec3' ]
|
11 |
+
|
12 |
+
var color2dark = {
|
13 |
+
'#FDE100': d3.color('#FDE100').darker(.2),
|
14 |
+
'#8e068e': d3.color('#8e068e').darker(2),
|
15 |
+
}
|
16 |
+
|
17 |
+
var colorScale = d3.interpolate(colors[0], colors[1])
|
18 |
+
|
19 |
+
var s = d3.select('#field-grass').node().offsetWidth/120
|
20 |
+
|
21 |
+
var width = 120*s
|
22 |
+
var height = Math.floor(75*s)
|
23 |
+
|
24 |
+
var cs = 20
|
25 |
+
var cells = d3.cross(
|
26 |
+
d3.range(0, width + cs, cs),
|
27 |
+
d3.range(0, height + cs, cs))
|
28 |
+
|
29 |
+
|
30 |
+
|
31 |
+
globalPlayers = decoratePlayers(players0)
|
32 |
+
globalPlayersH = decoratePlayers(playersleaklow)
|
33 |
+
|
34 |
+
function decoratePlayers(rawPlayers){
|
35 |
+
var players = rawPlayers.map(d => d.map(d => d*s))
|
36 |
+
players.forEach((d, i) => {
|
37 |
+
d.color = i < 11 ? colors[0] : colors[1]
|
38 |
+
d.isRed = i < 11 ? 1 : 0
|
39 |
+
d.i = i
|
40 |
+
})
|
41 |
+
|
42 |
+
players.renderFns = []
|
43 |
+
players.renderAll = () => players.renderFns.forEach(d => d())
|
44 |
+
|
45 |
+
return players
|
46 |
+
}
|
47 |
+
|
48 |
+
var playerOptions0 = [players1, players2, players0]
|
49 |
+
var playerOptions1 = [playersleaklow, playersleakhigh]
|
50 |
+
|
51 |
+
// addPlayAnimation(globalPlayers, '#field-grass', playerOptions0, 'mouseenter')
|
52 |
+
addPlayAnimation(globalPlayers, '#player-button', playerOptions0)
|
53 |
+
addPlayAnimation(globalPlayersH, '#high-button', playerOptions1, 'click', true)
|
54 |
+
|
55 |
+
function addPlayAnimation(players, selStr, playerOptions, eventStr='click', loop=false){
|
56 |
+
if (loop) {
|
57 |
+
window.loopInterval = d3.interval(playAnimation, 2500)
|
58 |
+
}
|
59 |
+
if (selStr) {
|
60 |
+
d3.selectAll(selStr).on(eventStr, function() {
|
61 |
+
if (loop) window.loopInterval.stop() // stop looping if the higher-or-lower button is pressed
|
62 |
+
playAnimation()
|
63 |
+
})
|
64 |
+
}
|
65 |
+
|
66 |
+
var curPlayerIndex = 0
|
67 |
+
function playAnimation(){
|
68 |
+
curPlayerIndex++
|
69 |
+
curPlayerIndex = curPlayerIndex % playerOptions.length
|
70 |
+
|
71 |
+
var nextPlayers = playerOptions[curPlayerIndex]
|
72 |
+
.map(d => d.map(d => d*s))
|
73 |
+
|
74 |
+
var interpolates = players
|
75 |
+
.map((d, i) => d3.interpolate(d, nextPlayers[i]))
|
76 |
+
|
77 |
+
var dur = 1000
|
78 |
+
if (playerOptions.animationTimer) playerOptions.animationTimer.stop()
|
79 |
+
playerOptions.animationTimer = d3.timer(time => {
|
80 |
+
var t = d3.clamp(0, time/dur, 1)
|
81 |
+
|
82 |
+
interpolates.forEach((interpolate, i) => {
|
83 |
+
var [x, y] = interpolate(t)
|
84 |
+
|
85 |
+
players[i][0] = x
|
86 |
+
players[i][1] = y
|
87 |
+
})
|
88 |
+
|
89 |
+
players.renderAll(t)
|
90 |
+
|
91 |
+
if (t == 1) playerOptions.animationTimer.stop()
|
92 |
+
})
|
93 |
+
}
|
94 |
+
}
|
95 |
+
|
96 |
+
function stopAnimations(){
|
97 |
+
if (playerOptions0.animationTimer) playerOptions0.animationTimer.stop()
|
98 |
+
if (playerOptions1.animationTimer) playerOptions1.animationTimer.stop()
|
99 |
+
}
|
100 |
+
|
101 |
+
|
102 |
+
function initField(name){
|
103 |
+
var marginBottom = 30
|
104 |
+
var marginTop = 35
|
105 |
+
var sel = d3.select('#field-' + name).html('').classed('field', true)
|
106 |
+
.st({marginBottom: marginBottom, marginTop: marginTop})
|
107 |
+
|
108 |
+
window.c = d3.conventions({
|
109 |
+
sel,
|
110 |
+
margin: {top: 0, left: 0, right: 0, bottom: 0},
|
111 |
+
width,
|
112 |
+
height,
|
113 |
+
layers: 'dcs'
|
114 |
+
})
|
115 |
+
|
116 |
+
var [divSel, ctx, svg] = c.layers
|
117 |
+
|
118 |
+
c.svg = c.svg.append('g').translate([.5, .5])
|
119 |
+
|
120 |
+
var isRegression = name.includes('regression')
|
121 |
+
var isVisiblePoints = name != 'playerless'
|
122 |
+
|
123 |
+
var pointName = isRegression || name == 'scatter' ? ' People' : ' Players'
|
124 |
+
var buttonSel = sel.append('div.button')
|
125 |
+
.st({top: pointName == ' People' ? 28 : -8, right: -8, position: 'absolute', background: '#fff'})
|
126 |
+
.text((isVisiblePoints ? 'Hide' : 'Show') + pointName)
|
127 |
+
.on('click', () => {
|
128 |
+
isVisiblePoints = !isVisiblePoints
|
129 |
+
buttonSel.text((isVisiblePoints ? 'Hide' : 'Show') + pointName)
|
130 |
+
playerSel.st({opacity: isVisiblePoints ? 1 : 0})
|
131 |
+
textSel.st({opacity: isVisiblePoints ? 1 : 0})
|
132 |
+
})
|
133 |
+
|
134 |
+
if (name == 'grass'){
|
135 |
+
c.svg.append('rect').at({width, height, fill: '#34A853'})
|
136 |
+
divSel.append('div.pointer').append('div')
|
137 |
+
}
|
138 |
+
|
139 |
+
var roundNum = d => isNaN(d) ? d : Math.round(d)
|
140 |
+
var chalkSel = c.svg.append('g')
|
141 |
+
chalkSel.append('path.white')
|
142 |
+
.at({d: ['M', Math.round(width/2), 0, 'V', height].map(roundNum).join(' '),})
|
143 |
+
chalkSel.append('circle.white')
|
144 |
+
.at({r: 10*s}).translate([width/2, height/2])
|
145 |
+
chalkSel.append('path.white')
|
146 |
+
.at({d: ['M', 0, (75 - 44)/2*s, 'h', 18*s, 'v', 44*s, 'H', 0].map(roundNum).join(' '),})
|
147 |
+
chalkSel.append('path.white')
|
148 |
+
.at({d: ['M', width, (75 - 44)/2*s, 'h', -18*s, 'v', 44*s, 'H', width].map(roundNum).join(' '),})
|
149 |
+
|
150 |
+
var drag = d3.drag()
|
151 |
+
.on('drag', function(d){
|
152 |
+
stopAnimations()
|
153 |
+
if (name === 'regression-leak') {
|
154 |
+
window.loopInterval.stop()
|
155 |
+
}
|
156 |
+
|
157 |
+
d[0] = Math.round(Math.max(0, Math.min(width, d3.event.x)))
|
158 |
+
d[1] = Math.round(Math.max(0, Math.min(height, d3.event.y)))
|
159 |
+
|
160 |
+
players.renderAll()
|
161 |
+
})
|
162 |
+
.subject(function(d){ return {x: d[0], y: d[1]} })
|
163 |
+
|
164 |
+
|
165 |
+
var players = name == 'regression-leak' ? globalPlayersH : globalPlayers
|
166 |
+
|
167 |
+
if (isRegression){
|
168 |
+
var byColor = d3.nestBy(players, d => d.color)
|
169 |
+
var regressionSel = c.svg.appendMany('path', byColor)
|
170 |
+
.at({stroke: d => color2dark[d.key], strokeWidth: 3.5, strokeDasharray: '4 4'})
|
171 |
+
.each(function(d){ d.sel = d3.select(this) })
|
172 |
+
}
|
173 |
+
|
174 |
+
var bgPlayerSel = c.svg.appendMany('circle.player', players)
|
175 |
+
.at({r: 15, fill: d => d.color, opacity: 0})
|
176 |
+
.translate(d => d)
|
177 |
+
.call(drag)
|
178 |
+
|
179 |
+
var playerSel = c.svg.appendMany('circle.player', players)
|
180 |
+
.at({r: 5, fill: d => d.color, opacity: isVisiblePoints ? 1 : 0})
|
181 |
+
.translate(d => d)
|
182 |
+
.call(drag)
|
183 |
+
|
184 |
+
var textSel = c.svg.appendMany('text.chart-title', name == 'playerless' ? [players[0], players[20]] : [players[0]])
|
185 |
+
.text(name == 'regression-leak' || name == 'scatter' ? 'New Hire' : name == 'playerless' ? 'Goalie' : '')
|
186 |
+
.st({pointerEvent: 'none'})
|
187 |
+
.at({dy: '.33em', opacity: isVisiblePoints ? 1 : 0, dx: (d, i) => i ? -8 : 8, textAnchor: (d, i) => i ? 'end' : 'start'})
|
188 |
+
|
189 |
+
if (name == 'scatter' || isRegression){
|
190 |
+
sel.st({marginBottom: marginBottom + 70})
|
191 |
+
sel.insert('div.axis.chart-title', ':first-child')
|
192 |
+
.html(`
|
193 |
+
<span style='background: ${colors[0]}'>Men's</span>
|
194 |
+
and
|
195 |
+
<span style='background: ${colors[1]}'>Women's</span>
|
196 |
+
Salaries`)
|
197 |
+
.st({marginBottom: 10, fontSize: 16})
|
198 |
+
|
199 |
+
c.x.domain([0, 20])
|
200 |
+
c.y.domain([40000, 90000])
|
201 |
+
|
202 |
+
c.xAxis.ticks(5)
|
203 |
+
c.yAxis.ticks(5).tickFormat(d => {
|
204 |
+
var rv = d3.format(',')(d).replace('9', '$9')
|
205 |
+
if (isMobile){
|
206 |
+
rv = rv.replace(',000', 'k').replace('40k', '')
|
207 |
+
}
|
208 |
+
|
209 |
+
return rv
|
210 |
+
})
|
211 |
+
|
212 |
+
|
213 |
+
|
214 |
+
chalkSel.selectAll('*').remove()
|
215 |
+
chalkSel.appendMany('path.white', c.x.ticks(5))
|
216 |
+
.at({d: d => ['M', Math.round(c.x(d)), '0 V ', c.height].join(' ')})
|
217 |
+
|
218 |
+
chalkSel.appendMany('path.white', c.y.ticks(5))
|
219 |
+
.at({d: d => ['M 0', Math.round(c.y(d)), 'H', c.width].join(' ')})
|
220 |
+
|
221 |
+
d3.drawAxis(c)
|
222 |
+
c.svg.selectAll('.axis').lower()
|
223 |
+
if (isMobile){
|
224 |
+
c.svg.selectAll('.y text')
|
225 |
+
.translate([35, 10])
|
226 |
+
.st({fill: name == 'scatter' ? '#000' : ''})
|
227 |
+
|
228 |
+
c.svg.selectAll('.x text').filter(d => d == 20).at({textAnchor: 'end'})
|
229 |
+
c.svg.selectAll('.x text').filter(d => d == 0).at({textAnchor: 'start'})
|
230 |
+
}
|
231 |
+
|
232 |
+
|
233 |
+
c.svg.select('.x').append('text.chart-title')
|
234 |
+
.text('Years at Company →')
|
235 |
+
.translate([c.width/2, 43])
|
236 |
+
.at({textAnchor: 'middle'})
|
237 |
+
}
|
238 |
+
|
239 |
+
|
240 |
+
|
241 |
+
render()
|
242 |
+
players.renderFns.push(render)
|
243 |
+
function render(){
|
244 |
+
renderSVG()
|
245 |
+
if (name != 'grass' && !isRegression)renderCanvas()
|
246 |
+
if (isRegression) renderRegression()
|
247 |
+
}
|
248 |
+
|
249 |
+
function renderSVG(){
|
250 |
+
if (playerSel){
|
251 |
+
playerSel.translate(d => d)
|
252 |
+
bgPlayerSel.translate(d => d)
|
253 |
+
textSel.translate(d => d)
|
254 |
+
}
|
255 |
+
}
|
256 |
+
|
257 |
+
function renderCanvas(){
|
258 |
+
cells.forEach(d => {
|
259 |
+
players.forEach(p => {
|
260 |
+
var dx = p[0] - d[0] - cs/2
|
261 |
+
var dy = p[1] - d[1] - cs/2
|
262 |
+
|
263 |
+
// p.dist = Math.sqrt(dx*dx + dy*dy)
|
264 |
+
// p.dist = dx*dx + dy*dy
|
265 |
+
p.dist = Math.pow(dx*dx + dy*dy, 1.5) + .00001
|
266 |
+
p.weight = 1/p.dist
|
267 |
+
|
268 |
+
return p.dist
|
269 |
+
})
|
270 |
+
|
271 |
+
var sum = d3.sum(players, d => d.isRed*d.weight)
|
272 |
+
var wsum = d3.sum(players, d => d.weight)
|
273 |
+
|
274 |
+
ctx.fillStyle = colorScale(1 - sum/wsum)
|
275 |
+
|
276 |
+
ctx.fillRect(d[0], d[1], cs, cs)
|
277 |
+
})
|
278 |
+
}
|
279 |
+
|
280 |
+
function renderRegression(){
|
281 |
+
byColor.forEach(d => {
|
282 |
+
var l = ss.linearRegressionLine(ss.linearRegression(d))
|
283 |
+
|
284 |
+
var x0 = 0
|
285 |
+
var x1 = c.width
|
286 |
+
|
287 |
+
d.sel.at({d: `M ${x0} ${l(x0)} L ${x1} ${l(x1)}`})
|
288 |
+
})
|
289 |
+
}
|
290 |
+
}
|
291 |
+
|
292 |
+
'grass prediction playerless scatter regression regression-leak'
|
293 |
+
.split(' ')
|
294 |
+
.forEach(initField)
|
295 |
+
|
296 |
+
|
public/data-leak/style.css
ADDED
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
body{
|
2 |
+
|
3 |
+
}
|
4 |
+
|
5 |
+
|
6 |
+
p{
|
7 |
+
margin-left: 0px auto;
|
8 |
+
margin-right: 0px auto;
|
9 |
+
margin: 0px auto;
|
10 |
+
margin-top: 1em;
|
11 |
+
margin-bottom: 1em;
|
12 |
+
}
|
13 |
+
h3, .post-summary, h1x, p{
|
14 |
+
max-width: 650px;
|
15 |
+
}
|
16 |
+
|
17 |
+
#recirc{
|
18 |
+
max-width: 760px;
|
19 |
+
}
|
20 |
+
|
21 |
+
|
22 |
+
.white{
|
23 |
+
stroke: #fff;
|
24 |
+
fill: none;
|
25 |
+
stroke-width: 1;
|
26 |
+
}
|
27 |
+
|
28 |
+
.player{
|
29 |
+
cursor: pointer;
|
30 |
+
stroke: #000;
|
31 |
+
stroke-width: 2;
|
32 |
+
}
|
33 |
+
|
34 |
+
.button{
|
35 |
+
border: .5px solid #000;
|
36 |
+
/*border-bottom-width: 4px;*/
|
37 |
+
/*border-right-width: 4px;*/
|
38 |
+
border-radius: 8px;
|
39 |
+
padding: 4px;
|
40 |
+
margin: 2px;
|
41 |
+
cursor: pointer;
|
42 |
+
display: inline-block;
|
43 |
+
/*font-family: monospace;*/
|
44 |
+
/*font-family: 'Roboto Slab', serif;*/
|
45 |
+
/*font-size: 16px;*/
|
46 |
+
user-select: none;
|
47 |
+
font-family: 'Google Sans', sans-serif;
|
48 |
+
font-family: 'Roboto', Helvetica, sans-serif;
|
49 |
+
|
50 |
+
/*font-weight: 300;*/
|
51 |
+
}
|
52 |
+
|
53 |
+
@media (min-width: 800px){
|
54 |
+
.button{
|
55 |
+
margin-bottom: -100px;
|
56 |
+
}
|
57 |
+
}
|
58 |
+
|
59 |
+
.inline-button{
|
60 |
+
display: inline;
|
61 |
+
}
|
62 |
+
|
63 |
+
.button:hover{
|
64 |
+
background: #eee !important;
|
65 |
+
}
|
66 |
+
|
67 |
+
.button:active{
|
68 |
+
}
|
69 |
+
|
70 |
+
canvas{
|
71 |
+
opacity: .9;
|
72 |
+
}
|
73 |
+
|
74 |
+
svg{
|
75 |
+
overflow: visible;
|
76 |
+
}
|
77 |
+
|
78 |
+
.axis{
|
79 |
+
font-size: 12px;
|
80 |
+
|
81 |
+
}
|
82 |
+
.axis{
|
83 |
+
color: #000;
|
84 |
+
}
|
85 |
+
.axis text{
|
86 |
+
fill: #999;
|
87 |
+
font-family: 'Roboto', Helvetica, sans-serif;
|
88 |
+
}
|
89 |
+
.axis text.chart-title{
|
90 |
+
fill: #000;
|
91 |
+
font-size: 16px;
|
92 |
+
}
|
93 |
+
.axis line{
|
94 |
+
stroke: #ccc;
|
95 |
+
display: none;
|
96 |
+
}
|
97 |
+
|
98 |
+
.domain{
|
99 |
+
stroke: #ccc;
|
100 |
+
display: none;
|
101 |
+
}
|
102 |
+
|
103 |
+
text, .chart-title{
|
104 |
+
user-select: none;
|
105 |
+
/*pointer-events: none;*/
|
106 |
+
}
|
107 |
+
|
108 |
+
|
109 |
+
.field{
|
110 |
+
font-family: 'Google Sans', sans-serif;
|
111 |
+
font-family: 'Roboto', Helvetica, sans-serif;
|
112 |
+
margin-top: 10px;
|
113 |
+
}
|
114 |
+
|
115 |
+
.chart-title span{
|
116 |
+
padding: 4px;
|
117 |
+
}
|
118 |
+
|
119 |
+
.chart-title span:last-child{
|
120 |
+
color: #fff;
|
121 |
+
}
|
122 |
+
|
123 |
+
.chart-title span:first-child{
|
124 |
+
color: #000;
|
125 |
+
}
|
126 |
+
|
127 |
+
#field-regression .white, #field-regression-leak .white{
|
128 |
+
stroke: #ccc;
|
129 |
+
}
|
130 |
+
|
131 |
+
#field-grass .button, #field-prediction .button{
|
132 |
+
display: none;
|
133 |
+
}
|
134 |
+
|
135 |
+
.face-container{
|
136 |
+
max-width: 400px;
|
137 |
+
|
138 |
+
margin: 0px auto;
|
139 |
+
}
|
140 |
+
.face-container img{
|
141 |
+
width: 100%;
|
142 |
+
}
|
143 |
+
|
144 |
+
.post-summary {
|
145 |
+
margin-bottom: 40px;
|
146 |
+
}
|
147 |
+
|
148 |
+
p {
|
149 |
+
margin: 10 auto;
|
150 |
+
}
|
151 |
+
|
152 |
+
|
153 |
+
|
154 |
+
.pointer{
|
155 |
+
height: 0px;
|
156 |
+
position: relative;
|
157 |
+
}
|
158 |
+
.pointer div {
|
159 |
+
overflow: visible;
|
160 |
+
content: "";
|
161 |
+
background-image: url(https://pair-code.github.io/interpretability/bert-tree/pointer.svg);
|
162 |
+
width: 27px;
|
163 |
+
height: 27px;
|
164 |
+
position: absolute;
|
165 |
+
left: -35px;
|
166 |
+
top: 0px;
|
167 |
+
}
|
168 |
+
|
169 |
+
|
170 |
+
.face-container:after{
|
171 |
+
content: "M. Fredrikson, S. Jha, and T. Ristenpart, “Model inversion attacks that exploit confidence information and basic countermeasures,” in CCS, 2015.";
|
172 |
+
font-size: 12px;
|
173 |
+
color: #888;
|
174 |
+
line-height: 14px;
|
175 |
+
display: block;
|
176 |
+
}
|
public/dataset-worldviews/README.md
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
## Photo todos
|
2 |
+
|
3 |
+
x highlight the active button
|
4 |
+
x firing when not expected?
|
5 |
+
x clear timer when clicked
|
6 |
+
- maybe convert to HTML?
|
public/dataset-worldviews/img/confusing_pointiness.png
ADDED
Git LFS Details
|
public/dataset-worldviews/img/confusing_pointiness.svg
ADDED
public/dataset-worldviews/img/confusing_shape_name.png
ADDED
Git LFS Details
|
public/dataset-worldviews/img/confusing_shape_name.svg
ADDED
public/dataset-worldviews/img/confusing_size.png
ADDED
Git LFS Details
|
public/dataset-worldviews/img/confusing_size.svg
ADDED
public/dataset-worldviews/img/data_labelers.png
ADDED
Git LFS Details
|
public/dataset-worldviews/img/data_labelers.svg
ADDED
public/dataset-worldviews/img/dataset-worldviews-shareimg.png
ADDED
Git LFS Details
|
public/dataset-worldviews/img/interface_default.png
ADDED
Git LFS Details
|
public/dataset-worldviews/img/interface_default.svg
ADDED
public/dataset-worldviews/img/interface_shape_name_false.png
ADDED
Git LFS Details
|
public/dataset-worldviews/img/interface_shape_name_false.svg
ADDED
public/dataset-worldviews/img/interface_shape_name_true.png
ADDED
Git LFS Details
|
public/dataset-worldviews/img/interface_shape_name_true.svg
ADDED
public/dataset-worldviews/img/labels_1.png
ADDED
Git LFS Details
|
public/dataset-worldviews/img/labels_1.svg
ADDED
public/dataset-worldviews/img/labels_2.png
ADDED
Git LFS Details
|
public/dataset-worldviews/img/labels_2.svg
ADDED
public/dataset-worldviews/img/labels_3.png
ADDED
Git LFS Details
|
public/dataset-worldviews/img/labels_3.svg
ADDED
public/dataset-worldviews/img/labels_4.png
ADDED
Git LFS Details
|
public/dataset-worldviews/img/labels_4.svg
ADDED