wsba-hockey 1.1.9__tar.gz → 1.2.1__tar.gz
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.
- {wsba_hockey-1.1.9/src/wsba_hockey.egg-info → wsba_hockey-1.2.1}/PKG-INFO +16 -15
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/README.md +15 -14
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/pyproject.toml +1 -1
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/src/wsba_hockey/tools/scraping.py +146 -170
- wsba_hockey-1.2.1/src/wsba_hockey/tools/utils/shared.py +75 -0
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/src/wsba_hockey/tools/xg_model.py +6 -1
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/src/wsba_hockey/wsba_main.py +47 -14
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1/src/wsba_hockey.egg-info}/PKG-INFO +16 -15
- wsba_hockey-1.2.1/src/wsba_hockey.egg-info/SOURCES.txt +18 -0
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/tests/tests.py +8 -2
- wsba_hockey-1.1.9/src/wsba_hockey/api/api/index.py +0 -162
- wsba_hockey-1.1.9/src/wsba_hockey/data_pipelines.py +0 -247
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/duckdb/vendor.py +0 -146
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/flatted/python/flatted.py +0 -149
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/flatted/python/test.py +0 -63
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/gyp_main.py +0 -45
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSNew.py +0 -367
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSProject.py +0 -206
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings.py +0 -1270
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings_test.py +0 -1547
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSToolFile.py +0 -59
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSUserFile.py +0 -153
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSUtil.py +0 -271
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/MSVSVersion.py +0 -574
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/__init__.py +0 -690
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/common.py +0 -661
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/common_test.py +0 -78
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/easy_xml.py +0 -165
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/easy_xml_test.py +0 -109
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/flock_tool.py +0 -55
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/analyzer.py +0 -808
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/android.py +0 -1173
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/cmake.py +0 -1321
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/compile_commands_json.py +0 -120
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/dump_dependency_json.py +0 -103
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/eclipse.py +0 -464
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/gypd.py +0 -89
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/gypsh.py +0 -58
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/make.py +0 -2714
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs.py +0 -3981
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs_test.py +0 -44
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja.py +0 -2936
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja_test.py +0 -55
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode.py +0 -1394
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode_test.py +0 -25
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/input.py +0 -3130
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/input_test.py +0 -98
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/mac_tool.py +0 -771
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/msvs_emulation.py +0 -1271
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/ninja_syntax.py +0 -174
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/simple_copy.py +0 -61
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/win_tool.py +0 -374
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/xcode_emulation.py +0 -1939
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/xcode_ninja.py +0 -302
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/xcodeproj_file.py +0 -3197
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/xml_fix.py +0 -65
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/test_gyp.py +0 -261
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/tools/graphviz.py +0 -102
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/tools/pretty_gyp.py +0 -156
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/tools/pretty_sln.py +0 -181
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/tools/pretty_vcproj.py +0 -339
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/test/fixtures/test-charmap.py +0 -31
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/update-gyp.py +0 -64
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/gyp_main.py +0 -45
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSNew.py +0 -367
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSProject.py +0 -206
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings.py +0 -1270
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings_test.py +0 -1547
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSToolFile.py +0 -59
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSUserFile.py +0 -153
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSUtil.py +0 -271
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/MSVSVersion.py +0 -574
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/__init__.py +0 -666
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/common.py +0 -654
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/common_test.py +0 -78
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/easy_xml.py +0 -165
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/easy_xml_test.py +0 -109
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/flock_tool.py +0 -55
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/analyzer.py +0 -808
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/android.py +0 -1173
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/cmake.py +0 -1321
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/compile_commands_json.py +0 -120
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/dump_dependency_json.py +0 -103
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/eclipse.py +0 -464
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/gypd.py +0 -89
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/gypsh.py +0 -58
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/make.py +0 -2518
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs.py +0 -3978
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs_test.py +0 -44
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja.py +0 -2936
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja_test.py +0 -55
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode.py +0 -1394
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode_test.py +0 -25
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/input.py +0 -3137
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/input_test.py +0 -98
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/mac_tool.py +0 -771
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/msvs_emulation.py +0 -1271
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/ninja_syntax.py +0 -174
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/simple_copy.py +0 -61
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/win_tool.py +0 -374
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/xcode_emulation.py +0 -1939
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/xcode_ninja.py +0 -302
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/xcodeproj_file.py +0 -3197
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/xml_fix.py +0 -65
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/setup.py +0 -42
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/test_gyp.py +0 -260
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/tools/graphviz.py +0 -102
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/tools/pretty_gyp.py +0 -156
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/tools/pretty_sln.py +0 -181
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/tools/pretty_vcproj.py +0 -339
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/test/fixtures/test-charmap.py +0 -31
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/update-gyp.py +0 -46
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/duos/app.py +0 -210
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/duos/calc.py +0 -163
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/game_stats/app.py +0 -401
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/game_stats/name_fix.py +0 -47
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/app.py +0 -101
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/plot.py +0 -71
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/rink_plot.py +0 -245
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/app.py +0 -108
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/plot.py +0 -95
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/rink_plot.py +0 -245
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/line-combos/app.py +0 -245
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/line-combos/plot.py +0 -275
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/line-combos/rink_plot.py +0 -245
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/app.py +0 -145
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/plot.py +0 -79
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/rink_plot.py +0 -245
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/app.py +0 -406
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/plot.py +0 -79
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/rink_plot.py +0 -245
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/app.py +0 -110
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/plot.py +0 -59
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/rink_plot.py +0 -245
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/app.py +0 -103
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/plot.py +0 -95
- wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/rink_plot.py +0 -245
- wsba_hockey-1.1.9/src/wsba_hockey/flask/app.py +0 -77
- wsba_hockey-1.1.9/src/wsba_hockey/tools/__init__.py +0 -0
- wsba_hockey-1.1.9/src/wsba_hockey/tools/utils/__init__.py +0 -1
- wsba_hockey-1.1.9/src/wsba_hockey/tools/utils/config.py +0 -14
- wsba_hockey-1.1.9/src/wsba_hockey/tools/utils/save_pages.py +0 -133
- wsba_hockey-1.1.9/src/wsba_hockey/tools/utils/shared.py +0 -450
- wsba_hockey-1.1.9/src/wsba_hockey/workspace.py +0 -28
- wsba_hockey-1.1.9/src/wsba_hockey.egg-info/SOURCES.txt +0 -151
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/LICENSE +0 -0
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/setup.cfg +0 -0
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/src/wsba_hockey/__init__.py +0 -0
- {wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/node-gyp/gyp/pylib/gyp/generator → wsba_hockey-1.2.1/src/wsba_hockey/tools}/__init__.py +0 -0
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/src/wsba_hockey/tools/agg.py +0 -0
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/src/wsba_hockey/tools/archive/old_scraping.py +0 -0
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/src/wsba_hockey/tools/plotting.py +0 -0
- {wsba_hockey-1.1.9/src/wsba_hockey/evidence/weakside-breakout/node_modules/sqlite3/node_modules/node-gyp/gyp/pylib/gyp/generator → wsba_hockey-1.2.1/src/wsba_hockey/tools/utils}/__init__.py +0 -0
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/src/wsba_hockey.egg-info/dependency_links.txt +0 -0
- {wsba_hockey-1.1.9 → wsba_hockey-1.2.1}/src/wsba_hockey.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: wsba_hockey
|
3
|
-
Version: 1.1
|
3
|
+
Version: 1.2.1
|
4
4
|
Summary: WeakSide Breakout's complete Python package of access to hockey data, primairly including the scraping of National Hockey League schedule, play-by-play, and shifts information.
|
5
5
|
Author-email: Owen Singh <owenbksingh@gmail.com>
|
6
6
|
Project-URL: Homepage, https://github.com/owensingh38/wsba_hockey/
|
@@ -28,30 +28,28 @@ import wsba_hockey as wsba
|
|
28
28
|
```
|
29
29
|
|
30
30
|
## ALL FEATURES
|
31
|
-
### Note: Features yet included are marked with *
|
32
|
-
|
33
31
|
|
34
32
|
## SCRAPING
|
35
33
|
### NHL Play-by-Play (of any game frame up to a full season)
|
36
34
|
#### Functions:
|
37
35
|
|
38
36
|
```python
|
39
|
-
wsba.nhl_scrape_game(
|
40
|
-
wsba.nhl_scrape_season(
|
37
|
+
wsba.nhl_scrape_game(2024020918,split_shifts=False,remove=['game-end'])
|
38
|
+
wsba.nhl_scrape_season(20242025,split_shifts=False,remove=['game-end'],local=True)
|
41
39
|
```
|
42
40
|
|
43
41
|
### NHL Season Information
|
44
42
|
|
45
43
|
```python
|
46
|
-
wsba.nhl_scrape_schedule(
|
47
|
-
wsba.nhl_scrape_seasons_info(seasons=[
|
44
|
+
wsba.nhl_scrape_schedule(20242025)
|
45
|
+
wsba.nhl_scrape_seasons_info(seasons=[20212022,20222023,20232024,20242025])
|
48
46
|
wsba.nhl_scrape_standings(arg = '2024-03-20')
|
49
47
|
```
|
50
48
|
|
51
49
|
### NHL Rosters and Player Information
|
52
50
|
|
53
51
|
```python
|
54
|
-
wsba.nhl_scrape_roster(
|
52
|
+
wsba.nhl_scrape_roster(20242025)
|
55
53
|
wsba.nhl_scrape_team_info()
|
56
54
|
```
|
57
55
|
|
@@ -66,7 +64,7 @@ wsba.nhl_scrape_prospects('BOS')
|
|
66
64
|
## DATA ANALYTICS
|
67
65
|
### Expected Goals
|
68
66
|
```python
|
69
|
-
pbp = wsba.nhl_scrape_game(
|
67
|
+
pbp = wsba.nhl_scrape_game(2024020918,split_shifts=False,remove=['game-end'])
|
70
68
|
pbp = wsba.nhl_apply_xG(pbp)
|
71
69
|
```
|
72
70
|
|
@@ -74,15 +72,15 @@ pbp = wsba.nhl_apply_xG(pbp)
|
|
74
72
|
|
75
73
|
### Stat Aggregation
|
76
74
|
```python
|
77
|
-
pbp = wsba.nhl_scrape_season(
|
75
|
+
pbp = wsba.nhl_scrape_season(20232024,remove=[], local = True)
|
78
76
|
wsba.nhl_calculate_stats(pbp,'skater',[2],['5v5','4v4','3v3'],shot_impact = True)
|
79
77
|
```
|
80
78
|
### Shot Plotting (Plots, Heatmaps, etc.)
|
81
79
|
```python
|
82
80
|
skater_dict = {
|
83
|
-
'Patrice Bergeron':[
|
81
|
+
'Patrice Bergeron':[20212022,'BOS']
|
84
82
|
}
|
85
|
-
pbp = wsba.nhl_scrape_season(
|
83
|
+
pbp = wsba.nhl_scrape_season(20212022,remove=[], local = True)
|
86
84
|
|
87
85
|
wsba.nhl_plot_skaters_shots(pbp,skater_dict,['5v5'],onice='for',legend=True)
|
88
86
|
wsba.nhl_plot_games(pbp,legend=True)
|
@@ -91,18 +89,21 @@ wsba.nhl_plot_games(pbp,legend=True)
|
|
91
89
|
## REPOSITORY
|
92
90
|
### Past Season Play-by-Play
|
93
91
|
```python
|
94
|
-
wsba.repo_load_pbp(seasons=[
|
92
|
+
wsba.repo_load_pbp(seasons=[20212022,20222023,20232024,20242025])
|
95
93
|
```
|
96
94
|
### Team Information
|
97
95
|
```python
|
98
96
|
wsba.repo_load_teaminfo()
|
99
|
-
wsba.repo_load_rosters(seasons=[
|
97
|
+
wsba.repo_load_rosters(seasons=[20212022,20222023,20232024,20242025])
|
100
98
|
```
|
101
99
|
### Schedule
|
102
100
|
```python
|
103
|
-
wsba.repo_load_schedule(seasons=[
|
101
|
+
wsba.repo_load_schedule(seasons=[20212022,20222023,20232024,20242025])
|
104
102
|
```
|
105
103
|
|
104
|
+
## DOCUMENTATION
|
105
|
+
View full documentation here: [WSBA Hockey Package Documentation](https://owensingh38.github.io/wsba_hockey/)
|
106
|
+
|
106
107
|
## ACKNOWLEDGEMENTS AND CREDITS
|
107
108
|
### Huge thanks to the following:
|
108
109
|
Harry Shomer - Creator of the hockey_scraper package, which contains select utils functions within this package and otherwise inspires the creation of this package.
|
@@ -13,30 +13,28 @@ import wsba_hockey as wsba
|
|
13
13
|
```
|
14
14
|
|
15
15
|
## ALL FEATURES
|
16
|
-
### Note: Features yet included are marked with *
|
17
|
-
|
18
16
|
|
19
17
|
## SCRAPING
|
20
18
|
### NHL Play-by-Play (of any game frame up to a full season)
|
21
19
|
#### Functions:
|
22
20
|
|
23
21
|
```python
|
24
|
-
wsba.nhl_scrape_game(
|
25
|
-
wsba.nhl_scrape_season(
|
22
|
+
wsba.nhl_scrape_game(2024020918,split_shifts=False,remove=['game-end'])
|
23
|
+
wsba.nhl_scrape_season(20242025,split_shifts=False,remove=['game-end'],local=True)
|
26
24
|
```
|
27
25
|
|
28
26
|
### NHL Season Information
|
29
27
|
|
30
28
|
```python
|
31
|
-
wsba.nhl_scrape_schedule(
|
32
|
-
wsba.nhl_scrape_seasons_info(seasons=[
|
29
|
+
wsba.nhl_scrape_schedule(20242025)
|
30
|
+
wsba.nhl_scrape_seasons_info(seasons=[20212022,20222023,20232024,20242025])
|
33
31
|
wsba.nhl_scrape_standings(arg = '2024-03-20')
|
34
32
|
```
|
35
33
|
|
36
34
|
### NHL Rosters and Player Information
|
37
35
|
|
38
36
|
```python
|
39
|
-
wsba.nhl_scrape_roster(
|
37
|
+
wsba.nhl_scrape_roster(20242025)
|
40
38
|
wsba.nhl_scrape_team_info()
|
41
39
|
```
|
42
40
|
|
@@ -51,7 +49,7 @@ wsba.nhl_scrape_prospects('BOS')
|
|
51
49
|
## DATA ANALYTICS
|
52
50
|
### Expected Goals
|
53
51
|
```python
|
54
|
-
pbp = wsba.nhl_scrape_game(
|
52
|
+
pbp = wsba.nhl_scrape_game(2024020918,split_shifts=False,remove=['game-end'])
|
55
53
|
pbp = wsba.nhl_apply_xG(pbp)
|
56
54
|
```
|
57
55
|
|
@@ -59,15 +57,15 @@ pbp = wsba.nhl_apply_xG(pbp)
|
|
59
57
|
|
60
58
|
### Stat Aggregation
|
61
59
|
```python
|
62
|
-
pbp = wsba.nhl_scrape_season(
|
60
|
+
pbp = wsba.nhl_scrape_season(20232024,remove=[], local = True)
|
63
61
|
wsba.nhl_calculate_stats(pbp,'skater',[2],['5v5','4v4','3v3'],shot_impact = True)
|
64
62
|
```
|
65
63
|
### Shot Plotting (Plots, Heatmaps, etc.)
|
66
64
|
```python
|
67
65
|
skater_dict = {
|
68
|
-
'Patrice Bergeron':[
|
66
|
+
'Patrice Bergeron':[20212022,'BOS']
|
69
67
|
}
|
70
|
-
pbp = wsba.nhl_scrape_season(
|
68
|
+
pbp = wsba.nhl_scrape_season(20212022,remove=[], local = True)
|
71
69
|
|
72
70
|
wsba.nhl_plot_skaters_shots(pbp,skater_dict,['5v5'],onice='for',legend=True)
|
73
71
|
wsba.nhl_plot_games(pbp,legend=True)
|
@@ -76,18 +74,21 @@ wsba.nhl_plot_games(pbp,legend=True)
|
|
76
74
|
## REPOSITORY
|
77
75
|
### Past Season Play-by-Play
|
78
76
|
```python
|
79
|
-
wsba.repo_load_pbp(seasons=[
|
77
|
+
wsba.repo_load_pbp(seasons=[20212022,20222023,20232024,20242025])
|
80
78
|
```
|
81
79
|
### Team Information
|
82
80
|
```python
|
83
81
|
wsba.repo_load_teaminfo()
|
84
|
-
wsba.repo_load_rosters(seasons=[
|
82
|
+
wsba.repo_load_rosters(seasons=[20212022,20222023,20232024,20242025])
|
85
83
|
```
|
86
84
|
### Schedule
|
87
85
|
```python
|
88
|
-
wsba.repo_load_schedule(seasons=[
|
86
|
+
wsba.repo_load_schedule(seasons=[20212022,20222023,20232024,20242025])
|
89
87
|
```
|
90
88
|
|
89
|
+
## DOCUMENTATION
|
90
|
+
View full documentation here: [WSBA Hockey Package Documentation](https://owensingh38.github.io/wsba_hockey/)
|
91
|
+
|
91
92
|
## ACKNOWLEDGEMENTS AND CREDITS
|
92
93
|
### Huge thanks to the following:
|
93
94
|
Harry Shomer - Creator of the hockey_scraper package, which contains select utils functions within this package and otherwise inspires the creation of this package.
|
@@ -90,22 +90,26 @@ def get_game_roster(json):
|
|
90
90
|
def get_game_coaches(game_id):
|
91
91
|
#Given game info, return head coaches for away and home team
|
92
92
|
|
93
|
-
#Retreive data
|
94
|
-
json = rs.get(f'https://api-web.nhle.com/v1/gamecenter/{game_id}/right-rail').json()
|
95
|
-
data = json['gameInfo']
|
96
|
-
|
97
|
-
#Add coaches
|
93
|
+
#Retreive data (or try to)
|
98
94
|
try:
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
coaches = {'away':away,
|
103
|
-
'home':home}
|
104
|
-
except KeyError:
|
105
|
-
return {}
|
95
|
+
json = rs.get(f'https://api-web.nhle.com/v1/gamecenter/{game_id}/right-rail').json()
|
96
|
+
data = json['gameInfo']
|
106
97
|
|
107
|
-
|
108
|
-
|
98
|
+
#Add coaches
|
99
|
+
try:
|
100
|
+
away = data['awayTeam']['headCoach']['default'].upper()
|
101
|
+
home = data['homeTeam']['headCoach']['default'].upper()
|
102
|
+
|
103
|
+
coaches = {'away':away,
|
104
|
+
'home':home}
|
105
|
+
except KeyError:
|
106
|
+
return {}
|
107
|
+
|
108
|
+
#Return: dict with coaches
|
109
|
+
return coaches
|
110
|
+
except rs.JSONDecodeError:
|
111
|
+
#Right-rail content is missing for some playoff games in 2019-20
|
112
|
+
return {}
|
109
113
|
|
110
114
|
def get_game_info(game_id):
|
111
115
|
#Given game_id, return game information
|
@@ -247,17 +251,20 @@ async def parse_json(info):
|
|
247
251
|
# x, y - Raw coordinates from JSON pbp
|
248
252
|
# x_adj, y_adj - Adjusted coordinates configuring the away offensive zone to the left and the home offensive zone to the right
|
249
253
|
#Some games (mostly preseason and all star games) do not include coordinates.
|
250
|
-
|
251
|
-
|
252
|
-
events =
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
254
|
+
if info['season'] in [20052006, 20062007, 20072008, 20082009, 20092010]:
|
255
|
+
#If the json is used as a supplement for the ESPN pbp data then remove unnecessary columns
|
256
|
+
events = events.drop(columns=['x','y','event_team_venue','period_seconds_elapsed','game_id',
|
257
|
+
'period_time_elapsed', 'shot_type', 'zone_code', 'event_player_1_id', 'event_player_2_id', 'event_player_3_id'],
|
258
|
+
errors='ignore')
|
259
|
+
else:
|
260
|
+
try:
|
261
|
+
events = adjust_coords(events)
|
262
|
+
except KeyError:
|
263
|
+
print(f"No coordinates found for game {info['game_id'][0]}...")
|
264
|
+
events['x_adj'] = np.nan
|
265
|
+
events['y_adj'] = np.nan
|
266
|
+
events['event_distance'] = np.nan
|
267
|
+
events['event_angle'] = np.nan
|
261
268
|
|
262
269
|
#Period time adjustments (only 'seconds_elapsed' is included in the resulting data)
|
263
270
|
events['period_seconds_elapsed'] = events['period_time_elasped'].apply(convert_to_seconds)
|
@@ -563,141 +570,78 @@ def espn_game_id(date,away,home):
|
|
563
570
|
return game_id
|
564
571
|
|
565
572
|
async def parse_espn(date,away,home):
|
566
|
-
#Given a date formatted as YYYY-MM-DD and teams, return game events
|
573
|
+
#Given a date formatted as YYYY-MM-DD and teams, return game events from ESPN
|
567
574
|
game_id = espn_game_id(date,away,home)
|
568
|
-
url = f'https://www.espn.com/nhl/playbyplay/_/gameId/{game_id}'
|
569
|
-
|
570
|
-
#Code modified from Patrick Bacon
|
571
|
-
|
572
|
-
#Retreive game events as json
|
573
|
-
page = rs.get(url, headers={'User-Agent': 'Mozilla/5.0'}, timeout = 500)
|
574
|
-
soup = BeautifulSoup(page.content.decode('ISO-8859-1'), 'lxml', multi_valued_attributes = None)
|
575
|
-
json = json_lib.loads(str(soup).split('"playGrps":')[1].split(',"tms"')[0])
|
576
|
-
|
577
|
-
#DataFrame of time-related info for events
|
578
|
-
clock_df = pd.DataFrame()
|
579
|
-
|
580
|
-
for period in range(0, len(json)):
|
581
|
-
clock_df = clock_df._append(pd.DataFrame(json[period]))
|
582
|
-
|
583
|
-
clock_df = clock_df[~pd.isna(clock_df.clock)]
|
584
|
-
|
585
|
-
# Needed to add .split(',"st":3')[0] for playoffs
|
586
|
-
|
587
|
-
#DataFrame of coordinates for events
|
588
|
-
coords_df = pd.DataFrame(json_lib.loads(str(soup).split('plays":')[1].split(',"st":1')[0].split(',"st":2')[0].split(',"st":3')[0]))
|
589
|
-
|
590
|
-
clock_df = clock_df.assign(
|
591
|
-
clock = clock_df.clock.apply(lambda x: x['displayValue'])
|
592
|
-
)
|
593
|
-
|
594
|
-
coords_df = coords_df.assign(
|
595
|
-
coords_x = coords_df[~pd.isna(coords_df.coordinate)].coordinate.apply(lambda x: x['x']).astype(int),
|
596
|
-
coords_y = coords_df[~pd.isna(coords_df.coordinate)].coordinate.apply(lambda y: y['y']).astype(int),
|
597
|
-
)
|
598
|
-
|
599
|
-
#Combine
|
600
|
-
espn_events = coords_df.merge(clock_df.loc[:, ['id', 'clock']])
|
601
|
-
|
602
|
-
espn_events = espn_events.assign(
|
603
|
-
period = espn_events['period'].apply(lambda x: x['number']),
|
604
|
-
minutes = espn_events['clock'].str.split(':').apply(lambda x: x[0]).astype(int),
|
605
|
-
seconds = espn_events['clock'].str.split(':').apply(lambda x: x[1]).astype(int),
|
606
|
-
event_type = espn_events['type'].apply(lambda x: x['txt'])
|
607
|
-
)
|
608
|
-
|
609
|
-
espn_events = espn_events.assign(coords_x = np.where((pd.isna(espn_events.coords_x)) & (pd.isna(espn_events.coords_y)) &
|
610
|
-
(espn_events.event_type=='Face Off'), 0, espn_events.coords_x
|
611
|
-
),
|
612
|
-
coords_y = np.where((pd.isna(espn_events.coords_x)) & (pd.isna(espn_events.coords_y)) &
|
613
|
-
(espn_events.event_type=='Face Off'), 0, espn_events.coords_y))
|
614
|
-
|
615
|
-
espn_events = espn_events[(~pd.isna(espn_events.coords_x)) & (~pd.isna(espn_events.coords_y))]
|
616
|
-
|
617
|
-
espn_events = espn_events.assign(
|
618
|
-
coords_x = espn_events.coords_x.astype(int),
|
619
|
-
coords_y = espn_events.coords_y.astype(int)
|
620
|
-
)
|
621
575
|
|
622
|
-
#
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
576
|
+
#Hidden ESPN API endpoint (akin to the gamecenter/{game_id}/play-by-play NHL endpoint)
|
577
|
+
url = f'https://site.api.espn.com/apis/site/v2/sports/hockey/nhl/summary?event={game_id}'
|
578
|
+
data = rs.get(url).json()
|
579
|
+
teams = data['boxscore']['teams']
|
580
|
+
|
581
|
+
#Retreive plays
|
582
|
+
espn_events = pd.json_normalize(data['plays']).rename(columns={
|
583
|
+
'period.number':'period',
|
584
|
+
'clock.displayValue':'period_time_elapsed',
|
585
|
+
'coordinate.x':'x',
|
586
|
+
'coordinate.y':'y',
|
587
|
+
'type.text':'event_type',
|
633
588
|
})
|
589
|
+
|
590
|
+
#Some games are missing plays on ESPN, for some reason
|
634
591
|
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
592
|
+
if espn_events.empty:
|
593
|
+
print(f"No coordinates found for game ...")
|
594
|
+
return pd.DataFrame(columns=['period','seconds_elapsed','event_type','event_team_abbr'])
|
595
|
+
else:
|
596
|
+
#Retreive event team venue with team data (maintain the team abbreviation fill-in at the bottom)
|
597
|
+
espn_events['event_team_venue'] = espn_events['team.id'].replace({
|
598
|
+
teams[0]['team']['id']: teams[0]['homeAway'],
|
599
|
+
teams[1]['team']['id']: teams[1]['homeAway']
|
600
|
+
})
|
601
|
+
|
602
|
+
#Rename events
|
603
|
+
#The turnover event includes just one player in the event information, meaning giveaways and takeaways will have no coordinates for play-by-plays created by ESPN scraping
|
604
|
+
espn_events['event_type'] = espn_events['event_type'].replace({
|
605
|
+
"Face Off":'faceoff',
|
606
|
+
"Hit":'hit',
|
607
|
+
"Shot":'shot-on-goal',
|
608
|
+
"Missed":'missed-shot',
|
609
|
+
"Blocked":'blocked-shot',
|
610
|
+
"Goal":'goal',
|
611
|
+
"Delayed Penalty":'delayed-penalty',
|
612
|
+
"Penalty":'penalty'
|
613
|
+
})
|
614
|
+
|
615
|
+
#Period time adjustments (only 'seconds_elapsed' is included in the resulting data)
|
616
|
+
espn_events['period_time_elapsed'] = espn_events['period_time_elapsed'].fillna('0:00')
|
617
|
+
espn_events['period_seconds_elapsed'] = espn_events['period_time_elapsed'].apply(convert_to_seconds)
|
618
|
+
espn_events['seconds_elapsed'] = ((espn_events['period']-1)*1200)+espn_events['period_seconds_elapsed']
|
619
|
+
|
620
|
+
#Add event team data
|
621
|
+
espn_events['event_team_abbr'] = espn_events['event_team_venue'].replace({
|
622
|
+
"away":away,
|
623
|
+
"home":home
|
624
|
+
})
|
625
|
+
|
626
|
+
#Add temporary game_id for coordinate adjustment
|
627
|
+
espn_events['game_id'] = game_id
|
628
|
+
|
629
|
+
#Coordinate adjustments:
|
630
|
+
# x, y - Raw coordinates from JSON pbp
|
631
|
+
# x_adj, y_adj - Adjusted coordinates configuring the away offensive zone to the left and the home offensive zone to the right
|
632
|
+
#Some games (mostly preseason and all star games) do not include coordinates.
|
633
|
+
try:
|
634
|
+
espn_events = adjust_coords(espn_events)
|
635
|
+
except KeyError:
|
636
|
+
print(f"No coordinates found for game ...")
|
637
|
+
|
638
|
+
espn_events['x_adj'] = np.nan
|
639
|
+
espn_events['y_adj'] = np.nan
|
640
|
+
espn_events['event_distance'] = np.nan
|
641
|
+
espn_events['event_angle'] = np.nan
|
649
642
|
|
650
|
-
|
651
|
-
|
652
|
-
espn_events['x_fixed'] = abs(espn_events['coords_x'])
|
653
|
-
espn_events['y_fixed'] = np.where(espn_events['coords_x']<0,-espn_events['coords_y'],espn_events['coords_y'])
|
654
|
-
espn_events['x_adj'] = np.where(espn_events['homeAway']=="home",espn_events['x_fixed'],-espn_events['x_fixed'])
|
655
|
-
espn_events['y_adj'] = np.where(espn_events['homeAway']=="home",espn_events['y_fixed'],-espn_events['y_fixed'])
|
656
|
-
espn_events['event_distance'] = np.sqrt(((89 - espn_events['x_fixed'])**2) + (espn_events['y_fixed']**2))
|
657
|
-
espn_events['event_angle'] = np.degrees(np.arctan2(abs(espn_events['y_fixed']), abs(89 - espn_events['x_fixed'])))
|
658
|
-
except TypeError:
|
659
|
-
print(f"No coordinates found for ESPN game...")
|
660
|
-
|
661
|
-
espn_events['x_fixed'] = np.nan
|
662
|
-
espn_events['y_fixed'] = np.nan
|
663
|
-
espn_events['x_adj'] = np.nan
|
664
|
-
espn_events['y_adj'] = np.nan
|
665
|
-
espn_events['event_distance'] = np.nan
|
666
|
-
espn_events['event_angle'] = np.nan
|
667
|
-
|
668
|
-
#Assign score and fenwick for each event
|
669
|
-
fenwick_events = ['missed-shot','shot-on-goal','goal']
|
670
|
-
ag = 0
|
671
|
-
ags = []
|
672
|
-
hg = 0
|
673
|
-
hgs = []
|
674
|
-
|
675
|
-
af = 0
|
676
|
-
afs = []
|
677
|
-
hf = 0
|
678
|
-
hfs = []
|
679
|
-
for event,team in zip(list(espn_events['event_type']),list(espn_events['homeAway'])):
|
680
|
-
if event in fenwick_events:
|
681
|
-
if team == "home":
|
682
|
-
hf += 1
|
683
|
-
if event == 'goal':
|
684
|
-
hg += 1
|
685
|
-
else:
|
686
|
-
af += 1
|
687
|
-
if event == 'goal':
|
688
|
-
ag += 1
|
689
|
-
|
690
|
-
ags.append(ag)
|
691
|
-
hgs.append(hg)
|
692
|
-
afs.append(af)
|
693
|
-
hfs.append(hf)
|
694
|
-
|
695
|
-
espn_events['away_score'] = ags
|
696
|
-
espn_events['home_score'] = hgs
|
697
|
-
espn_events['away_fenwick'] = afs
|
698
|
-
espn_events['home_fenwick'] = hfs
|
699
|
-
#Return: play-by-play events in supplied game from ESPN
|
700
|
-
return espn_events
|
643
|
+
#Return: play-by-play events in supplied game from ESPN
|
644
|
+
return espn_events
|
701
645
|
|
702
646
|
def assign_target(data):
|
703
647
|
#Assign target number to plays to assist with merging
|
@@ -712,26 +656,48 @@ def assign_target(data):
|
|
712
656
|
#Revert sort and return dataframe
|
713
657
|
return data.reset_index()
|
714
658
|
|
659
|
+
async def no_data():
|
660
|
+
#Allows the passage of espn_pbp data if it is not needed
|
661
|
+
pass
|
662
|
+
|
715
663
|
async def combine_pbp(info,sources):
|
716
664
|
#Given game info, return complete play-by-play data for provided game
|
717
665
|
|
718
666
|
#Create tasks
|
719
667
|
html_task = asyncio.create_task(parse_html(info))
|
720
668
|
if info['season'] in [20052006, 20062007, 20072008, 20082009, 20092010]:
|
721
|
-
|
669
|
+
espn_task = asyncio.create_task(parse_espn(str(info['game_date']),info['away_team_abbr'],info['home_team_abbr']))
|
722
670
|
json_type = 'espn'
|
723
671
|
else:
|
724
|
-
|
672
|
+
espn_task = asyncio.create_task(no_data())
|
725
673
|
json_type = 'nhl'
|
726
674
|
|
727
|
-
|
675
|
+
json_task = asyncio.create_task(parse_json(info))
|
676
|
+
|
677
|
+
html_pbp, json_pbp, espn_pbp = await asyncio.gather(html_task, json_task, espn_task)
|
728
678
|
|
729
679
|
#Route data combining - json if season is after 2009-2010:
|
730
680
|
if json_type == 'espn':
|
731
681
|
#ESPN x HTML
|
732
|
-
espn_pbp =
|
682
|
+
espn_pbp = espn_pbp.sort_values(['period','seconds_elapsed']).reset_index()
|
733
683
|
merge_col = ['period','seconds_elapsed','event_type','event_team_abbr']
|
684
|
+
|
685
|
+
#Add additional information to espn_pbp with NHL json data
|
686
|
+
espn_pbp = pd.merge(espn_pbp,json_pbp,how='left')
|
734
687
|
|
688
|
+
if sources:
|
689
|
+
dirs_html = f'sources/{info['season']}/HTML/'
|
690
|
+
dirs_json = f'sources/{info['season']}/JSON/'
|
691
|
+
|
692
|
+
if not os.path.exists(dirs_html):
|
693
|
+
os.makedirs(dirs_html)
|
694
|
+
if not os.path.exists(dirs_json):
|
695
|
+
os.makedirs(dirs_json)
|
696
|
+
|
697
|
+
html_pbp.to_csv(f'{dirs_html}{info['game_id']}_HTML.csv',index=False)
|
698
|
+
espn_pbp.to_csv(f'{dirs_json}{info['game_id']}_JSON.csv',index=False)
|
699
|
+
|
700
|
+
print(f' merging on columns...',end="")
|
735
701
|
#Merge pbp
|
736
702
|
df = pd.merge(html_pbp,espn_pbp,how='left',on=merge_col)
|
737
703
|
|
@@ -832,7 +798,7 @@ def parse_shifts_json(info):
|
|
832
798
|
def analyze_shifts(shift, id, name, pos, team):
|
833
799
|
#Collects teams in given shifts html (parsed by Beautiful Soup)
|
834
800
|
#Modified version of Harry Shomer's analyze_shifts function in the hockey_scraper package
|
835
|
-
shifts =
|
801
|
+
shifts = {}
|
836
802
|
|
837
803
|
shifts['player_name'] = name.upper()
|
838
804
|
shifts['player_id'] = id
|
@@ -869,28 +835,38 @@ def parse_shifts_html(info,home):
|
|
869
835
|
td, teams = get_soup(doc)
|
870
836
|
|
871
837
|
team = teams[0]
|
872
|
-
players =
|
838
|
+
players = {}
|
873
839
|
|
874
840
|
# Iterates through each player shifts table with the following data:
|
875
841
|
# Shift #, Period, Start, End, and Duration.
|
876
842
|
for t in td:
|
877
843
|
t = t.get_text()
|
878
|
-
if ',' in t: # If a comma exists it is a player
|
844
|
+
if ',' in t and re.match(r'\d+', t): # If a comma and number exists it is a player
|
879
845
|
name = t
|
880
846
|
|
881
847
|
name = name.split(',')
|
882
848
|
number = int(name[0][:2].strip())
|
883
|
-
|
884
|
-
|
849
|
+
#In very rare cases a player listed will be among the scratches for the same game.
|
850
|
+
#Keeping these is more likely than not misattribution
|
851
|
+
try:
|
852
|
+
id = rosters[str(number)][4]
|
853
|
+
players[id] = {}
|
885
854
|
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
855
|
+
#HTML shift functions assess one team at a time, which simplifies the lookup process with number to name and id
|
856
|
+
|
857
|
+
players[id]['name'] = rosters[str(number)][2]
|
858
|
+
players[id]['pos'] = rosters[str(number)][1]
|
890
859
|
|
891
|
-
|
860
|
+
players[id]['shifts'] = []
|
861
|
+
except KeyError:
|
862
|
+
continue
|
892
863
|
else:
|
893
|
-
|
864
|
+
#If id somehow is not assigned at any point before this is ran then just skip
|
865
|
+
try:
|
866
|
+
#Pushes shifts to current player
|
867
|
+
players[id]['shifts'].extend([t])
|
868
|
+
except UnboundLocalError:
|
869
|
+
continue
|
894
870
|
|
895
871
|
for key in players.keys():
|
896
872
|
# Create lists of shifts-table columns for analysis
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import os
|
2
|
+
import time
|
3
|
+
import json
|
4
|
+
from datetime import datetime, timedelta
|
5
|
+
import re
|
6
|
+
from bs4 import BeautifulSoup, SoupStrainer
|
7
|
+
|
8
|
+
## SHARED FUCNCTIONS ##
|
9
|
+
# Most code in this file originates (entirely or partially) from the hockey_scraper package by Harry Shomer #
|
10
|
+
|
11
|
+
dir = os.path.dirname(os.path.realpath(__file__))
|
12
|
+
|
13
|
+
with open(os.path.join(dir, "team_tri_codes.json"), "r" ,encoding="utf-8") as f:
|
14
|
+
TEAMS = json.load(f)['teams']
|
15
|
+
|
16
|
+
def get_team(team):
|
17
|
+
#Parse team header in HTML
|
18
|
+
return TEAMS.get(team.upper(), team.upper()).upper()
|
19
|
+
|
20
|
+
def convert_to_seconds(minutes):
|
21
|
+
#Convert time formatted as MM:SS in a period to raw seconds
|
22
|
+
if minutes == '-16:0-':
|
23
|
+
return '1200' #Sometimes in the html at the end of the game the time is -16:0-
|
24
|
+
|
25
|
+
#Validate time (invalid times are generally ignored)
|
26
|
+
try:
|
27
|
+
x = time.strptime(minutes.strip(' '), '%M:%S')
|
28
|
+
except ValueError:
|
29
|
+
return None
|
30
|
+
|
31
|
+
return timedelta(hours=x.tm_hour, minutes=x.tm_min, seconds=x.tm_sec).total_seconds()
|
32
|
+
|
33
|
+
def get_contents(game_html):
|
34
|
+
#Parse NHL HTML PBP document
|
35
|
+
parsers = ["html5lib", "lxml", "html.parser"]
|
36
|
+
strainer = SoupStrainer('td', attrs={'class': re.compile(r'bborder')})
|
37
|
+
|
38
|
+
for parser in parsers:
|
39
|
+
# parse_only only works with lxml for some reason
|
40
|
+
if parser == "lxml":
|
41
|
+
soup = BeautifulSoup(game_html, parser, parse_only=strainer)
|
42
|
+
else:
|
43
|
+
soup = BeautifulSoup(game_html, parser)
|
44
|
+
|
45
|
+
tds = soup.find_all("td", {"class": re.compile('.*bborder.*')})
|
46
|
+
|
47
|
+
if len(tds) > 0:
|
48
|
+
break
|
49
|
+
|
50
|
+
return tds
|
51
|
+
|
52
|
+
def get_soup(shifts_html):
|
53
|
+
#Convert html document to soup
|
54
|
+
parsers = ["lxml", "html.parser", "html5lib"]
|
55
|
+
|
56
|
+
for parser in parsers:
|
57
|
+
soup = BeautifulSoup(shifts_html, parser)
|
58
|
+
td = soup.findAll(True, {'class': ['playerHeading + border', 'lborder + bborder']})
|
59
|
+
|
60
|
+
if len(td) > 0:
|
61
|
+
break
|
62
|
+
|
63
|
+
return td, get_teams(soup)
|
64
|
+
|
65
|
+
def get_teams(soup):
|
66
|
+
#Find and return list of teams a given document's match (for HTML shifts parsing)
|
67
|
+
team = soup.find('td', class_='teamHeading + border') # Team for shifts
|
68
|
+
team = team.get_text()
|
69
|
+
|
70
|
+
#Find home team
|
71
|
+
teams = soup.find_all('td', {'align': 'center', 'style': 'font-size: 10px;font-weight:bold'})
|
72
|
+
regex = re.compile(r'>(.*)<br/?>')
|
73
|
+
home_team = regex.findall(str(teams[7]))
|
74
|
+
|
75
|
+
return [team, home_team[0]]
|