1 러시아 월드컵 예선 독일전 1

러시아 월드컵 예선 마지막 독일과의 경기는 2패로 16강 탈락이 확정되었지만, 독일을 2-0으로 물리치는 감동적인 경기였다. Tokyo R에서 Ryo Nakagawara 발표했던 것에 영감을 받아 독일과 월드컵 최종예선 데이터를 시각화하여 기록해서 남겨두고자 한다.

김영권(90+2)

첫번째 꼴

손흥민(90+6)

첫번째 꼴

2 정적 시각화 2 3 4

2.1 월드컵 참가선수 명단

러시아 월드컵에 참가한 선수명단은 위키 - 2018년_FIFA_월드컵_선수_명단 웹페이지에서 확인이 가능하다. 이를 위해서 먼저 rvest 팩키지를 활용하여 정적 웹페이지를 긁어와서 데이터 정제작업을 거처 월드컵 참가 선수 명단을 확정한다.

# 0. 환경설정 ------
# library(tidyverse)
# library(rvest)
# library(extrafont)
# loadfonts()
# # devtools::install_github("torvaney/ggsoccer")
# library(ggsoccer)  # create soccer pitch overlay
# library(tweenr)    # build frames for animation
# library(gganimate) # animate plots
# library(extrafont) # insert custom fonts into plots
# library(ggimage)   # insert images and emoji into plots

# 1. 데이터 ------
## 1.1. 월드컵 출전 선수 데이터 -----
Sys.setlocale("LC_ALL", "C")
world_url <- "https://ko.wikipedia.org/wiki/2018년_FIFA_월드컵_선수_명단"

player_dat <- world_url %>% 
    read_html() %>%
    html_nodes(xpath='//*[@id="mw-content-text"]/div/table[24]/tbody/tr/td/table') %>% 
    html_table(fill=TRUE) %>% 
    .[[1]]
 
Sys.setlocale("LC_ALL", "Korean")

player_df <- player_dat %>% 
    mutate(주장 = ifelse(str_detect(`선수 이름`, "\\("), "주장", "선수")) %>% 
    mutate(`선수 이름` = str_sub(`선수 이름`, 1, 3))
DT::datatable(player_df)

2.2 출전선수 명단

독일전은 2018-08-27 예선전이 열렸으며 골키퍼 조현우를 비롯한 선발선수명단이 각 포지션별로 발표되었다.

  • 골키퍼: 조현우
  • 수비: 이용 윤영선 김영권 홍철
  • 미드필드: 이재성 정우영 장현수 문선민
  • 공격: 구자철 손흥민

Soccer event logger를 활용하여 각 선수별 위치를 축구경기장에 클릭하여 좌표를 얻은 후에 선수명을 수작업을 붙여 놓고 이를 ggsoccer 팩키지 annotate_pitch(), theme_pitch() 함수를 사용해서 시각화한다.

# 2. 선발 라인업 ------

lineup_df <- tribble(
    ~x, ~y, ~name,
9.035, 50.00, "조현우",
24.46, 90.78, "이용",
19.10, 66.05, "운영선",
19.10, 30.52, "김영권",
25.17,  5.52, "홍철",
46.60, 91.57, "이재성",
45.89, 66.05, "정우영",
45.53, 30.42, "장현수",
43.39,  5.52, "문선민",
71.60, 30.26, "구자철",
72.67, 64.73, "손흥민")

ggplot(lineup_df) +
    annotate_pitch() +
    theme_pitch(aspect_ratio = NULL) +
    coord_flip() +
    geom_point(
        aes(x = x, y = y), size = 1.5) +
    geom_text(
        aes(x = x, y = y, label = name, family=c("NanumGothic")),
        vjust = 1.5, color = "blue")

2.3 독일전 골모음

정규 경기시간이 모두 소진된 후에 추가시간이 6분 주워졌고, 추가시간 2분 손흥민의 코너킥이 김영권에 이어지면서 독일전 첫골이 완성되었다.

# 1. 첫번째 골 데이터 ------

ball_df <- tribble(~from_x, ~from_y, ~to_x, ~to_y, 
                      0,    100,   6.78, 63.94,
                      6.78, 63.94, 7.5,  56.84,
                      7.5,  56.84, 4.64, 38.94,
                      4.64, 38.94, -0.53,47.36)

first_player_df <- tribble(~x, ~y, ~name, 
                   0,    100,   "손흥민",
                   6.78, 63.94, "이승우",
                   7.5,  56.84, "윤영선",
                   4.64, 38.94, "김영권")

ggplot(ball_df) +
    annotate_pitch() +
    geom_point(aes(x=from_x, y=from_y, size=1.5, color="red")) +
    geom_point(aes(x=to_x, y=to_y, size=1.5, color="red")) +
    geom_segment(aes(x = from_x, y = from_y, xend = to_x, yend = to_y),
                 arrow = arrow(length = unit(0.25, "cm"),
                               type = "closed")) +
    theme_pitch() +
    coord_flip() +
    xlim(-10, 51) +
    ylim(-15, 101) +
    labs(title="한국과 독일 월드컵 예선", 
         subtitle="김영권 첫골(90+2)") +
    theme(legend.position = "none") +
    geom_text(data=first_player_df, 
        aes(x = x, y = y, label = name, family=c("NanumGothic")),
        vjust = -1.5, color = "blue")

독일전 두번째 골은 연장 추가시간 막판 골키퍼까지 나와 만회골을 위해 혼신을 다하던 순간, 주세종이 골을 뽑아내고 이를 손흥민에 연결하여 여유있게 골을 골망에 넣어 두번째골이 완성되었다.

