What is the RVH?

Growing up almost every coach I had wanted me to stand-up ‘more’ - more being a relative term. Most made peace with the fact that I was going to try to make the same type of saves as Dominik Hasek or Patrick Roy but since I was still a kid, it would probably help if stood up once in a while. Still they had to choose their battles wisely, most picked the same hill to die on: bad angle shots. It was simple geometry really, with the right stick position you could stand there and cover 100% of the net. Let me tell you, there’s nothing worse than standing there while people hack away at your feet knowing you can’t cover the puck with your big toe and dropping to your knees with the puck that tight would create a hole anybody could hit.

By time I got to junior and had a goalie coach we worked in the Vertical-Horizontal (VH) to deal with shots from sharp angle. The strong pad would seal the post (vertical) and the back leg would drop sealing the ice (horizontal). There was always a risk of getting your stick tied up and/or getting beat between the post and skate, but used properly it was pretty tough to beat from range, however there were trade-offs. Leading with pad tied up the hands a bit and meant rebounds were more difficult to control. If there was a rebound the VH was configured well to push off the post, but only in one direction. If you had kept your knee tight to the goalline, but need to push to the top of the crease, too bad, you were pushing across the goalline.

The Reverse Vertical Horizontal (RVH) flipped the configuration of the pads, so the strong pad seals the ice (horizontal) and back leg remains anchored (vertical), freeing up the hands and stick more to make plays and allowing rotation with the back leg and push off with the post leg (I would have never dreamed of this, most nets growing up were easy to knock off whenever you needed a conveinent whistle). The back leg can anchor or drop into a butterfly quickly which gives the RVH more flexibility when repeling a play originating from a sharp angle compared to the VH.

This added flexibility has meant RVH has mostly supplanted VH as a tactic for sharp angle shots, but it’s not perfect either since it leaves a few holes along the post above the pad, particularly over the shoulder. Additionally, because of its flexibility some goaltender’s become too reliant on it, defaulting to it prematural or in situations that don’t call for it. After all, throughout the VH and RVH it was always an option to play sharp angle shots more passively standing up (perhaps anticipating a pass or change of angle) or more aggressively by moving off the post and squaring up. The RVH is a great tactic, but it’s up to the goalie to assess the shooter speed, handedness, and passing options and defensive support and making a read rather than simply defaulting to the RVH.

What does the data say?

As early as 2014, InGoal Magazine’s Greg Balloch discussed the RVH being over-used situationally and improperly, including at the NHL level. You don’t need to watch too many nights of highlights to see someone who’s 6’5" inexplicitly getting beat over the shoulder from a bad angle shot because they were leaning on the post in the RVH. Is this is a growing problem or just the cost of doing business while the benefits outweigh the negatives?

Looking at NHL play-by-play from 2010-2018, we can isolate shots from the RVH has been presumably been used properly and possibly improperly to see if there are any patterns in the:

Observing these metrics over the last 8 seasons might reveal a meaningful change in success rates, but it important to caution that while this appear to be a testable tactic, in a complex game like hockey, effects can be hard to pin down. We don’t have passing data to reveal if, for example, a more aggressive tactic led to more passing from the sharp angle to more dangerous locations, though the number of attempts per game might lend a hint.

Either way it’s possible macro trends don’t reveal anything meaningful since there’s much that unobserved and the data itself is imperfect. The coordinate data has been adjusted to hopefully improve accuracy of shot location.

That said, it is an interesting question, with a potentially meaningful and interesting answer in the data.

First, look at a sample of the data available.

## Load data
load("~/Documents/CWA/Hockey Data/goalie_shot_level_prep.RData")

