mervenoyan commited on
Commit
7341022
1 Parent(s): 9b4e98d

commit files to HF hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +118 -0
  2. .gitignore +14 -0
  3. CONTRIBUTING.md +28 -0
  4. LICENSE +202 -0
  5. README.md +6 -6
  6. package.json +18 -0
  7. public/anonymization/annotations.js +38 -0
  8. public/anonymization/index.html +268 -0
  9. public/anonymization/init.js +77 -0
  10. public/anonymization/make-axii.js +86 -0
  11. public/anonymization/make-estimates.js +227 -0
  12. public/anonymization/make-gs.js +105 -0
  13. public/anonymization/make-sel.js +78 -0
  14. public/anonymization/make-sliders.js +139 -0
  15. public/anonymization/make-slides.js +98 -0
  16. public/anonymization/make-students.js +184 -0
  17. public/anonymization/style-graph-scroll.css +160 -0
  18. public/anonymization/style.css +344 -0
  19. public/base-rate/script.js +317 -0
  20. public/base-rate/sliders.js +103 -0
  21. public/base-rate/style.css +134 -0
  22. public/data-leak/face.png +3 -0
  23. public/data-leak/index.html +170 -0
  24. public/data-leak/players0.js +456 -0
  25. public/data-leak/script.js +296 -0
  26. public/data-leak/style.css +176 -0
  27. public/dataset-worldviews/README.md +6 -0
  28. public/dataset-worldviews/img/confusing_pointiness.png +3 -0
  29. public/dataset-worldviews/img/confusing_pointiness.svg +203 -0
  30. public/dataset-worldviews/img/confusing_shape_name.png +3 -0
  31. public/dataset-worldviews/img/confusing_shape_name.svg +228 -0
  32. public/dataset-worldviews/img/confusing_size.png +3 -0
  33. public/dataset-worldviews/img/confusing_size.svg +203 -0
  34. public/dataset-worldviews/img/data_labelers.png +3 -0
  35. public/dataset-worldviews/img/data_labelers.svg +221 -0
  36. public/dataset-worldviews/img/dataset-worldviews-shareimg.png +3 -0
  37. public/dataset-worldviews/img/interface_default.png +3 -0
  38. public/dataset-worldviews/img/interface_default.svg +204 -0
  39. public/dataset-worldviews/img/interface_shape_name_false.png +3 -0
  40. public/dataset-worldviews/img/interface_shape_name_false.svg +234 -0
  41. public/dataset-worldviews/img/interface_shape_name_true.png +3 -0
  42. public/dataset-worldviews/img/interface_shape_name_true.svg +214 -0
  43. public/dataset-worldviews/img/labels_1.png +3 -0
  44. public/dataset-worldviews/img/labels_1.svg +265 -0
  45. public/dataset-worldviews/img/labels_2.png +3 -0
  46. public/dataset-worldviews/img/labels_2.svg +301 -0
  47. public/dataset-worldviews/img/labels_3.png +3 -0
  48. public/dataset-worldviews/img/labels_3.svg +137 -0
  49. public/dataset-worldviews/img/labels_4.png +3 -0
  50. 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: Hidden Bias
3
- emoji: 🐠
4
- colorFrom: yellow
5
- colorTo: pink
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: hidden-bias
3
+ emoji: 🪄
4
+ colorFrom: green
5
+ colorTo: purple
6
  sdk: static
7
  pinned: false
8
+ license: apache-2.0
9
+ app_file: public/hidden-bias/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
+ &nbsp;
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

  • SHA256: 0add1bf38c1dd5500cca94c45eb9b1c0cffcd0f588983c3af3e90aa4b2527057
  • Pointer size: 131 Bytes
  • Size of remote file: 149 kB
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

  • SHA256: 316c15a3e69f9c0fa4f1d4d3a9b1099fb0fafbf322eb2e2a03ad2ea1da1c3e89
  • Pointer size: 130 Bytes
  • Size of remote file: 18.4 kB
