wsba-hockey 1.0.3__py3-none-any.whl → 1.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. wsba_hockey/data_pipelines.py +183 -0
  2. wsba_hockey/evidence/weakside-breakout/node_modules/duckdb/vendor.py +146 -0
  3. wsba_hockey/evidence/weakside-breakout/node_modules/flatted/python/flatted.py +149 -0
  4. wsba_hockey/evidence/weakside-breakout/node_modules/flatted/python/test.py +63 -0
  5. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/gyp_main.py +45 -0
  6. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSNew.py +367 -0
  7. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSProject.py +206 -0
  8. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings.py +1270 -0
  9. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings_test.py +1547 -0
  10. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSToolFile.py +59 -0
  11. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSUserFile.py +153 -0
  12. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSUtil.py +271 -0
  13. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSVersion.py +574 -0
  14. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/__init__.py +690 -0
  15. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/common.py +661 -0
  16. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/common_test.py +78 -0
  17. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/easy_xml.py +165 -0
  18. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/easy_xml_test.py +109 -0
  19. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/flock_tool.py +55 -0
  20. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/__init__.py +0 -0
  21. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/analyzer.py +808 -0
  22. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/android.py +1173 -0
  23. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/cmake.py +1321 -0
  24. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/compile_commands_json.py +120 -0
  25. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/dump_dependency_json.py +103 -0
  26. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/eclipse.py +464 -0
  27. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/gypd.py +89 -0
  28. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/gypsh.py +58 -0
  29. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/make.py +2714 -0
  30. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs.py +3981 -0
  31. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs_test.py +44 -0
  32. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja.py +2936 -0
  33. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja_test.py +55 -0
  34. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode.py +1394 -0
  35. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode_test.py +25 -0
  36. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/input.py +3130 -0
  37. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/input_test.py +98 -0
  38. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/mac_tool.py +771 -0
  39. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/msvs_emulation.py +1271 -0
  40. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/ninja_syntax.py +174 -0
  41. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/simple_copy.py +61 -0
  42. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/win_tool.py +374 -0
  43. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/xcode_emulation.py +1939 -0
  44. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/xcode_ninja.py +302 -0
  45. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/xcodeproj_file.py +3197 -0
  46. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/xml_fix.py +65 -0
  47. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/test_gyp.py +261 -0
  48. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/tools/graphviz.py +102 -0
  49. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/tools/pretty_gyp.py +156 -0
  50. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/tools/pretty_sln.py +181 -0
  51. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/tools/pretty_vcproj.py +339 -0
  52. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/test/fixtures/test-charmap.py +31 -0
  53. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/update-gyp.py +64 -0
  54. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/gyp_main.py +45 -0
  55. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSNew.py +367 -0
  56. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSProject.py +206 -0
  57. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings.py +1270 -0
  58. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings_test.py +1547 -0
  59. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSToolFile.py +59 -0
  60. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSUserFile.py +153 -0
  61. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSUtil.py +271 -0
  62. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSVersion.py +574 -0
  63. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/__init__.py +666 -0
  64. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/common.py +654 -0
  65. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/common_test.py +78 -0
  66. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/easy_xml.py +165 -0
  67. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/easy_xml_test.py +109 -0
  68. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/flock_tool.py +55 -0
  69. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/__init__.py +0 -0
  70. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/analyzer.py +808 -0
  71. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/android.py +1173 -0
  72. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/cmake.py +1321 -0
  73. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/compile_commands_json.py +120 -0
  74. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/dump_dependency_json.py +103 -0
  75. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/eclipse.py +464 -0
  76. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/gypd.py +89 -0
  77. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/gypsh.py +58 -0
  78. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/make.py +2518 -0
  79. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs.py +3978 -0
  80. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs_test.py +44 -0
  81. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja.py +2936 -0
  82. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja_test.py +55 -0
  83. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode.py +1394 -0
  84. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode_test.py +25 -0
  85. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/input.py +3137 -0
  86. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/input_test.py +98 -0
  87. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/mac_tool.py +771 -0
  88. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/msvs_emulation.py +1271 -0
  89. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/ninja_syntax.py +174 -0
  90. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/simple_copy.py +61 -0
  91. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/win_tool.py +374 -0
  92. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/xcode_emulation.py +1939 -0
  93. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/xcode_ninja.py +302 -0
  94. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/xcodeproj_file.py +3197 -0
  95. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/xml_fix.py +65 -0
  96. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/setup.py +42 -0
  97. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/test_gyp.py +260 -0
  98. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/tools/graphviz.py +102 -0
  99. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/tools/pretty_gyp.py +156 -0
  100. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/tools/pretty_sln.py +181 -0
  101. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/tools/pretty_vcproj.py +339 -0
  102. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/test/fixtures/test-charmap.py +31 -0
  103. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/update-gyp.py +46 -0
  104. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/game_stats/app.py +401 -0
  105. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/game_stats/name_fix.py +47 -0
  106. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/app.py +108 -0
  107. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/plot.py +93 -0
  108. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/rink_plot.py +245 -0
  109. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/app.py +145 -0
  110. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/plot.py +77 -0
  111. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/rink_plot.py +245 -0
  112. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/app.py +389 -0
  113. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/plot.py +70 -0
  114. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/rink_plot.py +245 -0
  115. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/app.py +110 -0
  116. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/plot.py +58 -0
  117. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/rink_plot.py +245 -0
  118. wsba_hockey/tools/agg.py +242 -53
  119. wsba_hockey/tools/plotting.py +12 -17
  120. wsba_hockey/tools/scraping.py +149 -258
  121. wsba_hockey/tools/xg_model.py +357 -311
  122. wsba_hockey/workspace.py +22 -117
  123. wsba_hockey/wsba_main.py +493 -165
  124. {wsba_hockey-1.0.3.dist-info → wsba_hockey-1.0.4.dist-info}/METADATA +1 -1
  125. wsba_hockey-1.0.4.dist-info/RECORD +135 -0
  126. {wsba_hockey-1.0.3.dist-info → wsba_hockey-1.0.4.dist-info}/WHEEL +1 -1
  127. wsba_hockey/stats/calculate_viz/shot_impact.py +0 -2
  128. wsba_hockey-1.0.3.dist-info/RECORD +0 -19
  129. {wsba_hockey-1.0.3.dist-info → wsba_hockey-1.0.4.dist-info}/licenses/LICENSE +0 -0
  130. {wsba_hockey-1.0.3.dist-info → wsba_hockey-1.0.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,401 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ import wsba_hockey as wsba
4
+ from urllib.parse import *
5
+ from shiny import *
6
+ from shinywidgets import output_widget, render_widget
7
+ from name_fix import *
8
+
9
+ app_ui = ui.page_fluid(
10
+ ui.tags.link(
11
+ rel='stylesheet',
12
+ href='https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap'
13
+ ),
14
+ ui.tags.style(
15
+ """
16
+ body {
17
+ background-color: #09090b;
18
+ color: white;
19
+ font-family: 'Bebas Neue', sans-serif;
20
+ }
21
+
22
+ .custom-input input.form-control,
23
+ .custom-input .selectize-control,
24
+ .custom-input .selectize-input {
25
+ background-color: #09090b !important; /* black background */
26
+ color: white !important; /* white font color */
27
+ border-radius: 4px;
28
+ border: 1px solid #444;
29
+ }
30
+
31
+ .custom-input .selectize-dropdown,
32
+ .custom-input .selectize-dropdown-content {
33
+ background-color: #09090b !important;
34
+ color: white !important;
35
+ }
36
+
37
+ .custom-input .selectize-control.multi .item {
38
+ background-color: #09090b !important;
39
+ color: white !important;
40
+ border-radius: 4px;
41
+ padding: 2px 6px;
42
+ margin: 2px 4px 2px 0;
43
+ }
44
+
45
+ label.control-label {
46
+ color: white !important;
47
+ }
48
+
49
+ .selectize-control.multi {
50
+ width: 300px !important;
51
+ }
52
+
53
+ .form-row {
54
+ display: flex;
55
+ gap: 12px;
56
+ flex-wrap: wrap;
57
+ justify-content: center;
58
+ }
59
+
60
+ .submit-button {
61
+ display: flex;
62
+ justify-content: center;
63
+ }
64
+
65
+ .hide {
66
+ display: none;
67
+ }
68
+
69
+ .table thead tr {
70
+ --bs-table-bg: #09090b;
71
+ --bs-table-color-state: white;
72
+ }
73
+
74
+ .table thead tr th {
75
+ white-space: nowrap;
76
+ text-align: center;
77
+ color: white;
78
+ background-color: #09090b;
79
+ }
80
+
81
+ .table tbody tr {
82
+ --bs-table-bg: #09090b;
83
+ --bs-table-color-state: white;
84
+ }
85
+
86
+ .table tbody tr td {
87
+ white-space: nowrap;
88
+ text-align: center;
89
+ overflow: hidden;
90
+ text-overflow: ellipsis;
91
+ color: white;
92
+ background-color: #09090b;
93
+ }
94
+
95
+ .table thead th {
96
+ text-align: center !important;
97
+ }
98
+ """
99
+ ),
100
+ ui.tags.h2(ui.output_text('game_header'), style="text-align: center;"),
101
+ ui.tags.h4('Team Stats'),
102
+ ui.output_data_frame("team_stats"),
103
+ ui.tags.h4('Scoring Summary'),
104
+ ui.output_data_frame("scoring_summary"),
105
+ ui.output_ui("table_filters"),
106
+ ui.row(
107
+ ui.column(6, ui.tags.h4(ui.output_text('away_header'))),
108
+ ui.column(6, ui.tags.h4(ui.output_text('home_header')))
109
+ ),
110
+ ui.row(
111
+ ui.column(6, ui.output_data_frame("away_stats")),
112
+ ui.column(6, ui.output_data_frame("home_stats"))
113
+ ),
114
+ ui.row(
115
+ ui.column(6, ui.tags.h4(ui.output_text('away_goalie_header'))),
116
+ ui.column(6, ui.tags.h4(ui.output_text('home_goalie_header')))
117
+ ),
118
+ ui.row(
119
+ ui.column(6, ui.output_data_frame("away_goalie")),
120
+ ui.column(6, ui.output_data_frame("home_goalie"))
121
+ )
122
+ )
123
+
124
+ def server(input, output, session):
125
+ query = reactive.Value(None)
126
+ game_info = reactive.Value(None)
127
+
128
+ def get_schedule():
129
+ games = pd.read_csv('https://f005.backblazeb2.com/file/weakside-breakout/info/schedule.csv')
130
+
131
+ return games.loc[games['gameState'].isin(['OFF','FINAL'])]
132
+
133
+ @reactive.Effect
134
+ def query_params():
135
+ #Retreive query parameters
136
+ search = session.input[".clientdata_url_search"]()
137
+ q = parse_qs(urlparse(search).query)
138
+
139
+ print(q)
140
+
141
+ defaults = {
142
+ 'game_id':['2024020001'],
143
+ }
144
+
145
+ for key in defaults.keys():
146
+ if key not in q.keys():
147
+ q.update({key:defaults[key]})
148
+
149
+ query.set(q)
150
+
151
+ def active_params():
152
+ return query.get() or {}
153
+
154
+ @reactive.Effect
155
+ def get_game_info():
156
+ #Load params
157
+ query = active_params()
158
+
159
+ #Load game data
160
+ game = get_schedule()
161
+ game = game.loc[game['id'].astype(str).str.replace('.0','')==query['game_id'][0]]
162
+
163
+ game_info.set({
164
+ 'game_id':game['id'].iloc[0],
165
+ 'season':game['season'].iloc[0],
166
+ 'title':game['game_title'].iloc[0],
167
+ 'date':game['date'].iloc[0],
168
+ 'away_team_abbr':game['away_team_abbr'].iloc[0],
169
+ 'home_team_abbr':game['home_team_abbr'].iloc[0],
170
+ 'away_logo':game['awayTeam.darkLogo'].iloc[0],
171
+ 'home_logo':game['homeTeam.darkLogo'].iloc[0],
172
+ 'away_score':game['awayTeam.score'].iloc[0],
173
+ 'home_score':game['homeTeam.score'].iloc[0],
174
+ })
175
+ print(game_info.get())
176
+
177
+ game_df = reactive.Value(None)
178
+ @reactive.Effect
179
+ def active_game():
180
+ #Determine which season to load based on the input game_id
181
+ info = game_info.get()
182
+ season = info['season']
183
+ #Load appropriate dataframe
184
+ df = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/game_log/wsba_nhl_{season}_game_log.parquet')
185
+ goalie_df = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/game_log/goalie/wsba_nhl_{season}_game_log_goalie.parquet')
186
+ pbp = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/pbp/{season}.parquet')
187
+
188
+ game_df.set([df.loc[(df['Game']==info['game_id'])], pbp.loc[(pbp['game_id']==info['game_id'])&(pbp['event_type']=='goal')], goalie_df.loc[(goalie_df['Game']==info['game_id'])]])
189
+
190
+ @output
191
+ @render.ui
192
+ def table_filters():
193
+ all_strengths = ['4v5','5v4','5v5','Other','All']
194
+ return ui.panel_well(
195
+ ui.tags.div(
196
+ {"class": "form-row custom-input"},
197
+ ui.input_selectize('stat_type','Type',['Individual','On-Ice']),
198
+ ui.input_selectize('strength_state','Strength',all_strengths,selected='5v5'),
199
+ )
200
+ )
201
+
202
+ @output
203
+ @render.data_frame
204
+ def team_stats():
205
+ df = game_df.get()[0]
206
+ info = game_info.get()
207
+
208
+ df = df.loc[df['Strength']=='All'].groupby('Team').agg(
209
+ Fenwick=('Fi','sum'),
210
+ xG=('xGi','sum'),
211
+ Hits=('HF','sum'),
212
+ Give=('Give','sum'),
213
+ Take=('Take','sum'),
214
+ PIM=('PIM','sum')
215
+ ).reset_index()
216
+
217
+ score = pd.DataFrame([[info['away_team_abbr'],
218
+ info['away_score']],
219
+ [info['home_team_abbr'],
220
+ info['home_score']]],columns=['Team','Goals'])
221
+
222
+ df = pd.merge(df,score,how='left')
223
+ df = df[['Team','Goals','Fenwick','xG','Hits','Give','Take','PIM']]
224
+
225
+ df['xG'] = df['xG'].round(2)
226
+
227
+ print(df)
228
+
229
+ return render.DataTable(df,height='fit-content')
230
+
231
+ @output
232
+ @render.data_frame
233
+ def scoring_summary():
234
+ pbp = game_df.get()[1]
235
+
236
+ pbp = pbp[['event_num','period','seconds_elapsed','strength_state','description','away_score','home_score','xG']].rename(columns={
237
+ 'event_num':'#',
238
+ 'seconds_elapsed':'Seconds',
239
+ 'strength_state':'Strength State',
240
+ 'away_score':'Away Score',
241
+ 'home_score':'Home Score'
242
+ })
243
+
244
+ pbp['xG'] = (pbp['xG']*100).round(4)
245
+
246
+ return render.DataTable(pbp, height='fit-content')
247
+
248
+ @output
249
+ @render.text
250
+ def game_header():
251
+ return game_info.get()['title']
252
+
253
+ @output
254
+ @render.text
255
+ def away_header():
256
+ team = game_info.get()['away_team_abbr']
257
+
258
+ return f'{team} Stats'
259
+
260
+ @output
261
+ @render.text
262
+ def home_header():
263
+ team = game_info.get()['home_team_abbr']
264
+
265
+ return f'{team} Stats'
266
+
267
+ @output
268
+ @render.text
269
+ def away_goalie_header():
270
+ team = game_info.get()['away_team_abbr']
271
+
272
+ return f'{team} Goalie Stats'
273
+
274
+ @output
275
+ @render.text
276
+ def home_goalie_header():
277
+ team = game_info.get()['home_team_abbr']
278
+
279
+ return f'{team} Goalie Stats'
280
+
281
+ @output
282
+ @render.data_frame
283
+ def away_stats():
284
+ df = game_df.get()[0]
285
+ info = game_info.get()
286
+
287
+ if input.stat_type() == 'Individual':
288
+ cols = [
289
+ 'Player','ID','Position','Handedness','TOI','Gi','A1','A2','P1','P','Give','Take','HF','HA',
290
+ 'Fi','xGi'
291
+ ]
292
+ else:
293
+ cols = [
294
+ 'Player','ID','Position','Handedness','TOI','GF','GA','FF','FA','xGF','xGA','GF%','FF%','xGF%'
295
+ ]
296
+
297
+ df = df.loc[(df['Team']==info['away_team_abbr'])&(df['Strength']==input.strength_state()),cols]
298
+
299
+ for col in ['TOI','xGi','xGF','xGA','GF%','FF%','xGF%']:
300
+ try:
301
+ if '%' in col:
302
+ df[col] = (df[col]*100).round(2).astype(str) + '%'
303
+ else:
304
+ df[col] = df[col].round(2)
305
+ except:
306
+ continue
307
+
308
+ df = fix_names(df)
309
+
310
+ return render.DataTable(df.rename(columns={
311
+ 'Gi':'G',
312
+ 'Fi':'iFF',
313
+ 'xGi':'ixG'
314
+ }).drop(columns=['ID']).rename(columns={'Position':'POS','Handedness':'Hand'}))
315
+
316
+ @output
317
+ @render.data_frame
318
+ def home_stats():
319
+ df = game_df.get()[0]
320
+ info = game_info.get()
321
+
322
+ if input.stat_type() == 'Individual':
323
+ cols = [
324
+ 'Player','ID','TOI','Position','Handedness','Gi','A1','A2','P1','P','Give','Take','HF','HA',
325
+ 'Fi','xGi'
326
+ ]
327
+ else:
328
+ cols = [
329
+ 'Player','ID','TOI','Position','Handedness','GF','GA','FF','FA','xGF','xGA','GF%','FF%','xGF%'
330
+ ]
331
+
332
+ df = df.loc[(df['Team']==info['home_team_abbr'])&(df['Strength']==input.strength_state()),cols]
333
+
334
+ for col in ['TOI','xGi','xGF','xGA','GF%','FF%','xGF%']:
335
+ try:
336
+ if '%' in col:
337
+ df[col] = (df[col]*100).round(2).astype(str) + '%'
338
+ else:
339
+ df[col] = df[col].round(2)
340
+ except:
341
+ continue
342
+
343
+ df = fix_names(df)
344
+
345
+ return render.DataTable(df.rename(columns={
346
+ 'Gi':'G',
347
+ 'Fi':'iFF',
348
+ 'xGi':'ixG'
349
+ }).drop(columns=['ID']).rename(columns={'Position':'POS','Handedness':'Hand'}))
350
+
351
+ @output
352
+ @render.data_frame
353
+ def away_goalie():
354
+ df = game_df.get()[2]
355
+ info = game_info.get()
356
+
357
+ cols = [
358
+ 'Goalie','ID','TOI','Position','Handedness','GA','FA','xGA','GSAx'
359
+ ]
360
+
361
+ df = df.loc[(df['Team']==info['away_team_abbr'])&(df['Strength']==input.strength_state()),cols]
362
+
363
+ for col in ['TOI','xGA','GSAx']:
364
+ try:
365
+ if '%' in col:
366
+ df[col] = (df[col]*100).round(2).astype(str) + '%'
367
+ else:
368
+ df[col] = df[col].round(2)
369
+ except:
370
+ continue
371
+
372
+ df = fix_names(df,'Goalie')
373
+
374
+ return render.DataTable(df.drop(columns=['ID']).rename(columns={'Position':'POS','Handedness':'Hand'}))
375
+
376
+ @output
377
+ @render.data_frame
378
+ def home_goalie():
379
+ df = game_df.get()[2]
380
+ info = game_info.get()
381
+
382
+ cols = [
383
+ 'Goalie','ID','TOI','Position','Handedness','GA','FA','xGA','GSAx'
384
+ ]
385
+
386
+ df = df.loc[(df['Team']==info['home_team_abbr'])&(df['Strength']==input.strength_state()),cols]
387
+
388
+ for col in ['TOI','xGA','GSAx']:
389
+ try:
390
+ if '%' in col:
391
+ df[col] = (df[col]*100).round(2).astype(str) + '%'
392
+ else:
393
+ df[col] = df[col].round(2)
394
+ except:
395
+ continue
396
+
397
+ df = fix_names(df,'Goalie')
398
+
399
+ return render.DataTable(df.drop(columns=['ID']).rename(columns={'Position':'POS','Handedness':'Hand'}))
400
+
401
+ app = App(app_ui, server)
@@ -0,0 +1,47 @@
1
+ import requests as rs
2
+ import pandas as pd
3
+ import numpy as np
4
+
5
+ def scrape_info(player_ids):
6
+ #Given player id, return player information
7
+ infos = []
8
+ for player_id in player_ids:
9
+ player_id = int(player_id)
10
+ api = f'https://api-web.nhle.com/v1/player/{player_id}/landing'
11
+
12
+ data = pd.json_normalize(rs.get(api).json())
13
+
14
+ #Add name column
15
+ data['fullName'] = (data['firstName.default'] + " " + data['lastName.default']).str.upper()
16
+
17
+ #Append
18
+ infos.append(data)
19
+
20
+ df = pd.concat(infos)
21
+ #Return: player data
22
+ return df
23
+
24
+ def fix_names(stats,name='Player'):
25
+ missing = stats.loc[stats[name]=='0']
26
+ stats = stats.loc[stats[name]!='0']
27
+
28
+ if not missing.empty:
29
+ data = scrape_info(missing['ID'])[['fullName','playerId','position','shootsCatches']].rename(columns={
30
+ 'playerId':'ID',
31
+ 'fullName':'Player',
32
+ 'position':'Position',
33
+ 'headshot':'Headshot',
34
+ 'heightInInches':'Height (in)',
35
+ 'weightInPounds':'Weight (lbs)',
36
+ 'birthDate':'Birthday',
37
+ 'birthCountry':'Nationality',
38
+ 'shootsCatches':'Handedness'})
39
+
40
+ missing = missing.drop(columns=[name,'Position','Handedness'])
41
+ miss = pd.merge(missing,data,how='left',on=['ID'])
42
+
43
+ df = pd.concat([stats,miss]).sort_values([name,'ID'])
44
+
45
+ return df
46
+ else:
47
+ return stats
@@ -0,0 +1,108 @@
1
+ import pandas as pd
2
+ import plot as wsba_plt
3
+ import numpy as np
4
+ from urllib.parse import *
5
+ from shiny import *
6
+ from shinywidgets import output_widget, render_widget
7
+
8
+ app_ui = ui.page_fluid(
9
+ ui.tags.style(
10
+ "body {background:#09090b"
11
+ "}"
12
+ ),
13
+ output_widget("plot_skater"),
14
+ )
15
+
16
+ def server(input, output, session):
17
+ @output()
18
+ @render_widget
19
+ def plot_skater():
20
+ #Retreive query parameters
21
+ search = session.input[".clientdata_url_search"]()
22
+ query = parse_qs(urlparse(search).query)
23
+
24
+ print(query)
25
+ #If no input data is provided automatically provide a select skater and plot all 5v5 fenwick shots
26
+ defaults = {
27
+ 'skater':['8473419'],
28
+ 'season':['20182019'],
29
+ 'team':['BOS'],
30
+ 'strength_state':['5v5'],
31
+ 'season_type':['2']
32
+ }
33
+
34
+ for key in defaults.keys():
35
+ if key not in query.keys():
36
+ query.update({key:defaults[key]})
37
+
38
+ #Iterate through query and parse params with multiple selections
39
+ for param in query.keys():
40
+ q_string = query[param][0]
41
+ query[param] = q_string.split(',')
42
+
43
+ print(query)
44
+ #Determine which season to load based on the input
45
+ season = query['season'][0]
46
+ #Load appropriate dataframe
47
+ df = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/pbp/{season}.parquet')
48
+
49
+ #Prepare dataframe for plotting based on URL parameters
50
+ df = df.loc[(df['season'].astype(str).isin(query['season']))&(df['season_type'].astype(str).isin(query['season_type']))].replace({np.nan: None})
51
+ #Return empty rink if no data exists else continue
52
+ if df.empty:
53
+ return wsba_plt.wsba_rink()
54
+ else:
55
+ rink = wsba_plt.wsba_rink()
56
+
57
+ try:
58
+ for_plot = wsba_plt.heatmap(df,skater=query['skater'][0],team=query['team'][0],events=['missed-shot','shot-on-goal','goal'],strengths=query['strength_state'],onice='for')
59
+ against_plot = wsba_plt.heatmap(df,skater=query['skater'][0],team=query['team'][0],events=['missed-shot','shot-on-goal','goal'],strengths=query['strength_state'],onice='against')
60
+
61
+ for trace in for_plot.data:
62
+ rink.add_trace(trace)
63
+
64
+ for trace in against_plot.data:
65
+ rink.add_trace(trace)
66
+
67
+ try:
68
+ player = df.loc[df['event_player_1_id'].astype(str).str.replace('.0','')==query['skater'][0],'event_player_1_name'].to_list()[0]
69
+ except:
70
+ player = query['skater'][0]
71
+ season = int(season[0:4])
72
+ team = query['team'][0]
73
+ strengths = 'All Situations' if len(query['strength_state']) == 4 else query['strength_state']
74
+ span = 'Regular Season' if query['season_type'][0]=='2' else 'Playoffs'
75
+
76
+ return rink.update_layout(
77
+ title=dict(
78
+ text=f'{player} On-Ice xG at {strengths}; {season}-{season+1}, {span}, {team}',
79
+ x=0.5, y=0.96,
80
+ xanchor='center',
81
+ yanchor='top',
82
+ font=dict(color='white')
83
+ ),
84
+ ).add_annotation(
85
+ text='Lower xG',
86
+ xref="paper",
87
+ yref="paper",
88
+ xanchor='right',
89
+ yanchor='top',
90
+ font=dict(color='white'),
91
+ x=0.3,
92
+ y=0.04,
93
+ showarrow=False
94
+ ).add_annotation(
95
+ text='Higher xG',
96
+ xref="paper",
97
+ yref="paper",
98
+ xanchor='right',
99
+ yanchor='top',
100
+ font=dict(color='white'),
101
+ x=0.76,
102
+ y=0.04,
103
+ showarrow=False
104
+ )
105
+ except:
106
+ return wsba_plt.wsba_rink()
107
+
108
+ app = App(app_ui, server)
@@ -0,0 +1,93 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ import plotly.graph_objects as go
4
+ import matplotlib.pyplot as plt
5
+ import rink_plot
6
+ from scipy.interpolate import griddata
7
+ from scipy.ndimage import gaussian_filter
8
+
9
+ def wsba_rink(setting='full', vertical=False):
10
+ return rink_plot.rink(setting=setting, vertical=vertical)
11
+
12
+ def heatmap(df,skater,team,events,strengths,onice):
13
+ df['event_team_abbr_2'] = np.where(df['home_team_abbr']==df['event_team_abbr'],df['away_team_abbr'],df['home_team_abbr'])
14
+ df['strength_state_2'] = df['strength_state'].str[::-1]
15
+
16
+ df = df.loc[(df['event_type'].isin(events))&(df['x_adj'].notna())&(df['y_adj'].notna())]
17
+ if onice == 'for':
18
+ df['x'] = abs(df['x_adj'])
19
+ df['y'] = np.where(df['x_adj']<0,-df['y_adj'],df['y_adj'])
20
+ df['event_distance'] = abs(df['event_distance'].fillna(0))
21
+ df = df.loc[(df['event_distance']<=89)&(df['x']<=89)&(df['empty_net']==0)]
22
+
23
+ x_min = 0
24
+ x_max = 100
25
+ else:
26
+ df['x'] = -abs(df['x_adj'])
27
+ df['y'] = np.where(df['x_adj']>0,-df['y_adj'],df['y_adj'])
28
+ df['event_distance'] = -abs(df['event_distance'])
29
+ df = df.loc[(df['event_distance']>-89)&(df['x']>-89)&(df['empty_net']==0)]
30
+
31
+ x_min = -100
32
+ x_max = 0
33
+
34
+ df['home_on_ice'] = df['home_on_1_id'].astype(str) + ";" + df['home_on_2_id'].astype(str) + ";" + df['home_on_3_id'].astype(str) + ";" + df['home_on_4_id'].astype(str) + ";" + df['home_on_5_id'].astype(str) + ";" + df['home_on_6_id'].astype(str)
35
+ df['away_on_ice'] = df['away_on_1_id'].astype(str) + ";" + df['away_on_2_id'].astype(str) + ";" + df['away_on_3_id'].astype(str) + ";" + df['away_on_4_id'].astype(str) + ";" + df['away_on_5_id'].astype(str) + ";" + df['away_on_6_id'].astype(str)
36
+
37
+ df['onice_for'] = np.where(df['home_team_abbr']==df['event_team_abbr'],df['home_on_ice'],df['away_on_ice'])
38
+ df['onice_against'] = np.where(df['away_team_abbr']==df['event_team_abbr'],df['home_on_ice'],df['away_on_ice'])
39
+
40
+ df['strength_state'] = np.where(df['strength_state'].isin(['5v5','5v4','4v5']),df['strength_state'],'Other')
41
+
42
+ if strengths != 'all':
43
+ if onice == 'against':
44
+ df = df.loc[((df['strength_state_2'].isin(strengths)))]
45
+ else:
46
+ df = df.loc[((df['strength_state'].isin(strengths)))]
47
+
48
+ [x,y] = np.round(np.meshgrid(np.linspace(x_min,x_max,(x_max-x_min)),np.linspace(-42.5,42.5,85)))
49
+ xgoals = griddata((df['x'],df['y']),df['xG'],(x,y),method='cubic',fill_value=0)
50
+ xgoals = np.where(xgoals < 0,0,xgoals)
51
+ xgoals_smooth = gaussian_filter(xgoals,sigma=3)
52
+
53
+ if onice == 'for':
54
+ player_shots = df.loc[(df['onice_for'].str.contains(skater))&(df['event_team_abbr']==team)]
55
+ else:
56
+ player_shots = df.loc[(df['onice_against'].str.contains(skater))&(df['event_team_abbr_2']==team)]
57
+ [x,y] = np.round(np.meshgrid(np.linspace(x_min,x_max,(x_max-x_min)),np.linspace(-42.5,42.5,85)))
58
+ xgoals_player = griddata((player_shots['x'],player_shots['y']),player_shots['xG'],(x,y),method='cubic',fill_value=0)
59
+ xgoals_player = np.where(xgoals_player < 0,0,xgoals_player)
60
+
61
+ difference = (gaussian_filter(xgoals_player,sigma = 3)) - xgoals_smooth
62
+ data_min= difference.min()
63
+ data_max= difference.max()
64
+
65
+ if abs(data_min) > data_max:
66
+ data_max = data_min * -1
67
+ elif data_max > abs(data_min):
68
+ data_min = data_max * -1
69
+
70
+ fig = go.Figure(
71
+ data = go.Contour( x=np.linspace(x_min,x_max,(x_max-x_min)),
72
+ y=np.linspace(-42.5,42.5,85),
73
+ z=difference,
74
+ colorscale=[[0.0,'red'],[0.5,'#09090b'],[1.0,'blue']],
75
+ connectgaps=True,
76
+ contours=dict(
77
+ type='levels',
78
+ start = data_min,
79
+ end = data_max,
80
+ size=(data_max-data_min)/11
81
+ ),
82
+ colorbar=dict(
83
+ len = 0.7,
84
+ orientation='h',
85
+ showticklabels=False,
86
+ thickness=15,
87
+ yref='paper',
88
+ yanchor='top',
89
+ y=0
90
+ ))
91
+ )
92
+
93
+ return fig