## Print columns
head(goalie_shot_level_prep)
##       xG_raw         xR        Away_Coach Away_Goalie Away_Goalie_Id
## 1 0.10140381 0.05456016 WILLIE DESJARDINS RYAN MILLER        8468011
## 2 0.16793406 0.04447408 WILLIE DESJARDINS RYAN MILLER        8468011
## 3 0.02312121 0.02173777 WILLIE DESJARDINS RYAN MILLER        8468011
## 4 0.11102723 0.02010524 WILLIE DESJARDINS RYAN MILLER        8468011
## 5 0.02496245 0.01945708 WILLIE DESJARDINS RYAN MILLER        8468011
## 6 0.01541898 0.01617496 WILLIE DESJARDINS RYAN MILLER        8468011
##   Away_Players Away_Score Away_Team       Date
## 1            6          0       VAN 2016-10-23
## 2            6          0       VAN 2016-10-23
## 3            6          0       VAN 2016-10-23
## 4            6          0       VAN 2016-10-23
## 5            6          0       VAN 2016-10-23
## 6            6          0       VAN 2016-10-23
##                                                                        Description
## 1                                ANA ONGOAL - #42 MANSON, Wrist, Off. Zone, 25 ft.
## 2 ANA #7 COGLIANO(3), Slap, Off. Zone, 21 ft.Assists: #42 MANSON(1); #17 KESLER(3)
## 3                           ANA #15 GETZLAF, Wrist, Wide of Net, Off. Zone, 32 ft.
## 4                            VAN #36 HANSEN, Wrist, Wide of Net, Off. Zone, 19 ft.
## 5                             ANA #17 KESLER, Snap, Wide of Net, Off. Zone, 42 ft.
## 6                                ANA ONGOAL - #17 KESLER, Wrist, Off. Zone, 42 ft.
##   Ev_Team Ev_Zone Event Game_Id    Home_Coach Home_Goalie Home_Goalie_Id
## 1     ANA     Off  SHOT   20080 RANDY CARLYLE JOHN GIBSON        8476434
## 2     ANA     Off  GOAL   20080 RANDY CARLYLE JOHN GIBSON        8476434
## 3     ANA     Off  MISS   20080 RANDY CARLYLE JOHN GIBSON        8476434
## 4     VAN     Off  MISS   20080 RANDY CARLYLE JOHN GIBSON        8476434
## 5     ANA     Off  MISS   20080 RANDY CARLYLE JOHN GIBSON        8476434
## 6     ANA     Off  SHOT   20080 RANDY CARLYLE JOHN GIBSON        8476434
##   Home_Players Home_Score Home_Team Home_Zone Period Seconds_Elapsed
## 1            6          0       ANA       Off      1              31
## 2            6          0       ANA       Off      1              33
## 3            6          1       ANA       Off      1             126
## 4            6          1       ANA       Def      1             163
## 5            6          1       ANA       Off      1             184
## 6            6          1       ANA       Off      1             204
##   Strength Time_Elapsed       Type    awayPlayer1 awayPlayer1_id
## 1      5x5         0:31 WRIST SHOT   HENRIK SEDIN        8467876
## 2      5x5         0:33  SLAP SHOT   HENRIK SEDIN        8467876
## 3      5x5         2:06 WRIST SHOT BRANDON SUTTER        8474091
## 4      5x5         2:43 WRIST SHOT BRANDON SUTTER        8474091
## 5      5x5         3:04 WRIST SHOT   HENRIK SEDIN        8467876
## 6      5x5         3:24 WRIST SHOT   HENRIK SEDIN        8467876
##       awayPlayer2 awayPlayer2_id    awayPlayer3 awayPlayer3_id
## 1    DANIEL SEDIN        8467875 SVEN BAERTSCHI        8476466
## 2    DANIEL SEDIN        8467875 SVEN BAERTSCHI        8476466
## 3 MARKUS GRANLUND        8476440  JANNIK HANSEN        8471498
## 4 MARKUS GRANLUND        8476440  JANNIK HANSEN        8471498
## 5    DANIEL SEDIN        8467875 SVEN BAERTSCHI        8476466
## 6   LOUI ERIKSSON        8470626 SVEN BAERTSCHI        8476466
##         awayPlayer4 awayPlayer4_id     awayPlayer5 awayPlayer5_id
## 1 CHRISTOPHER TANEV        8475690 ALEXANDER EDLER        8471303
## 2 CHRISTOPHER TANEV        8475690 ALEXANDER EDLER        8471303
## 3 CHRISTOPHER TANEV        8475690 ALEXANDER EDLER        8471303
## 4        BEN HUTTON        8477018 ERIK GUDBRANSON        8475790
## 5        BEN HUTTON        8477018 ERIK GUDBRANSON        8475790
## 6        BEN HUTTON        8477018 ERIK GUDBRANSON        8475790
##   awayPlayer6 awayPlayer6_id     homePlayer1 homePlayer1_id homePlayer2
## 1 RYAN MILLER        8468011 ANDREW COGLIANO        8471699 RYAN KESLER
## 2 RYAN MILLER        8468011 ANDREW COGLIANO        8471699 RYAN KESLER
## 3 RYAN MILLER        8468011    RYAN GETZLAF        8470612 COREY PERRY
## 4 RYAN MILLER        8468011 ANDREW COGLIANO        8471699 RYAN KESLER
## 5 RYAN MILLER        8468011 ANDREW COGLIANO        8471699 RYAN KESLER
## 6 RYAN MILLER        8468011    RYAN GARBUTT        8476116 RYAN KESLER
##   homePlayer2_id       homePlayer3 homePlayer3_id    homePlayer4
## 1        8470616 JAKOB SILFVERBERG        8475164     CAM FOWLER
## 2        8470616 JAKOB SILFVERBERG        8475164     CAM FOWLER
## 3        8470621      NICK RITCHIE        8477941 CLAYTON STONER
## 4        8470616 JAKOB SILFVERBERG        8475164     CAM FOWLER
## 5        8470616 JAKOB SILFVERBERG        8475164 CLAYTON STONER
## 6        8470616 JAKOB SILFVERBERG        8475164   KEVIN BIEKSA
##   homePlayer4_id  homePlayer5 homePlayer5_id homePlayer6 homePlayer6_id
## 1        8475764  JOSH MANSON        8476312 JOHN GIBSON        8476434
## 2        8475764  JOSH MANSON        8476312 JOHN GIBSON        8476434
## 3        8470886 SAMI VATANEN        8475222 JOHN GIBSON        8476434
## 4        8475764  JOSH MANSON        8476312 JOHN GIBSON        8476434
## 5        8470886 SAMI VATANEN        8475222 JOHN GIBSON        8476434
## 6        8469598  JOSH MANSON        8476312 JOHN GIBSON        8476434
##     p1_ID         p1_name   p2_ID     p2_name   p3_ID     p3_name  xC  yC
## 1 8476312     JOSH MANSON       0                   0              65  -2
## 2 8471699 ANDREW COGLIANO 8476312 JOSH MANSON 8470616 RYAN KESLER  71  10
## 3 8470612    RYAN GETZLAF       0                   0              82 -32
## 4 8471498   JANNIK HANSEN       0                   0             -78  15
## 5 8470616     RYAN KESLER       0                   0              54 -23
## 6 8470616     RYAN KESLER       0                   0              65 -34
##     season  season2 Season_Type season_model lagged_Event lagged_Ev_Zone
## 1 20162017 20162017          RS    2017_2018         TURN            Def
## 2 20162017 20162017          RS    2017_2018         SHOT            Off
## 3 20162017 20162017          RS    2017_2018          HIT            Off
## 4 20162017 20162017          RS    2017_2018          HIT            Off
## 5 20162017 20162017          RS    2017_2018        BLOCK            Def
## 6 20162017 20162017          RS    2017_2018         MISS            Off
##   lagged_Seconds_Elapsed lagged_xC lagged_yC Goal EmptyNet_SA is_Rebound
## 1                     30       -79        31    0           0          0
## 2                     31        65        -2    1           0          1
## 3                    125        98       -24    0           0          0
## 4                    163        98       -14    0           0          0
## 5                    177        70         4    0           0          0
## 6                    184        54       -23    0           0          0
##   is_Bounce is_Rush Home_Shooter Direction X_unadj Y_unadj LS_Shot
## 1         0       1            1         1      65      -2       1
## 2         0       0            1         1      71      10       0
## 3         0       0            1         1      82     -32       1
## 4         0       0            0        -1      78     -15       1
## 5         0       0            1         1      54     -23       1
## 6         0       0            1         1      65     -34       1
##   LN_Last_Event_Time LastEV_Off_Faceoff LastEV_Def_Faceoff
## 1       0.0009995003                  0                  0
## 2       0.6936470556                  0                  0
## 3       0.0009995003                  0                  0
## 4       0.0000000000                  0                  0
## 5       1.9460529960                  0                  0
## 6       2.9957822723                  0                  0
##   LastEV_Neu_Faceoff LastEV_Off_Shot LastEV_Def_Shot LastEV_Neu_Shot
## 1                  0       0.0000000               0               0
## 2                  0       0.6936471               0               0
## 3                  0       0.0000000               0               0
## 4                  0       0.0000000               0               0
## 5                  0       1.9460530               0               0
## 6                  0       2.9957823               0               0
##   LastEV_Off_Give LastEV_Def_Give LastEV_Neu_Give LastEV_Distance
## 1    0.0009995003               0               0       147.73287
## 2    0.0000000000               0               0        13.41641
## 3    0.0000000000               0               0        17.88854
## 4    0.0000000000               0               0        20.02498
## 5    0.0000000000               0               0        31.38471
## 6    0.0000000000               0               0        15.55635
##   LastEV_FtperSec_Change LastEV_Angle_Change LastEV_AngleperSec_Change
## 1            147.7328670            15.21848                 15.218477
## 2              6.7082039            33.81825                 16.909123
## 3             17.8885438            32.89513                 32.895132
## 4              0.0000000            68.98906                  0.000000
## 5              4.4835300            45.19929                  6.457041
## 6              0.7778175            21.47178                  1.073589
##   LN_LastEV_AngleperFt_Change LN_LastEV_FtperSec_Change
## 1                  -2.2719251                 4.9954064
## 2                   0.9245617                 1.9033462
## 3                   0.6092186                 2.8841661
## 4                   1.2369963                -9.2103404
## 5                   0.3648300                 1.5004330
## 6                   0.3223429                -0.2511349
##   LN_LastEV_AngleperSec_Change  X   Y Shot_Distance_Unadj Shot_Distance
## 1                   2.72251684 62  -1            24.08319      27.01851
## 2                   2.82785921 67  12            20.59126      25.05993
## 3                   3.49332774 81 -32            32.75668      32.98485
## 4                  -9.21034037 75 -14            18.60108      19.79899
## 5                   1.86518669 51 -22            41.88078      43.90900
## 6                   0.07110021 62 -35            41.61730      44.20407
##   Shot_Angle Last_Shot_Distance Last_Shot_Angle Last_LS_Shot
## 1   87.87890                 NA              NA           NA
## 2   61.38954           27.01851        87.87890            1
## 3   14.03624           25.05993        61.38954            0
## 4   45.00000                 NA              NA           NA
## 5   59.93142           32.98485        14.03624            1
## 6   37.64762           43.90900        59.93142            1
##   Rebound_Distance_Change Rebound_Angle_Change Rebound_Angular_Velocity
## 1                 0.00000              0.00000                0.0000000
## 2                52.07844             30.73156                0.5901013
## 3                 0.00000              0.00000                0.0000000
## 4                 0.00000              0.00000                0.0000000
## 5                 0.00000              0.00000                0.0000000
## 6                 0.00000              0.00000                0.0000000
##   LN_Rebound_Angular_Velocity SA_Goalie_Id Away_State Home_State
## 1                    0.000000      8468011          5          5
## 2                   -0.527461      8468011          5          5
## 3                    0.000000      8468011          5          5
## 4                    0.000000      8476434          5          5
## 5                    0.000000      8468011          5          5
## 6                    0.000000      8468011          5          5
##   Results_inRebound Shooter_State Goalie_State Game_State State_Space
## 1                 1             5            5        5v5          10
## 2                 0             5            5        5v5          10
## 3                 0             5            5        5v5          10
## 4                 0             5            5        5v5          10
## 5                 0             5            5        5v5          10
## 6                 0             5            5        5v5          10
##   Shooter_State_Advantage shooterDOB Player_Position Shoots  goalieDOB
## 1                       0 1991-10-07               D      R 1980-07-17
## 2                       0 1987-06-14               L      L 1980-07-17
## 3                       0 1985-05-10               C      R 1980-07-17
## 4                       0 1986-03-15               R      R 1993-07-14
## 5                       0 1984-08-31               C      R 1980-07-17
## 6                       0 1984-08-31               C      R 1980-07-17
##   playerPositionCode Catches Shooter_Handedness Handed_Class Handed_Class2
## 1                  G       L                  R           RL      Opposite
## 2                  G       L                  L           LL          Same
## 3                  G       L                  R           RL      Opposite
## 4                  G       L                  R           RL      Opposite
## 5                  G       L                  R           RL      Opposite
## 6                  G       L                  R           RL      Opposite
##   Player_Position2 Cum_Goal Cum_Shots Regressed_Shooting_Indexed
## 1                D        0         0                  1.0000000
## 2                F        0         0                  1.0000000
## 3                F        0         0                  1.0000000
## 4                F        0         0                  1.0000000
## 5                F        0         0                  1.0000000
## 6                F        0         1                  0.9973404
##           xG    xG_team   SA_Goalie Replacement SA_Team NonRebound_Shot
## 1 0.10140381 0.10140381 RYAN MILLER           0     VAN               1
## 2 0.16793406 0.15090491 RYAN MILLER           0     VAN               0
## 3 0.02312121 0.02312121 RYAN MILLER           0     VAN               1
## 4 0.11102723 0.11102723 JOHN GIBSON           0     ANA               1
## 5 0.02496245 0.02496245 RYAN MILLER           0     VAN               1
## 6 0.01541898 0.01541898 RYAN MILLER           0     VAN               1
##   xG_FirstShot rebound_goal_probability        dob  Age
## 1   0.10140381                0.2640773 1980-07-17 36.2
## 2   0.00000000                0.2640773 1980-07-17 36.2
## 3   0.02312121                0.2640773 1980-07-17 36.2
## 4   0.11102723                0.2640773 1993-07-14 23.2
## 5   0.02496245                0.2640773 1980-07-17 36.2
## 6   0.01541898                0.2640773 1980-07-17 36.2