# 2. 두번째 골 데이터 ------

second_ball_df <- tribble(~from_x, ~from_y, ~to_x, ~to_y, 
                   69.28, 15.52, 75.89, 22.89,
                   75.89, 22.89, 24.28, 62.89,
                   24.28, 62.89, 2.85, 60.52,
                   2.85, 60.52, -1.07, 46.31)

second_player_df <- tribble(~x, ~y, ~name, 
                           69.28, 15.52, "주세종",
                           24.28, 62.89, "손흥민")

ggplot(second_ball_df) +
    annotate_pitch() +
    geom_point(aes(x=from_x, y=from_y, size=1.5, color="red")) +
    geom_point(aes(x=to_x, y=to_y, size=1.5, color="red")) +
    geom_segment(aes(x = from_x, y = from_y, xend = to_x, yend = to_y),
                 arrow = arrow(length = unit(0.25, "cm"),
                               type = "closed")) +
    theme_pitch() +
    coord_flip() +
    xlim(-10, 101) +
    ylim(-15, 101) +
    labs(title="한국과 독일 월드컵 예선", 
         subtitle="손흥민 두번째골(90+6)") +
    theme(legend.position = "none") +
    geom_text(data=second_player_df, 
              aes(x = x, y = y, label = name, family=c("NanumGothic")),
              vjust = -1.5, color = "blue")

3 동적 시각화

선수의 위치와 골의 이동경로를 설정하고 time 순서를 지정한다. 그리고 나서, gganimate 팩키지 transition_states() 함수를 사용해서 득점장면 애니메이션을 생성시킨다.

3.1 김영권 첫번째 골

독일전 첫번째 골의 경우 손흥민 코너킥으로 선수의 움직임이 크지 않은 상태에서 공이 손흥민 → 이승우 → 윤영선 → 김영권으로 연결되며 골로 연결된 경우다. 따라서 골의 움직임만 time을 주어 이동시키면 애니메이션을 간단히 제작할 수 있다.

# 1. 첫번째 골 데이터 ------

first_ball_df <- tribble(~x,      ~y,  ~time,
                         0,      100,   1,
                         6.78, 67.94,   2,
                         7.5,  56.84,   3,
                         4.64, 38.94,   4,
                         -0.8,  47,   5)

first_player_df <- tribble(~x, ~y, ~name, 
                           0,    100,   "손흥민",
                           9.78, 67.94, "이승우",
                           9.5,  56.84, "윤영선",
                           7.64, 38.94, "김영권")

ggplot(first_ball_df) +
    annotate_pitch() +
    theme_pitch() +
    coord_flip() +
    xlim(-10, 51) +
    ylim(-15, 101) +
    labs(title="한국과 독일 월드컵 예선", 
         subtitle="김영권 첫번째골(90+2)") +
    theme(legend.position = "none") +
    geom_label(data = first_player_df, aes(x = x, y = y, label = name)) +
    theme(text = element_text(family = "NanumBarunGothic")) +
    ggimage::geom_emoji(
        aes(x = x, 
            y = y),
        image = "26bd", size = 0.035) +
    transition_states(
        time,
        transition_length = 0.5,
        state_length = 0.0001,
        wrap = FALSE) +
    ease_aes("linear")

   # transition_manual(frames = time)

3.2 손흥민 두번째 골

두번째 손흥민의 골은 주세종이 독일 골키퍼로부터 공을 가로채서 이를 손흥민에 연결하고 손흥민이 빠른 주력을 이용하여 골대까지 전력질주하여 골을 넣은 경우라, 공뿐만 아니라 선수의 움직임도 함께 애니메이션화하여 시각화한다.

# 2. 두번째 골 데이터 ------

second_ball_df <- tribble(~x, ~y, ~time,
                          69.28, 15.52, 1,
                          75.89, 22.89, 2,
                          24.28, 62.89, 3,
                          24.28, 62.89, 4,
                          2.85, 60.52,  5,
                          -1.07, 46.31, 6)

sohn_movement_df <- tribble(~x, ~y, ~time, ~name,
                            35.28, 62.89, 1, "손흥민",
                            35.28, 62.89, 2, "손흥민",
                            35.28, 62.89, 3, "손흥민",
                            24.28, 62.89, 4, "손흥민",
                             2.85, 60.52, 5, "손흥민",
                             2.85, 60.52, 6, "손흥민")

joo_movement_df <- tribble(~x, ~y, ~time, ~name,
                           69.28, 15.52, 1, "주세종",
                           75.89, 22.89, 2, "주세종",
                           75.89, 22.89, 3, "주세종",
                           75.89, 22.89, 4, "주세종",
                           75.89, 22.89, 5, "주세종",
                           75.89, 22.89, 6, "주세종")

ggplot(second_ball_df) +
    annotate_pitch() +
    theme_pitch() +
    coord_flip() +
    xlim(-10, 101) +
    ylim(-15, 101) +
    labs(title="한국과 독일 월드컵 예선", 
         subtitle="손흥민 두번째골(90+6)") +
    theme(legend.position = "none") +
    geom_label(data = joo_movement_df, aes(x = x, y = y, label = name)) +
    geom_label(data = sohn_movement_df, aes(x = x, y = y, label = name)) +
    theme(text = element_text(family = "NanumBarunGothic")) +
    ggimage::geom_emoji(
        aes(x = x, 
            y = y),
        image = "26bd", size = 0.035) +
    transition_states(
        time,
        transition_length = 0.5,
        state_length = 0.0001,
        wrap = FALSE) +
    # transition_manual(frames = time)
    ease_aes("quadratic-out")