wsba-hockey 1.0.3__py3-none-any.whl → 1.0.5__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 (131) hide show
  1. wsba_hockey/__init__.py +1 -1
  2. wsba_hockey/data_pipelines.py +183 -0
  3. wsba_hockey/evidence/weakside-breakout/node_modules/duckdb/vendor.py +146 -0
  4. wsba_hockey/evidence/weakside-breakout/node_modules/flatted/python/flatted.py +149 -0
  5. wsba_hockey/evidence/weakside-breakout/node_modules/flatted/python/test.py +63 -0
  6. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/gyp_main.py +45 -0
  7. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSNew.py +367 -0
  8. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSProject.py +206 -0
  9. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings.py +1270 -0
  10. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings_test.py +1547 -0
  11. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSToolFile.py +59 -0
  12. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSUserFile.py +153 -0
  13. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSUtil.py +271 -0
  14. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSVersion.py +574 -0
  15. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/__init__.py +690 -0
  16. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/common.py +661 -0
  17. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/common_test.py +78 -0
  18. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/easy_xml.py +165 -0
  19. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/easy_xml_test.py +109 -0
  20. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/flock_tool.py +55 -0
  21. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/__init__.py +0 -0
  22. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/analyzer.py +808 -0
  23. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/android.py +1173 -0
  24. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/cmake.py +1321 -0
  25. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/compile_commands_json.py +120 -0
  26. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/dump_dependency_json.py +103 -0
  27. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/eclipse.py +464 -0
  28. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/gypd.py +89 -0
  29. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/gypsh.py +58 -0
  30. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/make.py +2714 -0
  31. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs.py +3981 -0
  32. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs_test.py +44 -0
  33. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja.py +2936 -0
  34. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja_test.py +55 -0
  35. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode.py +1394 -0
  36. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode_test.py +25 -0
  37. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/input.py +3130 -0
  38. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/input_test.py +98 -0
  39. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/mac_tool.py +771 -0
  40. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/msvs_emulation.py +1271 -0
  41. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/ninja_syntax.py +174 -0
  42. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/simple_copy.py +61 -0
  43. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/win_tool.py +374 -0
  44. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/xcode_emulation.py +1939 -0
  45. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/xcode_ninja.py +302 -0
  46. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/xcodeproj_file.py +3197 -0
  47. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/xml_fix.py +65 -0
  48. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/test_gyp.py +261 -0
  49. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/tools/graphviz.py +102 -0
  50. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/tools/pretty_gyp.py +156 -0
  51. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/tools/pretty_sln.py +181 -0
  52. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/tools/pretty_vcproj.py +339 -0
  53. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/test/fixtures/test-charmap.py +31 -0
  54. wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/update-gyp.py +64 -0
  55. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/gyp_main.py +45 -0
  56. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSNew.py +367 -0
  57. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSProject.py +206 -0
  58. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings.py +1270 -0
  59. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings_test.py +1547 -0
  60. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSToolFile.py +59 -0
  61. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSUserFile.py +153 -0
  62. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSUtil.py +271 -0
  63. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSVersion.py +574 -0
  64. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/__init__.py +666 -0
  65. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/common.py +654 -0
  66. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/common_test.py +78 -0
  67. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/easy_xml.py +165 -0
  68. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/easy_xml_test.py +109 -0
  69. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/flock_tool.py +55 -0
  70. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/__init__.py +0 -0
  71. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/analyzer.py +808 -0
  72. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/android.py +1173 -0
  73. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/cmake.py +1321 -0
  74. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/compile_commands_json.py +120 -0
  75. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/dump_dependency_json.py +103 -0
  76. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/eclipse.py +464 -0
  77. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/gypd.py +89 -0
  78. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/gypsh.py +58 -0
  79. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/make.py +2518 -0
  80. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs.py +3978 -0
  81. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs_test.py +44 -0
  82. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja.py +2936 -0
  83. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja_test.py +55 -0
  84. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode.py +1394 -0
  85. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode_test.py +25 -0
  86. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/input.py +3137 -0
  87. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/input_test.py +98 -0
  88. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/mac_tool.py +771 -0
  89. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/msvs_emulation.py +1271 -0
  90. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/ninja_syntax.py +174 -0
  91. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/simple_copy.py +61 -0
  92. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/win_tool.py +374 -0
  93. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/xcode_emulation.py +1939 -0
  94. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/xcode_ninja.py +302 -0
  95. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/xcodeproj_file.py +3197 -0
  96. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/xml_fix.py +65 -0
  97. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/setup.py +42 -0
  98. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/test_gyp.py +260 -0
  99. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/tools/graphviz.py +102 -0
  100. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/tools/pretty_gyp.py +156 -0
  101. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/tools/pretty_sln.py +181 -0
  102. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/tools/pretty_vcproj.py +339 -0
  103. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/test/fixtures/test-charmap.py +31 -0
  104. wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/update-gyp.py +46 -0
  105. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/game_stats/app.py +400 -0
  106. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/game_stats/name_fix.py +47 -0
  107. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/app.py +108 -0
  108. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/plot.py +93 -0
  109. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/rink_plot.py +245 -0
  110. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/app.py +145 -0
  111. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/plot.py +77 -0
  112. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/rink_plot.py +245 -0
  113. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/app.py +389 -0
  114. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/plot.py +70 -0
  115. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/rink_plot.py +245 -0
  116. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/app.py +110 -0
  117. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/plot.py +58 -0
  118. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/rink_plot.py +245 -0
  119. wsba_hockey/tools/agg.py +243 -54
  120. wsba_hockey/tools/plotting.py +25 -25
  121. wsba_hockey/tools/scraping.py +154 -263
  122. wsba_hockey/tools/xg_model.py +369 -315
  123. wsba_hockey/workspace.py +22 -117
  124. wsba_hockey/wsba_main.py +499 -167
  125. {wsba_hockey-1.0.3.dist-info → wsba_hockey-1.0.5.dist-info}/METADATA +1 -1
  126. wsba_hockey-1.0.5.dist-info/RECORD +135 -0
  127. {wsba_hockey-1.0.3.dist-info → wsba_hockey-1.0.5.dist-info}/WHEEL +1 -1
  128. wsba_hockey/stats/calculate_viz/shot_impact.py +0 -2
  129. wsba_hockey-1.0.3.dist-info/RECORD +0 -19
  130. {wsba_hockey-1.0.3.dist-info → wsba_hockey-1.0.5.dist-info}/licenses/LICENSE +0 -0
  131. {wsba_hockey-1.0.3.dist-info → wsba_hockey-1.0.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,400 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ from urllib.parse import *
4
+ from shiny import *
5
+ from shinywidgets import output_widget, render_widget
6
+ from name_fix import *
7
+
8
+ app_ui = ui.page_fluid(
9
+ ui.tags.link(
10
+ rel='stylesheet',
11
+ href='https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap'
12
+ ),
13
+ ui.tags.style(
14
+ """
15
+ body {
16
+ background-color: #09090b;
17
+ color: white;
18
+ font-family: 'Bebas Neue', sans-serif;
19
+ }
20
+
21
+ .custom-input input.form-control,
22
+ .custom-input .selectize-control,
23
+ .custom-input .selectize-input {
24
+ background-color: #09090b !important; /* black background */
25
+ color: white !important; /* white font color */
26
+ border-radius: 4px;
27
+ border: 1px solid #444;
28
+ }
29
+
30
+ .custom-input .selectize-dropdown,
31
+ .custom-input .selectize-dropdown-content {
32
+ background-color: #09090b !important;
33
+ color: white !important;
34
+ }
35
+
36
+ .custom-input .selectize-control.multi .item {
37
+ background-color: #09090b !important;
38
+ color: white !important;
39
+ border-radius: 4px;
40
+ padding: 2px 6px;
41
+ margin: 2px 4px 2px 0;
42
+ }
43
+
44
+ label.control-label {
45
+ color: white !important;
46
+ }
47
+
48
+ .selectize-control.multi {
49
+ width: 300px !important;
50
+ }
51
+
52
+ .form-row {
53
+ display: flex;
54
+ gap: 12px;
55
+ flex-wrap: wrap;
56
+ justify-content: center;
57
+ }
58
+
59
+ .submit-button {
60
+ display: flex;
61
+ justify-content: center;
62
+ }
63
+
64
+ .hide {
65
+ display: none;
66
+ }
67
+
68
+ .table thead tr {
69
+ --bs-table-bg: #09090b;
70
+ --bs-table-color-state: white;
71
+ }
72
+
73
+ .table thead tr th {
74
+ white-space: nowrap;
75
+ text-align: center;
76
+ color: white;
77
+ background-color: #09090b;
78
+ }
79
+
80
+ .table tbody tr {
81
+ --bs-table-bg: #09090b;
82
+ --bs-table-color-state: white;
83
+ }
84
+
85
+ .table tbody tr td {
86
+ white-space: nowrap;
87
+ text-align: center;
88
+ overflow: hidden;
89
+ text-overflow: ellipsis;
90
+ color: white;
91
+ background-color: #09090b;
92
+ }
93
+
94
+ .table thead th {
95
+ text-align: center !important;
96
+ }
97
+ """
98
+ ),
99
+ ui.tags.h2(ui.output_text('game_header'), style="text-align: center;"),
100
+ ui.tags.h4('Team Stats'),
101
+ ui.output_data_frame("team_stats"),
102
+ ui.tags.h4('Scoring Summary'),
103
+ ui.output_data_frame("scoring_summary"),
104
+ ui.output_ui("table_filters"),
105
+ ui.row(
106
+ ui.column(6, ui.tags.h4(ui.output_text('away_header'))),
107
+ ui.column(6, ui.tags.h4(ui.output_text('home_header')))
108
+ ),
109
+ ui.row(
110
+ ui.column(6, ui.output_data_frame("away_stats")),
111
+ ui.column(6, ui.output_data_frame("home_stats"))
112
+ ),
113
+ ui.row(
114
+ ui.column(6, ui.tags.h4(ui.output_text('away_goalie_header'))),
115
+ ui.column(6, ui.tags.h4(ui.output_text('home_goalie_header')))
116
+ ),
117
+ ui.row(
118
+ ui.column(6, ui.output_data_frame("away_goalie")),
119
+ ui.column(6, ui.output_data_frame("home_goalie"))
120
+ )
121
+ )
122
+
123
+ def server(input, output, session):
124
+ query = reactive.Value(None)
125
+ game_info = reactive.Value(None)
126
+
127
+ def get_schedule():
128
+ games = pd.read_csv('https://f005.backblazeb2.com/file/weakside-breakout/info/schedule.csv')
129
+
130
+ return games.loc[games['gameState'].isin(['OFF','FINAL'])]
131
+
132
+ @reactive.Effect
133
+ def query_params():
134
+ #Retreive query parameters
135
+ search = session.input[".clientdata_url_search"]()
136
+ q = parse_qs(urlparse(search).query)
137
+
138
+ print(q)
139
+
140
+ defaults = {
141
+ 'game_id':['2024020001'],
142
+ }
143
+
144
+ for key in defaults.keys():
145
+ if key not in q.keys():
146
+ q.update({key:defaults[key]})
147
+
148
+ query.set(q)
149
+
150
+ def active_params():
151
+ return query.get() or {}
152
+
153
+ @reactive.Effect
154
+ def get_game_info():
155
+ #Load params
156
+ query = active_params()
157
+
158
+ #Load game data
159
+ game = get_schedule()
160
+ game = game.loc[game['id'].astype(str).str.replace('.0','')==query['game_id'][0]]
161
+
162
+ game_info.set({
163
+ 'game_id':game['id'].iloc[0],
164
+ 'season':game['season'].iloc[0],
165
+ 'title':game['game_title'].iloc[0],
166
+ 'date':game['date'].iloc[0],
167
+ 'away_team_abbr':game['away_team_abbr'].iloc[0],
168
+ 'home_team_abbr':game['home_team_abbr'].iloc[0],
169
+ 'away_logo':game['awayTeam.darkLogo'].iloc[0],
170
+ 'home_logo':game['homeTeam.darkLogo'].iloc[0],
171
+ 'away_score':game['awayTeam.score'].iloc[0],
172
+ 'home_score':game['homeTeam.score'].iloc[0],
173
+ })
174
+ print(game_info.get())
175
+
176
+ game_df = reactive.Value(None)
177
+ @reactive.Effect
178
+ def active_game():
179
+ #Determine which season to load based on the input game_id
180
+ info = game_info.get()
181
+ season = info['season']
182
+ #Load appropriate dataframe
183
+ df = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/game_log/wsba_nhl_{season}_game_log.parquet')
184
+ goalie_df = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/game_log/goalie/wsba_nhl_{season}_game_log_goalie.parquet')
185
+ pbp = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/pbp/{season}.parquet')
186
+
187
+ 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'])]])
188
+
189
+ @output
190
+ @render.ui
191
+ def table_filters():
192
+ all_strengths = ['4v5','5v4','5v5','Other','All']
193
+ return ui.panel_well(
194
+ ui.tags.div(
195
+ {"class": "form-row custom-input"},
196
+ ui.input_selectize('stat_type','Type',['Individual','On-Ice']),
197
+ ui.input_selectize('strength_state','Strength',all_strengths,selected='5v5'),
198
+ )
199
+ )
200
+
201
+ @output
202
+ @render.data_frame
203
+ def team_stats():
204
+ df = game_df.get()[0]
205
+ info = game_info.get()
206
+
207
+ df = df.loc[df['Strength']=='All'].groupby('Team').agg(
208
+ Fenwick=('Fi','sum'),
209
+ xG=('xGi','sum'),
210
+ Hits=('HF','sum'),
211
+ Give=('Give','sum'),
212
+ Take=('Take','sum'),
213
+ PIM=('PIM','sum')
214
+ ).reset_index()
215
+
216
+ score = pd.DataFrame([[info['away_team_abbr'],
217
+ info['away_score']],
218
+ [info['home_team_abbr'],
219
+ info['home_score']]],columns=['Team','Goals'])
220
+
221
+ df = pd.merge(df,score,how='left')
222
+ df = df[['Team','Goals','Fenwick','xG','Hits','Give','Take','PIM']]
223
+
224
+ df['xG'] = df['xG'].round(2)
225
+
226
+ print(df)
227
+
228
+ return render.DataTable(df,height='fit-content')
229
+
230
+ @output
231
+ @render.data_frame
232
+ def scoring_summary():
233
+ pbp = game_df.get()[1]
234
+
235
+ pbp = pbp[['event_num','period','seconds_elapsed','strength_state','description','away_score','home_score','xG']].rename(columns={
236
+ 'event_num':'#',
237
+ 'seconds_elapsed':'Seconds',
238
+ 'strength_state':'Strength State',
239
+ 'away_score':'Away Score',
240
+ 'home_score':'Home Score'
241
+ })
242
+
243
+ pbp['xG'] = (pbp['xG']*100).round(4)
244
+
245
+ return render.DataTable(pbp, height='fit-content')
246
+
247
+ @output
248
+ @render.text
249
+ def game_header():
250
+ return game_info.get()['title']
251
+
252
+ @output
253
+ @render.text
254
+ def away_header():
255
+ team = game_info.get()['away_team_abbr']
256
+
257
+ return f'{team} Stats'
258
+
259
+ @output
260
+ @render.text
261
+ def home_header():
262
+ team = game_info.get()['home_team_abbr']
263
+
264
+ return f'{team} Stats'
265
+
266
+ @output
267
+ @render.text
268
+ def away_goalie_header():
269
+ team = game_info.get()['away_team_abbr']
270
+
271
+ return f'{team} Goalie Stats'
272
+
273
+ @output
274
+ @render.text
275
+ def home_goalie_header():
276
+ team = game_info.get()['home_team_abbr']
277
+
278
+ return f'{team} Goalie Stats'
279
+
280
+ @output
281
+ @render.data_frame
282
+ def away_stats():
283
+ df = game_df.get()[0]
284
+ info = game_info.get()
285
+
286
+ if input.stat_type() == 'Individual':
287
+ cols = [
288
+ 'Player','ID','Position','Handedness','TOI','Gi','A1','A2','P1','P','Give','Take','HF','HA',
289
+ 'Fi','xGi'
290
+ ]
291
+ else:
292
+ cols = [
293
+ 'Player','ID','Position','Handedness','TOI','GF','GA','FF','FA','xGF','xGA','GF%','FF%','xGF%'
294
+ ]
295
+
296
+ df = df.loc[(df['Team']==info['away_team_abbr'])&(df['Strength']==input.strength_state()),cols]
297
+
298
+ for col in ['TOI','xGi','xGF','xGA','GF%','FF%','xGF%']:
299
+ try:
300
+ if '%' in col:
301
+ df[col] = (df[col]*100).round(2).astype(str) + '%'
302
+ else:
303
+ df[col] = df[col].round(2)
304
+ except:
305
+ continue
306
+
307
+ df = fix_names(df)
308
+
309
+ return render.DataTable(df.rename(columns={
310
+ 'Gi':'G',
311
+ 'Fi':'iFF',
312
+ 'xGi':'ixG'
313
+ }).drop(columns=['ID']).rename(columns={'Position':'POS','Handedness':'Hand'}))
314
+
315
+ @output
316
+ @render.data_frame
317
+ def home_stats():
318
+ df = game_df.get()[0]
319
+ info = game_info.get()
320
+
321
+ if input.stat_type() == 'Individual':
322
+ cols = [
323
+ 'Player','ID','TOI','Position','Handedness','Gi','A1','A2','P1','P','Give','Take','HF','HA',
324
+ 'Fi','xGi'
325
+ ]
326
+ else:
327
+ cols = [
328
+ 'Player','ID','TOI','Position','Handedness','GF','GA','FF','FA','xGF','xGA','GF%','FF%','xGF%'
329
+ ]
330
+
331
+ df = df.loc[(df['Team']==info['home_team_abbr'])&(df['Strength']==input.strength_state()),cols]
332
+
333
+ for col in ['TOI','xGi','xGF','xGA','GF%','FF%','xGF%']:
334
+ try:
335
+ if '%' in col:
336
+ df[col] = (df[col]*100).round(2).astype(str) + '%'
337
+ else:
338
+ df[col] = df[col].round(2)
339
+ except:
340
+ continue
341
+
342
+ df = fix_names(df)
343
+
344
+ return render.DataTable(df.rename(columns={
345
+ 'Gi':'G',
346
+ 'Fi':'iFF',
347
+ 'xGi':'ixG'
348
+ }).drop(columns=['ID']).rename(columns={'Position':'POS','Handedness':'Hand'}))
349
+
350
+ @output
351
+ @render.data_frame
352
+ def away_goalie():
353
+ df = game_df.get()[2]
354
+ info = game_info.get()
355
+
356
+ cols = [
357
+ 'Goalie','ID','TOI','Position','Handedness','GA','FA','xGA','GSAx'
358
+ ]
359
+
360
+ df = df.loc[(df['Team']==info['away_team_abbr'])&(df['Strength']==input.strength_state()),cols]
361
+
362
+ for col in ['TOI','xGA','GSAx']:
363
+ try:
364
+ if '%' in col:
365
+ df[col] = (df[col]*100).round(2).astype(str) + '%'
366
+ else:
367
+ df[col] = df[col].round(2)
368
+ except:
369
+ continue
370
+
371
+ df = fix_names(df,'Goalie')
372
+
373
+ return render.DataTable(df.drop(columns=['ID']).rename(columns={'Position':'POS','Handedness':'Hand'}))
374
+
375
+ @output
376
+ @render.data_frame
377
+ def home_goalie():
378
+ df = game_df.get()[2]
379
+ info = game_info.get()
380
+
381
+ cols = [
382
+ 'Goalie','ID','TOI','Position','Handedness','GA','FA','xGA','GSAx'
383
+ ]
384
+
385
+ df = df.loc[(df['Team']==info['home_team_abbr'])&(df['Strength']==input.strength_state()),cols]
386
+
387
+ for col in ['TOI','xGA','GSAx']:
388
+ try:
389
+ if '%' in col:
390
+ df[col] = (df[col]*100).round(2).astype(str) + '%'
391
+ else:
392
+ df[col] = df[col].round(2)
393
+ except:
394
+ continue
395
+
396
+ df = fix_names(df,'Goalie')
397
+
398
+ return render.DataTable(df.drop(columns=['ID']).rename(columns={'Position':'POS','Handedness':'Hand'}))
399
+
400
+ 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