What does the data look like?

We only care about bad angle shots where a goalie might select the RVH tactic, either properly or improperly. To do this we can look at shots within 30ft of the net and sharper than a 45degree angle to the goalline (see below) or from below the goalline. Further we might want to breakout: 1) Shots within 10ft (RVH seals the ice off, perhaps helping in tight) 2) Shots within 45degrees to 22.5degrees (an area of the ice where RVH might be over-used) 3) Shots within 22.5degrees to 0degrees (an area of the ice where RVH is encouraged) 4) Shots behind the goalline (generally on recorded as a shot if it deflects off something and bounces, have goalies helped prevent these?) Flag and limit to shots in trapazoids on either side of the net. Create variables regarding rebounds, rebounds xG, and rebound shooting percentage. Plot on rink

rink <- fun.draw_rink() + coord_fixed()

shot_dist <- 12

## Take all shots and create features
all_nonrush_shots <- goalie_shot_level_prep %>%
      arrange(season, Game_Id, Seconds_Elapsed) %>%
      ## Flag if shot coordinate
      filter(EmptyNet_SA == 0) %>%
      filter(is_Rebound == 0) %>%
      ## Orient all shots on same end of rink
      mutate(xC = ifelse(Direction == 1, xC, -1 * xC),
             yC = ifelse(Direction == 1, yC, -1 * yC),
             Results_inRebound = ifelse(Results_inRebound == 1 & lead(Shot_Distance) <= 30, 1, 0)) %>%
      ## Limit variables
      select(xC, yC, is_Rebound, Results_inRebound, xR, Goal, SA_Goalie, Player_Position2, p1_name, season, Game_Id, Seconds_Elapsed, lagged_Event, Type, Strength, xG, Shooter_Handedness, Handed_Class, Game_State, Shot_Distance) %>%
      ## Create variables
      mutate(bad_angle = ifelse(((abs(yC) > 3) & (xC <= 89) & (xC >=79) & (xC >= (89 - (0.5 * abs(yC))))) | (xC > 89 & abs(yC) > 3)
                                  , 1, 0),
             rebound_xG = ifelse(bad_angle == 1 & Results_inRebound == 1, lead(xG), NA),
             rebound_actualG = ifelse(bad_angle == 1 & Results_inRebound == 1, lead(Goal), NA),
             rebound_yC = ifelse(bad_angle == 1 & Results_inRebound == 1, lead(yC), NA),
             rebound_xC = ifelse(bad_angle == 1 & Results_inRebound == 1, lead(xC), NA),
             strong_hand_shooter = ifelse((yC < 0 & Shooter_Handedness == "L") | (yC > 0 & Shooter_Handedness == "R"),
                                          "Strong", "Weak")
) 

