messenger_logo
Liên hệ qua Messenger
SciEco

Bùng nổ home run trong bóng chày: trực quan hóa dữ liệu cùng r

I
IEFPA
Ngày viết: 12/08/2026

Bóng chày luôn không ngừng biến đổi qua thời gian, nhưng ít có xu hướng nào thể hiện sự chuyển mình rõ rệt như sự gia tăng của những cú home run. Một thế kỷ trước, việc ghi được vài cú home run trong cả mùa giải đã là thành tích đáng kinh ngạc. Ngày nay, đó lại là kỳ vọng tối thiểu đối với mỗi đội bóng. Nhờ sự cải tiến trong huấn luyện, kỹ thuật phân tích dữ liệu và thậm chí là sự thay đổi cấu trúc quả bóng, số lượng home run đã tăng vọt qua từng mốc lịch sử. Hãy cùng khám phá câu chuyện này qua dữ liệu giải bóng chày chuyên nghiệp Mỹ từ năm 1901 đến nay bằng ngôn ngữ lập trình R.

Chuẩn bị và xử lý dữ liệu lịch sử với Lahman

Để thực hiện bài phân tích, chúng ta sử dụng bộ dữ liệu Lahman, một trong những cơ sở dữ liệu lịch sử toàn diện nhất về bóng chày. Dữ liệu chứa thông tin chi tiết về từng mùa giải, đội bóng và cầu thủ qua hơn một thế kỷ.

Bước đầu tiên là cài đặt các thư viện cần thiết và tính toán chỉ số home run trung bình trên mỗi trận đấu cho toàn giải đấu qua từng năm. Vì mỗi trận đấu luôn có hai đội tham gia, tổng số trận của toàn giải sẽ bằng tổng số trận các đội thi đấu chia cho hai.

1packages <- c("tidyverse", "Lahman", "scales", "gganimate", "gifski")
2to_install <- packages[!packages %in% installed.packages()[, "Package"]]
3if (length(to_install)) install.packages(to_install, dependencies = TRUE)
4library(tidyverse)
5library(Lahman)
6library(scales)
7library(gganimate)
8library(gifski)
9season_hr <- Teams %>%
10  filter(!is.na(HR), !is.na(G), yearID >= 1901) %>%
11  group_by(yearID) %>%
12  summarise(
13    hr_total = sum(HR, na.rm = TRUE),
14    games_team_total = sum(G, na.rm = TRUE),
15    .groups = "drop"
16  ) %>%
17  mutate(
18    games_league = games_team_total / 2,
19    hr_per_game = hr_total / games_league
20  )
21peak <- season_hr %>% slice_max(hr_per_game, n = 1)
22latest_year <- max(season_hr$yearID, na.rm = TRUE)

Xây dựng biểu đồ xu hướng theo thời gian

Sau khi đã chuẩn bị xong chuỗi dữ liệu, bước tiếp theo là dựng biểu đồ đường để quan sát xu hướng biến động. Chúng ta kết hợp đường xu hướng làm mịn bằng phương pháp loess để thấy rõ định hướng dài hạn, đồng thời đánh dấu điểm đỉnh cao nhất từng ghi nhận trong lịch sử.

1p_static <- ggplot(season_hr, aes(yearID, hr_per_game)) +
2  geom_line(linewidth = 1) +
3  geom_smooth(se = FALSE, method = "loess", span = 0.2) +
4  geom_point(data = peak, aes(yearID, hr_per_game), size = 3) +
5  ggrepel::geom_text_repel(
6    data = peak,
7    aes(label = paste0("Peak: ", yearID, "\n", round(hr_per_game, 2), " HR/game")),
8    nudge_y = 0.1,
9    min.segment.length = 0
10  ) +
11  scale_x_continuous(breaks = pretty_breaks()) +
12  scale_y_continuous(labels = label_number(accuracy = 0.01)) +
13  labs(
14    title = "The Home Run Boom in MLB",
15    subtitle = paste0("League-wide home runs per game, 1901–", latest_year),
16    x = "Season",
17    y = "Home runs per game (league-wide)",
18    caption = "Source: Lahman Baseball Database | Made in R"
19  ) +
20  theme_minimal(base_size = 13) +
21  theme(
22    plot.title = element_text(face = "bold"),
23    plot.caption = element_text(margin = margin(t = 8))
24  )
25ggsave("mlb_hr_per_game.png", p_static, width = 10, height = 6.2, dpi = 220)

So sánh xu hướng giữa Giải Mỹ và Giải Quốc gia

Mỗi liên đoàn trong giải đấu đều có những quy định và đặc thụ vận hành riêng. Việc tách dữ liệu theo hai liên đoàn AL và NL giúp xác định xem liệu đà tăng trưởng này xảy ra trên toàn bộ hệ thống hay chỉ tập trung ở một nhóm đội bóng nhất định.