public/dataset-worldviews/img/confusing_pointiness.svg ADDED
public/dataset-worldviews/img/confusing_shape_name.png ADDED

Git LFS Details

  • SHA256: f888efc52ef7b0e95a9139a7812eb0423aad1bf4b8957e9fb3af495e137817d6
  • Pointer size: 130 Bytes
  • Size of remote file: 20.1 kB
public/dataset-worldviews/img/confusing_shape_name.svg ADDED
public/dataset-worldviews/img/confusing_size.png ADDED

Git LFS Details

  • SHA256: 5cc8ab6b5d468ea18c3d95db149e56e1c5c40eba3fa873073b1a875cbab45aa5
  • Pointer size: 130 Bytes
  • Size of remote file: 17.2 kB
public/dataset-worldviews/img/confusing_size.svg ADDED
public/dataset-worldviews/img/data_labelers.png ADDED

Git LFS Details

  • SHA256: a0060c6efa9cd0b04af2adba9ef2d9ec594fc1d999f913a5b6541df8dc762fb1
  • Pointer size: 130 Bytes
  • Size of remote file: 28.5 kB
public/dataset-worldviews/img/data_labelers.svg ADDED
public/dataset-worldviews/img/dataset-worldviews-shareimg.png ADDED

Git LFS Details

  • SHA256: 40cd0385eba6e75ae2bb4a6e8cea44211c8e1a79d3a50005bd2cbd2125d47c3e
  • Pointer size: 131 Bytes
  • Size of remote file: 113 kB
public/dataset-worldviews/img/interface_default.png ADDED

Git LFS Details

  • SHA256: 894265a00b2a29567d5d3a829fe084421405a26d124e88501b71bdd826d25d02
  • Pointer size: 130 Bytes
  • Size of remote file: 13.5 kB
public/dataset-worldviews/img/interface_default.svg ADDED
public/dataset-worldviews/img/interface_shape_name_false.png ADDED

Git LFS Details

  • SHA256: 872c49b44c9f064c123380d0fc5239ffd47eb05c601132ec715215b4890a1c07
  • Pointer size: 130 Bytes
  • Size of remote file: 16.7 kB
public/dataset-worldviews/img/interface_shape_name_false.svg ADDED
public/dataset-worldviews/img/interface_shape_name_true.png ADDED

Git LFS Details

  • SHA256: b790f06457f7d9ff7475325805c05d22eb1880c766cf24432ef68f69657d7bb2
  • Pointer size: 130 Bytes
  • Size of remote file: 15 kB
public/dataset-worldviews/img/interface_shape_name_true.svg ADDED
public/dataset-worldviews/img/labels_1.png ADDED

Git LFS Details

  • SHA256: af5555c5a675bd1cc49c0bb693a193237363be4530c5b7aeda996106f32156bc
  • Pointer size: 129 Bytes
  • Size of remote file: 7.4 kB
public/dataset-worldviews/img/labels_1.svg ADDED
public/dataset-worldviews/img/labels_2.png ADDED

Git LFS Details

  • SHA256: 1ecb2a6f76a6f84bfb2268b9d0d11fd531713d52c6e77a852d7ad6982af59ece
  • Pointer size: 130 Bytes
  • Size of remote file: 15.7 kB
public/dataset-worldviews/img/labels_2.svg ADDED
public/dataset-worldviews/img/labels_3.png ADDED

Git LFS Details

  • SHA256: eefb72c539324e6f107f4c3ef188db8746c60d068d94b0249f2d34ba32234672
  • Pointer size: 129 Bytes
  • Size of remote file: 7.1 kB
public/dataset-worldviews/img/labels_3.svg ADDED
public/dataset-worldviews/img/labels_4.png ADDED

Git LFS Details

  • SHA256: 11140ecdcbfd6a13ee3be7af737b66812d15c83cf0f385e4f41dcbab21944d4b
  • Pointer size: 129 Bytes
  • Size of remote file: 7.97 kB
public/dataset-worldviews/img/labels_4.svg ADDED