## Limit to bad angle shots
bad_angle_shots <- all_nonrush_shots %>%
      filter(bad_angle == 1) %>%
      mutate(angle_type = ifelse(xC > 89,"BehindNet",
                          ifelse(Shot_Distance < shot_dist & (xC >= (89 - (0.25 * abs(yC)))),"Close-PoorAngle",
                          ifelse(Shot_Distance < shot_dist,"Close-DecentAngle",
                          ifelse((xC >= (89 - (0.25 * abs(yC)))),"Long-PoorAngle",
                          "Long-DecentAngle"))))) %>% 
      filter(angle_type != "BehindNet") 


angle_sh_pct <- bad_angle_shots %>%
      filter(Game_State %in% c("5v4","5v5")) %>%
      filter(bad_angle == 1) %>%
      group_by(angle_type) %>%
      summarise(yC = mean(ifelse(yC>0, yC, NA),na.rm=T),
                xC = mean(xC),
                shooting_angle = paste0(round(likelihood_mean_fun(n()-sum(Goal),n()),3) * 100,"%"))


## Plot shots  
rink_wshots <- rink + geom_jitter(aes(x = xC, y = yC, color = as.factor(angle_type)), alpha = 0.8,
                  data = bad_angle_shots) +
        ggthemes::scale_color_gdocs() +
        ggrepel::geom_label_repel(data = angle_sh_pct, size = 6, aes(x = xC, y = yC, label = shooting_angle, color = as.factor(angle_type))) +
        labs(color = "Angle Class",
             title = "Areas of Analysis\nCulumative Shooting Percentage Labelled") +
        theme(plot.title = bold_txt,
              legend.title=element_text(size=16), 
              legend.text=element_text(size=12)
              ) 