1season_hr_lg <- Teams %>%
2  filter(!is.na(HR), !is.na(G), yearID >= 1901, !is.na(lgID)) %>%
3  group_by(yearID, lgID) %>%
4  summarise(
5    hr_total = sum(HR, na.rm = TRUE),
6    games_team_total = sum(G, na.rm = TRUE),
7    .groups = "drop_last"
8  ) %>%
9  mutate(games_league = games_team_total / 2, hr_per_game = hr_total / games_league) %>%
10  ungroup()
11p_facets <- ggplot(season_hr_lg, aes(yearID, hr_per_game)) +
12  geom_line() +
13  geom_smooth(se = FALSE, method = "loess", span = 0.25) +
14  facet_wrap(~ lgID, nrow = 2) +
15  labs(
16    title = "AL vs NL: home runs per game over time",
17    subtitle = paste0("1901–", latest_year),
18    x = "Season",
19    y = "HR per game",
20    caption = "Source: Lahman Baseball Database | Made in R"
21  ) +
22  theme_minimal(base_size = 12) +
23  theme(plot.title = element_text(face = "bold"))
24ggsave("mlb_hr_per_game_al_vs_nl.png", p_facets, width = 10, height = 7, dpi = 220)

Tạo hiệu ứng động sinh động với gganimate

Trực quan hóa dạng động giúp người xem theo dõi hành trình lịch sử theo từng năm một cách tự nhiên. Sử dụng gói gganimate, đường biểu đồ sẽ vẽ tiến trình phát triển từ quá khứ đến hiện tại.

1p_anim <- ggplot(season_hr, aes(yearID, hr_per_game)) +
2  geom_line() +
3  geom_point(size = 1.5) +
4  labs(
5    title = "MLB Home Runs per Game — {closest_state}",
6    subtitle = paste0("1901–", latest_year),
7    x = "Season",
8    y = "HR per game",
9    caption = "Source: Lahman Baseball Database | Made in R (gganimate)"
10  ) +
11  theme_minimal(base_size = 13) +
12  transition_reveal(yearID)
13animate(
14  p_anim, width = 900, height = 540, fps = 30, duration = 12, end_pause = 10,
15  renderer = gifski_renderer("mlb_hr_per_game.gif")
16)

Bức tranh lịch sử đằng sau những con số

Nhìn vào đường biểu đồ, các giai đoạn phát triển của thể thao đỉnh cao hiện ra rất rõ ràng qua từng thập kỷ.

Giai đoạn bóng chết từ thập niên 1900 đến 1910 ghi nhận tỷ lệ cực kỳ thấp. Các cầu thủ ném bóng làm chủ hoàn toàn trận đấu do chất liệu quả bóng khi đó khá mềm và chiến thuật thi đấu thiên về kiểm soát bóng ngắn.

Giai đoạn từ thập niên 1920 đến 1950 đánh dấu sự xuất hiện của kỷ nguyên sức mạnh với đại diện tiêu biểu là Babe Ruth. Lối chơi chuyển dịch sang hướng tấn công chủ động và home run trở thành vũ khí hàng đầu.

Giai đoạn thập niên 1990 đến 2000 chứng kiến sự bùng nổ vượt ngưỡng trung bình 1 cú đánh mỗi trận. Kỹ thuật thể lực và chiến thuật tấn công đạt đỉnh cao mới.

Từ thập niên 2010 đến nay là cuộc cách mạng góc phát lực. Việc ứng dụng công nghệ theo dõi quỹ đạo giúp cầu thủ tối ưu hóa góc đánh nhằm tạo ra tầm bay xa nhất. Đến năm 2019, giải đấu đã chạm mốc kỷ lục lịch sử với 1.39 cú home run mỗi trận, gấp hơn 6 lần so với thời kỳ năm 1910.

Ý nghĩa đối với phân tích dữ liệu và thị trường dự đoán

Sự thay đổi về tần suất home run mang lại nhiều bài học giá trị cho các nhà phân tích dữ liệu và mô hình hóa:

Điều chỉnh hạn mức điểm số: Khi tổng số cú đánh tăng, điểm số trung bình mỗi trận tăng theo. Các mô hình dự đoán phải cập nhật điểm mốc tổng số bàn thắng lên cao hơn so với lịch sử.

Chỉ số ảnh hưởng của sân thi đấu: Kích thước và độ cao của các sân vận động tác động trực tiếp đến khả năng bóng ra ngoài biên. Sự kết hợp giữa lối đánh tối ưu góc vung gậy và yếu tố địa lý tạo ra biến số lớn cho các chỉ số dự báo cá nhân.