rink_wshots

ggsave(filename = "/Users/colander1/Downloads/rink_wshots.png", plot = rink_wshots, height = 12, width = 16)

Calculate Sv% and Rebound% From Bad Angle Shots

Calculate Shooting% From Bad Angle Shots

bad_angle_shots_data <- bad_angle_shots_sums %>%
    select(-c(`Bad Angle Shooting % - Expected`,`Bad Angle Rebound % - Expected`)) %>%
    melt(id.var = c("season","Game_State","angle_type")) %>%
    tidyr::separate(variable, c("Metric","Measure"), sep = "[-]", remove=T) %>%
    na.omit() %>%
    dcast(season + Game_State + angle_type + Metric ~ trimws(Measure), value.var = "value") %>%
    group_by(Metric, Game_State, angle_type) %>%
    mutate(Percentile = percent_rank(`Mean`))
## Warning: Expected 2 pieces. Missing pieces filled with `NA` in 128 rows [1,
## 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...].
bad_angle_shooting_line_plot <- bad_angle_shots_data %>%
    filter(Metric == "Bad Angle Shooting % ") %>%
    filter(angle_type != "BehindNet") %>%
    filter(Game_State == "5v5") %>%
    ggplot(aes(x=as.factor(season), y=Mean, color=as.factor(angle_type)
               )) +
    geom_hline(yintercept = 0, color = "grey50", size = 4) +
    geom_line(size = 5) +
    geom_errorbar(aes(ymin=Mean-StdDev, ymax=Mean+StdDev), width=.1, position=position_dodge(0.1), size = 6, alpha = 0.5) +
    facet_grid(Game_State~., scales = "free_y") +
    ggthemes::scale_color_gdocs() +
    theme_standard() +
    scale_y_continuous(labels = scales::percent) + 
    labs(y="Shooting %",x="Season",color = "",
         title = "Bad Angle Shots, Shooting %\n5v5, 2010 - 2018\n@crowdscoutsprts (github.com/C92Anderson/xG-Model)")

ggsave(filename = "/Users/colander1/Downloads/bad_angle_shooting_line_plot.png", plot = bad_angle_shooting_line_plot, height = 12, width = 18)

bad_angle_shooting_line_plot