Tránh thiên kiến dữ liệu gần: Tỷ lệ home run không tăng cố định mà luôn biến động theo các điều kiện vật lý như cấu tạo bóng hay quy định vùng ném bóng. Việc chỉ dùng dữ liệu ngắn hạn dễ dẫn đến những dự báo sai lệch khi luật chơi thay đổi.

✨ Giá trị đắt giá từ bài phân tích:

Phân tích chuỗi thời gian cần gắn liền với bối cảnh lịch sử. Sự thay đổi trong dữ liệu không chỉ đến từ yếu tố ngẫu nhiên mà phản ánh trực tiếp sự tiến hóa của công nghệ, vật liệu và chiến thuật trong thực tế. Việc kết hợp kỹ năng xử lý dữ liệu bằng R cùng tư duy lịch sử giúp nhà phân tích nhìn thấy bản chất thực sự đằng sau những đỉnh nhọn trên biểu đồ.

Câu hỏi tư duy hoặc bài tập ứng dụng:

Thử sử dụng gói dữ liệu Lahman để tính toán tỷ lệ strikeout trên mỗi trận đấu cùng giai đoạn 1901 đến nay. Hãy kiểm tra xem liệu sự gia tăng của home run có đi kèm với việc gia tăng số lần cầu thủ đánh hụt hay không, và biểu diễn sự tương quan đó trên cùng một biểu đồ trong R.


Bài viết khác
Dữ liệu hiện diện ở khắp mọi nơi trong kỷ nguyên số. Rất nhiều cơ quan chính phủ, tổ chức tài chính, trường đại học và nền tảng mạng xã hội cung cấp quyền truy cập vào kho dữ liệu của họ thông qua giao diện lập trình ứng dụng API. Các API này thường phản hồi và trả về dữ liệu dưới định dạng JSON. Trong bài viết này, chúng ta sẽ cùng khám phá cách tận dụng sức mạnh của Python ngay bên trong Stata để gửi yêu cầu truy xuất dữ liệu qua API, bóc tách cấu trúc dữ liệu JSON lồng nhau và chuyển đổi thành tập dữ liệu Stata sẵn sàng cho phân tích. Tìm hiểu về API và cấu trúc dữ liệu JSON Một API cho phép hệ thống này yêu cầu và nhận dữ liệu từ một hệ thống máy tính khác. Cú pháp của mỗi API có thể khác biệt tùy theo nhà cung cấp, nhưng thông thường cấu trúc truy vấn sẽ bao gồm một đường dẫn URL gốc đi kèm các tham số điều kiện. Chẳng hạn, đường dẫn dưới đây sử dụng openFDA API để truy xuất thông tin về các biến cố bất lợi của thuốc từ Cục Quản lý Thực phẩm và Dược phẩm Hoa Kỳ:
Trong các bài viết trước, chúng ta đã sử dụng phương thức `read_stata()` để đọc toàn bộ các tập dữ liệu của Stata vào các data frame của pandas. Điều này hoạt động hiệu quả khi bạn muốn đọc toàn bộ một tập dữ liệu của Stata vào Python. Tuy nhiên, đôi khi chúng ta muốn đọc một tập con của các biến số hoặc các quan sát, hoặc cả hai, từ một tập dữ liệu của Stata vào Python. Bài viết này sẽ giới thiệu mô-đun Giao diện Chức năng của Stata (SFI) và chỉ cho bạn cách sử dụng nó để đọc các tập dữ liệu một phần vào một data frame của pandas. Nếu bạn chưa quen với Python, việc tham khảo bốn bài viết đầu tiên trong chuỗi tích hợp Stata/Python có thể hữu ích trước khi tiếp tục. Sử Dụng Mô-đun SFI Để Chuyển Dữ Liệu Từ Stata Sang Python SFI là một mô-đun Python cho phép bạn truyền thông tin qua lại giữa Stata và Python. Bạn có thể sao chép toàn bộ hoặc một phần các tập dữ liệu, data frame, macro cục bộ và toàn cục, scalar và ma trận, thậm chí cả ma trận Mata toàn cục. Có quá nhiều tính năng để trình bày trong một bài blog. Vì vậy, hôm nay chúng ta sẽ tìm hiểu một tính năng mà bạn có thể sẽ sử dụng: đọc các tập dữ liệu Stata một phần vào Python. Chúng ta sẽ khám phá thêm các tính năng khác của SFI trong các bài viết sau.
SciEco
Science for Economics
Định hướng đào tạo phân tích dữ liệu, xây dựng chính sách, tối ưu hoá danh mục tài chính cá nhân và dự báo thị trường.
Liên hệ
Địa chỉ: Số 60, ngõ 41, Phố Thái Hà, Trung Liệt, Đống Đa, Hà Nội (Google Map)
Email: science.for.economics@gmail.com
Hotline: 03.57.94.7680 (Mrs. Hà)
Mạng xã hội